[
  {
    "path": ".gitattributes",
    "content": "*.pb binary\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": ".github/CODEOWNERS @SonarSource/remediation-ide-experience-squad\n"
  },
  {
    "path": ".github/renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"github>SonarSource/renovate-config:ide-xp-team\"\n  ]\n}\n"
  },
  {
    "path": ".github/workflows/PullRequestClosed.yml",
    "content": "name: Pull Request Closed\n\non:\n  pull_request:\n    types: [closed]\n\njobs:\n  PullRequestClosed_job:\n    name: Pull Request Closed\n    runs-on: sonar-xs-public\n    permissions:\n      id-token: write\n      pull-requests: read\n    # For external PR, ticket should be moved manually\n    if: |\n        github.event.pull_request.head.repo.full_name == github.repository\n    steps:\n      - id: secrets\n        uses: SonarSource/vault-action-wrapper@c154b4a417b51cb98dd71137f49bf20e77c56820 # 3.4.0\n        with:\n          secrets: |\n            development/kv/data/jira user | JIRA_USER;\n            development/kv/data/jira token | JIRA_TOKEN;\n      - uses: sonarsource/gh-action-lt-backlog/PullRequestClosed@v2\n        with:\n          github-token: ${{secrets.GITHUB_TOKEN}}\n          jira-user:  ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }}\n          jira-token: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/PullRequestCreated.yml",
    "content": "name: Pull Request Created\n\non:\n  pull_request:\n    types: [\"opened\"]\n\njobs:\n  PullRequestCreated_job:\n    name: Pull Request Created\n    runs-on: sonar-xs-public\n    permissions:\n      id-token: write\n    # For external PR, ticket should be created manually\n    if: |\n        github.event.pull_request.head.repo.full_name == github.repository\n    steps:\n      - id: secrets\n        uses: SonarSource/vault-action-wrapper@c154b4a417b51cb98dd71137f49bf20e77c56820 # 3.4.0\n        with:\n          secrets: |\n            development/github/token/{REPO_OWNER_NAME_DASH}-jira token | GITHUB_TOKEN;\n            development/kv/data/jira user | JIRA_USER;\n            development/kv/data/jira token | JIRA_TOKEN;\n      - uses: sonarsource/gh-action-lt-backlog/PullRequestCreated@v2\n        with:\n          github-token: ${{ fromJSON(steps.secrets.outputs.vault).GITHUB_TOKEN }}\n          jira-user:    ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }}\n          jira-token:   ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }}\n          jira-project: SLCORE\n"
  },
  {
    "path": ".github/workflows/RequestReview.yml",
    "content": "name: Request review\n\non:\n  pull_request:\n    types: [\"review_requested\"]\n\njobs:\n  RequestReview_job:\n    name: Request review\n    runs-on: sonar-xs-public\n    permissions:\n      id-token: write\n    # For external PR, ticket should be moved manually\n    if: |\n        github.event.pull_request.head.repo.full_name == github.repository\n    steps:\n      - id: secrets\n        uses: SonarSource/vault-action-wrapper@c154b4a417b51cb98dd71137f49bf20e77c56820 # 3.4.0\n        with:\n          secrets: |\n            development/github/token/{REPO_OWNER_NAME_DASH}-jira token | GITHUB_TOKEN;\n            development/kv/data/jira user | JIRA_USER;\n            development/kv/data/jira token | JIRA_TOKEN;\n      - uses: sonarsource/gh-action-lt-backlog/RequestReview@v2\n        with:\n          github-token: ${{ fromJSON(steps.secrets.outputs.vault).GITHUB_TOKEN }}\n          jira-user:    ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }}\n          jira-token:   ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/SubmitReview.yml",
    "content": "name: Submit Review\n\non:\n  pull_request_review:\n    types: [submitted]\n\njobs:\n  SubmitReview_job:\n    name: Submit Review\n    runs-on: sonar-xs-public\n    permissions:\n      id-token: write\n      pull-requests: read\n    # For external PR, ticket should be moved manually\n    if: |\n        github.event.pull_request.head.repo.full_name == github.repository\n        && (github.event.review.state == 'changes_requested'\n            || github.event.review.state == 'approved')\n    steps:\n      - id: secrets\n        uses: SonarSource/vault-action-wrapper@c154b4a417b51cb98dd71137f49bf20e77c56820 # 3.4.0\n        with:\n          secrets: |\n            development/kv/data/jira user | JIRA_USER;\n            development/kv/data/jira token | JIRA_TOKEN;\n      - uses: sonarsource/gh-action-lt-backlog/SubmitReview@v2\n        with:\n          github-token: ${{secrets.GITHUB_TOKEN}}\n          jira-user: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }}\n          jira-token: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Build\non:\n  push:\n    branches:\n      - master\n      - branch-*\n      - dogfood-*\n  pull_request:\n  merge_group:\n  workflow_dispatch:\n\nconcurrency:\n  group: >-\n    ${{ github.workflow }}-\n    ${{ github.event.pull_request.base.ref || 'push' }}-\n    ${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\nenv:\n  CACHE_BACKEND: s3\n\njobs:\n  build-number:\n    outputs:\n      BUILD_NUMBER: ${{ steps.build-number.outputs.BUILD_NUMBER }}\n    runs-on: sonar-xs-public\n    name: Get build number\n    permissions:\n      id-token: write\n    steps:\n      - uses: SonarSource/ci-github-actions/get-build-number@23d3a5700259f9890438851083904c6d5e87ff4e # 1.3.34\n        id: build-number\n\n  build:\n    runs-on: sonar-xs-public\n    needs: build-number\n    name: Build\n    permissions:\n      id-token: write\n      contents: write\n    env:\n      BUILD_NUMBER: ${{ needs.build-number.outputs.BUILD_NUMBER }}\n    outputs:\n      build_number: ${{ steps.build.outputs.build_number }}\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1\n        with:\n          version: 2026.4.11\n      - uses: SonarSource/ci-github-actions/build-maven@23d3a5700259f9890438851083904c6d5e87ff4e # 1.3.34\n        id: build\n        with:\n          sonar-platform: none\n          deploy-pull-request: true\n          artifactory-reader-role: private-reader\n          artifactory-deployer-role: qa-deployer\n          maven-args: -T 1C -P dist-no-arch,dist-windows_x64,dist-linux_x64,dist-linux_aarch64,dist-macosx_x64,dist-macosx_aarch64 -Dmaven.test.skip=true -Dsonar.skip=true\n      - name: Config Maven (cache setup)\n        run: |\n          mvn -B -e -V -Pits dependency:go-offline # populate cache including ITs deps too\n\n  test-linux:\n    needs: [ build-number, build ]\n    runs-on: sonar-m-public\n    name: Test (Linux, Sonar Next)\n    permissions:\n      id-token: write\n      contents: write\n    env:\n      BUILD_NUMBER: ${{ needs.build-number.outputs.BUILD_NUMBER }}\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n      - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1\n        with:\n          version: 2026.4.11\n      - name: Vault\n        id: secrets\n        uses: SonarSource/vault-action-wrapper@c154b4a417b51cb98dd71137f49bf20e77c56820 # 3.4.0\n        with:\n          secrets: |\n            development/kv/data/next url | NEXT_URL;\n            development/kv/data/next token | NEXT_TOKEN;\n      - name: Cache Sonar Scanner artifacts\n        id: sonar-scanner-cache\n        uses: SonarSource/ci-github-actions/cache@23d3a5700259f9890438851083904c6d5e87ff4e # 1.3.34\n        with:\n          path: ~/.sonar/cache\n          key: sonar-scanner-${{ runner.os }}\n      - uses: SonarSource/ci-github-actions/config-maven@23d3a5700259f9890438851083904c6d5e87ff4e # 1.3.34\n        id: config\n        with:\n          artifactory-reader-role: private-reader\n      - name: Run tests\n        env:\n          SONAR_HOST_URL: ${{ fromJSON(steps.secrets.outputs.vault).NEXT_URL }}\n          SONAR_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).NEXT_TOKEN }}\n          PROJECT_VERSION: ${{ steps.config.outputs.project-version }}\n          SCANNER_VERSION: 5.1.0.4751\n          PULL_REQUEST: ${{ github.event.pull_request.number || '' }}\n        run: |\n          mvn -B -Pcoverage -Dcommercial verify\n          maven_goals=(\"org.sonarsource.scanner.maven:sonar-maven-plugin:${SCANNER_VERSION}:sonar\")\n          sonar_props=(\"-Dsonar.host.url=${SONAR_HOST_URL}\" \"-Dsonar.token=${SONAR_TOKEN}\")\n          sonar_props+=(\"-Dsonar.projectVersion=${CURRENT_VERSION}\")\n          sonar_props+=(\"-Dsonar.coverage.jacoco.aggregateXmlReportPaths=${{ github.workspace }}/report-aggregate/target/site/jacoco-aggregate/jacoco.xml\")\n          echo \"Maven command: mvn ${maven_goals[*]} ${sonar_props[*]}\"\n          mvn -B \"${maven_goals[@]}\" \"${sonar_props[@]}\"\n      - name: Generate test report on failure\n        if: failure()\n        uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2 # v3.0.0\n        with:\n          name: QA Linux Test Report\n          reporter: java-junit\n          path: '**/target/surefire-reports/TEST-*.xml,**/target/failsafe-reports/*.xml'\n          list-suites: failed\n          list-tests: failed\n          fail-on-empty: false\n      - name: Upload failure diagnostics\n        if: failure()\n        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n        with:\n          name: linux-test-report\n          path: |\n            **/target/surefire-reports/**\n            **/target/failsafe-reports/**\n\n  test-windows:\n    needs: [ build-number, build ]\n    runs-on: github-windows-latest-m\n    name: Test (Windows)\n    permissions:\n      id-token: write\n      contents: write\n    env:\n      BUILD_NUMBER: ${{ needs.build-number.outputs.BUILD_NUMBER }}\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1\n        with:\n          version: 2026.4.11\n      - uses: SonarSource/ci-github-actions/config-maven@23d3a5700259f9890438851083904c6d5e87ff4e # 1.3.34\n        id: config\n        with:\n          artifactory-reader-role: private-reader\n      - name: Run tests\n        env:\n          MAVEN_OPTS: -Xmx4g\n          PROJECT_VERSION: ${{ steps.config.outputs.project-version }}\n        run: |\n          mvn -B -Dcommercial verify\n      - name: Generate test report on failure\n        if: failure()\n        uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2 # v3.0.0\n        with:\n          name: QA Windows Test Report\n          reporter: java-junit\n          path: '**/target/surefire-reports/TEST-*.xml,**/target/failsafe-reports/*.xml'\n          list-suites: failed\n          list-tests: failed\n          fail-on-empty: false\n      - name: Upload failure diagnostics\n        if: failure()\n        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n        with:\n          name: windows-test-report\n          path: |\n            **/target/surefire-reports/**\n            **/target/failsafe-reports/**\n\n  qa:\n    needs: [ build-number, build ]\n    runs-on: sonar-m-public\n    name: QA (${{ matrix.name }})\n    permissions:\n      id-token: write\n      contents: write\n    env:\n      BUILD_NUMBER: ${{ needs.build-number.outputs.BUILD_NUMBER }}\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - name: SonarCloudEU\n            sq_version: SonarCloudEU\n            category: \"-Dgroups=SonarCloud\"\n            sc: true\n            sc_token_path: sonarcloud-it\n            region: EU\n          - name: SonarCloudUS\n            sq_version: SonarCloudUS\n            category: \"-Dgroups=SonarCloud\"\n            sc: true\n            sc_token_path: sonarcloud-it-US\n            region: US\n          - name: SQDogfood\n            sq_version: DEV\n            category: \"-DexcludedGroups=SonarCloud\"\n          - name: SQLatest\n            sq_version: LATEST_RELEASE\n            category: \"-DexcludedGroups=SonarCloud\"\n          - name: SQLts99\n            sq_version: \"LATEST_RELEASE[9.9]\"\n            category: \"-DexcludedGroups=SonarCloud\"\n            customOrchestratorJavaVersion: 17\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1\n        with:\n          version: 2026.4.11\n      - name: Setup Java ${{ matrix.customOrchestratorJavaVersion }} for Orchestrator\n        if: matrix.customOrchestratorJavaVersion\n        run: |\n          mise install java@${{ matrix.customOrchestratorJavaVersion }}\n          echo \"ORCHESTRATOR_JAVA_HOME=$(mise where java@${{ matrix.customOrchestratorJavaVersion }})\" >> \"$GITHUB_ENV\"\n      - name: Compute month key\n        #Avoid caching for DEV since it is frequently changing\n        if: ${{ matrix.sc != true && matrix.sq_version != 'DEV' }}\n        id: month\n        shell: bash\n        run: |\n          THIS_MONTH=\"$(date +%Y-%m)\"\n          echo \"month=${THIS_MONTH}\" >> \"$GITHUB_OUTPUT\"\n          ORCHESTRATOR_HOME=\"${GITHUB_WORKSPACE}/orchestrator/${THIS_MONTH}\"\n          echo \"ORCHESTRATOR_HOME=${ORCHESTRATOR_HOME}\" >> \"$GITHUB_ENV\"\n          echo \"Create dir ${ORCHESTRATOR_HOME} if needed\"\n          mkdir -p \"${ORCHESTRATOR_HOME}\"\n      - uses: SonarSource/ci-github-actions/cache@23d3a5700259f9890438851083904c6d5e87ff4e # 1.3.34\n        if: ${{ matrix.sc != true && matrix.sq_version != 'DEV' }}\n        with:\n          path: ${{ github.workspace }}/orchestrator/${{ steps.month.outputs.month }}\n          key: cache-${{ runner.os }}-${{ steps.month.outputs.month }}-${{ matrix.name }} # Use matrix name to differentiate caches\n      - name: Vault (SonarCloud IT token)\n        if: ${{ matrix.sc == true }}\n        id: secrets-sc\n        uses: SonarSource/vault-action-wrapper@c154b4a417b51cb98dd71137f49bf20e77c56820 # 3.4.0\n        with:\n          secrets: |\n            development/team/sonarlint/kv/data/${{ matrix.sc_token_path }} token | SONARCLOUD_IT_TOKEN;\n      - name: Vault (GITHUB Token)\n        id: secrets-gh\n        uses: SonarSource/vault-action-wrapper@c154b4a417b51cb98dd71137f49bf20e77c56820 # 3.4.0\n        with:\n          secrets: |\n            development/github/token/licenses-ro token | GITHUB_TOKEN;\n\n      - uses: SonarSource/ci-github-actions/config-maven@23d3a5700259f9890438851083904c6d5e87ff4e # 1.3.34\n        with:\n          artifactory-reader-role: private-reader\n      - name: Run QA\n        if: ${{ github.event_name == 'pull_request' || github.ref_name == github.event.repository.default_branch || startsWith(github.ref_name, 'branch-') || startsWith(github.ref_name, 'dogfood-on-') }}\n        env:\n          MAVEN_OPTS: -Xmx4g\n          SONARCLOUD_IT_TOKEN: ${{ steps.secrets-sc.outputs.vault && fromJSON(steps.secrets-sc.outputs.vault).SONARCLOUD_IT_TOKEN || '' }}\n          SONARCLOUD_REGION: ${{ matrix.sc && matrix.region || '' }}\n          GITHUB_TOKEN: ${{ fromJSON(steps.secrets-gh.outputs.vault).GITHUB_TOKEN }}\n          SONAR_SEARCH_JAVAADDITIONALOPTS: -XX:-UseContainerSupport\n          SONAR_WEB_JAVAADDITIONALOPTS: -XX:-UseContainerSupport\n          SONAR_CE_JAVAADDITIONALOPTS: -XX:-UseContainerSupport\n        run: |\n          mvn -f its/pom.xml -Dsonar.runtimeVersion=${{ matrix.sq_version }} ${{ matrix.category }} verify surefire-report:report\n      - name: Generate QA test report on failure\n        if: failure()\n        uses: dorny/test-reporter@2dcf091ad558da2cabf16f6b423e02cd078c937a\n        with:\n          name: QA ${{ matrix.name }} Test Report\n          reporter: java-junit\n          path: '**/target/surefire-reports/TEST-*.xml,**/target/failsafe-reports/*.xml'\n          list-suites: failed\n          list-tests: failed\n          fail-on-empty: false\n      - name: Upload failure diagnostics\n        if: failure()\n        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n        with:\n          name: qa-test-report ${{ matrix.name }}\n          path: |\n            **/target/surefire-reports/**\n            **/target/failsafe-reports/**\n      - name: debug\n        if: failure()\n        shell: bash\n        run: |\n          echo \"=== Listing surefire-reports contents ===\"\n          find ./its/tests/target/surefire-reports -type f || true\n          echo \"=== Checking if directory is empty ===\"\n          [ -d ./its/tests/target/surefire-reports ] && ls -la ./its/tests/target/surefire-reports/ || echo \"Directory doesn't exist\"\n      - name: Inspect Orchestrator Cache\n        if: always()\n        shell: bash\n        run: |\n          echo \"=== Listing orchestrator cache contents ===\"\n          CACHE_DIR=\"${{ github.workspace }}/orchestrator/${{ steps.month.outputs.month }}\"\n          if [ -d \"${CACHE_DIR}\" ]; then\n            echo \"Directory exists: ${CACHE_DIR}\"\n            ls -lah \"${CACHE_DIR}\"\n            echo \"\"\n            echo \"=== Detailed file tree ===\"\n            find \"${CACHE_DIR}\" -type f -ls || true\n          else\n            echo \"Directory does not exist: ${CACHE_DIR}\"\n          fi\n\n  promote:\n    needs: [ build-number, build, qa, test-linux, test-windows ]\n    runs-on: sonar-xs-public\n    name: Promote\n    permissions:\n      id-token: write\n      contents: write\n    env:\n      BUILD_NUMBER: ${{ needs.build-number.outputs.BUILD_NUMBER }}\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - uses: SonarSource/ci-github-actions/promote@23d3a5700259f9890438851083904c6d5e87ff4e # 1.3.34\n        with:\n          promote-pull-request: true\n"
  },
  {
    "path": ".github/workflows/full-release.yml",
    "content": "name: Full release\n\non:\n  workflow_dispatch:\n    inputs:\n      short-description:\n        description: 'A short description for the release ticket'\n        required: true\n        type: string\n      branch:\n        description: 'The branch from which to release.'\n        required: false\n        default: 'master'\n        type: string\n\njobs:\n  release:\n    name: Release\n    uses: SonarSource/release-github-actions/.github/workflows/ide-automated-release.yml@6f94d0d49b2bb6d324f3110ef078e3a5bd95604e # 1.5.4\n    if: always() && !failure() && !cancelled()\n    permissions:\n      statuses: read\n      id-token: write\n      contents: write\n      actions: write\n      pull-requests: write\n    with:\n      jira-project-key: \"SLCORE\"\n      project-name: \"SonarLint Core\"\n      short-description: ${{ inputs.short-description }}\n      branch: ${{ inputs.branch }}\n\n  bump-version:\n    name: Create a PR to bump version\n    runs-on: sonar-xs-public\n    needs:\n      - release\n    permissions:\n      contents: write # write for peter-evans/create-pull-request, read for actions/checkout\n      pull-requests: write # write for peter-evans/create-pull-request\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n      - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1\n      - name: bump-version\n        env: \n          NEW_VERSION: ${{ needs.release.outputs.new-version }}\n        run: |\n          mvn versions:set -DgenerateBackupPoms=false -DnewVersion=\"$NEW_VERSION-SNAPSHOT\"\n      - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8\n        with:\n          author: ${{ github.actor }} <${{ github.actor }}>\n          commit-message: Prepare next development iteration\n          title: Prepare next development iteration\n          branch: bot/bump-project-version\n          branch-suffix: timestamp\n          delete-branch: true\n          base: master\n          draft: true # so that we don't actually need to re-open the PR and just make it ready for review\n          reviewers: ${{ github.actor }}\n          body: Bump the version for the new iteration. Created by release automation.\n"
  },
  {
    "path": ".github/workflows/notify-failure.yml",
    "content": "name: Notify Failure\non:\n  workflow_run:\n    workflows: [ \"Build\" ]\n    types:\n      - completed\n    branches:\n      - master\n\npermissions:\n  id-token: write\n\njobs:\n  notify:\n    runs-on: sonar-xs-public\n    name: Send Slack Notification\n    if: ${{ github.event.workflow_run.conclusion == 'failure' }}\n    steps:\n      - name: Vault Secrets\n        id: secrets\n        uses: SonarSource/vault-action-wrapper@c154b4a417b51cb98dd71137f49bf20e77c56820 # 3.4.0\n        with:\n          secrets: |\n            development/kv/data/slack token | SLACK_BOT_TOKEN;\n\n      - name: Slack Notification on Failure\n        uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1\n        with:\n          method: chat.postMessage\n          token: ${{ fromJSON(steps.secrets.outputs.vault).SLACK_BOT_TOKEN }}\n          # Slack channel squad-devex-flow-interrupts\n          payload: |\n            channel: C0ADM04C59A\n            text: \"Workflow failed in ${{ github.repository }}\"\n            blocks:\n              - type: \"section\"\n                text:\n                  type: \"mrkdwn\"\n                  text: \":x: *Repository:* ${{ github.repository }}\\n*Branch:* ${{ github.event.workflow_run.head_branch }}\\n*Workflow:* ${{ github.event.workflow_run.name }}\\n*Run:* <${{ github.event.workflow_run.html_url }}|#${{ github.event.workflow_run.run_number }}>\"\n"
  },
  {
    "path": ".github/workflows/releasability.yml",
    "content": "name: Releasability Status\non:\n    workflow_run:\n        workflows: [ \"Build\" ]  # Name must match the name of the build workflow\n        types: [ completed ]\n        branches:\n            - master\n            - branch-*\n\njobs:\n    releasability-status:\n        name: Releasability status\n        runs-on: sonar-xs-public\n        permissions:\n            id-token: write\n            statuses: write\n            contents: read\n        if: github.event.workflow_run.conclusion == 'success'\n        steps:\n            -   uses: SonarSource/gh-action_releasability/releasability-status@52f09917764eac5a80045d103bfa91e7eaf0c8d6 # 3.0.5\n                with:\n                    optional_checks: \"Jira\"\n                env:\n                    GITHUB_TOKEN: ${{ github.token }}\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: sonar-release\n\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        type: string\n        description: Version\n        required: true\n      releaseId:\n        type: string\n        description: Release ID\n        required: true\n      dryRun:\n        type: boolean\n        description: Flag to enable the dry-run execution\n        default: false\n        required: false\n\njobs:\n  release:\n    permissions:\n      id-token: write\n      contents: write\n    uses: SonarSource/gh-action_release/.github/workflows/main.yaml@4ac0f4304e2858f8144ac48037bb135b5fdac1ad # 6.7.1\n    with:\n      version: ${{ inputs.version }}\n      releaseId: ${{ inputs.releaseId }}\n      dryRun: ${{ inputs.dryRun }}\n      publishToBinaries: false\n      mavenCentralSync: true\n      # Slack channel squad-devex-private\n      slackChannel: C02E6L5C01H\n"
  },
  {
    "path": ".github/workflows/shadow_scans.yml",
    "content": "name: Shadow scans\non:\n  schedule:\n    # Run the workflow every day at 04:00 UTC\n    - cron: '0 4 * * *'\n  workflow_dispatch:\n\nenv:\n  CACHE_BACKEND: s3\n\njobs:\n  scan:\n    runs-on: sonar-xs-public\n    name: Scan on shadow platforms\n    permissions:\n      id-token: write\n      contents: write\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1\n        with:\n          version: 2026.4.11\n      - uses: SonarSource/ci-github-actions/build-maven@23d3a5700259f9890438851083904c6d5e87ff4e # 1.3.34 # dogfood\n        with:\n          maven-args: -Dcommercial -Dsonar.coverage.jacoco.xmlReportPaths=${{ github.workspace }}/report-aggregate/target/site/jacoco-aggregate/jacoco.xml\n          run-shadow-scans: true\n          artifactory-reader-role: private-reader\n          artifactory-deployer-role: qa-deployer\n          scanner-java-opts: '-Xmx2g'\n      - name: Run IRIS sync\n        uses: SonarSource/unified-dogfooding-actions/run-iris@v1\n        with:\n          primary_project_key: \"org.sonarsource.sonarlint.core:sonarlint-core-parent\"\n          primary_platform: \"Next\"\n          shadow1_project_key: \"org.sonarsource.sonarlint.core:sonarlint-core-parent\"\n          shadow1_platform: \"SQC-EU\"\n          shadow2_project_key: \"org.sonarsource.sonarlint.core:sonarlint-core-parent\"\n          shadow2_platform: \"SQC-US\"\n"
  },
  {
    "path": ".gitignore",
    "content": "# The following should be moved in related sub-directories\nserver/sonar-web/src/main/webapp/stylesheets/sonar-colorizer.css\nserver/sonar-web/src/main/webapp/deploy/plugins\nserver/sonar-web/src/main/webapp/deploy/bootstrap\nserver/sonar-web/src/main/webapp/deploy/maven/org\nserver/sonar-web/src/main/webapp/WEB-INF/log/\nserver/sonar-web/src/main/webapp/deploy/*.jar\nserver/sonar-web/src/main/webapp/deploy/jdbc-driver.txt\n\n\n# ---- Javadoc\ndocs.tar\n\n# ---- Maven\ntarget/\ndependency-reduced-pom.xml\n\n# ---- IntelliJ IDEA\n*.iws\n*.iml\n*.ipr\n.idea/\n\n# ---- Eclipse\n.classpath\n.project\n.settings\n.externalToolBuilders\n\n# ---- Mac OS X\n.DS_Store\nIcon?\n# Thumbnails\n._*\n# Files that might appear on external disk\n.Spotlight-V100\n.Trashes\n\n# ---- Windows\n# Windows image file caches\nThumbs.db\n# Folder config file\nDesktop.ini\n\n# ---- Sonar\n.sonar/\n.scannerwork\n\n# scripts patches, they are local to each developer\nscripts/patches/*.sh\nscripts/patches/*license*.txt\n\n# Visual Studio\n.vs\n\n# Run configurations\n.run\n"
  },
  {
    "path": ".mvn/wrapper/maven-wrapper.properties",
    "content": "wrapperVersion=3.3.4\ndistributionType=only-script\ndistributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.13/apache-maven-3.9.13-bin.zip\n"
  },
  {
    "path": ".sonarlint/connectedMode.json",
    "content": "{\n    \"sonarQubeUri\": \"https://next.sonarqube.com/sonarqube\",\n    \"projectKey\": \"org.sonarsource.sonarlint.core:sonarlint-core-parent\"\n}\n    "
  },
  {
    "path": "API_CHANGES.md",
    "content": "# 10.48\n\n## New features\n\n* Add `label` to `org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.PluginStateDto`.\n* Add `label` to `org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.ArtifactSourceDto`.\n* Add `language` field to `org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.PluginStatusDto`.\n* Add `serverVersion` to `org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.PluginStatusDto`.\n    * Contains the version of the SonarQube Server that provided the plugin (e.g. `\"10.8.1\"`).\n    * Non-null only when `source` is `SONARQUBE_SERVER`; `null` for all other sources (embedded, SonarQube Cloud, unavailable).\n* Introduce two new telemetry notification methods to `org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.TelemetryRpcService` to track usage of the \"Supported Languages\" panel:\n    * `supportedLanguagesPanelOpened` - call this each time the user opens the \"Supported Languages\" panel.\n    * `supportedLanguagesPanelCtaClicked` - call this each time the user clicks the \"set up connection/binding\" CTA button in the \"Supported Languages\" panel.\n\n# 10.46\n\n## New features\n\n* Introduce `org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.PluginRpcService`, accessible via `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer#getPluginService`.\n  * Use `getPluginStatuses` to populate the \"Supported Languages\" panel with the full list of known analyzer statuses (one entry per language, including unsupported ones).\n  * Pass a `configurationScopeId` to get statuses in the context of its bound connection, or `null` for standalone statuses.\n  * Each `org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.PluginStatusDto` contains: plugin name, state (`org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.PluginStateDto`), source (`org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.ArtifactSourceDto`), actual version, and overridden version if applicable.\n* Add a new `didChangePluginStatuses` notification to `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient`.\n  * Implement it to keep the \"Supported Languages\" panel up to date after the initial load.\n  * Called when plugin statuses change, e.g. after a sync with a connection or when a connection is removed.\n\n# 10.44\n\n## Breaking changes\n\n* Add `closedByUser` flag to `org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowMessageRequestResponse`. It must be set to `true` if the `showMessageRequest` was explicitly closed by the user (e.g. via clicking X on the notification)\n\n# 10.43\n\n## Breaking changes\n\n* Remove the flight recorder feature. This should be handled by clients\n\n# 10.42\n\n* Add `org.sonarsource.sonarlint.core.rpc.client.Sloop.getPid` to return the pid of the backend process.\n* Add a new `PROMOTIONAL_CAMPAIGNS` capability in `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability`. Clients using the feature need to declare it at initialization time. Enables promotional campaign notifications and tracking user responses to not show again or postpone showing them. Data about shown notifications and user responses is saved separately for each IDE in the similar manner to telemetry in path: `{sonarUserHome}/campaigns/{productKey}/campaigns`\n* Add a new `KIRO` value to `org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.AiAgent` enum.\n\n# 10.39\n\n## Breaking changes\n\n* Remove the `org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.DependencyRiskRpcService.getDependencyRiskDetails` method and associated DTOs.\n\n# 10.38\n\n## New features\n\n* Add a new `GESSIE_TELEMETRY` capability in `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability`. Clients using the feature need to declare it at initialization time. Enables sending data to Gessie (Generic Event System) alongside previous telemetry implementation.\n\n# 10.37\n\n## Deprecation\n\n* Deprecate 4-parameter constructor and remove deprecation of 2-parameter one of `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams`. Move back to an old constructor as not all IDEs were able to provide all data to a new one.\n* Remove Deprecation from `org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.TelemetryRpcService#addedManualBindings` method. It should be used again for manual binding events instead of parametrized `didUpdateBinding`.\n\n## New features\n\n* Add ide labs flags to `org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.TelemetryClientLiveAttributesResponse`.\n* Introduce 2 new methods to `org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.TelemetryRpcService` to record Ide Labs telemetry: `externalLinkClicked`, `feedbackLinkClicked`.\n* Introduce a new `org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.TelemetryRpcService.acceptedBindingSuggestion`. It should be used to for bindings created based on suggestions and pass `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin` instead of parametrized `didUpdateBinding`. \n\n# 10.36\n\n## Breaking Changes\n\n* `org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.AiAssistedIdeRpcService` has been renamed to `org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.AiAgentService`.\n* `org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.AiAssitedIde` has been renamed to `org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.AiAgent`.\n* `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer#getAiAssistedIdeRpcService` has been renamed to `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer#getAiAgentService`.\n* Replace `VSCODE` and `VSCODE_INSIDERS` with `GITHUB_COPILOT` in `org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.AiAgent` enum.\n    * This better reflects that the distinction is about the AI agent (GitHub Copilot), not the IDE\n\n## New features\n\n* Introduce a new `org.sonarsource.sonarlint.core.rpc.protocol.backend.labs.IdeLabsRpcService` service and a `joinIdeLabsProgram` method.\n  * Use it to allow users to join the SonarQube for IDE Labs program\n  * The method accepts user email and IDE name as parameters\n\n# 10.35\n\n## Breaking changes\n\n* Remove the `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.FeatureFlagsDto` class.\n* Remove unused methods from `org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService`:\n  * `getGlobalStandaloneConfiguration`\n  * `getGlobalConnectedConfiguration`\n  * `getAnalysisConfig`\n  * `getRuleDetails`\n\n## New features\n\n* Add a new `CONTEXT_GENERATION` value in `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability`. Clients using the feature need to declare it at initialization time. This is only accessible in dogfooding environments, and should be enabled in AI-related environments.\n* Introduce a new `org.sonarsource.sonarlint.core.rpc.protocol.backend.log.LogRpcService` service, with a new `setLogLevel` method. This allows clients to dynamically change the logging level.\n* Introduce a new constructor in `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams`, that accepts a `LogLevel` parameter.\n\n## Deprecation\n\n* Deprecate the `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams` previous constructor. Please use the new one and provide the log level.\n\n# 10.33\n\n## New features\n\n* Add a new endpoint `/sonarlint/api/analysis/automatic/config` in the embedded server to globally disable or enable the automatic analysis.\n  * This endpoint should be used by external clients such as MCP servers.\n* Add a new endpoint `/sonarlint/api/analysis/files` in our embedded server to analyze a list of files and return the issues, hotspots and taints found. \n  * This endpoint should be used by external clients such as MCP servers.\n* Add a new `getMCPServerConfiguration` method to `org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.ConnectionRpcService`\n  * It accepts `connectionId` and `token` as parameters\n  * It returns JSON string containing MCP server settings (without the `sonarqube` parent item)\n* Introduce a new `org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.AiAssistedIdeRpcService` service and a `getRuleFileContent` method.\n  * Use it to retrieve the content of the rule file to write to provide guidance to the agent when using the SonarQube MCP server.\n* Introduce an RPC notification `embeddedServerStarted` in `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient`\n  * It is sent by the backend to notify the client that the embedded server has started\n  * It contains the embedded server port\n  * Example usage is by the MCP Server to establish the bridge connection\n* Add a new `org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.TelemetryRpcService.mcpIntegrationEnabled` method.\n  * Should only be used by SonarQube MCP Server when integration with SQ:IDE is enabled and valid\n\n# 10.31\n\n## New features\n\n* Add a new `FLIGHT_RECORDER` value in `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability`. Clients using the feature need to declare it at initialization time. Important note: the `MONITORING` capability is also required by this feature.\n* Add a new optional backend-to-client notification `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient#flightRecorderStarted`. Clients can implement this notification to inform end users about a starting flight recorder session.\n* Add a new service  to the backend API: `org.sonarsource.sonarlint.core.rpc.protocol.backend.flightrecorder.FlightRecordingRpcService` can be used to interact with the flight recorder (e.g. to capture a thread dump of the current backend process)\n* Add a new `CURRENT_FILE_ANALYSIS_TYPE` to the `org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AnalysisReportingType` enum. This value can be used when reporting telemetry for forced analysis of currently open file.\n\n# 10.29\n\n## New features\n* Clients can now access more granular origin information via the `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionDto`, `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.connection.ConnectionSuggestionDto` and `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.AssistBindingParams`.\n* Added `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingMode` and `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin` to `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams` and allowed clients to provide this information when calling `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.ConfigurationRpcService#didUpdateBinding` which will trigger telemetry events for binding updates. Clients no longer need to call separate telemetry methods while adding bindings.\n\n## Potential breaking changes\n\n* Clients are normally not expected to use following constructors directly, but if they do perhaps in their tests, it's a potential breaking change.\n* Removed the `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionDto` constructor with `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionDto#isFromSharedConfiguration` parameter. Clients should use the `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionDto#origin` parameter if it is used in their tests. `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin.SHARED_CONFIGURATION` can be used if it was true, otherwise it wouldn't matter which other origin used, they can default to `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin.PROJECT_NAME`\n* Removed the `org.sonarsource.sonarlint.core.rpc.protocol.client.connection.ConnectionSuggestionDto` constructor with `org.sonarsource.sonarlint.core.rpc.protocol.client.connection.ConnectionSuggestionDto#isFromSharedConfiguration` parameter. Clients should use the `org.sonarsource.sonarlint.core.rpc.protocol.client.connection.ConnectionSuggestionDto#origin` parameter if it is used in their tests. `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin.SHARED_CONFIGURATION` can be used if it was true, otherwise it wouldn't matter which other origin used, they can default to `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin.PROJECT_NAME`\n* Removed the `org.sonarsource.sonarlint.core.rpc.protocol.client.binding.AssistBindingParams` constructor with `org.sonarsource.sonarlint.core.rpc.protocol.client.binding.AssistBindingParams#isFromSharedConfiguration` parameter. Clients should use the `org.sonarsource.sonarlint.core.rpc.protocol.client.binding.AssistBindingParams#origin` parameter if it is used in their tests. `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin.SHARED_CONFIGURATION` can be used if it was true, otherwise it wouldn't matter which other origin used, they can default to `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin.PROJECT_NAME`\n\n## Deprecation\n\n* Deprecate the `org.sonarsource.sonarlint.core.rpc.protocol.client.connection.ConnectionSuggestionDto#isFromSharedConfiguration` method since it is not used anymore. Use `org.sonarsource.sonarlint.core.rpc.protocol.client.connection.ConnectionSuggestionDto#getOrigin` instead.\n* Deprecate the `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionDto#isFromSharedConfiguration` method since it is not used anymore. Use `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionDto#getOrigin` instead.\n* Deprecate the `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.AssistBindingParams#isFromSharedConfiguration` method since it is not used anymore. Use `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.AssistBindingParams#getOrigin` instead.\n* Deprecate `isFromSharedConfiguration` parameter from `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.AssistBindingParams`.\n* Deprecate the `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.AssistBindingParams` constructor. Use the other one.\n* Deprecate the `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams` constructor. Use the other one.\n* Deprecate the `org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.TelemetryRpcService#addedManualBindings` method. This will be automatically handled by passing the `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin` and `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingMode` to the `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams` constructor during the `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.ConfigurationRpcService#didUpdateBinding` call.\n* Deprecate the `org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.TelemetryRpcService#addedImportedBindings` method. This will be automatically handled by passing the `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin` and `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingMode` to the `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams` constructor during the `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.ConfigurationRpcService#didUpdateBinding` call.\n* Deprecate the `org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.TelemetryRpcService#addedAutomaticBindings` method. This will be automatically handled by passing the `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin` and `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingMode` to the `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams` constructor during the `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.ConfigurationRpcService#didUpdateBinding` call.\n\n# 10.28\n\n## Breaking changes\n\n* Fields `vulnerabilityId` and `description` from `org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.GetDependencyRiskDetailsResponse` are now nullable.\n  * Dependency risks of type `PROHIBITED_LICENSE` do not have a vulnerability ID or description.\n\n## New features\n\n* Add `TEXT` language to `org.sonarsource.sonarlint.core.commons.api.SonarLanguage`\n  * Enabling this language allows detecting [text issues](https://rules.sonarsource.com/text/)\n* Add `GITHUBACTIONS` language to `org.sonarsource.sonarlint.core.commons.api.SonarLanguage`\n  * Enabling this language allows detecting [issues on GitHub Actions](https://rules.sonarsource.com/githubactions/)\n* Add new method `org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.DependencyRiskRpcService#checkSupported` to check if dependency risks are supported by the server\n  * It returns the reason in case the server does not support dependency risks\n\n## SCA\n\n* Introduce new fields `vulnerabilityId` and `cvssScore` in `org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.DependencyRiskDto`\n  * The `vulnerabilityId` is a unique identifier for the vulnerability such as `CVE-1234`, and the `cvssScore` is the Common Vulnerability Scoring System score for the vulnerability\n  * They are null in case the dependency risk is of type `PROHIBITED_LICENSE`\n\n# 10.27\n\n## Breaking changes\n\n* Merge `org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.DependencyRiskTrackingRpcService` into `org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.DependencyRiskRpcService`.\n* Rename `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient.didChangeScaIssues` to `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient.didChangeDependencyRisks`.\n\n## New features\n\n* Allow changing status of dependency risks (SCA issues) via `org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.DependencyRiskRpcService.changeStatus`.\n  * Required parameters are `configScopeId`, `dependencyRiskKey` and `transition`.\n  * If transition is `ACCEPT` or `SAFE`, a `comment` field is mandatory\n* Allow clients to open dependency risk in browser\n  * Introduce `org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.DependencyRiskRpcService.openDependencyRiskInBrowser` that accepts `configScopeId` and `dependencyRiskKey` (UUID) parameters\n* Allow clients to record interactions with dependency risks in telemetry\n  * Introduce `org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.TelemetryRpcService.dependencyRiskInvestigatedLocally` method\n* Add a new `org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.DependencyRiskRpcService.getDependencyRiskDetails`.\n\n# 10.26\n\n## New features\n\n* Add a new `SCA_SYNCHRONIZATION` value in `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability`. Clients using the feature need to declare it at initialization time.\n* Introduce a new `org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.ScaIssueTrackingRpcService` service and a `listAll` method\n* Introduce a new `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient.didChangeScaIssues` notification.\n\n# 10.25\n\n## Breaking changes\n\n* Add a new `ISSUE_STREAMING` value in `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability`. Clients using the feature need to declare it at initialization time.\n\n## New features\n\n* Add a new optional parameter to `org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth.HelpGenerateUserTokenParams` to track SonarQube Cloud account creation through token generation.\n\n## File exclusions\n\n* The RPC client method `org.sonarsource.sonarlint.core.rpc.protocol.backend.file.getFilesStatus` previously returned information exclusively about server exclusions. It now includes the same exclusion criteria as used during the analysis (client exclusions, gitignore, etc.).\n\n# 10.23\n\n## Deprecation\n\n* Deprecate the `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient#matchProjectBranch` method since it is not used anymore.\n\n# 10.22\n\n## New features\n\n* Add a new `org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.TelemetryRpcService.toolCalled` method.\n* Add a new `org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.ConnectionRpcService.getConnectionSuggestions` method.\n  * This method is used to get connection suggestions for a given configuration scope.\n  * The method should only be used when neither connection nor binding exists for a given configuration scope.\n  * If a connection already exists, a `org.sonarsource.sonarlint.core.rpc.protocol.backend.binding.BindingRpcService.getBindingSuggestions` should be used instead\n\n# 10.20\n\n## Breaking changes\n\n* Remove `org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth.UserTokenRpcService` and `org.sonarsource.sonarlint.core.rpc.impl.SonarLintRpcServerImpl.getUserTokenService`. They were not useful anymore.\n* Remove the `org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth.HelpGenerateUserTokenParams` deprecated constructor. Use the other one.\n* Remove the `org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.ConnectionRpcService#checkSmartNotificationsSupported` method and associated parameter and response. They were not useful anymore.\n* Remove the `org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarCloudConnectionDto` deprecated constructor. Use the other one.\n* Remove the `org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarCloudConnectionConfigurationDto` deprecated constructor. Use the other one.\n* Remove the `org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.GetEffectiveRuleDetailsParams` deprecated constructor. Use the other one.\n* Remove the `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.SonarCloudAlternativeEnvironmentDto` deprecated constructors. Use the other one.\n* Remove the `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.ClientConstantInfoDto` deprecated constructor and the `getPid` getter. They were not useful anymore.\n* Remove the `org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.GetOrganizationParams` deprecated constructor. Use the other one.\n\n## New features\n\n* Add a new constructor to `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams` constructor to provide flags as an enum values list.\n* Add `analysisReportingTriggered` method to `org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.TelemetryRpcService`\n  * It is used to report usage of analysis features such as the analysis of VCS changed files, all project files or for the pre-commit analysis\n  * Should not be implemented unless necessary\n\n## Deprecation\n\n* Deprecate `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.FeatureFlagsDto` class.\n\n# 10.19\n\n## Deprecation\n\n* Deprecate the `org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams` constructor, and replace it with a new one without the `startTime` parameter, that is no longer relevant.\n\n# 10.17\n\n## New features\n\n* Add a new constructor to `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.SonarCloudAlternativeEnvironmentDto` to accept a map of `SonarCloudRegion` to `SonarQubeCloudRegionDto`.\n    *  Per region a `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.SonarQubeCloudRegionDto` must be provided that contains the *base*, *API* and *WebSocket* URIs.\n    * `null` values are accepted for every URI - it will internally fallback to the actual region URIs for a `null` value encountered.\n\n## Deprecation\n\n* Deprecate the `org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.ConnectionRpcService.checkSmartNotificationsSupported` method. It always returns that notifications are supported.\n\n# 10.16\n\n## New features\n\n* Introduce a new `org.sonarsource.sonarlint.core.rpc.protocol.backend.remediation.aicodefix.AiCodeFixRpcService` class, containing a `suggestFix(SuggestFixParams)` method.\n* Introduce a new `isAiCodeFixable` method in `org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto`.\n\n## Deprecation\n\n* Deprecate the `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.SonarCloudAlternativeEnvironmentDto` constructor. It is replaced by an overload in which the new API base URL should be provided.\n\n# 10.14\n\n## Breaking changes\n\n* Add new method `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient#invalidToken` to notify client that WebAPI calls to SQS/SQC fails due to wrong token\n    * Client can implement this method to offer user to change credentials for the connection to fix the problem\n    * For now notification is being sent only for 401 Unauthorized HTTP response code since it's corresponds to malformed/wrong token and ignores 403 Forbidden response code since it's a user permissions problem that has to be addressed on the server\n    * Also once notification sent, backend doesn't attempt to send any requests to server anymore until credentials changed\n* Add `region` to `org.sonarsource.sonarlint.core.rpc.protocol.client.connection.SonarCloudConnectionParams` and `org.sonarsource.sonarlint.core.rpc.protocol.client.connection.SonarCloudConnectionSuggestionDto` to support multi-region SQC connection configuration\n    * Constructor without region parameter is removed\n\n* Removed `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient#didRaiseIssue` and associated types. See `raiseIssues` and `raiseHotspots` instead.\n* Removed `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer#getIssueTrackingService` and associated types. Tracking is managed by the backend.\n* Removed `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer#getSecurityHotspotMatchingService` and associated types. Tracking is managed by the backend.\n* Removed `org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionParams#getServerUrl()`. Use `getConnectionParams` instead.\n* Removed `org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService#analyzeFiles`. Use `analyzeFilesAndTrack` instead.\n* Removed deprecated methods in `org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedFindingDto`:\n  * `getSeverity`\n  * `getType`\n  * `getCleanCodeAttribute`\n  * `getImpacts`\n  * Use `getSeverityMode` instead.\n* Removed deprecated methods in `org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityDto`:\n  * `getSeverity`\n  * `getType`\n  * `getCleanCodeAttribute`\n  * `getImpacts`\n  * Use `getSeverityMode` instead.\n\n## New features\n\n* Add SonarCloud region parameter to \n  * `org.sonarsource.sonarlint.core.rpc.protocol.client.connection.SonarCloudConnectionParams` \n  * `org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarCloudConnectionDto`\n  * `org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarCloudConnectionConfigurationDto`\n  * `org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.FuzzySearchUserOrganizationsParams`\n  * `org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.GetOrganizationParams`\n  * `org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.ListUserOrganizationsParams` \n  * This is in order to support multi-region SQC connection configuration. Constructors without region parameter are deprecated\n* `org.sonarsource.sonarlint.core.commons.monitoring.MonitoringService#newTrace(String, String)` can be used internally\n  to initialize a manual trace in Sentry\n* When monitoring is enabled, 1% of all analysis requests are sent to Sentry's performance tracing feature \n* Two new system properties can be used to tune the behavior of the Sentry integration:\n  * `sonarlint.internal.monitoring.dsn` overrides the default [DSN](https://docs.sentry.io/concepts/key-terms/dsn-explainer/) (e.g. for tests)\n  * `sonarlint.internal.monitoring.tracesSampleRate`, parsed as a `java.lang.Double`, overrides the default sampling rate of analysis requests\n\n# 10.13\n\n## Breaking changes\n\n* New feature flag `enableMonitoring` in `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.FeatureFlagsDto` allows clients to opt into monitoring with Sentry\n\n## New features\n\n* Introduce `org.sonarsource.sonarlint.core.rpc.protocol.backend.dogfooding.DogfoodingRpcService.isDogfoodingEnvironment` method to allow clients to know if it is running in a dogfooding environment\n  * Will return `true` if `SONARSOURCE_DOGFOODING` environment variable is set and equals `\"1\"`\n  * Will return `false` in all other cases\n* Introduce opt-in monitoring via Sentry\n  * As a first step, the monitoring service is only initialized in dogfooding environments when the feature flag is set\n  * All logging events sent to the client at the `ERROR` level are reported as monitoring events\n\n# 10.12\n\n## Breaking changes\n\n* Adapt `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams.languageSpecificRequirements to accept org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.JsTsRequirementsDto` instead of `clientNodeJsPath`\n\n## New features\n\n* Introduce `bundlePath` initialization parameter in `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.JsTsRequirementsDto` to allow clients to provide the path to the unzipped es-lint bridge bundle\n  * The path will be passed down to the Js/Ts/CSS analyzer and will indicate that the analyzer does not need to unzip the bundle itself, thus reducing the usage of the `.sonarlint` temporary storage\n  * Provide `null` to keep the previous behavior\n\n# 10.11\n\n## Breaking changes\n\n* Signature of `org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidUpdateFileSystemParams#DidUpdateFileSystemParams` was changed\n  * Parameter `addedOrChangedFiles` was split into `addedFiles` and `changedFiles`\n* Removed parameter `branch` and `pullRequest` from `org.sonarsource.sonarlint.core.rpc.protocol.client.issue.IssueDetailsDto` as it should not be used anymore by the client.\n\n# 10.10\n\n## New features\n\n* Introduce a new method `org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService.shouldUseEnterpriseCSharpAnalyzer` to allow clients to know what kind of C# analyzer should be used for the analysis.\n  * The method returns a boolean value indicating whether the enterprise C# analyzer should be used or not\n  * The method returns `true` if a binding exists for config scope AND the related connected server has the enterprise C# plugin (`csharpenterprise`) installed\n  * The method returns `true` if binding exists with a SonarQube version < 10.8 (i.e. SQ versions that do not include repackaged dotnet analyzer) OR SonarCloud\n  * The method returns `false` in standalone mode or if connected to non-commercial edition of SonarQube with a version >= 10.8\n* Inject the relevant C# analyzer to analysis engines based on the above and share the path to the analyzer JAR as an analysis property for the OmniSharp plugin.\n\n## Breaking changes\n\n* Add two new constructor arguments to `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.OmnisharpRequirementsDto` for clients to declare the paths to the Open-Source and Enterprise C# analyzers.\n\n# 10.9\n\n## New features\n\n* A new attribute `severityMode` has been added to `org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedFindingDto`\n  and `org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityDto` that automatically contains either `StandardModeDetails` or `MQRModeDetails`\n  * A new type `StandardModeDetails` has been introduced, which contains information about severity and type\n  * A new type `MQRModeDetails` has been introduced, which contains information about clean code attribute and impacts\n  * You should display the finding accordingly to the information contained by `severityMode`\n* A new method `IssueRpcService#getEffectiveIssueDetails` has been added to the backend to allow clients to retrieve detailed information about an issue\n  * The method accepts a configuration scope ID and an issue ID (UUID) as parameters\n  * The method returns a `GetEffectiveIssueDetailsResponse` object containing the detailed information about the issue\n  * It is preferred to use this method instead of the `RulesRpcService#getEffectiveRuleDetails` when retrieving rule description details in the context of a specific issue, as this new method will provide more precise information based on the issue, like issue impacts & customized issue severity\n\n## Breaking changes\n\n* Remove the `org.sonarsource.sonarlint.core.serverconnection.ServerPathProvider` class.\n* Remove `severity` and `type` fields from `org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleDefinitionDto` as this class is only used for fetching standalone rule details, which should always have the Clean Code Attribute and Impacts\n\n## Deprecation\n\n* The following attributes have been deprecated from `org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedFindingDto` and\n  `org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityDto`, you should now use the new attribute `severityMode`\n  * `severity`\n  * `type`\n  * `cleanCodeAttribute`\n  * `impacts`\n\n# 10.7.1\n\n## Breaking changes\n\n* Add a new method to `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient#matchProjectBranch` allowing the backend to check whether the locally checked-out branch matches a requesting server branch\n\n# 10.7\n\n## Breaking changes\n\n* Signature of `org.sonarsource.sonarlint.core.rpc.client.SonarLintRpcClientDelegate#raiseHotspots` was changed\n  * Parameter `issuesByFileUri` has been rightfully replaced by `hotspotsByFileUri`\n  * This is purely a naming change, there is no functional impact\n\n## New features\n\n* Add return value `GetForcedNodeJsResponse` to `org.sonarsource.sonarlint.core.rpc.client.SonarLintRpcClientDelegate#didChangeClientNodeJsPath` indicating whether\n  the Node.js path is effective or not. If that's the case, the path and the version will be returned. \n  * It's not mandatory to use this return value. It is used by some IDEs to show the current Node.js version used.\n* Add a new system property `sonarlint.debug.active.rules` to log active rules in verbose mode when triggering an analysis\n\n# 10.6\n\n## Breaking changes\n\n* Signature of `org.sonarsource.sonarlint.core.rpc.client.SonarLintRpcClientDelegate#noBindingSuggestionFound` was changed\n  * Replaced parameter with `org.sonarsource.sonarlint.core.rpc.protocol.client.binding.NoBindingSuggestionFoundParams`\n  * Former parameter `projectKey` can now be accessed by `params.getProjectKey()`\n* Removed deprecated constructors from `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams`\n\n## New features\n\n* Add a field to `org.sonarsource.sonarlint.core.rpc.protocol.common.NoBindingSuggestionFoundParams` indicating whether the suggestion where\n  no binding was found by is SonarCloud or not, can be used to display a more precise notification in the IDE rather than a generic one\n* Add a signature to `SloopLauncher.start`, allowing clients to add custom JVM arguments to the start of the process\n\n# 10.4\n\n## Breaking changes\n\n* Add new `isUserDefined` parameter into `org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto`\n  * User-defined files will be included in the analysis. Non-user-defined files such as generated or library files will be excluded from\n    analysis when analysis is triggered by the backend. If the analysis was forced by the client, exclusions are not respected.\n\n* Introduce a new parameter in the constructor of `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.FeatureFlagsDto`: `canOpenFixSuggestion`.\n  * This flag lets clients completely disable the opening a fix suggestion in the IDE, which can be useful if the feature is not yet available in the client.\n* Introduce a new initialization parameter `TelemetryMigrationDto` to `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams`\n  * The parameter is nullable and should be used only by the SLVS to migrate its telemetry. All other clients should provide `null` as a value.\n\n## New features\n\n* Add a method to `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient` to allow the backend to request client-defined file\n  exclusions from the client before every standalone analysis.\n  * `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient#getFileExclusions` to request file exclusions\n\n* Add a field to `org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto` to allow the backend to distinguish non-user-defined\n  files to exclude from analysis\n\n* Add `showFixSuggestion` method to `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient`\n  * It's only available when the feature flag `canOpenFixSuggestion` is enabled\n  * When using this method, you will receive a single fix suggestion for a specific issue that should be displayed to the user\n  * The user should have the possibility to accept or decline the fix suggestion\n  * The fix suggestion can be displayed at different locations in the file\n\n* Add `fixSuggestionResolved` method to `org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.TelemetryRpcService`\n  * You should use this method whenever a fix suggestion has been accepted or declined\n  * If the fix has multiple changes (snippets), you should call the method once for each\n  * The `indexSnippet` should be filled if possible, it corresponds to the snippet index in the list of changes\n  * If you do not know if the fix was accepted or declined at the snippet level, you should call the method once for the whole fix\n\n### File events\n\n* Add the `didOpenFile` and `didCloseFile` methods to `org.sonarsource.sonarlint.core.rpc.protocol.backend.file.FileRpcService`.\n  * Clients are supposed to call these methods when a file is opened in the editor or closed.\n\n### Analysis\n\n* Add a new constructor in `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams` to let clients provide if automatic analysis is enabled.\n* Add a new `didChangeAutomaticAnalysisSetting` method in `org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService`\n  * Clients are expected to call it whenever users change the \"enable automatic analysis\" setting.\n* Add new methods to `org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService` to force analysis\n  * `analyzeFullProject` forces analysis all files of the project that was provided to backend by method `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient#listFiles`\n  * `analyzeFileList` forces analysis for the provided set of files\n  * `analyzeOpenFiles` forces analysis of all files that were reported as opened using `org.sonarsource.sonarlint.core.rpc.protocol.backend.file.FileRpcService#didOpenFile`\n  * `analyzeVCSChangedFiles` forces analysis of modified and not committed files\n\n# 10.3.2 \n\n## Breaking changes\n\n* Change `disabledLanguagesForAnalysis` parameter of `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams` introduced in 10.3 version to `disabledPluginKeysForAnalysis`\n  * Analysis will be disabled for plugins specified in `disabledPluginKeysForAnalysis` but it will be still possible to consume Rule Descriptions\n  * Can be null or empty if clients do not wish to disable analysis for any loaded plugin\n\n# 10.3\n\n## Breaking changes\n\n* Add new `disabledLanguagesForAnalysis` parameter into `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams`\n  * Analysis will be disabled for languages specified `disabledLanguagesForAnalysis` but it will be still possible to consume Rule Descriptions\n  * Can be null or empty if clients do not wish to disable analysis for any loaded plugin\n\n## New features\n\n* Add a method to `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient` to allow backend to request inferred analysis properties from the client before every analysis. It's important because properties may change depending on files being analysed.\n  * `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient#getInferredAnalysisProperties` to request inferred properties\n* Add a method to the `org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService` to let the client notify the backend with user defined analysis properties\n  * `org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService#didSetUserAnalysisProperties` to set user defined properties\n* For analysis, both user-defined and inferred properties will be merged. If the same property is inferred by the client and provided by the user - the inferred value will be used for analysis.\n\n### Open Issue in IDE\n\n* Add the `getConnectionParams` method to `org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionParams`\n  * It allows clients to get parameters to create either SonarQube or SonarCloud connection\n  * This field type is `Either<AssistSonarQubeConnection, AssistSonarCloudConnection>`\n  * Common methods of both connection types are added to the `AssistCreatingConnectionParams` class to provide users simplicity\n\n## Deprecation\n\n* Deprecate `isSonarCloud` parameter from `org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth.HelpGenerateUserTokenParams`\n  * This value on no longer needed on the backend side.\n\n* `org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionParams.getServerUrl` is only meaningful for SQ\n  connections. Use `getConnection().getLeft().getServerUrl()` instead to get the `serverUrl` of a SQ connection\n\n* The existing constructor in `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams` is now deprecated, the newly added constructor should be used instead (see above).\n\n# 10.2\n\n## Breaking changes\n\n* `org.sonarsource.sonarlint.core.rpc.client.SonarLintRpcClientDelegate#didDetectSecret` had no `configScopeId` parameter, it was added\n\n## New features\n\n### Analysis and tracking\n\n* Add the `analyzeFilesAndTrack` method to `org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService`.\n  * It allows clients to submit files for analysis, let the backend deal with issue tracking, and will lead to a later notification via `raiseIssues` and `raiseHotspots` (see below).\n  * Usages of `org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService#analyzeFiles` should be replaced by this new method.\n  * It accepts a `AnalyzeFilesAndTrackParams` object instead of the deprecated `AnalyzeFilesParams`. The extra flag `shouldFetchServerIssues` should be set to `true` when the analysis is triggered in response to a file open event.\n  * When using this method, implementation of the `didRaiseIssue` method of `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient` is no longer required. The new `raiseIssues` and `raiseHotspots` methods should be implemented instead (see below).\n\n* Add `raiseIssues` method to `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient` to report tracked issues.\n  * Will be called by the backend when issues should be raised to the user. The UI should be updated accordingly.\n  * The `org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaiseIssuesParams` class contains a list of issues to raise by file URI.\n  * Each raised issue went through issue tracking, and has potentially been matched with a previously known issue and/or a server issue in connected mode.\n  * This new method reports a collection of issues replacing the ones previously raised. Every call contains the full list of known issues.\n\n* Add `raiseHotspots` method to `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient` to report tracked Security Hotspots.\n  * Will be called by the backend when hotspots should be raised to the user. The UI should be updated accordingly.\n  * The `org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaiseHotspotsParams` class contains a list of hotspots to raise by file URI.\n  * Each raised hotspot went through hotspot tracking, and has potentially been matched with a previously known hotspot and/or a server hotspot in connected mode.\n  * This new method reports a collection of hotspots replacing the ones previously raised. Every call contains the full list of known hotspots.\n\n* Add `getRawIssues` method to `org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesResponse`\n  * It allows clients to get raised issues in the analysis response.\n  * This method is temporarily added and will be removed when the deprecated APIs have been dropped.\n\n## Deprecation\n\n* `org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService#analyzeFiles` and the underlying DTOs are deprecated, should be replaced by `analyzeFilesAndTrack`.\n* As a consequence, `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient.didRaiseIssue` and the underlying DTOs are now deprecated. It should be replaced by `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient.raiseIssues` and `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient.raiseHotspots`.\n* `org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.IssueTrackingRpcService` and the underlying DTOs are deprecated, the functionality is now handled by `analyzeFilesAndTrack`.\n* `org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.SecurityHotspotMatchingRpcService` and the underlying DTOs are deprecated, the functionality is now handled by `analyzeFilesAndTrack`.\n* `org.sonarsource.sonarlint.core.client.legacy.analysis.SonarLintAnalysisEngine` and all classes from the `sonarlint-java-client-legacy` module are now deprecated. Analysis should happen via `org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService#analyzeFilesAndTrack`, and the `sonarlint-java-client-legacy` artifact will soon be removed.\n* The `pid` parameter of the `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.ClientConstantInfoDto` constructor is not used anymore (the backend PID is used instead). The constructor is now deprecated, and a new constructor without this parameter was introduced and should be used. The `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.ClientConstantInfoDto.getPid` method is not used anymore and also deprecated.\n\n# 10.1\n\n## Breaking changes\n\n* Replace the last constructor parameter of `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams`.\n  * Clients should provide an instance of `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.LanguageSpecificRequirements`.\n  * The previous Node.js path parameter is now part of this new `LanguageSpecificRequirements`, together with configuration related to Omnisharp.\n  * For clients not executing analysis via the backend, or not supporting C#, a `null` value can be passes as the 2nd parameter of the `LanguageSpecificRequirements` constructor\n\n* Introduce a new parameter in the constructor of `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.ClientConstantInfoDto`.\n  * Clients should provide the PID of the host process.\n  * For clients not executing analysis via the backend, this parameter is not used, so a dummy value can be provided.\n\n* Introduce a new parameter in the constructor of `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.FeatureFlagsDto`: `enableTelemetry`.\n  * This flag lets clients completely disable the telemetry, which can be useful when using Sloop in the context of tests.\n  * The flag replaces the `sonarlint.telemetry.disabled` system property.\n  * For clients that want to keep the same behavior, they can read the system property on the client side and pass it to the `FeatureFlagsDto` constructor.\n\n* Stop leaking LSP4J types in API (SLCORE-663) and wrap them in SonarLint classes instead\n  * `org.eclipse.lsp4j.jsonrpc.messages.Either` replaced by `org.sonarsource.sonarlint.core.rpc.protocol.common.Either`\n  * `org.eclipse.lsp4j.jsonrpc.CancelChecker` replaced by `org.sonarsource.sonarlint.core.rpc.client.SonarLintCancelChecker`\n\n* Add new client method `org.sonarsource.sonarlint.core.rpc.client.SonarLintRpcClientDelegate#suggestConnection`.\n  * This method is used when binding settings are found for an unknown connection.\n  * Clients are expected to notify users about these.\n\n* Move class `org.sonarsource.sonarlint.core.rpc.protocol.backend.usertoken.RevokeTokenParams` to `org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth.RevokeTokenParams`.\n\n* Introduce a new parameter in the constructor of `org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto`.\n  * Clients can provide a detected language for the file. This is the opportunity to rely on the IDE's detected type.\n  * This is used for analysis, clients can pass `null` to keep the same behavior as before, or if no language was detected.\n\n## New features\n\n### Connected mode: sharing setup\n\n* Add methods to `org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.TelemetryRpcService` to track binding creation.\n  * `addedManualBindings` should be called by clients when a new binding is created manually.\n  * `addedImportedBindings` should be called by clients when a binding is created from a shared connected mode configuration file.\n  * `addedAutomaticBindings` should be called by clients when a binding is created using a suggestion from a server analysis settings files.\n\n* Add `isFromSharedConfiguration` field to `org.sonarsource.sonarlint.core.rpc.protocol.client.binding.AssistBindingParams` and `org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionDto`.\n  * This field tells the client whether the binding suggestion comes from a shared connected mode configuration file (`true`) or from analysis settings files (`false`).\n\n### Analysis\n\n* Add the `analyzeFiles` method in `org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService`.\n  * Clients can use this method to trigger an analysis. It's a request, so they can get a response with details about the analysis.\n  * They need to pass the list of URIs representing files to analyze.\n  * They also need to pass an \"analysis ID\", which is a unique ID used for correlating the analysis and issues that are raised via `didRaiseIssue` (see below).\n\n* Add the `didRaiseIssue` method in `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient`.\n  * This lets clients be informed when an issue is detected during analysis.\n  * They can do local tracking or stream the issue to the users.\n  * They can retrieve which analysis lead to this issue being raised with the \"analysis ID\" correlation ID.\n\n* Add the `didSkipLoadingPlugin` method in `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient`.\n  * This is called after an analysis when a plugin was not loaded.\n  * Clients are expected to notify users about these.\n\n* Add the `didDetectSecret` method in `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient`.\n  * This is called after an analysis when a secret was detected in one of the analyzed files.\n  * Clients are expected to notify users about these.\n  * The backend does not keep track of any notification regarding secrets detection. Clients will need to manage some cache to avoid notifying users too often.\n\n* Add the `promoteExtraEnabledLanguagesInConnectedMode` method in `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient`.\n  * This is called after an analysis in standalone mode when a language enabled only in connected mode was detected.\n  * Clients are expected to notify users about these.\n  * The backend does not keep track of any notification regarding this promotion. Clients will need to manage some cache to avoid notifying users too often.\n\n* Add the `getBaseDir` method in `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient`.\n  * This is called during an analysis to determine the base directory for the files being analyzed.\n  * Clients are expected to implement this request if they support analysis via the backend.\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "                   GNU LESSER GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\n  This version of the GNU Lesser General Public License incorporates\nthe terms and conditions of version 3 of the GNU General Public\nLicense, supplemented by the additional permissions listed below.\n\n  0. Additional Definitions.\n\n  As used herein, \"this License\" refers to version 3 of the GNU Lesser\nGeneral Public License, and the \"GNU GPL\" refers to version 3 of the GNU\nGeneral Public License.\n\n  \"The Library\" refers to a covered work governed by this License,\nother than an Application or a Combined Work as defined below.\n\n  An \"Application\" is any work that makes use of an interface provided\nby the Library, but which is not otherwise based on the Library.\nDefining a subclass of a class defined by the Library is deemed a mode\nof using an interface provided by the Library.\n\n  A \"Combined Work\" is a work produced by combining or linking an\nApplication with the Library.  The particular version of the Library\nwith which the Combined Work was made is also called the \"Linked\nVersion\".\n\n  The \"Minimal Corresponding Source\" for a Combined Work means the\nCorresponding Source for the Combined Work, excluding any source code\nfor portions of the Combined Work that, considered in isolation, are\nbased on the Application, and not on the Linked Version.\n\n  The \"Corresponding Application Code\" for a Combined Work means the\nobject code and/or source code for the Application, including any data\nand utility programs needed for reproducing the Combined Work from the\nApplication, but excluding the System Libraries of the Combined Work.\n\n  1. Exception to Section 3 of the GNU GPL.\n\n  You may convey a covered work under sections 3 and 4 of this License\nwithout being bound by section 3 of the GNU GPL.\n\n  2. Conveying Modified Versions.\n\n  If you modify a copy of the Library, and, in your modifications, a\nfacility refers to a function or data to be supplied by an Application\nthat uses the facility (other than as an argument passed when the\nfacility is invoked), then you may convey a copy of the modified\nversion:\n\n   a) under this License, provided that you make a good faith effort to\n   ensure that, in the event an Application does not supply the\n   function or data, the facility still operates, and performs\n   whatever part of its purpose remains meaningful, or\n\n   b) under the GNU GPL, with none of the additional permissions of\n   this License applicable to that copy.\n\n  3. Object Code Incorporating Material from Library Header Files.\n\n  The object code form of an Application may incorporate material from\na header file that is part of the Library.  You may convey such object\ncode under terms of your choice, provided that, if the incorporated\nmaterial is not limited to numerical parameters, data structure\nlayouts and accessors, or small macros, inline functions and templates\n(ten or fewer lines in length), you do both of the following:\n\n   a) Give prominent notice with each copy of the object code that the\n   Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the object code with a copy of the GNU GPL and this license\n   document.\n\n  4. Combined Works.\n\n  You may convey a Combined Work under terms of your choice that,\ntaken together, effectively do not restrict modification of the\nportions of the Library contained in the Combined Work and reverse\nengineering for debugging such modifications, if you also do each of\nthe following:\n\n   a) Give prominent notice with each copy of the Combined Work that\n   the Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the Combined Work with a copy of the GNU GPL and this license\n   document.\n\n   c) For a Combined Work that displays copyright notices during\n   execution, include the copyright notice for the Library among\n   these notices, as well as a reference directing the user to the\n   copies of the GNU GPL and this license document.\n\n   d) Do one of the following:\n\n       0) Convey the Minimal Corresponding Source under the terms of this\n       License, and the Corresponding Application Code in a form\n       suitable for, and under terms that permit, the user to\n       recombine or relink the Application with a modified version of\n       the Linked Version to produce a modified Combined Work, in the\n       manner specified by section 6 of the GNU GPL for conveying\n       Corresponding Source.\n\n       1) Use a suitable shared library mechanism for linking with the\n       Library.  A suitable mechanism is one that (a) uses at run time\n       a copy of the Library already present on the user's computer\n       system, and (b) will operate properly with a modified version\n       of the Library that is interface-compatible with the Linked\n       Version.\n\n   e) Provide Installation Information, but only if you would otherwise\n   be required to provide such information under section 6 of the\n   GNU GPL, and only to the extent that such information is\n   necessary to install and execute a modified version of the\n   Combined Work produced by recombining or relinking the\n   Application with a modified version of the Linked Version. (If\n   you use option 4d0, the Installation Information must accompany\n   the Minimal Corresponding Source and Corresponding Application\n   Code. If you use option 4d1, you must provide the Installation\n   Information in the manner specified by section 6 of the GNU GPL\n   for conveying Corresponding Source.)\n\n  5. Combined Libraries.\n\n  You may place library facilities that are a work based on the\nLibrary side by side in a single library together with other library\nfacilities that are not Applications and are not covered by this\nLicense, and convey such a combined library under terms of your\nchoice, if you do both of the following:\n\n   a) Accompany the combined library with a copy of the same work based\n   on the Library, uncombined with any other library facilities,\n   conveyed under the terms of this License.\n\n   b) Give prominent notice with the combined library that part of it\n   is a work based on the Library, and explaining where to find the\n   accompanying uncombined form of the same work.\n\n  6. Revised Versions of the GNU Lesser General Public License.\n\n  The Free Software Foundation may publish revised and/or new versions\nof the GNU Lesser General Public License from time to time. Such new\nversions will be similar in spirit to the present version, but may\ndiffer in detail to address new problems or concerns.\n\n  Each version is given a distinguishing version number. If the\nLibrary as you received it specifies that a certain numbered version\nof the GNU Lesser General Public License \"or any later version\"\napplies to it, you have the option of following the terms and\nconditions either of that published version or of any later version\npublished by the Free Software Foundation. If the Library as you\nreceived it does not specify a version number of the GNU Lesser\nGeneral Public License, you may choose any version of the GNU Lesser\nGeneral Public License ever published by the Free Software Foundation.\n\n  If the Library as you received it specifies that a proxy can decide\nwhether future versions of the GNU Lesser General Public License shall\napply, that proxy's public statement of acceptance of any version is\npermanent authorization for you to choose that version for the\nLibrary.\n"
  },
  {
    "path": "NOTICE.txt",
    "content": "SonarLint Core\nCopyright (C) SonarSource Sàrl\nmailto:info AT sonarsource DOT com\n\nThis product includes software developed at\nSonarSource (http://www.sonarsource.com/).\n"
  },
  {
    "path": "README.md",
    "content": "SonarLint Core\n==============\nThe core library to run SonarQube for IDE analysis (used by SonarQube for IDE Eclipse, IntelliJ and VS), as well as the SonarQube for IDE language server (used by SonarQube for IDE VSCode) with the goal of helping developers deliver [integrated code quality and security](https://www.sonarsource.com/solutions/for-developers/).\n\n[![Build Status](https://github.com/SonarSource/sonarlint-core/actions/workflows/build.yml/badge.svg)](https://github.com/SonarSource/sonarlint-core/actions)\n[![Quality Gate Status](https://next.sonarqube.com/sonarqube/api/project_badges/measure?project=org.sonarsource.sonarlint.core%3Asonarlint-core-parent&metric=alert_status)](https://next.sonarqube.com/sonarqube/dashboard?id=org.sonarsource.sonarlint.core%3Asonarlint-core-parent)\n\nHave Questions or Feedback?\n---------------------------\n\nFor SonarQube for IDE support questions (\"How do I?\", \"I got this error, why?\", ...), please first read the [FAQ](https://community.sonarsource.com/t/frequently-asked-questions/7204) and then head to the [SonarSource forum](https://community.sonarsource.com/c/help/sl). There are chances that a question similar to yours has already been answered. \n\nBe aware that this forum is a community, so the standard pleasantries (\"Hi\", \"Thanks\", ...) are expected. And if you don't get an answer to your thread, you should sit on your hands for at least three days before bumping it. Operators are not standing by. :-)\n\n\nContributing\n------------\n\nIf you would like to see a new feature, please create a new thread in the forum [\"Suggest new features\"](https://community.sonarsource.com/c/suggestions/features).\n\nPlease be aware that we are not actively looking for feature contributions. The truth is that it's extremely difficult for someone outside SonarSource to comply with our roadmap and expectations. Therefore, we typically only accept minor cosmetic changes and typo fixes.\n\nWith that in mind, if you would like to submit a code contribution, please create a pull request for this repository. Please explain your motives to contribute this change: what problem you are trying to fix, what improvement you are trying to make.\n\nMake sure that you follow our [code style](https://github.com/SonarSource/sonar-developer-toolset#code-style) and all tests are passing (Travis build is executed for each pull request).\n\nBuilding\n--------\n\nTo build sources locally follow these instructions.\n\n### Build and Run Unit Tests\n\n#### Prerequisites\n\nSome medium tests load plugins relying on Node.js, so make sure the latest LTS version is installed and `node` is in the PATH.\n\nExecute from the project base directory:\n\n    mvn verify\n\n### Run integration tests\n\nSee [Running Integration Tests](its/README.md)\n\nDocumentation\n-------------\n\nHave a look at the documentation [here](spec/README.adoc).\n\nLicense\n-------\n\nCopyright SonarSource.\n\nLicensed under the [GNU Lesser General Public License, Version 3.0](http://www.gnu.org/licenses/lgpl.txt)\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Reporting Security Issues\n\nA mature software vulnerability treatment process is a cornerstone of a robust information security management system. Contributions from the community play an important role in the evolution and security of our products, and in safeguarding the security and privacy of our users.\n\nIf you believe you have discovered a security vulnerability in Sonar's products, we encourage you to report it immediately.\n\nTo responsibly report a security issue, please email us at [security@sonarsource.com](mailto:security@sonarsource.com). Sonar’s security team will acknowledge your report, guide you through the next steps, or request additional information if necessary. Customers with a support contract can also report the vulnerability directly through the support channel.\n\nFor security vulnerabilities found in third-party libraries, please also contact the library's owner or maintainer directly.\n\n## Responsible Disclosure Policy\n\nFor more information about disclosing a security vulnerability to Sonar, please refer to our community post: [Responsible Vulnerability Disclosure](https://community.sonarsource.com/t/responsible-vulnerability-disclosure/9317)."
  },
  {
    "path": "backend/analysis-engine/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-backend-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../pom.xml</relativePath>\n  </parent>\n  <artifactId>sonarlint-analysis-engine</artifactId>\n  <name>SonarLint Core - Analysis Engine</name>\n  <description>Run analysis</description>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.code.findbugs</groupId>\n      <artifactId>jsr305</artifactId>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-plugin-commons</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-commons</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>commons-codec</groupId>\n      <artifactId>commons-codec</artifactId>\n    </dependency>\n\n    <!-- Tests -->\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-engine</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.assertj</groupId>\n      <artifactId>assertj-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-junit-jupiter</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.awaitility</groupId>\n      <artifactId>awaitility</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>ch.qos.logback</groupId>\n      <artifactId>logback-classic</artifactId>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-dependency-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>copy-open-source-plugins-for-mediumtests</id>\n            <phase>generate-test-resources</phase>\n            <goals>\n              <goal>copy</goal>\n            </goals>\n            <configuration>\n              <artifactItems>\n                <artifactItem>\n                  <groupId>org.sonarsource.python</groupId>\n                  <artifactId>sonar-python-plugin</artifactId>\n                  <version>5.18.0.31561</version>\n                  <type>jar</type>\n                </artifactItem>\n              </artifactItems>\n              <outputDirectory>${project.build.directory}/plugins</outputDirectory>\n              <overWriteReleases>false</overWriteReleases>\n              <overWriteSnapshots>true</overWriteSnapshots>\n              <stripVersion>false</stripVersion>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n    </plugins>\n  </build>\n\n  <profiles>\n    <!-- Workaround for https://issues.apache.org/jira/projects/MJAR/issues/MJAR-138 -->\n    <profile>\n      <id>conditionally-add-commons-tests-if-tests-not-skipped</id>\n      <activation>\n        <property>\n          <name>maven.test.skip</name>\n          <value>!true</value>\n        </property>\n      </activation>\n      <dependencies>\n        <dependency>\n          <groupId>${project.groupId}</groupId>\n          <artifactId>sonarlint-commons</artifactId>\n          <version>${project.version}</version>\n          <classifier>tests</classifier>\n          <type>test-jar</type>\n          <scope>test</scope>\n        </dependency>\n      </dependencies>\n    </profile>\n  </profiles>\n</project>\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/AnalysisQueue.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.PriorityQueue;\nimport java.util.function.Predicate;\nimport java.util.stream.Stream;\nimport org.sonarsource.sonarlint.core.analysis.command.AnalyzeCommand;\nimport org.sonarsource.sonarlint.core.analysis.command.Command;\nimport org.sonarsource.sonarlint.core.analysis.command.NotifyModuleEventCommand;\nimport org.sonarsource.sonarlint.core.analysis.command.ResetPluginsCommand;\nimport org.sonarsource.sonarlint.core.analysis.command.UnregisterModuleCommand;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\nimport static java.util.Map.entry;\n\npublic class AnalysisQueue {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  public static final String ANALYSIS_EXPIRATION_DELAY_PROPERTY_NAME = \"sonarqube.ide.internal.analysis.expiration.delay\";\n  private static final Duration ANALYSIS_EXPIRATION_DEFAULT_DELAY = Duration.ofMinutes(1);\n  private final Duration analysisExpirationDelay = getAnalysisExpirationDelay();\n\n  private final PriorityQueue<QueuedCommand> queue = new PriorityQueue<>(new CommandComparator());\n\n  public synchronized void post(Command command) {\n    queue.add(new QueuedCommand(command));\n    LOG.debug(\"Posting command in analysis queue: {}, new size is {}\", command, queue.size());\n    notifyAll();\n  }\n\n  public synchronized void wakeUp() {\n    notifyAll();\n  }\n\n  public synchronized List<Command> removeAll() {\n    var pendingTasks = new ArrayList<>(queue);\n    queue.clear();\n    return pendingTasks.stream().map(QueuedCommand::getCommand).toList();\n  }\n\n  public synchronized Command takeNextCommand() throws InterruptedException {\n    while (true) {\n      var firstReadyCommand = pollNextReadyCommand();\n      if (firstReadyCommand.isPresent()) {\n        var queuedCommand = firstReadyCommand.get();\n        LOG.debug(\"Picked command from the queue: {}, {} remaining\", queuedCommand.command, queue.size());\n        return tidyUp(queuedCommand);\n      }\n      // wait for a new command to come in\n      wait();\n    }\n  }\n\n  public synchronized void clearAllButAnalysesAndResets() {\n    removeAll(queuedCommand -> !(queuedCommand.command instanceof AnalyzeCommand) && !(queuedCommand.command instanceof ResetPluginsCommand));\n  }\n\n  private Optional<QueuedCommand> pollNextReadyCommand() {\n    var commandsToKeep = new ArrayList<QueuedCommand>();\n    // cannot use iterator as priority order is not guaranteed\n    while (!queue.isEmpty()) {\n      var candidateCommand = queue.poll();\n      if (candidateCommand.command.shouldCancelQueue()) {\n        candidateCommand.command.cancel();\n        LOG.debug(\"Not picking next command {}, is canceled\", candidateCommand.command);\n      } else {\n        if (candidateCommand.command.isReady()) {\n          queue.addAll(commandsToKeep);\n          return Optional.of(candidateCommand);\n        }\n        LOG.debug(\"Not picking next command {}, is not ready\", candidateCommand.command);\n        commandsToKeep.add(candidateCommand);\n      }\n    }\n    queue.addAll(commandsToKeep);\n    return Optional.empty();\n  }\n\n  private Command tidyUp(QueuedCommand nextCommand) {\n    cleanUpExpiredCommands(nextCommand);\n    return batchAutomaticAnalyses(nextCommand.command);\n  }\n\n  private void cleanUpExpiredCommands(QueuedCommand nextQueuedCommand) {\n    var notReadyCommands = removeAll(queuedCommand -> !queuedCommand.command.isReady() && queuedCommand.getQueuedTime().plus(analysisExpirationDelay).isBefore(Instant.now()));\n    if (!notReadyCommands.isEmpty()) {\n      LOG.debug(\"Canceling {} not ready analyses\", notReadyCommands.size());\n    }\n    if (nextQueuedCommand.command instanceof UnregisterModuleCommand unregisterCommand) {\n      var expiredCommands = removeAll(\n        queuedCommand -> (queuedCommand.command instanceof AnalyzeCommand analyzeCommand && analyzeCommand.getModuleKey().equals(unregisterCommand.getModuleKey()))\n          || queuedCommand.command instanceof NotifyModuleEventCommand);\n      if (!expiredCommands.isEmpty()) {\n        LOG.debug(\"Canceling {} analyses expired by module unregistration\", expiredCommands.size());\n      }\n    }\n  }\n\n  private Command batchAutomaticAnalyses(Command nextCommand) {\n    if (nextCommand instanceof AnalyzeCommand analyzeCommand && analyzeCommand.getTriggerType().canBeBatchedWithSameTriggerType()) {\n      var removedCommands = (List<AnalyzeCommand>) removeAll(otherQueuedCommand -> canBeBatched(analyzeCommand, otherQueuedCommand.command));\n      if (removedCommands.isEmpty()) {\n        return analyzeCommand;\n      }\n      LOG.debug(\"Batching {} analyses\", removedCommands.size() + 1);\n      return Stream.concat(Stream.of(analyzeCommand), removedCommands.stream())\n        .sorted((c1, c2) -> (int) (c1.getSequenceNumber() - c2.getSequenceNumber()))\n        .reduce(AnalyzeCommand::mergeWith)\n        // this last clause should never occur\n        .orElse(analyzeCommand);\n    }\n    return nextCommand;\n  }\n\n  private static boolean canBeBatched(AnalyzeCommand analyzeCommand, Command otherCommand) {\n    return otherCommand instanceof AnalyzeCommand otherAnalyzeCommand && otherAnalyzeCommand.getModuleKey().equals(analyzeCommand.getModuleKey())\n      && otherAnalyzeCommand.getTriggerType().canBeBatchedWithSameTriggerType();\n  }\n\n  private List<? extends Command> removeAll(Predicate<QueuedCommand> predicate) {\n    var iterator = queue.iterator();\n    var removedCommands = new ArrayList<Command>();\n    while (iterator.hasNext()) {\n      var queuedCommand = iterator.next();\n      if (predicate.test(queuedCommand)) {\n        iterator.remove();\n        queuedCommand.command.cancel();\n        removedCommands.add(queuedCommand.command);\n      }\n    }\n    return removedCommands;\n  }\n\n  private static class QueuedCommand {\n    private final Command command;\n    private final Instant queuedTime = Instant.now();\n\n    QueuedCommand(Command command) {\n      this.command = command;\n    }\n\n    public Command getCommand() {\n      return command;\n    }\n\n    public Instant getQueuedTime() {\n      return queuedTime;\n    }\n  }\n\n  private static class CommandComparator implements Comparator<QueuedCommand> {\n    private static final Map<Class<?>, Integer> COMMAND_TYPES_ORDERED = Map.ofEntries(\n      // reset commands should be pulled first from the queue, they cancel unregister and file event commands and make use of more up-to-date plugins for subsequent analyses\n      entry(ResetPluginsCommand.class, 0),\n      // then unregister commands might make file events and analyses irrelevant\n      entry(UnregisterModuleCommand.class, 1),\n      // then forwarding file events takes priority over analyses, to make sure they give more accurate results\n      entry(NotifyModuleEventCommand.class, 2),\n      // then analyses have the lowest priority\n      entry(AnalyzeCommand.class, 3));\n\n    @Override\n    public int compare(QueuedCommand queuedCommand, QueuedCommand otherQueuedCommand) {\n      var command = queuedCommand.command;\n      var otherCommand = otherQueuedCommand.command;\n      var commandRank = COMMAND_TYPES_ORDERED.get(command.getClass());\n      var otherCommandRank = COMMAND_TYPES_ORDERED.get(otherCommand.getClass());\n      return !Objects.equals(commandRank, otherCommandRank) ? (commandRank - otherCommandRank) :\n      // for same command types, respect insertion order\n        (int) (command.getSequenceNumber() - otherCommand.getSequenceNumber());\n    }\n  }\n\n  private static Duration getAnalysisExpirationDelay() {\n    try {\n      var analysisExpirationDelayFromSystemProperty = System.getProperty(ANALYSIS_EXPIRATION_DELAY_PROPERTY_NAME);\n      var parsedDelay = Duration.parse(analysisExpirationDelayFromSystemProperty);\n      LOG.debug(\"Overriding analysis expiration delay with value from system property: {}\", parsedDelay);\n      return parsedDelay;\n    } catch (RuntimeException e) {\n      LOG.debug(\"Using default analysis expiration delay: {}\", ANALYSIS_EXPIRATION_DEFAULT_DELAY);\n      return ANALYSIS_EXPIRATION_DEFAULT_DELAY;\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/AnalysisScheduler.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Supplier;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisSchedulerConfiguration;\nimport org.sonarsource.sonarlint.core.analysis.command.Command;\nimport org.sonarsource.sonarlint.core.analysis.command.ResetPluginsCommand;\nimport org.sonarsource.sonarlint.core.analysis.container.global.GlobalAnalysisContainer;\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.plugin.commons.LoadedPlugins;\n\npublic class AnalysisScheduler {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final Runnable CANCELING_TERMINATION = () -> {\n  };\n\n  private final AtomicReference<GlobalAnalysisContainer> globalAnalysisContainer = new AtomicReference<>();\n  private final AnalysisQueue analysisQueue = new AnalysisQueue();\n  private final Thread analysisThread = new Thread(this::executeQueuedCommands, \"sonarlint-analysis-scheduler\");\n  private final LogOutput logOutput;\n  private final AtomicReference<Runnable> termination = new AtomicReference<>();\n  private final AtomicReference<Command> executingCommand = new AtomicReference<>();\n\n  public AnalysisScheduler(AnalysisSchedulerConfiguration analysisGlobalConfig, LoadedPlugins loadedPlugins, @Nullable LogOutput logOutput) {\n    this.logOutput = logOutput;\n    // if the container cannot be started, the thread won't be started\n    var analysisContainer = new GlobalAnalysisContainer(analysisGlobalConfig, loadedPlugins);\n    analysisContainer.startComponents();\n    globalAnalysisContainer.set(analysisContainer);\n    analysisThread.start();\n  }\n\n  public void reset(Supplier<SchedulerResetConfiguration> pluginsWithConfigSupplier) {\n    post(new ResetPluginsCommand(globalAnalysisContainer, analysisQueue, pluginsWithConfigSupplier));\n  }\n\n  public void wakeUp() {\n    analysisQueue.wakeUp();\n  }\n\n  private void executeQueuedCommands() {\n    while (termination.get() == null) {\n      SonarLintLogger.get().setTarget(logOutput);\n      try {\n        var command = analysisQueue.takeNextCommand();\n        executingCommand.set(command);\n        if (termination.get() == CANCELING_TERMINATION) {\n          break;\n        }\n        executingCommand.get().execute(globalAnalysisContainer.get().getModuleRegistry());\n        executingCommand.set(null);\n      } catch (InterruptedException e) {\n        if (termination.get() != CANCELING_TERMINATION) {\n          LOG.error(\"Analysis engine interrupted\", e);\n        }\n      } catch (Exception e) {\n        LOG.debug(\"Analysis command failed\", e);\n      }\n    }\n    termination.get().run();\n  }\n\n  public void post(Command command) {\n    LOG.debug(\"Post: \" + Thread.currentThread().getName() + \" \" + Thread.currentThread().threadId());\n    LOG.debug(\"Posting command from Scheduler: \" + command);\n    if (termination.get() != null) {\n      LOG.error(\"Analysis engine stopping, ignoring command\");\n      command.cancel();\n      return;\n    }\n    if (!analysisThread.isAlive()) {\n      LOG.error(\"Analysis engine not started, ignoring command\");\n      command.cancel();\n      return;\n    }\n    var currentCommand = executingCommand.get();\n    if (currentCommand != null && command.shouldCancelPost(currentCommand)) {\n      LOG.debug(\"Cancelling queuing of command\");\n      currentCommand.cancel();\n    }\n    LOG.debug(\"Posting command from Scheduler to queue: \" + command);\n    analysisQueue.post(command);\n  }\n\n  public void stop() {\n    if (!analysisThread.isAlive()) {\n      return;\n    }\n    if (!termination.compareAndSet(null, CANCELING_TERMINATION)) {\n      // already terminating\n      return;\n    }\n    var command = executingCommand.getAndSet(null);\n    if (command != null) {\n      command.cancel();\n    }\n    analysisThread.interrupt();\n    analysisQueue.removeAll().forEach(Command::cancel);\n    globalAnalysisContainer.get().stopComponents();\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/SchedulerResetConfiguration.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisSchedulerConfiguration;\nimport org.sonarsource.sonarlint.core.plugin.commons.LoadedPlugins;\n\n/**\n * Pairs an {@link AnalysisSchedulerConfiguration} with the {@link LoadedPlugins} it was built\n * alongside, so both are always resolved atomically inside a\n * {@link org.sonarsource.sonarlint.core.analysis.command.ResetPluginsCommand}.\n */\npublic record SchedulerResetConfiguration(AnalysisSchedulerConfiguration config, LoadedPlugins plugins) {\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/AnalysisConfiguration.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.api;\n\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport javax.annotation.concurrent.Immutable;\nimport org.sonar.api.batch.rule.ActiveRule;\n\n@Immutable\npublic class AnalysisConfiguration {\n\n  private final List<ClientInputFile> inputFiles;\n  private final Map<String, String> extraProperties;\n  private final Path baseDir;\n  private final Collection<ActiveRule> activeRules;\n  private final String toString;\n\n  private AnalysisConfiguration(Builder builder) {\n    this.baseDir = builder.baseDir;\n    this.inputFiles = builder.inputFiles;\n    this.extraProperties = builder.extraProperties;\n    this.activeRules = builder.activeRules;\n    this.toString = generateToString();\n  }\n\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  public Map<String, String> extraProperties() {\n    return extraProperties;\n  }\n\n  public Path baseDir() {\n    return baseDir;\n  }\n\n  public List<ClientInputFile> inputFiles() {\n    return inputFiles;\n  }\n\n  public Collection<ActiveRule> activeRules() {\n    return activeRules;\n  }\n\n  @Override\n  public String toString() {\n    return toString;\n  }\n\n  private String generateToString() {\n    var sb = new StringBuilder();\n    sb.append(\"[\\n\");\n    generateToStringCommon(sb);\n    generateToStringActiveRules(sb);\n    generateToStringInputFiles(sb);\n    sb.append(\"]\\n\");\n    return sb.toString();\n  }\n\n  protected void generateToStringActiveRules(StringBuilder sb) {\n    if (\"true\".equals(System.getProperty(\"sonarlint.debug.active.rules\"))) {\n      sb.append(\"  activeRules: \").append(activeRules).append(\"\\n\");\n    } else {\n      // Group active rules by language and count occurrences\n      var languageCounts = new HashMap<String, Integer>();\n      for (var rule : activeRules) {\n        var languageKey = rule.ruleKey().toString().split(\":\")[0];\n        languageCounts.put(languageKey, languageCounts.getOrDefault(languageKey, 0) + 1);\n      }\n\n      sb.append(\"  activeRules: [\");\n      languageCounts.forEach((language, count) -> sb.append(count).append(\" \").append(language).append(\", \"));\n      if (!languageCounts.isEmpty()) {\n        // Remove the trailing comma and space\n        sb.setLength(sb.length() - 2);\n      }\n      sb.append(\"]\\n\");\n    }\n  }\n\n  protected void generateToStringCommon(StringBuilder sb) {\n    sb.append(\"  baseDir: \").append(baseDir()).append(\"\\n\");\n    sb.append(\"  extraProperties: \").append(extraProperties()).append(\"\\n\");\n  }\n\n  protected void generateToStringInputFiles(StringBuilder sb) {\n    sb.append(\"  inputFiles: [\\n\");\n    for (ClientInputFile inputFile : inputFiles()) {\n      sb.append(\"    \").append(inputFile.uri());\n      sb.append(\" (\").append(getCharsetLabel(inputFile)).append(\")\");\n      if (inputFile.isTest()) {\n        sb.append(\" [test]\");\n      }\n      var language = inputFile.language();\n      if (language != null) {\n        sb.append(\" [\").append(language.getSonarLanguageKey()).append(\"]\");\n      }\n      sb.append(\"\\n\");\n    }\n    sb.append(\"  ]\\n\");\n  }\n\n  private static String getCharsetLabel(ClientInputFile inputFile) {\n    var charset = inputFile.getCharset();\n    return charset != null ? charset.displayName() : \"default\";\n  }\n\n  public static final class Builder {\n    private final List<ClientInputFile> inputFiles = new ArrayList<>();\n    private final Map<String, String> extraProperties = new HashMap<>();\n    private Path baseDir;\n    private final Collection<ActiveRule> activeRules = new ArrayList<>();\n\n    private Builder() {\n    }\n\n    public Builder addInputFiles(ClientInputFile... inputFiles) {\n      Collections.addAll(this.inputFiles, inputFiles);\n      return this;\n    }\n\n    public Builder addInputFiles(Collection<? extends ClientInputFile> inputFiles) {\n      this.inputFiles.addAll(inputFiles);\n      return this;\n    }\n\n    public Builder addInputFile(ClientInputFile inputFile) {\n      this.inputFiles.add(inputFile);\n      return this;\n    }\n\n    public Builder putAllExtraProperties(Map<String, String> extraProperties) {\n      extraProperties.forEach(this::putExtraProperty);\n      return this;\n    }\n\n    public Builder putExtraProperty(String key, String value) {\n      this.extraProperties.put(key.trim(), value);\n      return this;\n    }\n\n    public Builder setBaseDir(Path baseDir) {\n      this.baseDir = baseDir;\n      return this;\n    }\n\n    public Builder addActiveRules(ActiveRule... activeRules) {\n      Collections.addAll(this.activeRules, activeRules);\n      return this;\n    }\n\n    public Builder addActiveRules(Collection<? extends ActiveRule> activeRules) {\n      this.activeRules.addAll(activeRules);\n      return this;\n    }\n\n    public Builder addActiveRule(ActiveRule activeRules) {\n      this.activeRules.add(activeRules);\n      return this;\n    }\n\n    public AnalysisConfiguration build() {\n      return new AnalysisConfiguration(this);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/AnalysisResults.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.api;\n\nimport java.time.Duration;\nimport java.util.Collection;\nimport java.util.LinkedHashMap;\nimport java.util.LinkedHashSet;\nimport java.util.Map;\nimport java.util.Set;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\n\npublic class AnalysisResults {\n  private final Set<ClientInputFile> failedAnalysisFiles = new LinkedHashSet<>();\n  private final Map<ClientInputFile, SonarLanguage> languagePerFile = new LinkedHashMap<>();\n  private Duration duration = Duration.ZERO;\n\n  public void addFailedAnalysisFile(ClientInputFile inputFile) {\n    failedAnalysisFiles.add(inputFile);\n  }\n\n  /**\n   * Detected languages for each file.\n   * The values in the map can be null if no language was detected for some files.\n   */\n  public Map<ClientInputFile, SonarLanguage> languagePerFile() {\n    return languagePerFile;\n  }\n\n  public void setLanguageForFile(ClientInputFile file, @Nullable SonarLanguage language) {\n    this.languagePerFile.put(file, language);\n  }\n\n  /**\n   * Input files for which there were analysis errors. The analyzers failed to correctly handle these files, and therefore there might be issues\n   * missing or no issues at all for these files.\n   */\n  public Collection<ClientInputFile> failedAnalysisFiles() {\n    return failedAnalysisFiles;\n  }\n\n  public Duration getDuration() {\n    return duration;\n  }\n\n  public void setDuration(Duration duration) {\n    this.duration = duration;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/AnalysisSchedulerConfiguration.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.api;\n\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.function.Function;\nimport javax.annotation.Nullable;\nimport javax.annotation.concurrent.Immutable;\n\n@Immutable\npublic class AnalysisSchedulerConfiguration {\n\n  private static final String NODE_EXECUTABLE_PROPERTY = \"sonar.nodejs.executable\";\n\n  private final Path workDir;\n  private final Map<String, String> extraProperties;\n  private final Path nodeJsPath;\n  private final long clientPid;\n  private final Function<String, ClientModuleFileSystem> fileSystemProvider;\n\n  private AnalysisSchedulerConfiguration(Builder builder) {\n    this.workDir = builder.workDir;\n    this.extraProperties = new LinkedHashMap<>(builder.extraProperties);\n    this.nodeJsPath = builder.nodeJsPath;\n    this.clientPid = builder.clientPid;\n    this.fileSystemProvider = builder.fileSystemProvider;\n  }\n\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  public Path getWorkDir() {\n    return workDir;\n  }\n\n  public long getClientPid() {\n    return clientPid;\n  }\n\n  public Function<String, ClientModuleFileSystem> getFileSystemProvider() {\n    return fileSystemProvider;\n  }\n\n  public Map<String, String> getEffectiveSettings() {\n    Map<String, String> props = new HashMap<>(extraProperties);\n    if (nodeJsPath != null) {\n      props.put(NODE_EXECUTABLE_PROPERTY, nodeJsPath.toString());\n    }\n    return props;\n  }\n\n  public static final class Builder {\n    private Path workDir;\n    private Map<String, String> extraProperties = Collections.emptyMap();\n    private Path nodeJsPath;\n    private long clientPid;\n    private Function<String, ClientModuleFileSystem> fileSystemProvider = key -> null;\n\n    private Builder() {\n\n    }\n\n    /**\n     * Override default work dir (~/.sonarlint/work)\n     */\n    public Builder setWorkDir(Path workDir) {\n      this.workDir = workDir;\n      return this;\n    }\n\n    /**\n     * Properties that will be passed to global extensions\n     */\n    public Builder setExtraProperties(Map<String, String> extraProperties) {\n      this.extraProperties = extraProperties;\n      return this;\n    }\n\n    /**\n     * Set the location of the nodejs executable used by some analyzers.\n     */\n    public Builder setNodeJs(@Nullable Path nodeJsPath) {\n      this.nodeJsPath = nodeJsPath;\n      return this;\n    }\n\n    public Builder setClientPid(long clientPid) {\n      this.clientPid = clientPid;\n      return this;\n    }\n\n    public Builder setFileSystemProvider(Function<String, ClientModuleFileSystem> fileSystemProvider) {\n      this.fileSystemProvider = fileSystemProvider;\n      return this;\n    }\n\n    public AnalysisSchedulerConfiguration build() {\n      return new AnalysisSchedulerConfiguration(this);\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/ClientInputFile.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.api;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.nio.charset.Charset;\nimport javax.annotation.CheckForNull;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\n\n/**\n * InputFile as provided by client\n * @since 1.1\n */\npublic interface ClientInputFile {\n\n  /**\n   * The absolute file path. It needs to correspond to a file in the filesystem because some plugins don't use {@link #contents} \n   * or {@link inputStream} yet, and will attempt to access the file directly.\n   * @deprecated avoid calling this method if possible, since it may require to create a temporary copy of the file\n   */\n  @Deprecated\n  String getPath();\n\n  /**\n   * Flag an input file as test file. Analyzers may apply different rules on test files.\n   */\n  boolean isTest();\n\n  /**\n   * Charset to be used to read file content. If null it means the charset is unknown and analysis will likely use JVM default encoding to read the file.\n   */\n  @CheckForNull\n  Charset getCharset();\n\n  /**\n   * Language key of the file. If not null, language detection based on the file name suffix is skipped. The file will be analyzed by an analyzer that can\n   * handle the language.\n   */\n  @CheckForNull\n  default SonarLanguage language() {\n    return null;\n  }\n\n  /**\n   * Allow clients to pass their own object to ease mapping back to IDE file.\n   */\n  <G> G getClientObject();\n\n  /**\n   *  Gets a stream of the contents of the file.\n   */\n  InputStream inputStream() throws IOException;\n\n  /**\n   *  Gets the contents of the file. \n   */\n  String contents() throws IOException;\n\n  /**\n   * Logical relative path with '/' separators. Used to apply SonarLintPathPatterns and by some analyzers. Example: 'src/main/java/Foo.java'.\n   * Can be project relative path when it makes sense.\n   */\n  String relativePath();\n\n  /**\n   * URI to uniquely identify this file.\n   */\n  URI uri();\n\n  default boolean isDirty() {\n    return false;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/ClientInputFileEdit.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.api;\n\nimport java.util.List;\n\npublic class ClientInputFileEdit {\n  private final ClientInputFile target;\n  private final List<TextEdit> textEdits;\n\n  public ClientInputFileEdit(ClientInputFile target, List<TextEdit> textEdits) {\n    this.target = target;\n    this.textEdits = textEdits;\n  }\n\n  public ClientInputFile target() {\n    return target;\n  }\n\n  public List<TextEdit> textEdits() {\n    return textEdits;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/ClientModuleFileEvent.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.api;\n\nimport org.sonarsource.sonarlint.plugin.api.module.file.ModuleFileEvent;\n\npublic class ClientModuleFileEvent {\n  private final ClientInputFile target;\n\n  private final ModuleFileEvent.Type type;\n\n  private ClientModuleFileEvent(ClientInputFile target, ModuleFileEvent.Type type) {\n    this.target = target;\n    this.type = type;\n  }\n\n  public static ClientModuleFileEvent of(ClientInputFile target, ModuleFileEvent.Type type) {\n    return new ClientModuleFileEvent(target, type);\n  }\n\n  public ClientInputFile target() {\n    return target;\n  }\n\n  public ModuleFileEvent.Type type() {\n    return type;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/ClientModuleFileSystem.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.api;\n\nimport java.util.stream.Stream;\nimport org.sonar.api.batch.fs.InputFile;\n\npublic interface ClientModuleFileSystem {\n  Stream<ClientInputFile> files(String suffix, InputFile.Type type);\n  Stream<ClientInputFile> files();\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/ClientModuleInfo.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.api;\n\npublic class ClientModuleInfo {\n  private final String key;\n  private final ClientModuleFileSystem clientFileSystem;\n\n  public ClientModuleInfo(String key, ClientModuleFileSystem clientFileSystem) {\n    this.key = key;\n    this.clientFileSystem = clientFileSystem;\n  }\n\n  public String key() {\n    return key;\n  }\n\n  public ClientModuleFileSystem fileSystem() {\n    return clientFileSystem;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/DefaultLocation.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.api;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonar.api.batch.fs.TextRange;\n\npublic class DefaultLocation implements IssueLocation {\n  private final String message;\n  private final ClientInputFile inputFile;\n  private final org.sonarsource.sonarlint.core.commons.api.TextRange textRange;\n\n  public DefaultLocation(@Nullable ClientInputFile inputFile, @Nullable TextRange textRange, @Nullable String message) {\n    this.textRange = textRange != null ? WithTextRange.convert(textRange) : null;\n    this.inputFile = inputFile;\n    this.message = message;\n  }\n\n  @Override\n  public ClientInputFile getInputFile() {\n    return inputFile;\n  }\n\n  @Override\n  public String getMessage() {\n    return message;\n  }\n\n  @CheckForNull\n  @Override\n  public org.sonarsource.sonarlint.core.commons.api.TextRange getTextRange() {\n    return textRange;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/Flow.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.api;\n\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport org.sonar.api.batch.sensor.issue.IssueLocation;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputFile;\n\npublic class Flow {\n  private final List<org.sonarsource.sonarlint.core.analysis.api.IssueLocation> locations;\n\n  public Flow(List<IssueLocation> issueLocations) {\n    this.locations = issueLocations.stream()\n      .map(i -> new DefaultLocation(\n        i.inputComponent().isFile() ? ((SonarLintInputFile) i.inputComponent()).getClientInputFile() : null,\n        i.textRange(),\n        i.message()))\n      .collect(Collectors.toList());\n  }\n\n  public List<org.sonarsource.sonarlint.core.analysis.api.IssueLocation> locations() {\n    return locations;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/Issue.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.api;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonar.api.batch.rule.ActiveRule;\nimport org.sonar.api.rule.RuleKey;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\n\npublic class Issue implements IssueLocation {\n  private final String primaryMessage;\n  private final ClientInputFile clientInputFile;\n  private final List<Flow> flows;\n  private final List<QuickFix> quickFixes;\n  private final Optional<String> ruleDescriptionContextKey;\n  private final TextRange textRange;\n  private final ActiveRule activeRule;\n  private final Map<SoftwareQuality, ImpactSeverity> overriddenImpacts;\n\n  public Issue(ActiveRule activeRule, @Nullable String primaryMessage, Map<SoftwareQuality, ImpactSeverity> overriddenImpacts, @Nullable org.sonar.api.batch.fs.TextRange textRange,\n    @Nullable ClientInputFile clientInputFile, List<Flow> flows, List<QuickFix> quickFixes, Optional<String> ruleDescriptionContextKey) {\n    this.activeRule = activeRule;\n    this.overriddenImpacts = overriddenImpacts;\n    this.textRange = Optional.ofNullable(textRange).map(WithTextRange::convert).orElse(null);\n    this.primaryMessage = primaryMessage;\n    this.clientInputFile = clientInputFile;\n    this.flows = flows;\n    this.quickFixes = quickFixes;\n    this.ruleDescriptionContextKey = ruleDescriptionContextKey;\n  }\n\n  public ActiveRule getActiveRule() {\n    return activeRule;\n  }\n\n  public RuleKey getRuleKey() {\n    return activeRule.ruleKey();\n  }\n\n  @Override\n  public String getMessage() {\n    return primaryMessage;\n  }\n\n  @Override\n  @CheckForNull\n  public ClientInputFile getInputFile() {\n    return clientInputFile;\n  }\n\n  public List<Flow> flows() {\n    return flows;\n  }\n\n  public List<QuickFix> quickFixes() {\n    return quickFixes;\n  }\n\n  @Override\n  @CheckForNull\n  public TextRange getTextRange() {\n    return textRange;\n  }\n\n  public Map<SoftwareQuality, ImpactSeverity> getOverriddenImpacts() {\n    return overriddenImpacts;\n  }\n\n  public Optional<String> getRuleDescriptionContextKey() {\n    return ruleDescriptionContextKey;\n  }\n\n  @Override\n  public String toString() {\n    var sb = new StringBuilder();\n    sb.append(\"[\");\n    sb.append(\"rule=\").append(getRuleKey());\n    if (textRange != null) {\n      var startLine = textRange.getStartLine();\n      sb.append(\", line=\").append(startLine);\n    }\n    if (clientInputFile != null) {\n      sb.append(\", file=\").append(clientInputFile.uri());\n    }\n    sb.append(\"]\");\n    return sb.toString();\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/IssueLocation.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.api;\n\nimport javax.annotation.CheckForNull;\n\npublic interface IssueLocation extends WithTextRange {\n\n  @CheckForNull\n  String getMessage();\n\n  /**\n   * @return null for global issues\n   */\n  @CheckForNull\n  ClientInputFile getInputFile();\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/QuickFix.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.api;\n\nimport java.util.List;\n\npublic class QuickFix {\n\n  private final List<ClientInputFileEdit> inputFileEdits;\n  private final String message;\n\n  public QuickFix(List<ClientInputFileEdit> inputFileEdits, String message) {\n    this.inputFileEdits = inputFileEdits;\n    this.message = message;\n  }\n\n  public List<ClientInputFileEdit> inputFileEdits() {\n    return inputFileEdits;\n  }\n\n  public String message() {\n    return message;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/TextEdit.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.api;\n\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\n\npublic class TextEdit {\n  private final TextRange range;\n  private final String newText;\n\n  public TextEdit(TextRange range, String newText) {\n    this.range = range;\n    this.newText = newText;\n  }\n\n  public TextRange range() {\n    return range;\n  }\n\n  public String newText() {\n    return newText;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/TriggerType.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.api;\n\npublic enum TriggerType {\n  AUTO(true),\n  FORCED(false);\n\n  private final boolean canBeBatchedWithSameTriggerType;\n\n  TriggerType(boolean canBeBatchedWithSameTriggerType) {\n    this.canBeBatchedWithSameTriggerType = canBeBatchedWithSameTriggerType;\n  }\n\n  public boolean canBeBatchedWithSameTriggerType() {\n    return canBeBatchedWithSameTriggerType;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/WithTextRange.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.api;\n\nimport javax.annotation.CheckForNull;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\n\npublic interface WithTextRange {\n\n  /**\n   * @return null for file level issues\n   */\n  @CheckForNull\n  TextRange getTextRange();\n\n  @CheckForNull\n  default Integer getStartLine() {\n    var textRange = getTextRange();\n    return textRange != null ? textRange.getStartLine() : null;\n  }\n\n  @CheckForNull\n  default Integer getStartLineOffset() {\n    var textRange = getTextRange();\n    return textRange != null ? textRange.getStartLineOffset() : null;\n  }\n\n  @CheckForNull\n  default Integer getEndLine() {\n    var textRange = getTextRange();\n    return textRange != null ? textRange.getEndLine() : null;\n  }\n\n  @CheckForNull\n  default Integer getEndLineOffset() {\n    var textRange = getTextRange();\n    return textRange != null ? textRange.getEndLineOffset() : null;\n  }\n\n  static TextRange convert(org.sonar.api.batch.fs.TextRange analyzerTextRange) {\n    return new TextRange(\n      analyzerTextRange.start().line(),\n      analyzerTextRange.start().lineOffset(),\n      analyzerTextRange.end().line(),\n      analyzerTextRange.end().lineOffset());\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/package-info.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.analysis.api;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/command/AnalyzeCommand.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.command;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.CompletionException;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisConfiguration;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisResults;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.analysis.api.Issue;\nimport org.sonarsource.sonarlint.core.analysis.api.TriggerType;\nimport org.sonarsource.sonarlint.core.analysis.container.global.ModuleRegistry;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.ProgressIndicator;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.commons.progress.TaskManager;\nimport org.sonarsource.sonarlint.core.commons.tracing.Trace;\n\nimport static org.sonarsource.sonarlint.core.commons.util.StringUtils.pluralize;\n\npublic class AnalyzeCommand extends Command {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final String moduleKey;\n  private final UUID analysisId;\n  private final TriggerType triggerType;\n  private final Supplier<AnalysisConfiguration> configurationSupplier;\n  private final Consumer<Issue> issueListener;\n  @Nullable\n  private final Trace trace;\n  private final CompletableFuture<AnalysisResults> futureResult;\n  private final SonarLintCancelMonitor cancelMonitor;\n  private final TaskManager taskManager;\n  private final Consumer<List<ClientInputFile>> analysisStarted;\n  private final Supplier<Boolean> isReadySupplier;\n  private final Set<URI> files;\n  private final Map<String, String> extraProperties;\n\n  public AnalyzeCommand(String moduleKey, UUID analysisId, TriggerType triggerType, Supplier<AnalysisConfiguration> configurationSupplier, Consumer<Issue> issueListener,\n    @Nullable Trace trace, SonarLintCancelMonitor cancelMonitor, TaskManager taskManager, Consumer<List<ClientInputFile>> analysisStarted, Supplier<Boolean> isReadySupplier,\n    Set<URI> files, Map<String, String> extraProperties) {\n    this(moduleKey, analysisId, triggerType, configurationSupplier, issueListener, trace, cancelMonitor, taskManager, analysisStarted, isReadySupplier, files, extraProperties,\n      new CompletableFuture<>());\n  }\n\n  public AnalyzeCommand(String moduleKey, UUID analysisId, TriggerType triggerType, Supplier<AnalysisConfiguration> configurationSupplier, Consumer<Issue> issueListener,\n    @Nullable Trace trace, SonarLintCancelMonitor cancelMonitor, TaskManager taskManager, Consumer<List<ClientInputFile>> analysisStarted, Supplier<Boolean> isReadySupplier,\n    Set<URI> files, Map<String, String> extraProperties, CompletableFuture<AnalysisResults> futureResult) {\n    this.moduleKey = moduleKey;\n    this.analysisId = analysisId;\n    this.triggerType = triggerType;\n    this.configurationSupplier = configurationSupplier;\n    this.issueListener = issueListener;\n    this.trace = trace;\n    this.cancelMonitor = cancelMonitor;\n    this.taskManager = taskManager;\n    this.analysisStarted = analysisStarted;\n    this.isReadySupplier = isReadySupplier;\n    this.files = files;\n    this.extraProperties = extraProperties;\n    this.futureResult = futureResult;\n\n    taskManager.trackNewTask(analysisId, cancelMonitor);\n  }\n\n  @Override\n  public boolean isReady() {\n    return isReadySupplier.get();\n  }\n\n  public String getModuleKey() {\n    return moduleKey;\n  }\n\n  public TriggerType getTriggerType() {\n    return triggerType;\n  }\n\n  public CompletableFuture<AnalysisResults> getFutureResult() {\n    return futureResult;\n  }\n\n  public Set<URI> getFiles() {\n    return files;\n  }\n\n  public Map<String, String> getExtraProperties() {\n    return extraProperties;\n  }\n\n  @Override\n  public void execute(ModuleRegistry moduleRegistry) {\n    try {\n      var configuration = configurationSupplier.get();\n      taskManager.runExistingTask(moduleKey, analysisId, \"Analyzing \" + pluralize(configuration.inputFiles().size(), \"file\"), null, true, true,\n        indicator -> execute(moduleRegistry, indicator, configuration), cancelMonitor);\n    } catch (Exception e) {\n      handleAnalysisFailed(e);\n    }\n  }\n\n  void execute(ModuleRegistry moduleRegistry, ProgressIndicator progressIndicator, AnalysisConfiguration configuration) {\n    try {\n      doExecute(moduleRegistry, progressIndicator, configuration);\n    } catch (Exception e) {\n      handleAnalysisFailed(e);\n    }\n  }\n\n  void doExecute(ModuleRegistry moduleRegistry, ProgressIndicator progressIndicator, AnalysisConfiguration analysisConfig) {\n    try {\n      LOG.info(\"Starting analysis with configuration: {}\", analysisConfig);\n      var analysisResults = doRunAnalysis(moduleRegistry, progressIndicator, analysisConfig);\n      futureResult.complete(analysisResults);\n    } catch (CompletionException e) {\n      handleAnalysisFailed(e.getCause());\n    } catch (Exception e) {\n      handleAnalysisFailed(e);\n    }\n  }\n\n  private void handleAnalysisFailed(Throwable throwable) {\n    LOG.error(\"Error during analysis\", throwable);\n    futureResult.completeExceptionally(throwable);\n  }\n\n  private AnalysisResults doRunAnalysis(ModuleRegistry moduleRegistry, ProgressIndicator progressIndicator, AnalysisConfiguration configuration) {\n    var startTime = System.currentTimeMillis();\n    analysisStarted.accept(configuration.inputFiles());\n    if (configuration.inputFiles().isEmpty()) {\n      LOG.info(\"No file to analyze\");\n      futureResult.complete(new AnalysisResults());\n      return new AnalysisResults();\n    }\n    var moduleContainer = moduleRegistry.getContainerFor(moduleKey);\n    Throwable originalException = null;\n    doIfTraceIsSet(t -> {\n      int filesCount = configuration.inputFiles().size();\n      t.setData(\"filesCount\", filesCount);\n      var fileSizes = new ArrayList<>(filesCount);\n      var languages = new HashSet<>();\n      configuration.inputFiles().forEach(f -> {\n        try {\n          fileSizes.add(f.contents().length());\n        } catch (IOException | IllegalStateException e) {\n          fileSizes.add(0);\n        }\n        Optional.ofNullable(f.language())\n          .map(SonarLanguage::getSonarLanguageKey)\n          .ifPresent(languages::add);\n      });\n      t.setData(\"fileSizes\", fileSizes);\n      t.setData(\"languages\", languages);\n      t.setData(\"activeRulesCount\", configuration.activeRules().size());\n\n    });\n    try {\n      var issueCounter = new AtomicInteger(0);\n      Consumer<Issue> issueCountingListener = issue -> {\n        issueCounter.incrementAndGet();\n        issueListener.accept(issue);\n      };\n      var result = moduleContainer.analyze(configuration, issueCountingListener, progressIndicator, trace);\n      doIfTraceIsSet(t -> {\n        t.setData(\"failedFilesCount\", result.failedAnalysisFiles().size());\n        t.setData(\"foundIssuesCount\", issueCounter.get());\n        t.finishSuccessfully();\n      });\n      result.setDuration(Duration.ofMillis(System.currentTimeMillis() - startTime));\n      return result;\n    } catch (Throwable e) {\n      originalException = e;\n      doIfTraceIsSet(t -> t.finishExceptionally(e));\n      throw e;\n    } finally {\n      try {\n        if (moduleContainer.isTransient()) {\n          moduleContainer.stopComponents();\n        }\n      } catch (Exception e) {\n        if (originalException != null) {\n          e.addSuppressed(originalException);\n        }\n        throw e;\n      }\n    }\n  }\n\n  private void doIfTraceIsSet(Consumer<Trace> action) {\n    if (trace != null) {\n      action.accept(trace);\n    }\n  }\n\n  public AnalyzeCommand mergeWith(AnalyzeCommand otherNewerAnalyzeCommand) {\n    var analysisConfiguration = configurationSupplier.get();\n    var newerAnalysisConfiguration = otherNewerAnalyzeCommand.configurationSupplier.get();\n    var mergedInputFiles = new ArrayList<>(newerAnalysisConfiguration.inputFiles());\n    var newInputFileUris = newerAnalysisConfiguration.inputFiles().stream().map(ClientInputFile::uri).collect(Collectors.toSet());\n    for (ClientInputFile inputFile : analysisConfiguration.inputFiles()) {\n      if (!newInputFileUris.contains(inputFile.uri())) {\n        mergedInputFiles.add(inputFile);\n      }\n    }\n    var mergedAnalysisConfiguration = AnalysisConfiguration.builder()\n      .addActiveRules(newerAnalysisConfiguration.activeRules())\n      .setBaseDir(newerAnalysisConfiguration.baseDir())\n      .putAllExtraProperties(newerAnalysisConfiguration.extraProperties())\n      .addInputFiles(mergedInputFiles)\n      .build();\n    return new AnalyzeCommand(moduleKey, analysisId, triggerType, () -> mergedAnalysisConfiguration, issueListener, trace, new SonarLintCancelMonitor(), taskManager,\n      analysisStarted, isReadySupplier, mergedInputFiles.stream().map(ClientInputFile::uri).collect(Collectors.toSet()), newerAnalysisConfiguration.extraProperties(),\n      futureResult);\n  }\n\n  @Override\n  public void cancel() {\n    if (!cancelMonitor.isCanceled()) {\n      cancelMonitor.cancel();\n    }\n    if (!futureResult.isCancelled()) {\n      futureResult.cancel(true);\n    }\n  }\n\n  @Override\n  public boolean shouldCancelPost(Command executingCommand) {\n    if (!(executingCommand instanceof AnalyzeCommand analyzeCommand)) {\n      return false;\n    }\n    if (cancelMonitor.isCanceled() || futureResult.isCancelled()) {\n      return true;\n    }\n    var triggerTypesMatch = getTriggerType() == analyzeCommand.getTriggerType();\n    var filesMatch = Objects.equals(getFiles(), analyzeCommand.getFiles());\n    var extraPropertiesMatch = Objects.equals(getExtraProperties(), analyzeCommand.getExtraProperties());\n    return triggerTypesMatch && filesMatch && extraPropertiesMatch;\n  }\n\n  @Override\n  public boolean shouldCancelQueue() {\n    return cancelMonitor.isCanceled() || futureResult.isCancelled();\n  }\n\n  @CheckForNull\n  public Trace getTrace() {\n    return trace;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/command/Command.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.command;\n\nimport java.util.concurrent.atomic.AtomicLong;\nimport org.sonarsource.sonarlint.core.analysis.container.global.ModuleRegistry;\n\npublic abstract class Command {\n  private static final AtomicLong analysisGlobalNumber = new AtomicLong();\n  private final long sequenceNumber = analysisGlobalNumber.incrementAndGet();\n\n  public abstract void execute(ModuleRegistry moduleRegistry);\n\n  public final long getSequenceNumber() {\n    return sequenceNumber;\n  }\n\n  public boolean isReady() {\n    return true;\n  }\n\n  public void cancel() {\n    // most commands are not cancelable\n  }\n\n  public boolean shouldCancelPost(Command executingCommand) {\n    return false;\n  }\n\n  public boolean shouldCancelQueue() {\n    return false;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/command/NotifyModuleEventCommand.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.command;\n\nimport org.sonarsource.sonarlint.core.analysis.api.ClientModuleFileEvent;\nimport org.sonarsource.sonarlint.core.analysis.container.global.ModuleRegistry;\nimport org.sonarsource.sonarlint.core.analysis.container.module.ModuleFileEventNotifier;\n\npublic class NotifyModuleEventCommand extends Command {\n  private final String moduleKey;\n  private final ClientModuleFileEvent event;\n\n  public NotifyModuleEventCommand(String moduleKey, ClientModuleFileEvent event) {\n    this.moduleKey = moduleKey;\n    this.event = event;\n  }\n\n  @Override\n  public void execute(ModuleRegistry moduleRegistry) {\n    var moduleContainer = moduleRegistry.getContainerIfStarted(moduleKey);\n    if (moduleContainer != null) {\n      moduleContainer.getComponentByType(ModuleFileEventNotifier.class).fireModuleFileEvent(event);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/command/ResetPluginsCommand.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.command;\n\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Supplier;\nimport org.sonarsource.sonarlint.core.analysis.AnalysisQueue;\nimport org.sonarsource.sonarlint.core.analysis.SchedulerResetConfiguration;\nimport org.sonarsource.sonarlint.core.analysis.container.global.GlobalAnalysisContainer;\nimport org.sonarsource.sonarlint.core.analysis.container.global.ModuleRegistry;\n\npublic class ResetPluginsCommand extends Command {\n\n  private final Supplier<SchedulerResetConfiguration> schedulerResetConfigurationSupplier;\n  private final AtomicReference<GlobalAnalysisContainer> globalAnalysisContainer;\n  private final AnalysisQueue analysisQueue;\n\n  public ResetPluginsCommand(AtomicReference<GlobalAnalysisContainer> globalAnalysisContainer, AnalysisQueue analysisQueue,\n    Supplier<SchedulerResetConfiguration> schedulerResetConfigurationSupplier) {\n    this.schedulerResetConfigurationSupplier = schedulerResetConfigurationSupplier;\n    this.globalAnalysisContainer = globalAnalysisContainer;\n    this.analysisQueue = analysisQueue;\n  }\n\n  @Override\n  public void execute(ModuleRegistry moduleRegistry) {\n    globalAnalysisContainer.get().stopComponents();\n    var pluginsWithConfig = schedulerResetConfigurationSupplier.get();\n    globalAnalysisContainer.set(new GlobalAnalysisContainer(pluginsWithConfig.config(), pluginsWithConfig.plugins()));\n    globalAnalysisContainer.get().startComponents();\n    analysisQueue.clearAllButAnalysesAndResets();\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/command/UnregisterModuleCommand.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.command;\n\nimport org.sonarsource.sonarlint.core.analysis.container.global.ModuleRegistry;\n\npublic final class UnregisterModuleCommand extends Command {\n  private final String moduleKey;\n\n  public UnregisterModuleCommand(String moduleKey) {\n    this.moduleKey = moduleKey;\n  }\n\n  @Override\n  public void execute(ModuleRegistry moduleRegistry) {\n    moduleRegistry.unregisterModule(moduleKey);\n  }\n\n  public String getModuleKey() {\n    return moduleKey;\n  }\n\n  @Override\n  public boolean shouldCancelPost(Command executingCommand) {\n    return executingCommand instanceof AnalyzeCommand analyzeCommand && analyzeCommand.getModuleKey().equals(moduleKey);\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/command/package-info.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.analysis.command;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/ContainerLifespan.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container;\n\npublic enum ContainerLifespan {\n  INSTANCE, MODULE, ANALYSIS\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/AnalysisConfigurationProvider.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis;\n\nimport org.sonar.api.config.Configuration;\nimport org.sonarsource.sonarlint.core.plugin.commons.sonarapi.ConfigurationBridge;\nimport org.springframework.context.annotation.Bean;\n\npublic class AnalysisConfigurationProvider {\n\n  private Configuration analysisConfig;\n\n  @Bean(\"configuration\")\n  public Configuration provide(AnalysisSettings settings) {\n    if (analysisConfig == null) {\n      this.analysisConfig = new ConfigurationBridge(settings);\n    }\n    return analysisConfig;\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/AnalysisContainer.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis;\n\nimport org.sonar.api.batch.rule.CheckFactory;\nimport org.sonar.api.resources.Languages;\nimport org.sonar.api.scan.filesystem.PathResolver;\nimport org.sonarsource.sonarlint.core.analysis.container.ContainerLifespan;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.FileIndexer;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.FileMetadata;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.InputFileBuilder;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.InputFileIndex;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.LanguageDetection;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintFileSystem;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputProject;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.IssueFilters;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.EnforceIssuesFilter;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.IgnoreIssuesFilter;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.SonarLintNoSonarFilter;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.pattern.IssueExclusionPatternInitializer;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.pattern.IssueInclusionPatternInitializer;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.scanner.IssueExclusionsLoader;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.sensor.SensorOptimizer;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.sensor.SensorsExecutor;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.sensor.SonarLintSensorStorage;\nimport org.sonarsource.sonarlint.core.analysis.container.global.AnalysisExtensionInstaller;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.DefaultSensorContext;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.noop.NoOpFileLinesContextFactory;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.ProgressIndicator;\nimport org.sonarsource.sonarlint.core.plugin.commons.container.SpringComponentContainer;\n\npublic class AnalysisContainer extends SpringComponentContainer {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final ProgressIndicator cancelMonitor;\n\n  public AnalysisContainer(SpringComponentContainer globalContainer, ProgressIndicator progressIndicator) {\n    super(globalContainer);\n    this.cancelMonitor = progressIndicator;\n  }\n\n  @Override\n  protected void doBeforeStart() {\n    addCoreComponents();\n    addPluginExtensions();\n  }\n\n  private void addCoreComponents() {\n    add(\n      cancelMonitor,\n      SonarLintInputProject.class,\n      NoOpFileLinesContextFactory.class,\n\n      // temp\n      new AnalysisTempFolderProvider(),\n\n      // file system\n      PathResolver.class,\n\n      // lang\n      Languages.class,\n\n      AnalysisSettings.class,\n      new AnalysisConfigurationProvider(),\n\n      // file system\n      InputFileIndex.class,\n      InputFileBuilder.class,\n      FileMetadata.class,\n      LanguageDetection.class,\n      FileIndexer.class,\n      SonarLintFileSystem.class,\n\n      // Exclusions using SonarQube properties\n      EnforceIssuesFilter.class,\n      IgnoreIssuesFilter.class,\n      IssueExclusionPatternInitializer.class,\n      IssueInclusionPatternInitializer.class,\n      IssueExclusionsLoader.class,\n\n      SensorOptimizer.class,\n      SensorsExecutor.class,\n\n      DefaultSensorContext.class,\n      SonarLintSensorStorage.class,\n      IssueFilters.class,\n\n      // rules\n      CheckFactory.class,\n\n      // issues\n      SonarLintNoSonarFilter.class);\n  }\n\n  private void addPluginExtensions() {\n    getParent().getComponentByType(AnalysisExtensionInstaller.class).install(this, ContainerLifespan.ANALYSIS);\n  }\n\n  @Override\n  protected void doAfterStart() {\n    LOG.debug(\"Start analysis\");\n    // Don't initialize Sensors before the FS is indexed\n    getComponentByType(FileIndexer.class).index();\n    getComponentByType(SensorsExecutor.class).execute();\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/AnalysisSettings.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport org.sonar.api.config.PropertyDefinitions;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisConfiguration;\nimport org.sonarsource.sonarlint.core.analysis.container.global.GlobalSettings;\nimport org.sonarsource.sonarlint.core.plugin.commons.sonarapi.MapSettings;\n\npublic class AnalysisSettings extends MapSettings {\n\n  public AnalysisSettings(GlobalSettings globalSettings, AnalysisConfiguration analysisConfig, PropertyDefinitions propertyDefinitions) {\n    super(propertyDefinitions, mergeInOrder(globalSettings, analysisConfig));\n  }\n\n  private static Map<String, String> mergeInOrder(GlobalSettings globalSettings, AnalysisConfiguration analysisConfig) {\n    Map<String, String> result = new HashMap<>(globalSettings.getProperties());\n    result.putAll(analysisConfig.extraProperties());\n    return result;\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/AnalysisTempFolderProvider.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis;\n\nimport java.io.File;\nimport javax.annotation.Nullable;\nimport org.sonar.api.utils.TempFolder;\nimport org.springframework.context.annotation.Bean;\n\npublic class AnalysisTempFolderProvider {\n\n  private final NoTempFilesDuringAnalysis instance = new NoTempFilesDuringAnalysis();\n\n  @Bean(\"tempFolder\")\n  public TempFolder provide() {\n    return instance;\n  }\n\n  private static class NoTempFilesDuringAnalysis implements TempFolder {\n\n    @Override\n    public File newDir() {\n      throw throwUOEFolder();\n    }\n\n    @Override\n    public File newDir(String name) {\n      throw throwUOEFolder();\n    }\n\n    private static UnsupportedOperationException throwUOEFolder() {\n      return new UnsupportedOperationException(\"Don't create temp folders during analysis\");\n    }\n\n    @Override\n    public File newFile() {\n      throw throwUOEFiles();\n    }\n\n    private static UnsupportedOperationException throwUOEFiles() {\n      return new UnsupportedOperationException(\"Don't create temp files during analysis\");\n    }\n\n    @Override\n    public File newFile(@Nullable String prefix, @Nullable String suffix) {\n      throw throwUOEFiles();\n    }\n\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/IssueListenerHolder.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis;\n\nimport java.util.function.Consumer;\nimport org.sonarsource.sonarlint.core.analysis.api.Issue;\n\n/**\n * We need a dedicated class for dependency injection\n *\n */\npublic class IssueListenerHolder {\n  private final Consumer<Issue> wrapped;\n\n  public IssueListenerHolder(Consumer<Issue> issueListener) {\n    this.wrapped = issueListener;\n  }\n\n  public void handle(Issue issue) {\n    wrapped.accept(issue);\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/SonarLintPathPattern.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis;\n\nimport javax.annotation.Nullable;\nimport org.apache.commons.io.FilenameUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.Strings;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.utils.PathUtils;\nimport org.sonar.api.utils.WildcardPattern;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\n/**\n * Path relative to module basedir\n */\npublic class SonarLintPathPattern {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  final WildcardPattern pattern;\n\n  public SonarLintPathPattern(String pattern) {\n    if (pattern.startsWith(\"file:\")) {\n      LOG.warn(\"Unsupported path pattern: \" + pattern);\n      pattern = pattern.replaceAll(\"^file:/*\", \"\");\n    }\n    if (!pattern.startsWith(\"**/\")) {\n      pattern = \"**/\" + pattern;\n    }\n    this.pattern = WildcardPattern.create(pattern);\n  }\n\n  public static SonarLintPathPattern[] create(String[] s) {\n    var result = new SonarLintPathPattern[s.length];\n    for (var i = 0; i < s.length; i++) {\n      result[i] = new SonarLintPathPattern(s[i]);\n    }\n    return result;\n  }\n\n  public boolean match(InputFile inputFile) {\n    return match(inputFile.relativePath(), true);\n  }\n\n  public boolean match(String filePath) {\n    return match(filePath, true);\n  }\n\n  public boolean match(InputFile inputFile, boolean caseSensitiveFileExtension) {\n    return match(inputFile.relativePath(), caseSensitiveFileExtension);\n  }\n\n  public boolean match(String filePath, boolean caseSensitiveFileExtension) {\n    var path = PathUtils.sanitize(filePath);\n    if (!caseSensitiveFileExtension) {\n      var extension = sanitizeExtension(FilenameUtils.getExtension(path));\n      if (StringUtils.isNotBlank(extension)) {\n        path = Strings.CI.removeEnd(path, extension);\n        path = path + extension;\n      }\n    }\n    return path != null && pattern.match(path);\n  }\n\n  @Override\n  public String toString() {\n    return pattern.toString();\n  }\n\n  static String sanitizeExtension(@Nullable String suffix) {\n    return StringUtils.lowerCase(Strings.CS.removeStart(suffix, \".\"));\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/AbstractFilePredicate.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.util.stream.StreamSupport;\nimport org.sonar.api.batch.fs.FilePredicate;\nimport org.sonar.api.batch.fs.FileSystem.Index;\nimport org.sonar.api.batch.fs.InputFile;\n\n/**\n * Partial implementation of {@link FilePredicate}.\n * @since 5.1\n */\npublic abstract class AbstractFilePredicate implements OptimizedFilePredicate {\n\n  protected static final int DEFAULT_PRIORITY = 10;\n\n  @Override\n  public Iterable<InputFile> filter(Iterable<InputFile> target) {\n    return StreamSupport.stream(target.spliterator(), false).filter(AbstractFilePredicate.this::apply).toList();\n  }\n\n  @Override\n  public Iterable<InputFile> get(Index index) {\n    return filter(index.inputFiles());\n  }\n\n  @Override\n  public int priority() {\n    return DEFAULT_PRIORITY;\n  }\n\n  @Override\n  public final int compareTo(OptimizedFilePredicate o) {\n    return o.priority() - priority();\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/AndPredicate.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport org.sonar.api.batch.fs.FilePredicate;\nimport org.sonar.api.batch.fs.FileSystem.Index;\nimport org.sonar.api.batch.fs.InputFile;\n\n/**\n * @since 4.2\n */\nclass AndPredicate extends AbstractFilePredicate {\n\n  private final List<OptimizedFilePredicate> predicates = new ArrayList<>();\n\n  private AndPredicate() {\n  }\n\n  public static FilePredicate create(Collection<FilePredicate> predicates) {\n    if (predicates.isEmpty()) {\n      return TruePredicate.TRUE;\n    }\n    var result = new AndPredicate();\n    for (FilePredicate filePredicate : predicates) {\n      if (filePredicate == TruePredicate.TRUE) {\n        continue;\n      } else if (filePredicate == FalsePredicate.FALSE) {\n        return FalsePredicate.FALSE;\n      } else if (filePredicate instanceof AndPredicate andPredicate) {\n        result.predicates.addAll(andPredicate.predicates);\n      } else {\n        result.predicates.add(OptimizedFilePredicateAdapter.create(filePredicate));\n      }\n    }\n    Collections.sort(result.predicates);\n    return result;\n  }\n\n  @Override\n  public boolean apply(InputFile f) {\n    for (OptimizedFilePredicate predicate : predicates) {\n      if (!predicate.apply(f)) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  @Override\n  public Iterable<InputFile> filter(Iterable<InputFile> target) {\n    var result = target;\n    for (OptimizedFilePredicate predicate : predicates) {\n      result = predicate.filter(result);\n    }\n    return result;\n  }\n\n  @Override\n  public Iterable<InputFile> get(Index index) {\n    if (predicates.isEmpty()) {\n      return index.inputFiles();\n    }\n    // Optimization, use get on first predicate then filter with next predicates\n    var result = predicates.get(0).get(index);\n    for (var i = 1; i < predicates.size(); i++) {\n      result = predicates.get(i).filter(result);\n    }\n    return result;\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/DefaultFilePredicates.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.io.File;\nimport java.net.URI;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\nimport org.sonar.api.batch.fs.FilePredicate;\nimport org.sonar.api.batch.fs.FilePredicates;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.fs.InputFile.Status;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.SonarLintPathPattern;\n\n/**\n * Factory of {@link org.sonar.api.batch.fs.FilePredicate}\n *\n * @since 4.2\n */\npublic class DefaultFilePredicates implements FilePredicates {\n\n  /**\n   * Returns a predicate that always evaluates to true\n   */\n  @Override\n  public FilePredicate all() {\n    return TruePredicate.TRUE;\n  }\n\n  /**\n   * Returns a predicate that always evaluates to false\n   */\n  @Override\n  public FilePredicate none() {\n    return FalsePredicate.FALSE;\n  }\n\n  @Override\n  public FilePredicate hasAbsolutePath(String s) {\n    throw new UnsupportedOperationException(\"hasAbsolutePath\");\n  }\n\n  /**\n   * non-normalized path and Windows-style path are supported\n   */\n  @Override\n  public FilePredicate hasRelativePath(String s) {\n    throw new UnsupportedOperationException(\"hasRelativePath\");\n  }\n\n  @Override\n  public FilePredicate hasURI(URI uri) {\n    return new URIPredicate(uri);\n  }\n\n  @Override\n  public FilePredicate matchesPathPattern(String inclusionPattern) {\n    return new PathPatternPredicate(new SonarLintPathPattern(inclusionPattern));\n  }\n\n  @Override\n  public FilePredicate matchesPathPatterns(String[] inclusionPatterns) {\n    if (inclusionPatterns.length == 0) {\n      return TruePredicate.TRUE;\n    }\n    var predicates = new FilePredicate[inclusionPatterns.length];\n    for (var i = 0; i < inclusionPatterns.length; i++) {\n      predicates[i] = new PathPatternPredicate(new SonarLintPathPattern(inclusionPatterns[i]));\n    }\n    return or(predicates);\n  }\n\n  @Override\n  public FilePredicate doesNotMatchPathPattern(String exclusionPattern) {\n    return not(matchesPathPattern(exclusionPattern));\n  }\n\n  @Override\n  public FilePredicate doesNotMatchPathPatterns(String[] exclusionPatterns) {\n    if (exclusionPatterns.length == 0) {\n      return TruePredicate.TRUE;\n    }\n    return not(matchesPathPatterns(exclusionPatterns));\n  }\n\n  @Override\n  public FilePredicate hasPath(String s) {\n    throw new UnsupportedOperationException(\"hasPath\");\n  }\n\n  @Override\n  public FilePredicate is(File ioFile) {\n    // Needed for SonarCFamily\n    return hasURI(ioFile.toURI());\n  }\n\n  @Override\n  public FilePredicate hasLanguage(String language) {\n    return new LanguagePredicate(language);\n  }\n\n  @Override\n  public FilePredicate hasLanguages(Collection<String> languages) {\n    List<FilePredicate> list = new ArrayList<>();\n    for (String language : languages) {\n      list.add(hasLanguage(language));\n    }\n    return or(list);\n  }\n\n  @Override\n  public FilePredicate hasLanguages(String... languages) {\n    List<FilePredicate> list = new ArrayList<>();\n    for (String language : languages) {\n      list.add(hasLanguage(language));\n    }\n    return or(list);\n  }\n\n  @Override\n  public FilePredicate hasType(InputFile.Type type) {\n    return new TypePredicate(type);\n  }\n\n  @Override\n  public FilePredicate not(FilePredicate p) {\n    return new NotPredicate(p);\n  }\n\n  @Override\n  public FilePredicate or(Collection<FilePredicate> or) {\n    return OrPredicate.create(or);\n  }\n\n  @Override\n  public FilePredicate or(FilePredicate... or) {\n    return OrPredicate.create(Arrays.asList(or));\n  }\n\n  @Override\n  public FilePredicate or(FilePredicate first, FilePredicate second) {\n    return OrPredicate.create(Arrays.asList(first, second));\n  }\n\n  @Override\n  public FilePredicate and(Collection<FilePredicate> and) {\n    return AndPredicate.create(and);\n  }\n\n  @Override\n  public FilePredicate and(FilePredicate... and) {\n    return AndPredicate.create(Arrays.asList(and));\n  }\n\n  @Override\n  public FilePredicate and(FilePredicate first, FilePredicate second) {\n    return AndPredicate.create(Arrays.asList(first, second));\n  }\n\n  @Override\n  public FilePredicate hasFilename(String s) {\n    return new FilenamePredicate(s);\n  }\n\n  @Override\n  public FilePredicate hasExtension(String s) {\n    return new FileExtensionPredicate(s);\n  }\n\n  @Override\n  public FilePredicate hasStatus(Status status) {\n    throw new UnsupportedOperationException(\"hasStatus\");\n  }\n\n  @Override\n  public FilePredicate hasAnyStatus() {\n    return new StatusPredicate(null);\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/DefaultTextPointer.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport org.sonar.api.batch.fs.TextPointer;\n\npublic class DefaultTextPointer implements TextPointer {\n\n  private final int line;\n  private final int lineOffset;\n\n  public DefaultTextPointer(int line, int lineOffset) {\n    this.line = line;\n    this.lineOffset = lineOffset;\n  }\n\n  @Override\n  public int line() {\n    return line;\n  }\n\n  @Override\n  public int lineOffset() {\n    return lineOffset;\n  }\n\n  @Override\n  public String toString() {\n    return \"[line=\" + line + \", lineOffset=\" + lineOffset + \"]\";\n  }\n\n  @Override\n  public boolean equals(Object obj) {\n    if (obj == null || obj.getClass() != this.getClass()) {\n      return false;\n    }\n    var other = (DefaultTextPointer) obj;\n    return other.line == this.line && other.lineOffset == this.lineOffset;\n  }\n\n  @Override\n  public int hashCode() {\n    return 37 * this.line + lineOffset;\n  }\n\n  @Override\n  public int compareTo(TextPointer o) {\n    if (this.line == o.line()) {\n      return Integer.compare(this.lineOffset, o.lineOffset());\n    }\n    return Integer.compare(this.line, o.line());\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/DefaultTextRange.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport org.sonar.api.batch.fs.TextPointer;\nimport org.sonar.api.batch.fs.TextRange;\n\npublic class DefaultTextRange implements TextRange {\n\n  private final TextPointer start;\n  private final TextPointer end;\n\n  public DefaultTextRange(TextPointer start, TextPointer end) {\n    this.start = start;\n    this.end = end;\n  }\n\n  @Override\n  public TextPointer start() {\n    return start;\n  }\n\n  @Override\n  public TextPointer end() {\n    return end;\n  }\n\n  @Override\n  public boolean overlap(TextRange another) {\n    // [A,B] and [C,D]\n    // B > C && D > A\n    return this.end.compareTo(another.start()) > 0 && another.end().compareTo(this.start) > 0;\n  }\n\n  @Override\n  public String toString() {\n    return \"Range[from \" + start + \" to \" + end + \"]\";\n  }\n\n  @Override\n  public boolean equals(Object obj) {\n    if (obj == null || obj.getClass() != this.getClass()) {\n      return false;\n    }\n    var other = (DefaultTextRange) obj;\n    return start.equals(other.start) && end.equals(other.end);\n  }\n\n  @Override\n  public int hashCode() {\n    return start.hashCode() * 17 + end.hashCode();\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/FalsePredicate.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.util.Collections;\nimport org.sonar.api.batch.fs.FilePredicate;\nimport org.sonar.api.batch.fs.FileSystem.Index;\nimport org.sonar.api.batch.fs.InputFile;\n\nclass FalsePredicate extends AbstractFilePredicate {\n\n  static final FilePredicate FALSE = new FalsePredicate();\n\n  @Override\n  public boolean apply(InputFile inputFile) {\n    return false;\n  }\n\n  @Override\n  public Iterable<InputFile> filter(Iterable<InputFile> target) {\n    return Collections.emptyList();\n  }\n\n  @Override\n  public Iterable<InputFile> get(Index index) {\n    return Collections.emptyList();\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/FileExtensionPredicate.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.util.Locale;\nimport org.sonar.api.batch.fs.FileSystem;\nimport org.sonar.api.batch.fs.InputFile;\n\npublic class FileExtensionPredicate extends AbstractFilePredicate {\n\n  private final String extension;\n\n  public FileExtensionPredicate(String extension) {\n    this.extension = lowercase(extension);\n  }\n\n  @Override\n  public boolean apply(InputFile inputFile) {\n    return extension.equals(getExtension(inputFile));\n  }\n\n  @Override\n  public Iterable<InputFile> get(FileSystem.Index index) {\n    return index.getFilesByExtension(extension);\n  }\n\n  public static String getExtension(InputFile inputFile) {\n    return getExtension(inputFile.filename());\n  }\n\n  static String getExtension(String name) {\n    var index = name.lastIndexOf('.');\n    if (index < 0) {\n      return \"\";\n    }\n    return lowercase(name.substring(index + 1));\n  }\n\n  private static String lowercase(String extension) {\n    return extension.toLowerCase(Locale.ENGLISH);\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/FileIndexer.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.net.URI;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.fs.InputFileFilter;\nimport org.sonar.api.utils.MessageException;\nimport org.sonarsource.api.sonarlint.SonarLintSide;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisConfiguration;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisResults;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.scanner.IssueExclusionsLoader;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\n/**\n * Index input files into {@link InputFileIndex}.\n */\n@SonarLintSide\npublic class FileIndexer {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final InputFileBuilder inputFileBuilder;\n  private final AnalysisConfiguration analysisConfiguration;\n  private final AnalysisResults analysisResult;\n  private final List<InputFileFilter> filters;\n  private final IssueExclusionsLoader issueExclusionsLoader;\n  private final InputFileIndex inputFileCache;\n\n  private ProgressReport progressReport;\n\n  public FileIndexer(InputFileIndex inputFileCache, InputFileBuilder inputFileBuilder, AnalysisConfiguration analysisConfiguration,\n    AnalysisResults analysisResult, IssueExclusionsLoader issueExclusionsLoader,\n    Optional<List<InputFileFilter>> filters) {\n    this.inputFileCache = inputFileCache;\n    this.inputFileBuilder = inputFileBuilder;\n    this.analysisConfiguration = analysisConfiguration;\n    this.analysisResult = analysisResult;\n    this.issueExclusionsLoader = issueExclusionsLoader;\n    this.filters = filters.orElse(List.of());\n  }\n\n  public void index() {\n    progressReport = new ProgressReport(\"Report about progress of file indexation\", TimeUnit.SECONDS.toMillis(10));\n    progressReport.start(\"Index files\");\n\n    var progress = new Progress();\n\n    try {\n      indexFiles(inputFileCache, progress, analysisConfiguration.inputFiles());\n    } catch (Exception e) {\n      progressReport.stop(null);\n      throw e;\n    }\n    var totalIndexed = progress.count();\n    progressReport.stop(totalIndexed + \" \" + pluralizeFiles(totalIndexed) + \" indexed\");\n  }\n\n  private static String pluralizeFiles(int count) {\n    return count == 1 ? \"file\" : \"files\";\n  }\n\n  private void indexFiles(InputFileIndex inputFileCache, Progress progress, Iterable<ClientInputFile> inputFiles) {\n    for (ClientInputFile file : inputFiles) {\n      indexFile(inputFileCache, progress, file);\n    }\n  }\n\n  private void indexFile(InputFileIndex inputFileCache, Progress progress, ClientInputFile file) {\n    var inputFile = inputFileBuilder.create(file);\n    if (accept(inputFile)) {\n      analysisResult.setLanguageForFile(file, inputFile.getLanguage());\n      indexFile(inputFileCache, progress, inputFile);\n      issueExclusionsLoader.addMulticriteriaPatterns(inputFile);\n    }\n  }\n\n  private void indexFile(final InputFileIndex inputFileCache, final Progress status, final SonarLintInputFile inputFile) {\n    inputFileCache.doAdd(inputFile);\n    status.markAsIndexed(inputFile);\n  }\n\n  private boolean accept(InputFile indexedFile) {\n    // InputFileFilter extensions. Might trigger generation of metadata\n    for (InputFileFilter filter : filters) {\n      if (!filter.accept(indexedFile)) {\n        LOG.debug(\"'{}' excluded by {}\", indexedFile, filter.getClass().getName());\n        return false;\n      }\n    }\n    return true;\n  }\n\n  private class Progress {\n    private final Set<URI> indexed = new HashSet<>();\n\n    void markAsIndexed(SonarLintInputFile inputFile) {\n      if (indexed.contains(inputFile.uri())) {\n        throw MessageException.of(\"File \" + inputFile + \" can't be indexed twice.\");\n      }\n      indexed.add(inputFile.uri());\n      var size = indexed.size();\n      progressReport.message(() -> size + \" files indexed...  (last one was \" + inputFile.uri() + \")\");\n    }\n\n    int count() {\n      return indexed.size();\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/FileMetadata.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.net.URI;\nimport java.nio.charset.Charset;\nimport java.util.ArrayList;\nimport java.util.List;\nimport javax.annotation.Nullable;\nimport org.sonarsource.api.sonarlint.SonarLintSide;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\n/**\n * Computes hash of files. Ends of Lines are ignored, so files with\n * same content but different EOL encoding have the same hash.\n */\n@SonarLintSide\npublic class FileMetadata {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private static final char LINE_FEED = '\\n';\n  private static final char CARRIAGE_RETURN = '\\r';\n\n  public abstract static class CharHandler {\n\n    protected void handleAll(char c) {\n    }\n\n    protected void handleIgnoreEoL(char c) {\n    }\n\n    protected void newLine() {\n    }\n\n    protected void eof() {\n    }\n  }\n\n  private static class LineCounter extends CharHandler {\n    private int lines = 1;\n    boolean alreadyLoggedInvalidCharacter = false;\n    private final URI fileUri;\n    private final Charset encoding;\n\n    LineCounter(URI fileUri, Charset encoding) {\n      this.fileUri = fileUri;\n      this.encoding = encoding;\n    }\n\n    @Override\n    protected void handleAll(char c) {\n      if (!alreadyLoggedInvalidCharacter && c == '\\ufffd') {\n        LOG.warn(\"Invalid character encountered in file '{}' at line {} for encoding {}. Please fix file content or configure the encoding.\",\n          fileUri,\n          lines, encoding);\n        alreadyLoggedInvalidCharacter = true;\n      }\n    }\n\n    @Override\n    protected void newLine() {\n      lines++;\n    }\n\n    public int lines() {\n      return lines;\n    }\n\n  }\n\n  private static class LineOffsetCounter extends CharHandler {\n    private int currentOriginalOffset = 0;\n    private final List<Integer> originalLineOffsets = new ArrayList<>();\n    private int lastValidOffset = 0;\n\n    public LineOffsetCounter() {\n      originalLineOffsets.add(0);\n    }\n\n    @Override\n    protected void handleAll(char c) {\n      currentOriginalOffset++;\n    }\n\n    @Override\n    protected void newLine() {\n      originalLineOffsets.add(currentOriginalOffset);\n    }\n\n    @Override\n    protected void eof() {\n      lastValidOffset = currentOriginalOffset;\n    }\n\n    public List<Integer> getOriginalLineOffsets() {\n      return originalLineOffsets;\n    }\n\n    public int getLastValidOffset() {\n      return lastValidOffset;\n    }\n\n  }\n\n  /**\n   * Compute hash of an inputStream ignoring line ends differences.\n   * Maximum performance is needed.\n   */\n  public Metadata readMetadata(InputStream stream, Charset encoding, URI fileUri, @Nullable CharHandler otherHandler) {\n    var lineCounter = new LineCounter(fileUri, encoding);\n    var lineOffsetCounter = new LineOffsetCounter();\n    try (Reader reader = new BufferedReader(new InputStreamReader(stream, encoding))) {\n      CharHandler[] handlers;\n      if (otherHandler != null) {\n        handlers = new CharHandler[] {lineCounter, lineOffsetCounter, otherHandler};\n      } else {\n        handlers = new CharHandler[] {lineCounter, lineOffsetCounter};\n      }\n      read(reader, handlers);\n    } catch (IOException e) {\n      throw new IllegalStateException(String.format(\"Fail to read file '%s' with encoding '%s'\", fileUri, encoding), e);\n    }\n    return new Metadata(lineCounter.lines(), lineOffsetCounter.getOriginalLineOffsets().stream().mapToInt(i -> i).toArray(), lineOffsetCounter.getLastValidOffset());\n  }\n\n  private static void read(Reader reader, CharHandler... handlers) throws IOException {\n    char c;\n    var i = reader.read();\n    var afterCR = false;\n    while (i != -1) {\n      c = (char) i;\n      if (afterCR) {\n        for (CharHandler handler : handlers) {\n          if (c == CARRIAGE_RETURN) {\n            handler.newLine();\n            handler.handleAll(c);\n          } else if (c == LINE_FEED) {\n            handler.handleAll(c);\n            handler.newLine();\n          } else {\n            handler.newLine();\n            handler.handleIgnoreEoL(c);\n            handler.handleAll(c);\n          }\n        }\n        afterCR = c == CARRIAGE_RETURN;\n      } else if (c == LINE_FEED) {\n        for (CharHandler handler : handlers) {\n          handler.handleAll(c);\n          handler.newLine();\n        }\n      } else if (c == CARRIAGE_RETURN) {\n        afterCR = true;\n        for (CharHandler handler : handlers) {\n          handler.handleAll(c);\n        }\n      } else {\n        for (CharHandler handler : handlers) {\n          handler.handleIgnoreEoL(c);\n          handler.handleAll(c);\n        }\n      }\n      i = reader.read();\n    }\n    for (CharHandler handler : handlers) {\n      if (afterCR) {\n        handler.newLine();\n      }\n      handler.eof();\n    }\n  }\n\n  public static class Metadata {\n    private final int lines;\n    private final int[] originalLineOffsets;\n    private final int lastValidOffset;\n\n    public Metadata(int lines, int[] originalLineOffsets, int lastValidOffset) {\n      this.lines = lines;\n      this.originalLineOffsets = originalLineOffsets;\n      this.lastValidOffset = lastValidOffset;\n    }\n\n    public int lines() {\n      return lines;\n    }\n\n    public int[] originalLineOffsets() {\n      return originalLineOffsets;\n    }\n\n    public int lastValidOffset() {\n      return lastValidOffset;\n    }\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/FilenamePredicate.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport org.sonar.api.batch.fs.FileSystem;\nimport org.sonar.api.batch.fs.InputFile;\n\npublic class FilenamePredicate extends AbstractFilePredicate {\n  private final String filename;\n\n  public FilenamePredicate(String filename) {\n    this.filename = filename;\n  }\n\n  @Override\n  public boolean apply(InputFile inputFile) {\n    return filename.equals(inputFile.filename());\n  }\n\n  @Override\n  public Iterable<InputFile> get(FileSystem.Index index) {\n    return index.getFilesByName(filename);\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/InputFileBuilder.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.Charset;\nimport org.sonar.api.batch.fs.InputFile.Type;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.scanner.IssueExclusionsLoader;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic class InputFileBuilder {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final LanguageDetection langDetection;\n  private final FileMetadata fileMetadata;\n  private final IssueExclusionsLoader exclusionsScanner;\n\n  public InputFileBuilder(LanguageDetection langDetection, FileMetadata fileMetadata, IssueExclusionsLoader exclusionsScanner) {\n    this.langDetection = langDetection;\n    this.fileMetadata = fileMetadata;\n    this.exclusionsScanner = exclusionsScanner;\n  }\n\n  SonarLintInputFile create(ClientInputFile inputFile) {\n    var defaultInputFile = new SonarLintInputFile(inputFile, f -> {\n      LOG.debug(\"Initializing metadata of file {}\", f.uri());\n      var charset = f.charset();\n      InputStream stream;\n      try {\n        stream = f.inputStream();\n      } catch (IOException e) {\n        throw new IllegalStateException(\"Failed to open a stream on file: \" + f.uri(), e);\n      }\n      return fileMetadata.readMetadata(stream, charset != null ? charset : Charset.defaultCharset(), f.uri(), exclusionsScanner.createCharHandlerFor(f));\n    });\n    defaultInputFile.setType(inputFile.isTest() ? Type.TEST : Type.MAIN);\n    var fileLanguage = inputFile.language();\n    if (fileLanguage != null) {\n      LOG.debug(\"Language of file \\\"{}\\\" is set to \\\"{}\\\"\", inputFile.uri(), fileLanguage);\n      defaultInputFile.setLanguage(fileLanguage);\n    } else {\n      defaultInputFile.setLanguage(langDetection.language(defaultInputFile));\n    }\n\n    return defaultInputFile;\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/InputFileIndex.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.util.LinkedHashMap;\nimport java.util.LinkedHashSet;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.SortedSet;\nimport java.util.TreeSet;\nimport org.sonar.api.batch.fs.FileSystem;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonarsource.api.sonarlint.SonarLintSide;\n\n@SonarLintSide\npublic class InputFileIndex implements FileSystem.Index {\n\n  private final Set<InputFile> inputFiles = new LinkedHashSet<>();\n  private final Map<String, Set<InputFile>> filesByNameIndex = new LinkedHashMap<>();\n  private final Map<String, Set<InputFile>> filesByExtensionIndex = new LinkedHashMap<>();\n  private final SortedSet<String> languages = new TreeSet<>();\n\n  @Override\n  public Iterable<InputFile> inputFiles() {\n    return inputFiles;\n  }\n\n  public void doAdd(InputFile inputFile) {\n    if (inputFile.language() != null) {\n      languages.add(inputFile.language());\n    }\n    inputFiles.add(inputFile);\n    filesByNameIndex.computeIfAbsent(inputFile.filename(), f -> new LinkedHashSet<>()).add(inputFile);\n    filesByExtensionIndex.computeIfAbsent(FileExtensionPredicate.getExtension(inputFile), f -> new LinkedHashSet<>()).add(inputFile);\n  }\n\n  @Override\n  public InputFile inputFile(String relativePath) {\n    throw new UnsupportedOperationException(\"inputFile(String relativePath)\");\n  }\n\n  @Override\n  public Iterable<InputFile> getFilesByName(String filename) {\n    return filesByNameIndex.get(filename);\n  }\n\n  @Override\n  public Iterable<InputFile> getFilesByExtension(String extension) {\n    return filesByExtensionIndex.get(extension);\n  }\n\n  protected SortedSet<String> languages() {\n    return languages;\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/Language.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.util.Arrays;\nimport java.util.Collection;\n\npublic final class Language {\n\n  private final String key;\n  private final String name;\n  private final String[] fileSuffixes;\n\n  public Language(String key, String name, String... fileSuffixes) {\n    this.key = key;\n    this.name = name;\n    this.fileSuffixes = fileSuffixes;\n  }\n\n  /**\n   * For example \"java\".\n   */\n  public String key() {\n    return key;\n  }\n\n  /**\n   * For example \"Java\"\n   */\n  public String name() {\n    return name;\n  }\n\n  /**\n   * For example [\"jav\", \"java\"].\n   */\n  public Collection<String> fileSuffixes() {\n    return Arrays.asList(fileSuffixes);\n  }\n\n  @Override\n  public String toString() {\n    return name;\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/LanguageDetection.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.net.URI;\nimport java.text.MessageFormat;\nimport java.util.LinkedHashMap;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport javax.annotation.CheckForNull;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.Strings;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.config.Configuration;\nimport org.sonar.api.utils.MessageException;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\n/**\n * Detect language of a source file based on its suffix and configured patterns.\n */\npublic class LanguageDetection {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  /**\n   * Lower-case extension -> languages\n   */\n  private final Map<SonarLanguage, String[]> extensionsByLanguage = new LinkedHashMap<>();\n\n  public LanguageDetection(Configuration config) {\n    for (SonarLanguage language : SonarLanguage.values()) {\n      var extensions = config.get(language.getFileSuffixesPropKey()).isPresent() ? config.getStringArray(language.getFileSuffixesPropKey())\n        : language.getDefaultFileSuffixes();\n      for (var i = 0; i < extensions.length; i++) {\n        var suffix = extensions[i];\n        extensions[i] = sanitizeExtension(suffix);\n      }\n      extensionsByLanguage.put(language, extensions);\n    }\n  }\n\n  @CheckForNull\n  public SonarLanguage language(InputFile inputFile) {\n    return detectLanguage(inputFile.filename(), inputFile.uri());\n  }\n\n  private SonarLanguage detectLanguage(String fileName, URI fileUri) {\n    SonarLanguage detectedLanguage = null;\n    for (Entry<SonarLanguage, String[]> languagePatterns : extensionsByLanguage.entrySet()) {\n      if (isCandidateForLanguage(fileName, languagePatterns.getValue())) {\n        if (detectedLanguage == null) {\n          detectedLanguage = languagePatterns.getKey();\n        } else {\n          // Language was already forced by another pattern\n          throw MessageException.of(MessageFormat.format(\"Language of file \\\"{0}\\\" can not be decided as the file extension matches both {1} and {2}\",\n            fileUri, getDetails(detectedLanguage), getDetails(languagePatterns.getKey())));\n        }\n      }\n    }\n    if (detectedLanguage != null) {\n      LOG.debug(\"Language of file \\\"{}\\\" is detected to be \\\"{}\\\"\", fileUri, detectedLanguage);\n      return detectedLanguage;\n    }\n    return null;\n  }\n\n  private static boolean isCandidateForLanguage(String fileName, String[] extensions) {\n    for (String extension : extensions) {\n      if (fileName.toLowerCase(Locale.ENGLISH).endsWith(\".\" + extension)) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  private String getDetails(SonarLanguage detectedLanguage) {\n    return detectedLanguage + \": \" + String.join(\",\", extensionsByLanguage.get(detectedLanguage));\n  }\n\n  public static String sanitizeExtension(String suffix) {\n    return StringUtils.lowerCase(Strings.CS.removeStart(suffix, \".\"));\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/LanguagePredicate.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport org.sonar.api.batch.fs.InputFile;\n\n/**\n * @since 4.2\n */\nclass LanguagePredicate extends AbstractFilePredicate {\n  private final String language;\n\n  LanguagePredicate(String language) {\n    this.language = language;\n  }\n\n  @Override\n  public boolean apply(InputFile f) {\n    return language.equals(f.language());\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/NotPredicate.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport org.sonar.api.batch.fs.FilePredicate;\nimport org.sonar.api.batch.fs.InputFile;\n\n/**\n * @since 4.2\n */\nclass NotPredicate extends AbstractFilePredicate {\n\n  private final FilePredicate predicate;\n\n  NotPredicate(FilePredicate predicate) {\n    this.predicate = predicate;\n  }\n\n  @Override\n  public boolean apply(InputFile f) {\n    return !predicate.apply(f);\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/OptimizedFilePredicate.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport org.sonar.api.batch.fs.FilePredicate;\nimport org.sonar.api.batch.fs.FileSystem;\nimport org.sonar.api.batch.fs.InputFile;\n\n/**\n * Optimized version of FilePredicate allowing to speed up query by looking at InputFile by index.\n */\npublic interface OptimizedFilePredicate extends FilePredicate, Comparable<OptimizedFilePredicate> {\n\n  /**\n   * Filter provided files to keep only the ones that are valid for this predicate\n   */\n  Iterable<InputFile> filter(Iterable<InputFile> inputFiles);\n\n  /**\n   * Get all files that are valid for this predicate.\n   */\n  Iterable<InputFile> get(FileSystem.Index index);\n\n  /**\n   * For optimization. FilePredicates will be applied in priority order. For example when doing\n   * p.and(p1, p2, p3) then p1, p2 and p3 will be applied according to their priority value. Higher priority value\n   * are applied first.\n   * Assign a high priority when the predicate will likely highly reduce the set of InputFiles to filter. Also\n   * {@link RelativePathPredicate} and AbsolutePathPredicate have a high priority since they are using cache index.\n   */\n  int priority();\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/OptimizedFilePredicateAdapter.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport org.sonar.api.batch.fs.FilePredicate;\nimport org.sonar.api.batch.fs.InputFile;\n\nclass OptimizedFilePredicateAdapter extends AbstractFilePredicate {\n\n  private final FilePredicate unoptimizedPredicate;\n\n  private OptimizedFilePredicateAdapter(FilePredicate unoptimizedPredicate) {\n    this.unoptimizedPredicate = unoptimizedPredicate;\n  }\n\n  @Override\n  public boolean apply(InputFile inputFile) {\n    return unoptimizedPredicate.apply(inputFile);\n  }\n\n  public static OptimizedFilePredicate create(FilePredicate predicate) {\n    if (predicate instanceof OptimizedFilePredicate optimizedFilePredicate) {\n      return optimizedFilePredicate;\n    } else {\n      return new OptimizedFilePredicateAdapter(predicate);\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/OrPredicate.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport org.sonar.api.batch.fs.FilePredicate;\nimport org.sonar.api.batch.fs.InputFile;\n\n/**\n * @since 4.2\n */\nclass OrPredicate extends AbstractFilePredicate {\n\n  private final Collection<FilePredicate> predicates = new ArrayList<>();\n\n  private OrPredicate() {\n  }\n\n  public static FilePredicate create(Collection<FilePredicate> predicates) {\n    if (predicates.isEmpty()) {\n      return TruePredicate.TRUE;\n    }\n    var result = new OrPredicate();\n    for (FilePredicate filePredicate : predicates) {\n      if (filePredicate == TruePredicate.TRUE) {\n        return TruePredicate.TRUE;\n      } else if (filePredicate == FalsePredicate.FALSE) {\n        continue;\n      } else if (filePredicate instanceof OrPredicate orPredicate) {\n        result.predicates.addAll(orPredicate.predicates);\n      } else {\n        result.predicates.add(filePredicate);\n      }\n    }\n    return result;\n  }\n\n  @Override\n  public boolean apply(InputFile f) {\n    for (FilePredicate predicate : predicates) {\n      if (predicate.apply(f)) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  Collection<FilePredicate> predicates() {\n    return predicates;\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/PathPatternPredicate.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.SonarLintPathPattern;\n\n/**\n * @since 4.2\n */\nclass PathPatternPredicate extends AbstractFilePredicate {\n\n  private final SonarLintPathPattern pattern;\n\n  PathPatternPredicate(SonarLintPathPattern pattern) {\n    this.pattern = pattern;\n  }\n\n  @Override\n  public boolean apply(InputFile f) {\n    return pattern.match(f);\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/ProgressReport.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.util.function.Supplier;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic class ProgressReport implements Runnable {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final long period;\n  private Supplier<String> messageSupplier = () -> \"\";\n  private final Thread thread;\n  private String stopMessage = null;\n  private volatile boolean stop = false;\n\n  public ProgressReport(String threadName, long period) {\n    this.period = period;\n    thread = new Thread(this, threadName);\n    thread.setDaemon(true);\n  }\n\n  @Override\n  public void run() {\n    while (!stop) {\n      try {\n        Thread.sleep(period);\n        log(messageSupplier.get());\n      } catch (InterruptedException e) {\n        break;\n      }\n    }\n    if (stopMessage != null) {\n      log(stopMessage);\n    }\n  }\n\n  public void start(String startMessage) {\n    log(startMessage);\n    thread.start();\n  }\n\n  public void message(Supplier<String> messageSupplier) {\n    this.messageSupplier = messageSupplier;\n  }\n\n  public void stop(@Nullable String stopMessage) {\n    this.stopMessage = stopMessage;\n    this.stop = true;\n    thread.interrupt();\n    try {\n      thread.join(1000);\n    } catch (InterruptedException e) {\n      // Ignore\n    }\n  }\n\n  private static void log(String message) {\n    LOG.info(message);\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/SonarLintFileSystem.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.io.File;\nimport java.nio.charset.Charset;\nimport java.nio.file.Path;\nimport java.util.SortedSet;\nimport java.util.stream.StreamSupport;\nimport org.sonar.api.batch.fs.FilePredicate;\nimport org.sonar.api.batch.fs.FilePredicates;\nimport org.sonar.api.batch.fs.FileSystem;\nimport org.sonar.api.batch.fs.InputDir;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisConfiguration;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic class SonarLintFileSystem implements FileSystem {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final DefaultFilePredicates filePredicates;\n  private final Path baseDir;\n  private Charset encoding;\n\n  private final InputFileIndex inputFileCache;\n\n  public SonarLintFileSystem(AnalysisConfiguration analysisConfiguration, InputFileIndex inputFileCache) {\n    this.inputFileCache = inputFileCache;\n    this.baseDir = analysisConfiguration.baseDir();\n    this.filePredicates = new DefaultFilePredicates();\n  }\n\n  @Override\n  public File workDir() {\n    LOG.warn(\"No workDir in SonarLint\");\n    return baseDir();\n  }\n\n  @Override\n  public InputDir inputDir(File dir) {\n    return new SonarLintInputDir(dir.toPath());\n  }\n\n  @Override\n  public FilePredicates predicates() {\n    return filePredicates;\n  }\n\n  @Override\n  public File baseDir() {\n    return baseDir.toFile();\n  }\n\n  private SonarLintFileSystem setEncoding(Charset c) {\n    LOG.debug(\"Setting filesystem encoding: \" + c);\n    this.encoding = c;\n    return this;\n  }\n\n  @Override\n  public Charset encoding() {\n    if (encoding == null) {\n      setEncoding(StreamSupport.stream(inputFiles().spliterator(), false)\n        .map(InputFile::charset)\n        .findFirst()\n        .orElse(Charset.defaultCharset()));\n    }\n    return encoding;\n  }\n\n  @Override\n  public InputFile inputFile(FilePredicate predicate) {\n    var files = inputFiles(predicate);\n    var iterator = files.iterator();\n    if (!iterator.hasNext()) {\n      return null;\n    }\n    var first = iterator.next();\n    if (!iterator.hasNext()) {\n      return first;\n    }\n\n    var sb = new StringBuilder();\n    sb.append(\"expected one element but was: <\" + first);\n    for (var i = 0; i < 4 && iterator.hasNext(); i++) {\n      sb.append(\", \" + iterator.next());\n    }\n    if (iterator.hasNext()) {\n      sb.append(\", ...\");\n    }\n    sb.append('>');\n\n    throw new IllegalArgumentException(sb.toString());\n\n  }\n\n  public Iterable<InputFile> inputFiles() {\n    return inputFiles(filePredicates.all());\n  }\n\n  @Override\n  public Iterable<InputFile> inputFiles(FilePredicate predicate) {\n    return OptimizedFilePredicateAdapter.create(predicate).get(inputFileCache);\n  }\n\n  @Override\n  public boolean hasFiles(FilePredicate predicate) {\n    return inputFiles(predicate).iterator().hasNext();\n  }\n\n  @Override\n  public Iterable<File> files(FilePredicate predicate) {\n    return () -> StreamSupport.stream(inputFiles(predicate).spliterator(), false)\n      .map(InputFile::file)\n      .iterator();\n  }\n\n  @Override\n  public SortedSet<String> languages() {\n    return inputFileCache.languages();\n  }\n\n  @Override\n  public File resolvePath(String path) {\n    throw new UnsupportedOperationException(\"resolvePath\");\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/SonarLintInputDir.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.io.File;\nimport java.net.URI;\nimport java.nio.file.Path;\nimport org.sonar.api.batch.fs.InputDir;\nimport org.sonar.api.utils.PathUtils;\n\n/**\n * This is a simple placeholder. Issues on directories will be reported as project level issues.\n */\npublic class SonarLintInputDir implements InputDir {\n\n  private final Path path;\n\n  public SonarLintInputDir(Path path) {\n    this.path = path;\n  }\n\n  @Override\n  public String relativePath() {\n    return absolutePath();\n  }\n\n  @Override\n  public String absolutePath() {\n    return PathUtils.sanitize(path().toString());\n  }\n\n  @Override\n  public File file() {\n    return path().toFile();\n  }\n\n  @Override\n  public Path path() {\n    return path;\n  }\n\n  @Override\n  public String key() {\n    return absolutePath();\n  }\n\n  @Override\n  public URI uri() {\n    return path.toUri();\n  }\n\n  @Override\n  public boolean isFile() {\n    return false;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) {\n      return true;\n    }\n\n    if (!(o instanceof SonarLintInputDir dir)) {\n      return false;\n    }\n\n    return path().equals(dir.path());\n  }\n\n  @Override\n  public int hashCode() {\n    return path().hashCode();\n  }\n\n  @Override\n  public String toString() {\n    return \"[path=\" + path() + \"]\";\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/SonarLintInputFile.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.nio.charset.Charset;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.function.Function;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.apache.commons.codec.digest.DigestUtils;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.fs.TextPointer;\nimport org.sonar.api.batch.fs.TextRange;\nimport org.sonar.api.utils.PathUtils;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.FileMetadata.Metadata;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\n\npublic class SonarLintInputFile implements InputFile {\n\n  private final ClientInputFile clientInputFile;\n  private final String relativePath;\n  private SonarLanguage language;\n  private Type type;\n  private Metadata metadata;\n  private final Function<SonarLintInputFile, Metadata> metadataGenerator;\n  private boolean ignoreAllIssues;\n  private final Set<Integer> noSonarLines = new HashSet<>();\n  private Collection<int[]> ignoreIssuesOnlineRanges;\n\n  public SonarLintInputFile(ClientInputFile clientInputFile, Function<SonarLintInputFile, Metadata> metadataGenerator) {\n    this.clientInputFile = clientInputFile;\n    this.metadataGenerator = metadataGenerator;\n    this.relativePath = PathUtils.sanitize(clientInputFile.relativePath());\n  }\n\n  public void checkMetadata() {\n    if (metadata == null) {\n      this.metadata = metadataGenerator.apply(this);\n    }\n  }\n\n  public ClientInputFile getClientInputFile() {\n    return clientInputFile;\n  }\n\n  @Override\n  public String relativePath() {\n    return relativePath;\n  }\n\n  public SonarLintInputFile setLanguage(@Nullable SonarLanguage language) {\n    this.language = language;\n    return this;\n  }\n\n  public SonarLintInputFile setType(Type type) {\n    this.type = type;\n    return this;\n  }\n\n  @CheckForNull\n  @Override\n  public String language() {\n    return language != null ? language.getSonarLanguageKey() : null;\n  }\n\n  @CheckForNull\n  public SonarLanguage getLanguage() {\n    return language;\n  }\n\n  @Override\n  public Type type() {\n    return type;\n  }\n\n  /**\n   * @deprecated avoid calling this method if possible, since it may require to create a temporary copy of the file\n   */\n  @Deprecated(forRemoval = false)\n  @Override\n  public String absolutePath() {\n    return PathUtils.sanitize(clientInputFile.getPath());\n  }\n\n  /**\n   * @deprecated avoid calling this method if possible, since it may require to create a temporary copy of the file\n   */\n  @Deprecated\n  @Override\n  public File file() {\n    return path().toFile();\n  }\n\n  /**\n   * @deprecated avoid calling this method if possible, since it may require to create a temporary copy of the file\n   */\n  @Deprecated\n  @Override\n  public Path path() {\n    return Paths.get(clientInputFile.getPath());\n  }\n\n  @Override\n  public InputStream inputStream() throws IOException {\n    return clientInputFile.inputStream();\n  }\n\n  @Override\n  public String contents() throws IOException {\n    return clientInputFile.contents();\n  }\n\n  @Override\n  public Status status() {\n    return Status.ADDED;\n  }\n\n  /**\n   * Component key.\n   */\n  @Override\n  public String key() {\n    return uri().toString();\n  }\n\n  @Override\n  public URI uri() {\n    return clientInputFile.uri();\n  }\n\n  @Override\n  public Charset charset() {\n    var charset = clientInputFile.getCharset();\n    return charset != null ? charset : Charset.defaultCharset();\n  }\n\n  @Override\n  public String md5Hash() {\n    try {\n      return DigestUtils.md5Hex(contents());\n    } catch (IOException e) {\n      throw new IllegalStateException(\"Unable to compute md5Hash for \" + uri(), e);\n    }\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) {\n      return true;\n    }\n\n    if (!(o instanceof SonarLintInputFile file)) {\n      return false;\n    }\n\n    return uri().equals(file.uri());\n  }\n\n  @Override\n  public int hashCode() {\n    return uri().hashCode();\n  }\n\n  @Override\n  public String toString() {\n    return \"[uri=\" + uri() + \"]\";\n  }\n\n  @Override\n  public boolean isFile() {\n    return true;\n  }\n\n  @Override\n  public String filename() {\n    return Paths.get(relativePath).getFileName().toString();\n  }\n\n  @Override\n  public int lines() {\n    checkMetadata();\n    return metadata.lines();\n  }\n\n  @Override\n  public boolean isEmpty() {\n    checkMetadata();\n    return metadata.lastValidOffset() == 0;\n  }\n\n  @Override\n  public TextPointer newPointer(int line, int lineOffset) {\n    checkMetadata();\n    return new DefaultTextPointer(line, lineOffset);\n  }\n\n  @Override\n  public TextRange newRange(TextPointer start, TextPointer end) {\n    checkMetadata();\n    return newRangeValidPointers(start, end);\n  }\n\n  @Override\n  public TextRange newRange(int startLine, int startLineOffset, int endLine, int endLineOffset) {\n    checkMetadata();\n    var start = newPointer(startLine, startLineOffset);\n    var end = newPointer(endLine, endLineOffset);\n    return newRangeValidPointers(start, end);\n  }\n\n  @Override\n  public TextRange selectLine(int line) {\n    checkMetadata();\n    var startPointer = newPointer(line, 0);\n    var endPointer = newPointer(line, lineLength(line));\n    return newRangeValidPointers(startPointer, endPointer);\n  }\n\n  private static TextRange newRangeValidPointers(TextPointer start, TextPointer end) {\n    return new DefaultTextRange(start, end);\n  }\n\n  private int lineLength(int line) {\n    return lastValidGlobalOffsetForLine(line) - metadata.originalLineOffsets()[line - 1];\n  }\n\n  private int lastValidGlobalOffsetForLine(int line) {\n    return line < this.metadata.lines() ? (metadata.originalLineOffsets()[line] - 1) : metadata.lastValidOffset();\n  }\n\n  public void noSonarAt(Set<Integer> noSonarLines) {\n    this.noSonarLines.addAll(noSonarLines);\n  }\n\n  public boolean hasNoSonarAt(int line) {\n    return this.noSonarLines.contains(line);\n  }\n\n  public boolean isIgnoreAllIssues() {\n    checkMetadata();\n    return ignoreAllIssues;\n  }\n\n  public void setIgnoreAllIssues(boolean ignoreAllIssues) {\n    this.ignoreAllIssues = ignoreAllIssues;\n  }\n\n  public void addIgnoreIssuesOnLineRanges(Collection<int[]> lineRanges) {\n    if (this.ignoreIssuesOnlineRanges == null) {\n      this.ignoreIssuesOnlineRanges = new ArrayList<>();\n    }\n    this.ignoreIssuesOnlineRanges.addAll(lineRanges);\n  }\n\n  public boolean isIgnoreAllIssuesOnLine(@Nullable Integer line) {\n    checkMetadata();\n    if (line == null || ignoreIssuesOnlineRanges == null) {\n      return false;\n    }\n    return ignoreIssuesOnlineRanges.stream().anyMatch(r -> r[0] <= line && line <= r[1]);\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/SonarLintInputProject.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport org.sonar.api.batch.fs.InputModule;\nimport org.sonar.api.scanner.fs.InputProject;\n\npublic class SonarLintInputProject implements InputModule, InputProject {\n\n  public static final String SONARLINT_FAKE_PROJECT_KEY = \"sonarlint\";\n\n  @Override\n  public String key() {\n    return SONARLINT_FAKE_PROJECT_KEY;\n  }\n\n  @Override\n  public boolean isFile() {\n    return false;\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/StatusPredicate.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport javax.annotation.Nullable;\nimport org.sonar.api.batch.fs.InputFile;\n\npublic class StatusPredicate extends AbstractFilePredicate {\n\n  private final InputFile.Status status;\n\n  StatusPredicate(@Nullable InputFile.Status status) {\n    this.status = status;\n  }\n\n  @Override\n  public boolean apply(InputFile f) {\n    return status == null || status == f.status();\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/TruePredicate.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport org.sonar.api.batch.fs.FilePredicate;\nimport org.sonar.api.batch.fs.FileSystem.Index;\nimport org.sonar.api.batch.fs.InputFile;\n\nclass TruePredicate extends AbstractFilePredicate {\n\n  static final FilePredicate TRUE = new TruePredicate();\n\n  @Override\n  public boolean apply(InputFile inputFile) {\n    return true;\n  }\n\n  @Override\n  public Iterable<InputFile> get(Index index) {\n    return index.inputFiles();\n  }\n\n  @Override\n  public Iterable<InputFile> filter(Iterable<InputFile> target) {\n    return target;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/TypePredicate.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport org.sonar.api.batch.fs.InputFile;\n\n/**\n * @since 4.2\n */\nclass TypePredicate extends AbstractFilePredicate {\n\n  private final InputFile.Type type;\n\n  TypePredicate(InputFile.Type type) {\n    this.type = type;\n  }\n\n  @Override\n  public boolean apply(InputFile f) {\n    return type == f.type();\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/URIPredicate.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.net.URI;\nimport org.sonar.api.batch.fs.InputFile;\n\nclass URIPredicate extends AbstractFilePredicate {\n\n  private final URI uri;\n\n  URIPredicate(URI uri) {\n    this.uri = uri;\n  }\n\n  @Override\n  public boolean apply(InputFile f) {\n    return uri.equals(f.uri());\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/package-info.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n/**\n * This package is a part of bootstrap process, so we should take care about backward compatibility.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/DefaultIssueFilterChain.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue;\n\nimport java.util.List;\nimport org.sonar.api.scan.issue.filter.FilterableIssue;\nimport org.sonar.api.scan.issue.filter.IssueFilter;\nimport org.sonar.api.scan.issue.filter.IssueFilterChain;\n\npublic class DefaultIssueFilterChain implements IssueFilterChain {\n  private final List<IssueFilter> filters;\n\n  public DefaultIssueFilterChain(List<IssueFilter> filters) {\n    this.filters = filters;\n  }\n\n  @Override\n  public boolean accept(FilterableIssue issue) {\n    if (filters.isEmpty()) {\n      return true;\n    } else {\n      return filters.get(0).accept(issue, new DefaultIssueFilterChain(filters.subList(1, filters.size())));\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/IssueFilters.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue;\n\nimport java.util.List;\nimport java.util.Optional;\nimport org.sonar.api.batch.fs.InputComponent;\nimport org.sonar.api.scan.issue.filter.FilterableIssue;\nimport org.sonar.api.scan.issue.filter.IssueFilter;\nimport org.sonar.api.scan.issue.filter.IssueFilterChain;\nimport org.sonarsource.api.sonarlint.SonarLintSide;\nimport org.sonarsource.sonarlint.core.analysis.api.Issue;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.DefaultFilterableIssue;\n\n@SonarLintSide\npublic class IssueFilters {\n  private final List<IssueFilter> filters;\n\n  public IssueFilters(Optional<List<IssueFilter>> exclusionFilters) {\n    this.filters = exclusionFilters.orElse(List.of());\n  }\n\n  public boolean accept(InputComponent inputComponent, Issue rawIssue) {\n    IssueFilterChain filterChain = new DefaultIssueFilterChain(filters);\n    FilterableIssue fIssue = new DefaultFilterableIssue(rawIssue, inputComponent);\n    return filterChain.accept(fIssue);\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/SensorInputFileEdit.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.sensor.issue.fix.InputFileEdit;\nimport org.sonar.api.batch.sensor.issue.fix.NewInputFileEdit;\nimport org.sonar.api.batch.sensor.issue.fix.NewTextEdit;\nimport org.sonar.api.batch.sensor.issue.fix.TextEdit;\n\npublic class SensorInputFileEdit implements InputFileEdit, NewInputFileEdit, org.sonarsource.sonarlint.plugin.api.issue.NewInputFileEdit {\n\n  private final List<TextEdit> textEdits = new ArrayList<>();\n\n  private InputFile inputFile;\n\n  @Override\n  public SensorInputFileEdit on(InputFile inputFile) {\n    this.inputFile = inputFile;\n    return this;\n  }\n\n  @Override\n  public SensorTextEdit newTextEdit() {\n    return new SensorTextEdit();\n  }\n\n  @Override\n  public NewInputFileEdit addTextEdit(NewTextEdit newTextEdit) {\n    textEdits.add((SensorTextEdit) newTextEdit);\n    return this;\n  }\n\n  @Override\n  public org.sonarsource.sonarlint.plugin.api.issue.NewInputFileEdit addTextEdit(org.sonarsource.sonarlint.plugin.api.issue.NewTextEdit newTextEdit) {\n    // legacy method from sonarlint-plugin-api, keep for backward compatibility and remove later\n    textEdits.add((SensorTextEdit) newTextEdit);\n    return this;\n  }\n\n  @Override\n  public InputFile target() {\n    return inputFile;\n  }\n\n  @Override\n  public List<TextEdit> textEdits() {\n    return Collections.unmodifiableList(textEdits);\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/SensorQuickFix.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.sonar.api.batch.sensor.issue.fix.InputFileEdit;\nimport org.sonar.api.batch.sensor.issue.fix.NewInputFileEdit;\nimport org.sonar.api.batch.sensor.issue.fix.NewQuickFix;\nimport org.sonar.api.batch.sensor.issue.fix.QuickFix;\n\npublic class SensorQuickFix implements QuickFix, NewQuickFix, org.sonarsource.sonarlint.plugin.api.issue.NewQuickFix {\n\n  private final List<InputFileEdit> inputFileEdits = new ArrayList<>();\n\n  private String message;\n\n  @Override\n  public SensorQuickFix message(String message) {\n    this.message = message;\n    return this;\n  }\n\n  @Override\n  public SensorInputFileEdit newInputFileEdit() {\n    return new SensorInputFileEdit();\n  }\n\n  @Override\n  public NewQuickFix addInputFileEdit(NewInputFileEdit newInputFileEdit) {\n    inputFileEdits.add((SensorInputFileEdit) newInputFileEdit);\n    return this;\n  }\n\n  @Override\n  public SensorQuickFix addInputFileEdit(org.sonarsource.sonarlint.plugin.api.issue.NewInputFileEdit newInputFileEdit) {\n    // legacy method from sonarlint-plugin-api, keep for backward compatibility and remove later\n    inputFileEdits.add((SensorInputFileEdit) newInputFileEdit);\n    return this;\n  }\n\n  @Override\n  public String message() {\n    return message;\n  }\n\n  @Override\n  public List<InputFileEdit> inputFileEdits() {\n    return inputFileEdits;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/SensorTextEdit.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue;\n\nimport org.sonar.api.batch.fs.TextRange;\nimport org.sonar.api.batch.sensor.issue.fix.NewTextEdit;\nimport org.sonar.api.batch.sensor.issue.fix.TextEdit;\n\npublic class SensorTextEdit implements TextEdit, NewTextEdit, org.sonarsource.sonarlint.plugin.api.issue.NewTextEdit {\n  private TextRange range;\n  private String newText;\n\n  @Override\n  public SensorTextEdit at(TextRange range) {\n    this.range = range;\n    return this;\n  }\n\n  @Override\n  public SensorTextEdit withNewText(String newText) {\n    this.newText = newText;\n    return this;\n  }\n\n  @Override\n  public TextRange range() {\n    return range;\n  }\n\n  @Override\n  public String newText() {\n    return newText;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/TextRangeUtils.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue;\n\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\n\npublic class TextRangeUtils {\n\n  private TextRangeUtils() {\n  }\n\n  public static TextRange convert(org.sonar.api.batch.fs.TextRange analyzerTextRange) {\n    return new TextRange(\n      analyzerTextRange.start().line(),\n      analyzerTextRange.start().lineOffset(),\n      analyzerTextRange.end().line(),\n      analyzerTextRange.end().lineOffset());\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/EnforceIssuesFilter.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport org.sonar.api.scan.issue.filter.FilterableIssue;\nimport org.sonar.api.scan.issue.filter.IssueFilter;\nimport org.sonar.api.scan.issue.filter.IssueFilterChain;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputFile;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.pattern.IssueInclusionPatternInitializer;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.pattern.IssuePattern;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.DefaultFilterableIssue;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic class EnforceIssuesFilter implements IssueFilter {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final List<IssuePattern> multicriteriaPatterns;\n\n  public EnforceIssuesFilter(IssueInclusionPatternInitializer patternInitializer) {\n    this.multicriteriaPatterns = Collections.unmodifiableList(new ArrayList<>(patternInitializer.getMulticriteriaPatterns()));\n  }\n\n  @Override\n  public boolean accept(FilterableIssue issue, IssueFilterChain chain) {\n    var atLeastOneRuleMatched = false;\n    var atLeastOnePatternFullyMatched = false;\n    IssuePattern matchingPattern = null;\n\n    for (IssuePattern pattern : multicriteriaPatterns) {\n      if (pattern.matchRule(issue.ruleKey())) {\n        atLeastOneRuleMatched = true;\n        var component = ((DefaultFilterableIssue) issue).getComponent();\n        if (component.isFile()) {\n          var file = (SonarLintInputFile) component;\n          if (pattern.matchFile(file.relativePath())) {\n            atLeastOnePatternFullyMatched = true;\n            matchingPattern = pattern;\n          }\n        }\n      }\n    }\n\n    if (atLeastOneRuleMatched) {\n      if (atLeastOnePatternFullyMatched) {\n        LOG.debug(\"Issue {} enforced by pattern {}\", issue, matchingPattern);\n      }\n      return atLeastOnePatternFullyMatched;\n    } else {\n      return chain.accept(issue);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/IgnoreIssuesFilter.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport org.sonar.api.batch.fs.InputComponent;\nimport org.sonar.api.scan.issue.filter.FilterableIssue;\nimport org.sonar.api.scan.issue.filter.IssueFilter;\nimport org.sonar.api.scan.issue.filter.IssueFilterChain;\nimport org.sonar.api.utils.WildcardPattern;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputFile;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.DefaultFilterableIssue;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic class IgnoreIssuesFilter implements IssueFilter {\n\n  private final Map<InputComponent, List<WildcardPattern>> rulePatternByComponent = new HashMap<>();\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  @Override\n  public boolean accept(FilterableIssue issue, IssueFilterChain chain) {\n    var component = ((DefaultFilterableIssue) issue).getComponent();\n    if ((component.isFile() && ((SonarLintInputFile) component).isIgnoreAllIssues())\n      || (component.isFile() && ((SonarLintInputFile) component).isIgnoreAllIssuesOnLine(issue.line()))) {\n      return false;\n    }\n    if (hasRuleMatchFor(component, issue)) {\n      return false;\n    }\n    return chain.accept(issue);\n  }\n\n  public void addRuleExclusionPatternForComponent(SonarLintInputFile inputFile, WildcardPattern rulePattern) {\n    if (\"*\".equals(rulePattern.toString())) {\n      inputFile.setIgnoreAllIssues(true);\n    } else {\n      rulePatternByComponent.computeIfAbsent(inputFile, x -> new LinkedList<>()).add(rulePattern);\n    }\n  }\n\n  private boolean hasRuleMatchFor(InputComponent component, FilterableIssue issue) {\n    for (WildcardPattern pattern : rulePatternByComponent.getOrDefault(component, Collections.emptyList())) {\n      if (pattern.match(issue.ruleKey().toString())) {\n        LOG.debug(\"Issue {} ignored by exclusion pattern {}\", issue, pattern);\n        return true;\n      }\n    }\n    return false;\n\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/SonarLintNoSonarFilter.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore;\n\nimport java.util.Set;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.issue.NoSonarFilter;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputFile;\n\npublic class SonarLintNoSonarFilter extends NoSonarFilter {\n\n  @Override\n  public NoSonarFilter noSonarInFile(InputFile inputFile, Set<Integer> noSonarLines) {\n    ((SonarLintInputFile) inputFile).noSonarAt(noSonarLines);\n    return this;\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/package-info.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/pattern/AbstractPatternInitializer.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.pattern;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.apache.commons.lang3.StringUtils;\nimport org.sonar.api.config.Configuration;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic abstract class AbstractPatternInitializer {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final Configuration config;\n\n  private List<IssuePattern> multicriteriaPatterns;\n\n  protected AbstractPatternInitializer(Configuration config) {\n    this.config = config;\n    initPatterns();\n  }\n\n  protected Configuration getSettings() {\n    return config;\n  }\n\n  public List<IssuePattern> getMulticriteriaPatterns() {\n    return multicriteriaPatterns;\n  }\n\n  public boolean hasConfiguredPatterns() {\n    return hasMulticriteriaPatterns();\n  }\n\n  public boolean hasMulticriteriaPatterns() {\n    return !multicriteriaPatterns.isEmpty();\n  }\n\n  protected final void initPatterns() {\n    // Patterns Multicriteria\n    multicriteriaPatterns = new ArrayList<>();\n    for (String id : config.getStringArray(getMulticriteriaConfigurationKey())) {\n      var propPrefix = getMulticriteriaConfigurationKey() + \".\" + id + \".\";\n      var filePathPattern = config.get(propPrefix + \"resourceKey\").orElse(null);\n      if (StringUtils.isBlank(filePathPattern)) {\n        LOG.debug(\"Issue exclusions are misconfigured. File pattern is mandatory for each entry of '\" + getMulticriteriaConfigurationKey() + \"'\");\n        continue;\n      }\n      var ruleKeyPattern = config.get(propPrefix + \"ruleKey\").orElse(null);\n      if (StringUtils.isBlank(ruleKeyPattern)) {\n        LOG.debug(\"Issue exclusions are misconfigured. Rule key pattern is mandatory for each entry of '\" + getMulticriteriaConfigurationKey() + \"'\");\n        continue;\n      }\n      var pattern = new IssuePattern(filePathPattern, ruleKeyPattern);\n\n      multicriteriaPatterns.add(pattern);\n    }\n  }\n\n  protected abstract String getMulticriteriaConfigurationKey();\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/pattern/BlockIssuePattern.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.pattern;\n\npublic class BlockIssuePattern {\n  private final String beginBlockRegexp;\n  private final String endBlockRegexp;\n\n  public BlockIssuePattern(String beginBlockRegexp, String endBlockRegexp) {\n    this.beginBlockRegexp = beginBlockRegexp;\n    this.endBlockRegexp = endBlockRegexp;\n  }\n\n  public String getBeginBlockRegexp() {\n    return beginBlockRegexp;\n  }\n\n  public String getEndBlockRegexp() {\n    return endBlockRegexp;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/pattern/IssueExclusionPatternInitializer.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.pattern;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport javax.annotation.Nullable;\nimport org.apache.commons.lang3.StringUtils;\nimport org.sonar.api.config.Configuration;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic class IssueExclusionPatternInitializer extends AbstractPatternInitializer {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  public static final String EXCLUSION_KEY_PREFIX = \"sonar.issue.ignore\";\n  public static final String BLOCK_SUFFIX = \".block\";\n  public static final String PATTERNS_BLOCK_KEY = EXCLUSION_KEY_PREFIX + BLOCK_SUFFIX;\n  public static final String BEGIN_BLOCK_REGEXP = \"beginBlockRegexp\";\n  public static final String END_BLOCK_REGEXP = \"endBlockRegexp\";\n  public static final String ALLFILE_SUFFIX = \".allfile\";\n  public static final String PATTERNS_ALLFILE_KEY = EXCLUSION_KEY_PREFIX + ALLFILE_SUFFIX;\n  public static final String FILE_REGEXP = \"fileRegexp\";\n  private List<BlockIssuePattern> blockPatterns;\n  private List<String> allFilePatterns;\n\n  public IssueExclusionPatternInitializer(Configuration config) {\n    super(config);\n    loadFileContentPatterns();\n  }\n\n  @Override\n  protected String getMulticriteriaConfigurationKey() {\n    return EXCLUSION_KEY_PREFIX + \".multicriteria\";\n  }\n\n  @Override\n  public boolean hasConfiguredPatterns() {\n    return hasFileContentPattern() || hasMulticriteriaPatterns();\n  }\n\n  private void loadFileContentPatterns() {\n    // Patterns Block\n    blockPatterns = new ArrayList<>();\n    for (String id : getSettings().getStringArray(PATTERNS_BLOCK_KEY)) {\n      var propPrefix = PATTERNS_BLOCK_KEY + \".\" + id + \".\";\n      var beginBlockRegexp = getSettings().get(propPrefix + BEGIN_BLOCK_REGEXP).orElse(null);\n      if (StringUtils.isBlank(beginBlockRegexp)) {\n        LOG.debug(\"Issue exclusions are misconfigured. Start block regexp is mandatory for each entry of '\" + PATTERNS_BLOCK_KEY + \"'\");\n        continue;\n      }\n      var endBlockRegexp = getSettings().get(propPrefix + END_BLOCK_REGEXP).orElse(null);\n      // As per configuration help, missing second field means: from start regexp to EOF\n      var pattern = new BlockIssuePattern(nullToEmpty(beginBlockRegexp), nullToEmpty(endBlockRegexp));\n      blockPatterns.add(pattern);\n    }\n    blockPatterns = Collections.unmodifiableList(blockPatterns);\n\n    // Patterns All File\n    allFilePatterns = new ArrayList<>();\n    for (String id : getSettings().getStringArray(PATTERNS_ALLFILE_KEY)) {\n      var propPrefix = PATTERNS_ALLFILE_KEY + \".\" + id + \".\";\n      var allFileRegexp = getSettings().get(propPrefix + FILE_REGEXP).orElse(null);\n      if (StringUtils.isBlank(allFileRegexp)) {\n        LOG.debug(\"Issue exclusions are misconfigured. Remove blank entries from '\" + PATTERNS_ALLFILE_KEY + \"'\");\n        continue;\n      }\n      allFilePatterns.add(nullToEmpty(allFileRegexp));\n    }\n    allFilePatterns = Collections.unmodifiableList(allFilePatterns);\n  }\n\n  private static String nullToEmpty(@Nullable String str) {\n    if (str == null) {\n      return \"\";\n    }\n    return str;\n  }\n\n  public List<BlockIssuePattern> getBlockPatterns() {\n    return blockPatterns;\n  }\n\n  public List<String> getAllFilePatterns() {\n    return allFilePatterns;\n  }\n\n  public boolean hasFileContentPattern() {\n    return !(blockPatterns.isEmpty() && allFilePatterns.isEmpty());\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/pattern/IssueInclusionPatternInitializer.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.pattern;\n\nimport org.sonar.api.config.Configuration;\n\npublic class IssueInclusionPatternInitializer extends AbstractPatternInitializer {\n\n  public static final String INCLUSION_KEY_PREFIX = \"sonar.issue.enforce\";\n\n  public IssueInclusionPatternInitializer(Configuration config) {\n    super(config);\n  }\n\n  @Override\n  protected String getMulticriteriaConfigurationKey() {\n    return INCLUSION_KEY_PREFIX + \".multicriteria\";\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/pattern/IssuePattern.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.pattern;\n\nimport javax.annotation.Nullable;\nimport org.apache.commons.lang3.builder.ToStringBuilder;\nimport org.apache.commons.lang3.builder.ToStringStyle;\nimport org.sonar.api.rule.RuleKey;\nimport org.sonar.api.utils.WildcardPattern;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.SonarLintPathPattern;\n\npublic class IssuePattern {\n\n  private final SonarLintPathPattern pathPattern;\n  private final WildcardPattern rulePattern;\n\n  public IssuePattern(String pathPattern, String rulePattern) {\n    this.pathPattern = new SonarLintPathPattern(pathPattern);\n    this.rulePattern = WildcardPattern.create(rulePattern);\n  }\n\n  public WildcardPattern getRulePattern() {\n    return rulePattern;\n  }\n\n  public boolean matchRule(RuleKey rule) {\n    return rulePattern.match(rule.toString());\n  }\n\n  public boolean matchFile(@Nullable String filePath) {\n    return filePath != null && pathPattern.match(filePath);\n  }\n\n  @Override\n  public String toString() {\n    return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/pattern/package-info.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.pattern;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/scanner/IssueExclusionsLoader.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.scanner;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport javax.annotation.CheckForNull;\nimport org.apache.commons.lang3.StringUtils;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.FileMetadata.CharHandler;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputFile;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.IgnoreIssuesFilter;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.pattern.BlockIssuePattern;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.pattern.IssueExclusionPatternInitializer;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.pattern.IssuePattern;\n\npublic class IssueExclusionsLoader {\n\n  private final List<java.util.regex.Pattern> allFilePatterns;\n  private final List<DoubleRegexpMatcher> blockMatchers;\n  private final IgnoreIssuesFilter ignoreIssuesFilter;\n  private final IssueExclusionPatternInitializer patternsInitializer;\n  private final boolean enableCharHandler;\n\n  public IssueExclusionsLoader(IssueExclusionPatternInitializer patternsInitializer, IgnoreIssuesFilter ignoreIssuesFilter) {\n    this.patternsInitializer = patternsInitializer;\n    this.ignoreIssuesFilter = ignoreIssuesFilter;\n    this.allFilePatterns = new ArrayList<>();\n    this.blockMatchers = new ArrayList<>();\n\n    for (String pattern : patternsInitializer.getAllFilePatterns()) {\n      allFilePatterns.add(java.util.regex.Pattern.compile(pattern));\n    }\n    for (BlockIssuePattern pattern : patternsInitializer.getBlockPatterns()) {\n      blockMatchers.add(new DoubleRegexpMatcher(\n        java.util.regex.Pattern.compile(pattern.getBeginBlockRegexp()),\n        java.util.regex.Pattern.compile(pattern.getEndBlockRegexp())));\n    }\n    enableCharHandler = !allFilePatterns.isEmpty() || !blockMatchers.isEmpty();\n  }\n\n  public void addMulticriteriaPatterns(SonarLintInputFile inputFile) {\n    for (IssuePattern pattern : patternsInitializer.getMulticriteriaPatterns()) {\n      if (pattern.matchFile(inputFile.relativePath())) {\n        ignoreIssuesFilter.addRuleExclusionPatternForComponent(inputFile, pattern.getRulePattern());\n      }\n    }\n  }\n\n  @CheckForNull\n  public CharHandler createCharHandlerFor(SonarLintInputFile inputFile) {\n    if (enableCharHandler) {\n      return new IssueExclusionsRegexpScanner(inputFile, allFilePatterns, blockMatchers);\n    }\n    return null;\n  }\n\n  public static class DoubleRegexpMatcher {\n\n    private final java.util.regex.Pattern firstPattern;\n    private final java.util.regex.Pattern secondPattern;\n\n    DoubleRegexpMatcher(java.util.regex.Pattern firstPattern, java.util.regex.Pattern secondPattern) {\n      this.firstPattern = firstPattern;\n      this.secondPattern = secondPattern;\n    }\n\n    boolean matchesFirstPattern(String line) {\n      return firstPattern.matcher(line).find();\n    }\n\n    boolean matchesSecondPattern(String line) {\n      return hasSecondPattern() && secondPattern.matcher(line).find();\n    }\n\n    boolean hasSecondPattern() {\n      return StringUtils.isNotEmpty(secondPattern.toString());\n    }\n  }\n\n  @Override\n  public String toString() {\n    return \"Issues Exclusions - Source Scanner\";\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/scanner/IssueExclusionsRegexpScanner.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.scanner;\n\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.FileMetadata.CharHandler;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputFile;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.scanner.IssueExclusionsLoader.DoubleRegexpMatcher;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic class IssueExclusionsRegexpScanner extends CharHandler {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final StringBuilder sb = new StringBuilder();\n  private final List<Pattern> allFilePatterns;\n  private final List<DoubleRegexpMatcher> blockMatchers;\n  private final SonarLintInputFile inputFile;\n\n  private int lineIndex = 1;\n  private final List<LineExclusion> lineExclusions = new ArrayList<>();\n  private LineExclusion currentLineExclusion = null;\n  private int fileLength = 0;\n  private DoubleRegexpMatcher currentMatcher;\n  private boolean ignoreAllIssues;\n\n  IssueExclusionsRegexpScanner(SonarLintInputFile inputFile, List<Pattern> allFilePatterns, List<DoubleRegexpMatcher> blockMatchers) {\n    this.allFilePatterns = allFilePatterns;\n    this.blockMatchers = blockMatchers;\n    this.inputFile = inputFile;\n    LOG.debug(\"Evaluate issue exclusions for '{}'\", inputFile.relativePath());\n  }\n\n  @Override\n  public void handleIgnoreEoL(char c) {\n    if (ignoreAllIssues) {\n      // Optimization\n      return;\n    }\n    sb.append(c);\n  }\n\n  @Override\n  public void newLine() {\n    if (ignoreAllIssues) {\n      // Optimization\n      return;\n    }\n    processLine(sb.toString());\n    sb.setLength(0);\n    lineIndex++;\n  }\n\n  @Override\n  public void eof() {\n    if (ignoreAllIssues) {\n      // Optimization\n      return;\n    }\n    processLine(sb.toString());\n\n    if (currentMatcher != null && !currentMatcher.hasSecondPattern()) {\n      // this will happen when there is a start block regexp but no end block regexp\n      endExclusion(lineIndex + 1);\n    }\n\n    // now create the new line-based pattern for this file if there are exclusions\n    fileLength = lineIndex;\n    if (!lineExclusions.isEmpty()) {\n      var lineRanges = convertLineExclusionsToLineRanges();\n      LOG.debug(\"  - Line exclusions found: {}\", lineRanges.stream().map(LineRange::toString).collect(Collectors.joining(\",\")));\n      inputFile.addIgnoreIssuesOnLineRanges(lineRanges.stream().map(r -> new int[] {r.from(), r.to()}).toList());\n    }\n  }\n\n  private void processLine(String line) {\n    if (line.trim().isEmpty()) {\n      return;\n    }\n\n    // first check the single regexp patterns that can be used to totally exclude a file\n    for (Pattern pattern : allFilePatterns) {\n      if (pattern.matcher(line).find()) {\n        // nothing more to do on this file\n        LOG.debug(\"  - Exclusion pattern '{}': all issues in this file will be ignored.\", pattern);\n        ignoreAllIssues = true;\n        inputFile.setIgnoreAllIssues(true);\n        return;\n      }\n    }\n\n    // then check the double regexps if we're still here\n    checkDoubleRegexps(line, lineIndex);\n  }\n\n  private Set<LineRange> convertLineExclusionsToLineRanges() {\n    Set<LineRange> lineRanges = HashSet.newHashSet(lineExclusions.size());\n    for (LineExclusion lineExclusion : lineExclusions) {\n      lineRanges.add(lineExclusion.toLineRange(fileLength));\n    }\n    return lineRanges;\n  }\n\n  private void checkDoubleRegexps(String line, int lineIndex) {\n    if (currentMatcher == null) {\n      for (DoubleRegexpMatcher matcher : blockMatchers) {\n        if (matcher.matchesFirstPattern(line)) {\n          startExclusion(lineIndex);\n          currentMatcher = matcher;\n          break;\n        }\n      }\n    } else {\n      if (currentMatcher.matchesSecondPattern(line)) {\n        endExclusion(lineIndex);\n        currentMatcher = null;\n      }\n    }\n  }\n\n  private void startExclusion(int lineIndex) {\n    currentLineExclusion = new LineExclusion(lineIndex);\n    lineExclusions.add(currentLineExclusion);\n  }\n\n  private void endExclusion(int lineIndex) {\n    currentLineExclusion.setEnd(lineIndex);\n    currentLineExclusion = null;\n  }\n\n  private static class LineExclusion {\n    private final int start;\n    private int end;\n\n    LineExclusion(int start) {\n      this.start = start;\n      this.end = -1;\n    }\n\n    void setEnd(int end) {\n      this.end = end;\n    }\n\n    public LineRange toLineRange(int fileLength) {\n      return new LineRange(start, end == -1 ? fileLength : end);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/scanner/LineRange.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.scanner;\n\nimport java.util.LinkedHashSet;\nimport java.util.Objects;\nimport java.util.Set;\n\npublic class LineRange {\n  private final int from;\n  private final int to;\n\n  public LineRange(int line) {\n    this(line, line);\n  }\n\n  public LineRange(int from, int to) {\n    if (from > to) {\n      throw new IllegalArgumentException(String.format(\"Line range is not valid: %s must be greater or equal than %s\", to, from));\n    }\n\n    this.from = from;\n    this.to = to;\n  }\n\n  public boolean in(int lineId) {\n    return from <= lineId && lineId <= to;\n  }\n\n  public Set<Integer> toLines() {\n    Set<Integer> lines = LinkedHashSet.newLinkedHashSet(to - from + 1);\n    for (var index = from; index <= to; index++) {\n      lines.add(index);\n    }\n    return lines;\n  }\n\n  public int from() {\n    return from;\n  }\n\n  public int to() {\n    return to;\n  }\n\n  @Override\n  public String toString() {\n    return \"[\" + from + \"-\" + to + \"]\";\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(from, to);\n  }\n\n  @Override\n  public boolean equals(Object obj) {\n    if (this == obj) {\n      return true;\n    }\n    if ((obj == null) || (getClass() != obj.getClass())) {\n      return false;\n    }\n    return !fieldsDiffer((LineRange) obj);\n  }\n\n  private boolean fieldsDiffer(LineRange other) {\n    return from != other.from || to != other.to;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/scanner/package-info.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.scanner;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/package-info.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/package-info.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.analysis.container.analysis;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/sensor/SensorOptimizer.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.sensor;\n\nimport org.sonar.api.batch.fs.FileSystem;\nimport org.sonar.api.batch.rule.ActiveRules;\nimport org.sonar.api.config.Configuration;\nimport org.sonarsource.api.sonarlint.SonarLintSide;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.DefaultSensorDescriptor;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\n@SonarLintSide\npublic class SensorOptimizer {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final FileSystem fs;\n  private final ActiveRules activeRules;\n  private final Configuration config;\n\n  public SensorOptimizer(FileSystem fs, ActiveRules activeRules, Configuration config) {\n    this.fs = fs;\n    this.activeRules = activeRules;\n    this.config = config;\n  }\n\n  /**\n   * Decide if the given Sensor should be executed.\n   */\n  public boolean shouldExecute(DefaultSensorDescriptor descriptor) {\n    if (!fsCondition(descriptor)) {\n      LOG.debug(\"'{}' skipped because there are no related files in the current project\", descriptor.name());\n      return false;\n    }\n    if (!activeRulesCondition(descriptor)) {\n      LOG.debug(\"'{}' skipped because there are no related rules activated\", descriptor.name());\n      return false;\n    }\n    if (!settingsCondition(descriptor)) {\n      LOG.debug(\"'{}' skipped because one of the required properties is missing\", descriptor.name());\n      return false;\n    }\n    return true;\n  }\n\n  private boolean settingsCondition(DefaultSensorDescriptor descriptor) {\n    if (descriptor.configurationPredicate() != null) {\n      return descriptor.configurationPredicate().test(config);\n    }\n    return true;\n  }\n\n  private boolean activeRulesCondition(DefaultSensorDescriptor descriptor) {\n    if (!descriptor.ruleRepositories().isEmpty()) {\n      for (String repoKey : descriptor.ruleRepositories()) {\n        if (!activeRules.findByRepository(repoKey).isEmpty()) {\n          return true;\n        }\n      }\n      return false;\n    }\n    return true;\n  }\n\n  private boolean fsCondition(DefaultSensorDescriptor descriptor) {\n    if (!descriptor.languages().isEmpty() || descriptor.type() != null) {\n      var langPredicate = descriptor.languages().isEmpty() ? fs.predicates().all() : fs.predicates().hasLanguages(descriptor.languages());\n\n      var typePredicate = descriptor.type() == null ? fs.predicates().all() : fs.predicates().hasType(descriptor.type());\n      return fs.hasFiles(fs.predicates().and(langPredicate, typePredicate));\n    }\n    return true;\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/sensor/SensorsExecutor.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.sensor;\n\nimport java.lang.annotation.Annotation;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.sonar.api.batch.DependedUpon;\nimport org.sonar.api.batch.DependsUpon;\nimport org.sonar.api.batch.Phase;\nimport org.sonar.api.batch.sensor.Sensor;\nimport org.sonar.api.batch.sensor.SensorContext;\nimport org.sonar.api.scanner.sensor.ProjectSensor;\nimport org.sonar.api.utils.AnnotationUtils;\nimport org.sonar.api.utils.dag.DirectAcyclicGraph;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.DefaultSensorContext;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.DefaultSensorDescriptor;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.tracing.Trace;\n\nimport static org.sonarsource.sonarlint.core.commons.tracing.Trace.startChild;\n\n/**\n * Execute Sensors.\n */\npublic class SensorsExecutor {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final SensorOptimizer sensorOptimizer;\n  private final List<ProjectSensor> sensors;\n  private final DefaultSensorContext context;\n  @Nullable\n  private final Trace trace;\n\n  public SensorsExecutor(DefaultSensorContext context, SensorOptimizer sensorOptimizer, Optional<Trace> trace, Optional<List<ProjectSensor>> sensors) {\n    this.context = context;\n    this.sensors = sensors.orElse(List.of());\n    this.sensorOptimizer = sensorOptimizer;\n    this.trace = trace.orElse(null);\n  }\n\n  public void execute() {\n    var sensorGroups = sensors.stream().collect(Collectors.partitioningBy(s -> {\n      var isModernGlobalSensor = !(s instanceof Sensor);\n      if (isModernGlobalSensor) {\n        return true;\n      } else {\n        var descriptor = new DefaultSensorDescriptor();\n        s.describe(descriptor);\n        return descriptor.isGlobal();\n      }\n    }));\n\n    var moduleSensors = sensorGroups.get(false);\n    var globalSensors = sensorGroups.get(true);\n\n    executeSensors(moduleSensors);\n    executeSensors(globalSensors);\n  }\n\n  private void executeSensors(List<ProjectSensor> sensors) {\n    for (var sensor : sort(sensors)) {\n      if (context.isCancelled()) {\n        LOG.debug(\"Analysis is canceled\");\n        return;\n      }\n      var descriptor = new DefaultSensorDescriptor();\n      sensor.describe(descriptor);\n      if (sensorOptimizer.shouldExecute(descriptor)) {\n        executeSensor(context, sensor, descriptor, trace);\n      }\n    }\n  }\n\n  private static void executeSensor(SensorContext context, ProjectSensor sensor, DefaultSensorDescriptor descriptor, @Nullable Trace trace) {\n    var sensorName = descriptor.name() != null ? descriptor.name() : describe(sensor);\n    LOG.debug(\"Execute Sensor: {}\", sensorName);\n    try {\n      startChild(trace, \"executeSensor\", sensorName, () -> sensor.execute(context));\n    } catch (Throwable t) {\n      LOG.error(\"Error executing sensor: '{}'\", sensorName, t);\n    }\n  }\n\n  static String describe(Object o) {\n    try {\n      if (o.getClass().getMethod(\"toString\").getDeclaringClass() != Object.class) {\n        var str = o.toString();\n        if (str != null) {\n          return str;\n        }\n      }\n    } catch (Exception e) {\n      // fallback\n    }\n\n    return o.getClass().getName();\n  }\n\n  private static <T> Collection<T> sort(Collection<T> extensions) {\n    var dag = new DirectAcyclicGraph();\n\n    for (T extension : extensions) {\n      dag.add(extension);\n      for (Object dependency : getDependencies(extension)) {\n        dag.add(extension, dependency);\n      }\n      for (Object generates : getDependents(extension)) {\n        dag.add(generates, extension);\n      }\n      completePhaseDependencies(dag, extension);\n    }\n    List<?> sortedList = dag.sort();\n\n    return (Collection<T>) sortedList.stream()\n      .filter(extensions::contains)\n      .toList();\n  }\n\n  /**\n   * Extension dependencies\n   */\n  private static <T> List<Object> getDependencies(T extension) {\n    return new ArrayList<>(evaluateAnnotatedClasses(extension, DependsUpon.class));\n  }\n\n  /**\n   * Objects that depend upon this extension.\n   */\n  private static <T> List<Object> getDependents(T extension) {\n    return new ArrayList<>(evaluateAnnotatedClasses(extension, DependedUpon.class));\n  }\n\n  private static void completePhaseDependencies(DirectAcyclicGraph dag, Object extension) {\n    var phase = evaluatePhase(extension);\n    dag.add(extension, phase);\n    for (Phase.Name name : Phase.Name.values()) {\n      if (phase.compareTo(name) < 0) {\n        dag.add(name, extension);\n      } else if (phase.compareTo(name) > 0) {\n        dag.add(extension, name);\n      }\n    }\n  }\n\n  private static Phase.Name evaluatePhase(Object extension) {\n    var phaseAnnotation = AnnotationUtils.getAnnotation(extension, Phase.class);\n    if (phaseAnnotation != null) {\n      return phaseAnnotation.name();\n    }\n    return Phase.Name.DEFAULT;\n  }\n\n  static List<Object> evaluateAnnotatedClasses(Object extension, Class<? extends Annotation> annotation) {\n    List<Object> results = new ArrayList<>();\n    Class<?> aClass = extension.getClass();\n    while (aClass != null) {\n      evaluateClass(aClass, annotation, results);\n      aClass = aClass.getSuperclass();\n    }\n\n    return results;\n  }\n\n  private static void evaluateClass(Class<?> extensionClass, Class<? extends Annotation> annotationClass, List<Object> results) {\n    Annotation annotation = extensionClass.getAnnotation(annotationClass);\n    if (annotation != null) {\n      if (annotation.annotationType().isAssignableFrom(DependsUpon.class)) {\n        results.addAll(Arrays.asList(((DependsUpon) annotation).value()));\n\n      } else if (annotation.annotationType().isAssignableFrom(DependedUpon.class)) {\n        results.addAll(Arrays.asList(((DependedUpon) annotation).value()));\n      }\n    }\n\n    var interfaces = extensionClass.getInterfaces();\n    for (Class<?> anInterface : interfaces) {\n      evaluateClass(anInterface, annotationClass, results);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/sensor/SonarLintSensorStorage.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.sensor;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\nimport org.apache.commons.lang3.Strings;\nimport org.sonar.api.batch.fs.InputComponent;\nimport org.sonar.api.batch.rule.ActiveRules;\nimport org.sonar.api.batch.sensor.code.NewSignificantCode;\nimport org.sonar.api.batch.sensor.coverage.NewCoverage;\nimport org.sonar.api.batch.sensor.cpd.NewCpdTokens;\nimport org.sonar.api.batch.sensor.error.AnalysisError;\nimport org.sonar.api.batch.sensor.highlighting.NewHighlighting;\nimport org.sonar.api.batch.sensor.internal.SensorStorage;\nimport org.sonar.api.batch.sensor.issue.ExternalIssue;\nimport org.sonar.api.batch.sensor.issue.Issue;\nimport org.sonar.api.batch.sensor.issue.Issue.Flow;\nimport org.sonar.api.batch.sensor.issue.fix.QuickFix;\nimport org.sonar.api.batch.sensor.measure.Measure;\nimport org.sonar.api.batch.sensor.rule.AdHocRule;\nimport org.sonar.api.batch.sensor.symbol.NewSymbolTable;\nimport org.sonar.api.issue.impact.Severity;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisResults;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFileEdit;\nimport org.sonarsource.sonarlint.core.analysis.api.TextEdit;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.IssueListenerHolder;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputFile;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.IssueFilters;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.TextRangeUtils;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.DefaultSonarLintIssue;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\n\npublic class SonarLintSensorStorage implements SensorStorage {\n\n  private final ActiveRules activeRules;\n  private final IssueFilters filters;\n  private final IssueListenerHolder issueListener;\n  private final AnalysisResults analysisResult;\n\n  public SonarLintSensorStorage(ActiveRules activeRules, IssueFilters filters, IssueListenerHolder issueListener, AnalysisResults analysisResult) {\n    this.activeRules = activeRules;\n    this.filters = filters;\n    this.issueListener = issueListener;\n    this.analysisResult = analysisResult;\n  }\n\n  @Override\n  public void store(Measure newMeasure) {\n    // NO-OP\n  }\n\n  @Override\n  public void store(Issue issue) {\n    if (!(issue instanceof DefaultSonarLintIssue sonarLintIssue)) {\n      throw new IllegalArgumentException(\"Trying to store a non-SonarLint issue?\");\n    }\n    var inputComponent = sonarLintIssue.primaryLocation().inputComponent();\n\n    var activeRule = activeRules.find(sonarLintIssue.ruleKey());\n    if ((activeRule == null) || noSonar(inputComponent, sonarLintIssue)) {\n      return;\n    }\n\n    var primaryMessage = sonarLintIssue.primaryLocation().message();\n    var flows = mapFlows(sonarLintIssue.flows());\n    var quickFixes = transform(sonarLintIssue.quickFixes());\n    var overriddenImpacts = transform(sonarLintIssue.overridenImpacts());\n\n    var newIssue = new org.sonarsource.sonarlint.core.analysis.api.Issue(activeRule, primaryMessage, overriddenImpacts, issue.primaryLocation().textRange(),\n      inputComponent.isFile() ? ((SonarLintInputFile) inputComponent).getClientInputFile() : null, flows, quickFixes, sonarLintIssue.ruleDescriptionContextKey());\n    if (filters.accept(inputComponent, newIssue)) {\n      issueListener.handle(newIssue);\n    }\n  }\n\n  private static List<org.sonarsource.sonarlint.core.analysis.api.QuickFix> transform(List<QuickFix> quickFixes) {\n    return quickFixes.stream().map(SonarLintSensorStorage::transform).toList();\n  }\n\n  private static Map<SoftwareQuality, ImpactSeverity> transform(Map<org.sonar.api.issue.impact.SoftwareQuality, Severity> overriddenImpacts) {\n    return overriddenImpacts.entrySet().stream()\n      .map(e -> Map.entry(SoftwareQuality.valueOf(e.getKey().name()), ImpactSeverity.valueOf(e.getValue().name())))\n      .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\n  }\n\n  private static org.sonarsource.sonarlint.core.analysis.api.QuickFix transform(QuickFix qf) {\n    return new org.sonarsource.sonarlint.core.analysis.api.QuickFix(\n      qf.inputFileEdits().stream().map(edit -> new ClientInputFileEdit(\n        ((SonarLintInputFile) edit.target()).getClientInputFile(),\n        edit.textEdits().stream().map(textEdit -> new TextEdit(TextRangeUtils.convert(textEdit.range()), textEdit.newText())).toList())).toList(),\n      qf.message());\n  }\n\n  private static boolean noSonar(InputComponent inputComponent, Issue issue) {\n    var textRange = issue.primaryLocation().textRange();\n    return inputComponent.isFile()\n      && textRange != null\n      && ((SonarLintInputFile) inputComponent).hasNoSonarAt(textRange.start().line())\n      && !Strings.CI.contains(issue.ruleKey().rule(), \"nosonar\");\n  }\n\n  private static List<org.sonarsource.sonarlint.core.analysis.api.Flow> mapFlows(List<Flow> flows) {\n    return flows.stream()\n      .map(f -> new org.sonarsource.sonarlint.core.analysis.api.Flow(new ArrayList<>(f.locations())))\n      .filter(f -> !f.locations().isEmpty())\n      .toList();\n  }\n\n  @Override\n  public void store(NewHighlighting highlighting) {\n    // NO-OP\n  }\n\n  @Override\n  public void store(NewCoverage defaultCoverage) {\n    // NO-OP\n  }\n\n  @Override\n  public void store(NewCpdTokens defaultCpdTokens) {\n    // NO-OP\n  }\n\n  @Override\n  public void store(NewSymbolTable symbolTable) {\n    // NO-OP\n  }\n\n  @Override\n  public void store(AnalysisError analysisError) {\n    var clientInputFile = ((SonarLintInputFile) analysisError.inputFile()).getClientInputFile();\n    analysisResult.addFailedAnalysisFile(clientInputFile);\n  }\n\n  @Override\n  public void storeProperty(String key, String value) {\n    // NO-OP\n  }\n\n  @Override\n  public void store(ExternalIssue issue) {\n    // NO-OP\n  }\n\n  @Override\n  public void store(NewSignificantCode significantCode) {\n    // NO-OP\n  }\n\n  @Override\n  public void store(AdHocRule adHocRule) {\n    // NO-OP\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/analysis/sensor/package-info.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.sensor;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/global/AnalysisExtensionInstaller.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.global;\n\nimport org.sonar.api.batch.sensor.Sensor;\nimport org.sonar.api.config.Configuration;\nimport org.sonar.api.utils.AnnotationUtils;\nimport org.sonarsource.api.sonarlint.SonarLintSide;\nimport org.sonarsource.sonarlint.core.analysis.container.ContainerLifespan;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\nimport org.sonarsource.sonarlint.core.plugin.commons.ExtensionInstaller;\nimport org.sonarsource.sonarlint.core.plugin.commons.ExtensionUtils;\nimport org.sonarsource.sonarlint.core.plugin.commons.LoadedPlugins;\nimport org.sonarsource.sonarlint.core.plugin.commons.container.ExtensionContainer;\nimport org.sonarsource.sonarlint.plugin.api.SonarLintRuntime;\n\npublic class AnalysisExtensionInstaller extends ExtensionInstaller {\n\n  private final LoadedPlugins loadedPlugins;\n\n  public AnalysisExtensionInstaller(SonarLintRuntime sonarRuntime, LoadedPlugins loadedPlugins, Configuration bootConfiguration) {\n    super(sonarRuntime, bootConfiguration);\n    this.loadedPlugins = loadedPlugins;\n  }\n\n  public void install(ExtensionContainer container, ContainerLifespan lifespan) {\n    super.install(container, loadedPlugins.getAnalysisPluginInstancesByKeys(),\n      (pluginKey, extension) -> lifespan.equals(getSonarLintSideLifespan(extension)) && onlySonarSourceSensor(pluginKey, extension));\n  }\n\n  private static ContainerLifespan getSonarLintSideLifespan(Object extension) {\n    var annotation = AnnotationUtils.getAnnotation(extension, SonarLintSide.class);\n    if (annotation != null) {\n      var lifespan = annotation.lifespan();\n      if (SonarLintSide.MULTIPLE_ANALYSES.equals(lifespan) || \"INSTANCE\".equals(lifespan)) {\n        return ContainerLifespan.INSTANCE;\n      }\n      if (\"MODULE\".equals(lifespan)) {\n        return ContainerLifespan.MODULE;\n      }\n      if (SonarLintSide.SINGLE_ANALYSIS.equals(lifespan)) {\n        return ContainerLifespan.ANALYSIS;\n      }\n    }\n    return null;\n  }\n\n  private boolean onlySonarSourceSensor(String pluginKey, Object extension) {\n    return SonarPlugin.findByKey(pluginKey).isPresent() || loadedPlugins.getAdditionalAllowedPlugins().contains(pluginKey) || isNotSensor(extension);\n  }\n\n  private static boolean isNotSensor(Object extension) {\n    return !ExtensionUtils.isType(extension, Sensor.class);\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/global/GlobalAnalysisContainer.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.global;\n\nimport java.time.Clock;\nimport org.sonar.api.SonarQubeVersion;\nimport org.sonar.api.utils.System2;\nimport org.sonar.api.utils.UriReader;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisSchedulerConfiguration;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.plugin.commons.ApiVersions;\nimport org.sonarsource.sonarlint.core.plugin.commons.LoadedPlugins;\nimport org.sonarsource.sonarlint.core.plugin.commons.container.SpringComponentContainer;\nimport org.sonarsource.sonarlint.core.plugin.commons.sonarapi.SonarLintRuntimeImpl;\n\npublic class GlobalAnalysisContainer extends SpringComponentContainer {\n  protected static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private GlobalExtensionContainer globalExtensionContainer;\n  private ModuleRegistry moduleRegistry;\n  private final AnalysisSchedulerConfiguration analysisGlobalConfig;\n  private final LoadedPlugins loadedPlugins;\n\n  public GlobalAnalysisContainer(AnalysisSchedulerConfiguration analysisGlobalConfig, LoadedPlugins loadedPlugins) {\n    this.analysisGlobalConfig = analysisGlobalConfig;\n    this.loadedPlugins = loadedPlugins;\n  }\n\n  @Override\n  protected void doBeforeStart() {\n    var sonarPluginApiVersion = ApiVersions.loadSonarPluginApiVersion();\n    var sonarlintPluginApiVersion = ApiVersions.loadSonarLintPluginApiVersion();\n\n    add(\n      analysisGlobalConfig,\n      loadedPlugins,\n      GlobalSettings.class,\n      new GlobalConfigurationProvider(),\n      AnalysisExtensionInstaller.class,\n      new SonarQubeVersion(sonarPluginApiVersion),\n      new SonarLintRuntimeImpl(sonarPluginApiVersion, sonarlintPluginApiVersion, analysisGlobalConfig.getClientPid()),\n\n      new GlobalTempFolderProvider(),\n      UriReader.class,\n      Clock.systemDefaultZone(),\n      System2.INSTANCE);\n  }\n\n  @Override\n  protected void doAfterStart() {\n    declarePluginProperties();\n    globalExtensionContainer = new GlobalExtensionContainer(this);\n    globalExtensionContainer.startComponents();\n    this.moduleRegistry = new ModuleRegistry(globalExtensionContainer, analysisGlobalConfig.getFileSystemProvider());\n  }\n\n  @Override\n  public SpringComponentContainer stopComponents() {\n    try {\n      if (moduleRegistry != null) {\n        moduleRegistry.stopAll();\n      }\n      if (globalExtensionContainer != null) {\n        globalExtensionContainer.stopComponents();\n      }\n    } catch (Exception e) {\n      LOG.error(\"Cannot close analysis engine\", e);\n    } finally {\n      super.stopComponents();\n    }\n    return this;\n  }\n\n  private void declarePluginProperties() {\n    loadedPlugins.getAnalysisPluginInstancesByKeys().values().forEach(this::declareProperties);\n  }\n\n  // Visible for medium tests\n  public ModuleRegistry getModuleRegistry() {\n    return moduleRegistry;\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/global/GlobalConfigurationProvider.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.global;\n\nimport org.sonar.api.config.Configuration;\nimport org.sonarsource.sonarlint.core.plugin.commons.sonarapi.ConfigurationBridge;\nimport org.springframework.context.annotation.Bean;\n\npublic class GlobalConfigurationProvider {\n\n  private Configuration globalConfig;\n\n  @Bean(\"configuration\")\n  public Configuration provide(GlobalSettings settings) {\n    if (globalConfig == null) {\n      this.globalConfig = new ConfigurationBridge(settings);\n    }\n    return globalConfig;\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/global/GlobalExtensionContainer.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.global;\n\nimport org.sonarsource.sonarlint.core.analysis.container.ContainerLifespan;\nimport org.sonarsource.sonarlint.core.plugin.commons.container.SpringComponentContainer;\n\n/**\n * Used to load plugin global extensions\n */\npublic class GlobalExtensionContainer extends SpringComponentContainer {\n\n  public GlobalExtensionContainer(SpringComponentContainer parent) {\n    super(parent);\n  }\n\n  @Override\n  protected void doBeforeStart() {\n    getParent().getComponentByType(AnalysisExtensionInstaller.class).install(this, ContainerLifespan.INSTANCE);\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/global/GlobalSettings.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.global;\n\nimport org.sonar.api.config.PropertyDefinitions;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisSchedulerConfiguration;\nimport org.sonarsource.sonarlint.core.plugin.commons.sonarapi.MapSettings;\n\npublic class GlobalSettings extends MapSettings {\n\n  public GlobalSettings(AnalysisSchedulerConfiguration config, PropertyDefinitions propertyDefinitions) {\n    super(propertyDefinitions, config.getEffectiveSettings());\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/global/GlobalTempFolder.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.global;\n\nimport java.io.File;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.DefaultTempFolder;\n\npublic class GlobalTempFolder extends DefaultTempFolder {\n\n  public GlobalTempFolder(File tempDir, boolean deleteOnExit) {\n    super(tempDir, deleteOnExit);\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/global/GlobalTempFolderProvider.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.global;\n\nimport java.io.IOException;\nimport java.nio.file.DirectoryStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.attribute.BasicFileAttributes;\nimport java.util.concurrent.TimeUnit;\nimport org.apache.commons.io.FileUtils;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisSchedulerConfiguration;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.springframework.context.annotation.Bean;\n\npublic class GlobalTempFolderProvider {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final long CLEAN_MAX_AGE = TimeUnit.DAYS.toMillis(7);\n  private static final String TMP_NAME_PREFIX = \".sonarlinttmp_\";\n\n  private GlobalTempFolder tempFolder;\n\n  @Bean(\"globalTempFolder\")\n  public GlobalTempFolder provide(AnalysisSchedulerConfiguration globalConfiguration) {\n    if (tempFolder == null) {\n      tempFolder = cleanAndCreateTempFolder(globalConfiguration.getWorkDir());\n    }\n    return tempFolder;\n  }\n\n  private static GlobalTempFolder cleanAndCreateTempFolder(Path workingPath) {\n    try {\n      cleanTempFolders(workingPath);\n    } catch (IOException e) {\n      LOG.error(String.format(\"failed to clean global working directory: %s\", workingPath), e);\n    }\n    var tempDir = createTempFolder(workingPath);\n    return new GlobalTempFolder(tempDir.toFile(), true);\n  }\n\n  private static Path createTempFolder(Path workingPath) {\n    try {\n      Files.createDirectories(workingPath);\n    } catch (IOException e) {\n      throw new IllegalStateException(\"Failed to create working path: \" + workingPath, e);\n    }\n\n    try {\n      return Files.createTempDirectory(workingPath, TMP_NAME_PREFIX);\n    } catch (IOException e) {\n      throw new IllegalStateException(\"Failed to create temporary folder in \" + workingPath, e);\n    }\n  }\n\n  private static void cleanTempFolders(Path path) throws IOException {\n    if (Files.exists(path)) {\n      try (var stream = Files.newDirectoryStream(path, new CleanFilter())) {\n        for (Path p : stream) {\n          FileUtils.deleteQuietly(p.toFile());\n        }\n      }\n    }\n  }\n\n  private static class CleanFilter implements DirectoryStream.Filter<Path> {\n    @Override\n    public boolean accept(Path path) throws IOException {\n      if (!Files.isDirectory(path) || !path.getFileName().toString().startsWith(TMP_NAME_PREFIX)) {\n        return false;\n      }\n\n      var threshold = System.currentTimeMillis() - CLEAN_MAX_AGE;\n\n      // we could also check the timestamp in the name, instead\n      BasicFileAttributes attrs;\n\n      try {\n        attrs = Files.readAttributes(path, BasicFileAttributes.class);\n      } catch (IOException ioe) {\n        LOG.error(String.format(\"Couldn't read file attributes for %s : \", path), ioe);\n        return false;\n      }\n\n      var creationTime = attrs.creationTime().toMillis();\n      return creationTime < threshold;\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/global/ModuleRegistry.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.global;\n\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.Function;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientModuleFileSystem;\nimport org.sonarsource.sonarlint.core.analysis.container.module.ModuleContainer;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.plugin.commons.container.SpringComponentContainer;\n\npublic class ModuleRegistry {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final ConcurrentHashMap<String, ModuleContainer> moduleContainersByKey = new ConcurrentHashMap<>();\n  private final SpringComponentContainer parent;\n  private final Function<String, ClientModuleFileSystem> fileSystemProvider;\n\n  public ModuleRegistry(SpringComponentContainer parent, Function<String, ClientModuleFileSystem> fileSystemProvider) {\n    this.parent = parent;\n    this.fileSystemProvider = fileSystemProvider;\n  }\n\n  public ModuleContainer getContainerFor(String moduleKey) {\n    return moduleContainersByKey.computeIfAbsent(moduleKey, key -> createContainer(key, fileSystemProvider.apply(key)));\n  }\n\n  @Nullable\n  public ModuleContainer getContainerIfStarted(String moduleKey) {\n    return moduleContainersByKey.get(moduleKey);\n  }\n\n  private ModuleContainer createContainer(Object moduleKey, @Nullable ClientModuleFileSystem clientFileSystem) {\n    LOG.debug(\"Creating container for module '\" + moduleKey + \"'\");\n    var moduleContainer = new ModuleContainer(parent, false);\n    if (clientFileSystem != null) {\n      moduleContainer.add(clientFileSystem);\n    }\n    moduleContainer.startComponents();\n    return moduleContainer;\n  }\n\n  public void unregisterModule(String moduleKey) {\n    var container = moduleContainersByKey.remove(moduleKey);\n    if (container != null) {\n      container.stopComponents();\n    }\n  }\n\n  public void stopAll() {\n    moduleContainersByKey.values().forEach(SpringComponentContainer::stopComponents);\n    moduleContainersByKey.clear();\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/global/package-info.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.analysis.container.global;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/module/DefaultModuleFileEvent.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.module;\n\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonarsource.sonarlint.plugin.api.module.file.ModuleFileEvent;\n\npublic class DefaultModuleFileEvent implements ModuleFileEvent {\n\n  private final InputFile target;\n  private final ModuleFileEvent.Type type;\n\n  private DefaultModuleFileEvent(InputFile target, Type type) {\n    this.target = target;\n    this.type = type;\n  }\n\n  public static DefaultModuleFileEvent of(InputFile target, Type type) {\n    return new DefaultModuleFileEvent(target, type);\n  }\n\n  @Override\n  public InputFile getTarget() {\n    return target;\n  }\n\n  @Override\n  public Type getType() {\n    return type;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/module/ModuleContainer.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.module;\n\nimport java.util.function.Consumer;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisConfiguration;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisResults;\nimport org.sonarsource.sonarlint.core.analysis.api.Issue;\nimport org.sonarsource.sonarlint.core.analysis.container.ContainerLifespan;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.AnalysisContainer;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.IssueListenerHolder;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.FileMetadata;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.LanguageDetection;\nimport org.sonarsource.sonarlint.core.analysis.container.global.AnalysisExtensionInstaller;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.ActiveRulesAdapter;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.SonarLintModuleFileSystem;\nimport org.sonarsource.sonarlint.core.commons.tracing.Trace;\nimport org.sonarsource.sonarlint.core.commons.progress.ProgressIndicator;\nimport org.sonarsource.sonarlint.core.plugin.commons.container.SpringComponentContainer;\n\nimport static org.sonarsource.sonarlint.core.commons.tracing.Trace.startChild;\n\npublic class ModuleContainer extends SpringComponentContainer {\n\n  private final boolean isTransient;\n\n  public ModuleContainer(SpringComponentContainer parent, boolean isTransient) {\n    super(parent);\n    this.isTransient = isTransient;\n  }\n\n  @Override\n  protected void doBeforeStart() {\n    add(\n      SonarLintModuleFileSystem.class,\n      ModuleInputFileBuilder.class,\n      FileMetadata.class,\n      LanguageDetection.class,\n\n      ModuleFileEventNotifier.class);\n    getParent().getComponentByType(AnalysisExtensionInstaller.class).install(this, ContainerLifespan.MODULE);\n  }\n\n  public boolean isTransient() {\n    return isTransient;\n  }\n\n  public AnalysisResults analyze(AnalysisConfiguration configuration, Consumer<Issue> issueListener, ProgressIndicator progressIndicator, @Nullable Trace trace) {\n    var analysisContainer = startChild(trace, \"newAnalysisContainer\", \"analyze\", () -> new AnalysisContainer(this, progressIndicator));\n    analysisContainer.add(configuration);\n    analysisContainer.add(new IssueListenerHolder(issueListener));\n    analysisContainer.add(startChild(trace, \"newActiveRulesAdapter\", \"analyze\", () ->\n      new ActiveRulesAdapter(configuration.activeRules())));\n    var defaultAnalysisResult = new AnalysisResults();\n    analysisContainer.add(defaultAnalysisResult);\n    if (trace != null) {\n      analysisContainer.add(trace);\n    }\n    analysisContainer.execute(trace);\n    return defaultAnalysisResult;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/module/ModuleFileEventNotifier.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.module;\n\nimport java.util.List;\nimport java.util.Optional;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientModuleFileEvent;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.plugin.api.module.file.ModuleFileEvent;\nimport org.sonarsource.sonarlint.plugin.api.module.file.ModuleFileListener;\n\npublic class ModuleFileEventNotifier {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final List<ModuleFileListener> listeners;\n  private final ModuleInputFileBuilder inputFileBuilder;\n\n  public ModuleFileEventNotifier(Optional<List<ModuleFileListener>> listeners, ModuleInputFileBuilder inputFileBuilder) {\n    this.listeners = listeners.orElse(List.of());\n    this.inputFileBuilder = inputFileBuilder;\n  }\n\n  public void fireModuleFileEvent(ClientModuleFileEvent event) {\n    ModuleFileEvent apiEvent = DefaultModuleFileEvent.of(inputFileBuilder.create(event.target()), event.type());\n    listeners.forEach(l -> tryFireModuleFileEvent(l, apiEvent));\n  }\n\n  private static void tryFireModuleFileEvent(ModuleFileListener listener, ModuleFileEvent event) {\n    try {\n      listener.process(event);\n    } catch (Exception e) {\n      LOG.error(\"Error processing file event\", e);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/module/ModuleInputFileBuilder.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.module;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.Charset;\nimport org.sonar.api.batch.fs.InputFile.Type;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.FileMetadata;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.LanguageDetection;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputFile;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic class ModuleInputFileBuilder {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final LanguageDetection langDetection;\n  private final FileMetadata fileMetadata;\n\n  public ModuleInputFileBuilder(LanguageDetection langDetection, FileMetadata fileMetadata) {\n    this.langDetection = langDetection;\n    this.fileMetadata = fileMetadata;\n  }\n\n  public SonarLintInputFile create(ClientInputFile inputFile) {\n    var defaultInputFile = new SonarLintInputFile(inputFile, f -> {\n      LOG.debug(\"Initializing metadata of file {}\", f.uri());\n      var charset = f.charset();\n      InputStream stream;\n      try {\n        stream = f.inputStream();\n      } catch (IOException e) {\n        throw new IllegalStateException(\"Failed to open a stream on file: \" + f.uri(), e);\n      }\n      return fileMetadata.readMetadata(stream, charset != null ? charset : Charset.defaultCharset(), f.uri(), null);\n    });\n    defaultInputFile.setType(inputFile.isTest() ? Type.TEST : Type.MAIN);\n    var fileLanguage = inputFile.language();\n    if (fileLanguage != null) {\n      LOG.debug(\"Language of file \\\"{}\\\" is set to \\\"{}\\\"\", inputFile.uri(), fileLanguage);\n      defaultInputFile.setLanguage(fileLanguage);\n    } else {\n      defaultInputFile.setLanguage(langDetection.language(defaultInputFile));\n    }\n\n    return defaultInputFile;\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/module/package-info.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.analysis.container.module;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/container/package-info.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.analysis.container;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/package-info.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/ActiveRulesAdapter.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport org.sonar.api.batch.rule.ActiveRule;\nimport org.sonar.api.batch.rule.ActiveRules;\nimport org.sonar.api.rule.RuleKey;\n\npublic class ActiveRulesAdapter implements ActiveRules {\n  private final Collection<ActiveRule> allActiveRules;\n  private final Map<String, List<ActiveRule>> activeRulesByRepository = new HashMap<>();\n  private final Map<String, List<ActiveRule>> activeRulesByLanguage = new HashMap<>();\n  private final Map<String, Map<String, ActiveRule>> activeRulesByRepositoryAndKey = new HashMap<>();\n  private final Map<String, Map<String, ActiveRule>> activeRulesByRepositoryAndInternalKey = new HashMap<>();\n\n  public ActiveRulesAdapter(Collection<ActiveRule> activeRules) {\n    allActiveRules = List.copyOf(activeRules);\n    for (ActiveRule r : allActiveRules) {\n      if (r.internalKey() != null) {\n        activeRulesByRepositoryAndInternalKey.computeIfAbsent(r.ruleKey().repository(), x -> new HashMap<>()).put(r.internalKey(), r);\n      }\n      activeRulesByRepositoryAndKey.computeIfAbsent(r.ruleKey().repository(), x -> new HashMap<>()).put(r.ruleKey().rule(), r);\n      activeRulesByRepository.computeIfAbsent(r.ruleKey().repository(), x -> new ArrayList<>()).add(r);\n      activeRulesByLanguage.computeIfAbsent(r.language(), x -> new ArrayList<>()).add(r);\n    }\n  }\n\n  @Override\n  public ActiveRule find(RuleKey ruleKey) {\n    return activeRulesByRepositoryAndKey.getOrDefault(ruleKey.repository(), Collections.emptyMap())\n      .get(ruleKey.rule());\n  }\n\n  @Override\n  public Collection<ActiveRule> findAll() {\n    return allActiveRules;\n  }\n\n  @Override\n  public Collection<ActiveRule> findByRepository(String repository) {\n    return activeRulesByRepository.getOrDefault(repository, Collections.emptyList());\n  }\n\n  @Override\n  public Collection<ActiveRule> findByLanguage(String language) {\n    return activeRulesByLanguage.getOrDefault(language, Collections.emptyList());\n  }\n\n  @Override\n  public ActiveRule findByInternalKey(String repository, String internalKey) {\n    return activeRulesByRepositoryAndInternalKey.containsKey(repository) ? activeRulesByRepositoryAndInternalKey.get(repository).get(internalKey) : null;\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/DefaultAnalysisError.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi;\n\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.fs.TextPointer;\nimport org.sonar.api.batch.sensor.error.AnalysisError;\nimport org.sonar.api.batch.sensor.error.NewAnalysisError;\nimport org.sonar.api.batch.sensor.internal.SensorStorage;\n\nimport static java.util.Objects.requireNonNull;\nimport static org.sonar.api.utils.Preconditions.checkArgument;\nimport static org.sonar.api.utils.Preconditions.checkState;\n\npublic class DefaultAnalysisError extends DefaultStorable implements NewAnalysisError, AnalysisError {\n  private InputFile inputFile;\n  private String message;\n  private TextPointer location;\n\n  public DefaultAnalysisError() {\n    super(null);\n  }\n\n  public DefaultAnalysisError(SensorStorage storage) {\n    super(storage);\n  }\n\n  @Override\n  public InputFile inputFile() {\n    return inputFile;\n  }\n\n  @Override\n  public String message() {\n    return message;\n  }\n\n  @Override\n  public TextPointer location() {\n    return location;\n  }\n\n  @Override\n  public NewAnalysisError onFile(InputFile inputFile) {\n    checkArgument(inputFile != null, \"Cannot use a inputFile that is null\");\n    checkState(this.inputFile == null, \"onFile() already called\");\n    this.inputFile = inputFile;\n    return this;\n  }\n\n  @Override\n  public NewAnalysisError message(String message) {\n    this.message = message;\n    return this;\n  }\n\n  @Override\n  public NewAnalysisError at(TextPointer location) {\n    checkState(this.location == null, \"at() already called\");\n    this.location = location;\n    return this;\n  }\n\n  @Override\n  protected void doSave() {\n    requireNonNull(this.inputFile, \"inputFile is mandatory on AnalysisError\");\n    storage.store(this);\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/DefaultFilterableIssue.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi;\n\nimport org.apache.commons.lang3.builder.ToStringBuilder;\nimport org.apache.commons.lang3.builder.ToStringStyle;\nimport org.sonar.api.batch.fs.InputComponent;\nimport org.sonar.api.batch.fs.TextRange;\nimport org.sonar.api.rule.RuleKey;\nimport org.sonar.api.scan.issue.filter.FilterableIssue;\nimport org.sonarsource.sonarlint.core.analysis.api.Issue;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.DefaultTextPointer;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.DefaultTextRange;\n\npublic class DefaultFilterableIssue implements FilterableIssue {\n  private final Issue rawIssue;\n  private final InputComponent component;\n\n  public DefaultFilterableIssue(Issue rawIssue, InputComponent component) {\n    this.rawIssue = rawIssue;\n    this.component = component;\n  }\n\n  @Override\n  public String componentKey() {\n    return component.key();\n  }\n\n  @Override\n  public RuleKey ruleKey() {\n    return rawIssue.getRuleKey();\n  }\n\n  @Override\n  public String severity() {\n    throw unsupported();\n  }\n\n  @Override\n  public String message() {\n    throw unsupported();\n  }\n\n  @Override\n  public Integer line() {\n    return rawIssue.getStartLine();\n  }\n\n  @Override\n  public String projectKey() {\n    throw unsupported();\n  }\n\n  @Override\n  public String toString() {\n    return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);\n  }\n\n  private static UnsupportedOperationException unsupported() {\n    return new UnsupportedOperationException(\"Not available for issues filters\");\n  }\n\n  @Override\n  public Double gap() {\n    throw unsupported();\n  }\n\n  public InputComponent getComponent() {\n    return component;\n  }\n\n  @Override\n  public TextRange textRange() {\n    var textRange = rawIssue.getTextRange();\n    if (textRange == null) {\n      return null;\n    }\n    return new DefaultTextRange(new DefaultTextPointer(textRange.getStartLine(), textRange.getStartLineOffset()),\n      new DefaultTextPointer(textRange.getEndLine(), textRange.getEndLineOffset()));\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/DefaultFlow.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi;\n\nimport java.util.List;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonar.api.batch.sensor.issue.Issue;\nimport org.sonar.api.batch.sensor.issue.IssueLocation;\nimport org.sonar.api.batch.sensor.issue.NewIssue;\n\npublic class DefaultFlow implements Issue.Flow {\n  private final List<IssueLocation> locations;\n  private final String description;\n  private final NewIssue.FlowType type;\n\n  public DefaultFlow(List<IssueLocation> locations, @Nullable String description, NewIssue.FlowType type) {\n    this.locations = locations;\n    this.description = description;\n    this.type = type;\n  }\n\n  @Override\n  public List<IssueLocation> locations() {\n    return locations;\n  }\n\n  @CheckForNull\n  @Override\n  public String description() {\n    return description;\n  }\n\n  @Override\n  public NewIssue.FlowType type() {\n    return type;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/DefaultSensorContext.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi;\n\nimport java.io.InputStream;\nimport java.io.Serializable;\nimport org.sonar.api.SonarRuntime;\nimport org.sonar.api.batch.fs.FileSystem;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.fs.InputModule;\nimport org.sonar.api.batch.rule.ActiveRules;\nimport org.sonar.api.batch.sensor.SensorContext;\nimport org.sonar.api.batch.sensor.cache.ReadCache;\nimport org.sonar.api.batch.sensor.cache.WriteCache;\nimport org.sonar.api.batch.sensor.code.NewSignificantCode;\nimport org.sonar.api.batch.sensor.coverage.NewCoverage;\nimport org.sonar.api.batch.sensor.cpd.NewCpdTokens;\nimport org.sonar.api.batch.sensor.error.NewAnalysisError;\nimport org.sonar.api.batch.sensor.highlighting.NewHighlighting;\nimport org.sonar.api.batch.sensor.internal.SensorStorage;\nimport org.sonar.api.batch.sensor.issue.NewExternalIssue;\nimport org.sonar.api.batch.sensor.issue.NewIssue;\nimport org.sonar.api.batch.sensor.measure.NewMeasure;\nimport org.sonar.api.batch.sensor.rule.NewAdHocRule;\nimport org.sonar.api.batch.sensor.symbol.NewSymbolTable;\nimport org.sonar.api.config.Configuration;\nimport org.sonar.api.config.Settings;\nimport org.sonar.api.scanner.fs.InputProject;\nimport org.sonar.api.utils.Version;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputProject;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.noop.NoOpNewCoverage;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.noop.NoOpNewCpdTokens;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.noop.NoOpNewHighlighting;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.noop.NoOpNewMeasure;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.noop.NoOpNewSignificantCode;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.noop.NoOpNewSymbolTable;\nimport org.sonarsource.sonarlint.core.commons.progress.ProgressIndicator;\n\npublic class DefaultSensorContext implements SensorContext {\n\n  private static final NoOpNewHighlighting NO_OP_NEW_HIGHLIGHTING = new NoOpNewHighlighting();\n  private static final NoOpNewSymbolTable NO_OP_NEW_SYMBOL_TABLE = new NoOpNewSymbolTable();\n  private static final NoOpNewCpdTokens NO_OP_NEW_CPD_TOKENS = new NoOpNewCpdTokens();\n  private static final NoOpNewCoverage NO_OP_NEW_COVERAGE = new NoOpNewCoverage();\n  private static final NoOpNewSignificantCode NO_OP_NEW_SIGNIFICANT_CODE = new NoOpNewSignificantCode();\n\n  private final Settings settings;\n  private final FileSystem fs;\n  private final ActiveRules activeRules;\n  private final SensorStorage sensorStorage;\n  private final SonarLintInputProject project;\n  private final SonarRuntime sqRuntime;\n  private final ProgressIndicator progressIndicator;\n  private final Configuration config;\n\n  public DefaultSensorContext(SonarLintInputProject project, Settings settings, Configuration config, FileSystem fs, ActiveRules activeRules, SensorStorage sensorStorage,\n    SonarRuntime sqRuntime, ProgressIndicator progressIndicator) {\n    this.project = project;\n    this.settings = settings;\n    this.config = config;\n    this.fs = fs;\n    this.activeRules = activeRules;\n    this.sensorStorage = sensorStorage;\n    this.sqRuntime = sqRuntime;\n    this.progressIndicator = progressIndicator;\n  }\n\n  @Override\n  public Settings settings() {\n    return settings;\n  }\n\n  @Override\n  public Configuration config() {\n    return config;\n  }\n\n  @Override\n  public FileSystem fileSystem() {\n    return fs;\n  }\n\n  @Override\n  public ActiveRules activeRules() {\n    return activeRules;\n  }\n\n  @Override\n  public <G extends Serializable> NewMeasure<G> newMeasure() {\n    return new NoOpNewMeasure<>();\n  }\n\n  @Override\n  public NewIssue newIssue() {\n    return new DefaultSonarLintIssue(project, fs.baseDir().toPath(), sensorStorage);\n  }\n\n  @Override\n  public NewHighlighting newHighlighting() {\n    return NO_OP_NEW_HIGHLIGHTING;\n  }\n\n  @Override\n  public NewCoverage newCoverage() {\n    return NO_OP_NEW_COVERAGE;\n  }\n\n  @Override\n  public InputModule module() {\n    return project;\n  }\n\n  @Override\n  public InputProject project() {\n    return project;\n  }\n\n  @Override\n  public Version getSonarQubeVersion() {\n    return sqRuntime.getApiVersion();\n  }\n\n  @Override\n  public SonarRuntime runtime() {\n    return sqRuntime;\n  }\n\n  @Override\n  public NewSymbolTable newSymbolTable() {\n    return NO_OP_NEW_SYMBOL_TABLE;\n  }\n\n  @Override\n  public NewCpdTokens newCpdTokens() {\n    return NO_OP_NEW_CPD_TOKENS;\n  }\n\n  @Override\n  public NewAnalysisError newAnalysisError() {\n    return new DefaultAnalysisError(sensorStorage);\n  }\n\n  @Override\n  public boolean isCancelled() {\n    return progressIndicator.isCanceled();\n  }\n\n  @Override\n  public void addContextProperty(String key, String value) {\n    // NO OP\n  }\n\n  @Override\n  public void markForPublishing(InputFile inputFile) {\n    // NO OP\n  }\n\n  @Override\n  public void markAsUnchanged(InputFile inputFile) {\n    // NO OP\n  }\n\n  @Override\n  public NewExternalIssue newExternalIssue() {\n    throw unsupported();\n  }\n\n  @Override\n  public NewSignificantCode newSignificantCode() {\n    return NO_OP_NEW_SIGNIFICANT_CODE;\n  }\n\n  @Override\n  public NewAdHocRule newAdHocRule() {\n    throw unsupported();\n  }\n\n  private static UnsupportedOperationException unsupported() {\n    return new UnsupportedOperationException(\"Not supported in SonarLint\");\n  }\n\n  @Override\n  public boolean canSkipUnchangedFiles() {\n    return false;\n  }\n\n  @Override\n  public boolean isCacheEnabled() {\n    return false;\n  }\n\n  @Override\n  public ReadCache previousCache() {\n    throw unsupported();\n  }\n\n  @Override\n  public WriteCache nextCache() {\n    throw unsupported();\n  }\n\n  @Override\n  public void addTelemetryProperty(String key, String value) {\n    // PLUGINAPI-95 NO OP\n  }\n\n  @Override\n  public void addAnalysisData(String s, String s1, InputStream inputStream) {\n    // PLUGINAPI-117 analysis data storage\n    // NO OP\n  }\n\n  @Override\n  public boolean isFeatureAvailable(String s) {\n    return false;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/DefaultSensorDescriptor.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi;\n\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.function.Predicate;\nimport javax.annotation.Nullable;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.sensor.SensorDescriptor;\nimport org.sonar.api.config.Configuration;\n\npublic class DefaultSensorDescriptor implements SensorDescriptor {\n\n  private String name;\n  private String[] languages = new String[0];\n  private InputFile.Type type = null;\n  private String[] ruleRepositories = new String[0];\n  private boolean global = false;\n  private Predicate<Configuration> configurationPredicate;\n\n  public String name() {\n    return name;\n  }\n\n  public Collection<String> languages() {\n    return Arrays.asList(languages);\n  }\n\n  @Nullable\n  public InputFile.Type type() {\n    return type;\n  }\n\n  public Collection<String> ruleRepositories() {\n    return Arrays.asList(ruleRepositories);\n  }\n\n  public Predicate<Configuration> configurationPredicate() {\n    return configurationPredicate;\n  }\n\n  public boolean isGlobal() {\n    return global;\n  }\n\n  @Override\n  public DefaultSensorDescriptor name(String name) {\n    this.name = name;\n    return this;\n  }\n\n  @Override\n  public DefaultSensorDescriptor onlyOnLanguage(String languageKey) {\n    return onlyOnLanguages(languageKey);\n  }\n\n  @Override\n  public DefaultSensorDescriptor onlyOnLanguages(String... languageKeys) {\n    this.languages = languageKeys;\n    return this;\n  }\n\n  @Override\n  public DefaultSensorDescriptor onlyOnFileType(InputFile.Type type) {\n    this.type = type;\n    return this;\n  }\n\n  @Override\n  public DefaultSensorDescriptor createIssuesForRuleRepository(String... repositoryKey) {\n    return createIssuesForRuleRepositories(repositoryKey);\n  }\n\n  @Override\n  public DefaultSensorDescriptor createIssuesForRuleRepositories(String... repositoryKeys) {\n    this.ruleRepositories = repositoryKeys;\n    return this;\n  }\n\n  @Override\n  public SensorDescriptor global() {\n    this.global = true;\n    return this;\n  }\n\n  @Override\n  public DefaultSensorDescriptor onlyWhenConfiguration(Predicate<Configuration> configurationPredicate) {\n    this.configurationPredicate = configurationPredicate;\n    return this;\n  }\n\n  @Override\n  public SensorDescriptor processesFilesIndependently() {\n    // Not used by SonarLint\n    return this;\n  }\n\n  @Override\n  public SensorDescriptor processesHiddenFiles() {\n    // Not used by SonarLint\n    return this;\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/DefaultSonarLintIssue.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi;\n\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.EnumMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.apache.commons.lang3.StringUtils;\nimport org.sonar.api.batch.fs.InputDir;\nimport org.sonar.api.batch.rule.Severity;\nimport org.sonar.api.batch.sensor.internal.SensorStorage;\nimport org.sonar.api.batch.sensor.issue.Issue;\nimport org.sonar.api.batch.sensor.issue.IssueLocation;\nimport org.sonar.api.batch.sensor.issue.NewIssue;\nimport org.sonar.api.batch.sensor.issue.NewIssueLocation;\nimport org.sonar.api.batch.sensor.issue.fix.QuickFix;\nimport org.sonar.api.issue.impact.SoftwareQuality;\nimport org.sonar.api.rule.RuleKey;\nimport org.sonar.api.utils.PathUtils;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputProject;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.SensorQuickFix;\nimport org.sonarsource.sonarlint.plugin.api.issue.NewQuickFix;\nimport org.sonarsource.sonarlint.plugin.api.issue.NewSonarLintIssue;\n\nimport static java.util.Objects.requireNonNull;\n\npublic class DefaultSonarLintIssue extends DefaultStorable implements Issue, NewIssue, NewSonarLintIssue {\n\n  private final SonarLintInputProject project;\n  private final Path baseDir;\n  protected DefaultSonarLintIssueLocation primaryLocation;\n  protected List<Flow> flows = new ArrayList<>();\n  private RuleKey ruleKey;\n  private Severity overriddenSeverity;\n  private final List<QuickFix> quickFixes;\n  private Optional<String> ruleDescriptionContextKey = Optional.empty();\n  private final Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> overriddenImpacts;\n  private final List<String> internalTags = new ArrayList<>();\n\n  public DefaultSonarLintIssue(SonarLintInputProject project, Path baseDir, @Nullable SensorStorage storage) {\n    super(storage);\n    this.project = project;\n    this.baseDir = baseDir;\n    this.quickFixes = new ArrayList<>();\n    this.overriddenImpacts = new EnumMap<>(SoftwareQuality.class);\n  }\n\n  @Override\n  public NewIssueLocation newLocation() {\n    return new DefaultSonarLintIssueLocation();\n  }\n\n  @Override\n  public NewIssue setRuleDescriptionContextKey(@Nullable String ruleDescriptionContextKey) {\n    this.ruleDescriptionContextKey = Optional.ofNullable(ruleDescriptionContextKey);\n    return this;\n  }\n\n  @Override\n  public NewIssue setCodeVariants(@Nullable Iterable<String> iterable) {\n    // not implemented\n    return this;\n  }\n\n  @Override\n  public NewIssue addInternalTag(String tag) {\n    internalTags.add(tag);\n    return this;\n  }\n\n  @Override\n  public NewIssue addInternalTags(Collection<String> tags) {\n    internalTags.addAll(tags);\n    return this;\n  }\n\n  @Override\n  public NewIssue setInternalTags(@Nullable Collection<String> tags) {\n    internalTags.clear();\n    if (tags != null) {\n      addInternalTags(tags);\n    }\n    return this;\n  }\n\n  @Override\n  public DefaultSonarLintIssue forRule(RuleKey ruleKey) {\n    this.ruleKey = ruleKey;\n    return this;\n  }\n\n  @Override\n  public RuleKey ruleKey() {\n    return this.ruleKey;\n  }\n\n  @Override\n  public DefaultSonarLintIssue gap(@Nullable Double gap) {\n    // Gap not used in SonarLint\n    return this;\n  }\n\n  @Override\n  public DefaultSonarLintIssue overrideSeverity(@Nullable Severity severity) {\n    this.overriddenSeverity = severity;\n    return this;\n  }\n\n  @Override\n  public Severity overriddenSeverity() {\n    return this.overriddenSeverity;\n  }\n\n  @Override\n  public DefaultSonarLintIssue overrideImpact(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity) {\n    overriddenImpacts.put(softwareQuality, severity);\n    return this;\n  }\n\n  @Override\n  public Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> overridenImpacts() {\n    return overriddenImpacts;\n  }\n\n  @Override\n  public Double gap() {\n    throw new UnsupportedOperationException(\"No gap in SonarLint\");\n  }\n\n  @Override\n  public IssueLocation primaryLocation() {\n    return primaryLocation;\n  }\n\n  @Override\n  public List<Flow> flows() {\n    return this.flows;\n  }\n\n  @Override\n  public DefaultSonarLintIssue at(NewIssueLocation primaryLocation) {\n    this.primaryLocation = rewriteLocation((DefaultSonarLintIssueLocation) primaryLocation);\n    return this;\n  }\n\n  @Override\n  public NewIssue addLocation(NewIssueLocation secondaryLocation) {\n    return addFlow(List.of(secondaryLocation));\n  }\n\n  @Override\n  public NewIssue addFlow(Iterable<NewIssueLocation> locations) {\n    return addFlow(locations, FlowType.UNDEFINED, null);\n  }\n\n  @Override\n  public NewIssue addFlow(Iterable<NewIssueLocation> flowLocations, FlowType flowType, @Nullable String flowDescription) {\n    List<IssueLocation> flowAsList = new ArrayList<>();\n    for (NewIssueLocation issueLocation : flowLocations) {\n      flowAsList.add(rewriteLocation((DefaultSonarLintIssueLocation) issueLocation));\n    }\n    flows.add(new DefaultFlow(flowAsList, flowDescription, flowType));\n    return this;\n  }\n\n  private DefaultSonarLintIssueLocation rewriteLocation(DefaultSonarLintIssueLocation location) {\n    var component = location.inputComponent();\n    Optional<Path> dirOrModulePath = Optional.empty();\n\n    if (component instanceof InputDir dirComponent) {\n      dirOrModulePath = Optional.of(baseDir.relativize(dirComponent.path()));\n    }\n\n    if (dirOrModulePath.isPresent()) {\n      var path = PathUtils.sanitize(dirOrModulePath.get().toString());\n      var fixedLocation = new DefaultSonarLintIssueLocation();\n      fixedLocation.on(project);\n      var fullMessage = new StringBuilder();\n      if (!StringUtils.isEmpty(path)) {\n        fullMessage.append(\"[\").append(path).append(\"] \");\n      }\n      fullMessage.append(location.message());\n      fixedLocation.message(fullMessage.toString());\n      return fixedLocation;\n    } else {\n      return location;\n    }\n  }\n\n  @Override\n  public void doSave() {\n    requireNonNull(this.ruleKey, \"ruleKey is mandatory on issue\");\n    storage.store(this);\n  }\n\n  @Override\n  public SensorQuickFix newQuickFix() {\n    return new SensorQuickFix();\n  }\n\n  @Override\n  public DefaultSonarLintIssue addQuickFix(NewQuickFix newQuickFix) {\n    // legacy method from sonarlint-plugin-api, keep for backward compatibility and remove later\n    quickFixes.add((QuickFix) newQuickFix);\n    return this;\n  }\n\n  @Override\n  public DefaultSonarLintIssue addQuickFix(org.sonar.api.batch.sensor.issue.fix.NewQuickFix newQuickFix) {\n    quickFixes.add((QuickFix) newQuickFix);\n    return this;\n  }\n\n  @Override\n  public List<QuickFix> quickFixes() {\n    return Collections.unmodifiableList(quickFixes);\n  }\n\n  @CheckForNull\n  @Override\n  public List<String> codeVariants() {\n    return Collections.emptyList();\n  }\n\n  @Override\n  public List<String> internalTags() {\n    return Collections.unmodifiableList(internalTags);\n  }\n\n  @Override\n  public NewIssue setQuickFixAvailable(boolean qfAvailable) {\n    // not relevant in SonarLint\n    return this;\n  }\n\n  @Override\n  public boolean isQuickFixAvailable() {\n    return !quickFixes.isEmpty();\n  }\n\n  @Override\n  public Optional<String> ruleDescriptionContextKey() {\n    return ruleDescriptionContextKey;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/DefaultSonarLintIssueLocation.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi;\n\nimport java.util.Collections;\nimport java.util.List;\nimport org.apache.commons.lang3.Strings;\nimport org.sonar.api.batch.fs.InputComponent;\nimport org.sonar.api.batch.fs.TextRange;\nimport org.sonar.api.batch.sensor.issue.IssueLocation;\nimport org.sonar.api.batch.sensor.issue.MessageFormatting;\nimport org.sonar.api.batch.sensor.issue.NewIssueLocation;\nimport org.sonar.api.batch.sensor.issue.NewMessageFormatting;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.noop.NoOpNewMessageFormatting;\n\nimport static java.util.Objects.requireNonNull;\nimport static org.apache.commons.lang3.StringUtils.abbreviate;\nimport static org.apache.commons.lang3.StringUtils.trimToEmpty;\n\npublic class DefaultSonarLintIssueLocation implements NewIssueLocation, IssueLocation {\n\n  private InputComponent component;\n  private TextRange textRange;\n  private String message;\n\n  @Override\n  public DefaultSonarLintIssueLocation on(InputComponent component) {\n    requireNonNull(component, \"Component can't be null\");\n    this.component = component;\n    return this;\n  }\n\n  @Override\n  public DefaultSonarLintIssueLocation at(TextRange location) {\n    this.textRange = location;\n    return this;\n  }\n\n  @Override\n  public DefaultSonarLintIssueLocation message(String message) {\n    this.message = abbreviate(trimToEmpty(sanitizeNulls(message)), MESSAGE_MAX_SIZE);\n    return this;\n  }\n\n  @Override\n  public NewIssueLocation message(String message, List<NewMessageFormatting> newMessageFormatting) {\n    // ignore formatting for now\n    return message(message);\n  }\n\n  @Override\n  public NewMessageFormatting newMessageFormatting() {\n    return new NoOpNewMessageFormatting();\n  }\n\n  private static String sanitizeNulls(String message) {\n    return Strings.CS.replace(message, \"\\u0000\", \"[NULL]\");\n  }\n\n  @Override\n  public InputComponent inputComponent() {\n    return this.component;\n  }\n\n  @Override\n  public TextRange textRange() {\n    return textRange;\n  }\n\n  @Override\n  public String message() {\n    return this.message;\n  }\n\n  @Override\n  public List<MessageFormatting> messageFormattings() {\n    return Collections.emptyList();\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/DefaultStorable.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi;\n\nimport javax.annotation.Nullable;\nimport org.apache.commons.lang3.builder.ToStringBuilder;\nimport org.apache.commons.lang3.builder.ToStringStyle;\nimport org.sonar.api.batch.sensor.internal.SensorStorage;\n\nimport static java.util.Objects.requireNonNull;\nimport static org.sonar.api.utils.Preconditions.checkState;\n\nabstract class DefaultStorable {\n\n  protected final SensorStorage storage;\n  private boolean saved = false;\n\n  protected DefaultStorable(@Nullable SensorStorage storage) {\n    this.storage = storage;\n  }\n\n  public final void save() {\n    requireNonNull(this.storage, \"No persister on this object\");\n    checkState(!saved, \"This object was already saved\");\n    doSave();\n    this.saved = true;\n  }\n\n  protected abstract void doSave();\n\n  @Override\n  public String toString() {\n    return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/DefaultTempFolder.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.FileVisitResult;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.SimpleFileVisitor;\nimport java.nio.file.attribute.BasicFileAttributes;\nimport javax.annotation.Nullable;\nimport org.apache.commons.io.FileUtils;\nimport org.sonar.api.Startable;\nimport org.sonar.api.utils.TempFolder;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic class DefaultTempFolder implements TempFolder, Startable {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final File tempDir;\n  private final boolean deleteOnExit;\n\n  public DefaultTempFolder(File tempDir) {\n    this(tempDir, false);\n  }\n\n  public DefaultTempFolder(File tempDir, boolean deleteOnExit) {\n    this.tempDir = tempDir;\n    this.deleteOnExit = deleteOnExit;\n  }\n\n  @Override\n  public File newDir() {\n    return createTempDir(tempDir.toPath()).toFile();\n  }\n\n  private static Path createTempDir(Path baseDir) {\n    try {\n      return Files.createTempDirectory(baseDir, null);\n    } catch (IOException e) {\n      throw new IllegalStateException(\"Failed to create temp directory\", e);\n    }\n  }\n\n  @Override\n  public File newDir(String name) {\n    var dir = new File(tempDir, name);\n    try {\n      FileUtils.forceMkdir(dir);\n    } catch (IOException e) {\n      throw new IllegalStateException(\"Failed to create temp directory - \" + dir, e);\n    }\n    return dir;\n  }\n\n  @Override\n  public File newFile() {\n    return newFile(null, null);\n  }\n\n  @Override\n  public File newFile(@Nullable String prefix, @Nullable String suffix) {\n    return createTempFile(tempDir.toPath(), prefix, suffix).toFile();\n  }\n\n  private static Path createTempFile(Path baseDir, @Nullable String prefix, @Nullable String suffix) {\n    try {\n      return Files.createTempFile(baseDir, prefix, suffix);\n    } catch (IOException e) {\n      throw new IllegalStateException(\"Failed to create temp file\", e);\n    }\n  }\n\n  public void clean() {\n    try {\n      if (tempDir.exists()) {\n        Files.walkFileTree(tempDir.toPath(), DeleteRecursivelyFileVisitor.INSTANCE);\n      }\n    } catch (IOException e) {\n      LOG.error(\"Failed to delete temp folder\", e);\n    }\n  }\n\n  @Override\n  public void start() {\n    // Nothing\n  }\n\n  @Override\n  public void stop() {\n    if (deleteOnExit) {\n      clean();\n    }\n  }\n\n  private static final class DeleteRecursivelyFileVisitor extends SimpleFileVisitor<Path> {\n    public static final DeleteRecursivelyFileVisitor INSTANCE = new DeleteRecursivelyFileVisitor();\n\n    @Override\n    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\n      Files.deleteIfExists(file);\n      return FileVisitResult.CONTINUE;\n    }\n\n    @Override\n    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {\n      Files.deleteIfExists(dir);\n      return FileVisitResult.CONTINUE;\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/SonarLintModuleFileSystem.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi;\n\nimport java.util.stream.Stream;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientModuleFileSystem;\nimport org.sonarsource.sonarlint.core.analysis.container.module.ModuleInputFileBuilder;\nimport org.sonarsource.sonarlint.plugin.api.module.file.ModuleFileSystem;\n\npublic class SonarLintModuleFileSystem implements ModuleFileSystem {\n\n  private final ClientModuleFileSystem clientFileSystem;\n  private final ModuleInputFileBuilder inputFileBuilder;\n\n  public SonarLintModuleFileSystem(ClientModuleFileSystem clientFileSystem, ModuleInputFileBuilder inputFileBuilder) {\n    this.clientFileSystem = clientFileSystem;\n    this.inputFileBuilder = inputFileBuilder;\n  }\n\n  @Override\n  public Stream<InputFile> files(String suffix, InputFile.Type type) {\n    return clientFileSystem.files(suffix, type)\n      .map(inputFileBuilder::create);\n  }\n\n  @Override\n  public Stream<InputFile> files() {\n    return clientFileSystem.files()\n      .map(inputFileBuilder::create);\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/noop/NoOpFileLinesContext.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi.noop;\n\nimport org.sonar.api.measures.FileLinesContext;\n\npublic class NoOpFileLinesContext implements FileLinesContext {\n\n  @Override\n  public void setIntValue(String metricKey, int line, int value) {\n  }\n\n  @Override\n  public void setStringValue(String metricKey, int line, String value) {\n  }\n\n  @Override\n  public void save() {\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/noop/NoOpFileLinesContextFactory.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi.noop;\n\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.measures.FileLinesContext;\nimport org.sonar.api.measures.FileLinesContextFactory;\n\npublic class NoOpFileLinesContextFactory implements FileLinesContextFactory {\n\n  @Override\n  public FileLinesContext createFor(InputFile inputFile) {\n    return new NoOpFileLinesContext();\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/noop/NoOpNewCoverage.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi.noop;\n\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.sensor.coverage.NewCoverage;\n\npublic class NoOpNewCoverage implements NewCoverage {\n\n  @Override\n  public NewCoverage onFile(InputFile inputFile) {\n    // no op\n    return this;\n  }\n\n  @Override\n  public NewCoverage lineHits(int line, int hits) {\n    // no op\n    return this;\n  }\n\n  @Override\n  public NewCoverage conditions(int line, int conditions, int coveredConditions) {\n    // no op\n    return this;\n  }\n\n  @Override\n  public void save() {\n    // no op\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/noop/NoOpNewCpdTokens.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi.noop;\n\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.fs.TextRange;\nimport org.sonar.api.batch.sensor.cpd.NewCpdTokens;\n\npublic class NoOpNewCpdTokens implements NewCpdTokens {\n  @Override\n  public void save() {\n    // Do nothing\n  }\n\n  @Override\n  public NoOpNewCpdTokens onFile(InputFile inputFile) {\n    // Do nothing\n    return this;\n  }\n\n  @Override\n  public NoOpNewCpdTokens addToken(TextRange range, String image) {\n    // Do nothing\n    return this;\n  }\n\n  @Override\n  public NoOpNewCpdTokens addToken(int startLine, int startLineOffset, int endLine, int endLineOffset, String image) {\n    // Do nothing\n    return this;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/noop/NoOpNewHighlighting.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi.noop;\n\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.fs.TextRange;\nimport org.sonar.api.batch.sensor.highlighting.NewHighlighting;\nimport org.sonar.api.batch.sensor.highlighting.TypeOfText;\n\npublic class NoOpNewHighlighting implements NewHighlighting {\n  @Override\n  public void save() {\n    // Do nothing\n  }\n\n  @Override\n  public NoOpNewHighlighting onFile(InputFile inputFile) {\n    // Do nothing\n    return this;\n  }\n\n  @Override\n  public NoOpNewHighlighting highlight(int startLine, int startLineOffset, int endLine, int endLineOffset, TypeOfText typeOfText) {\n    // Do nothing\n    return this;\n  }\n\n  @Override\n  public NoOpNewHighlighting highlight(TextRange range, TypeOfText typeOfText) {\n    // Do nothing\n    return this;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/noop/NoOpNewMeasure.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi.noop;\n\nimport java.io.Serializable;\nimport org.sonar.api.batch.fs.InputComponent;\nimport org.sonar.api.batch.measure.Metric;\nimport org.sonar.api.batch.sensor.measure.NewMeasure;\n\npublic class NoOpNewMeasure<G extends Serializable> implements NewMeasure<G> {\n\n  @Override\n  public NoOpNewMeasure<G> on(InputComponent component) {\n    // do nothing\n    return this;\n  }\n\n  @Override\n  public NoOpNewMeasure<G> forMetric(Metric<G> metric) {\n    // do nothing\n    return this;\n  }\n\n  @Override\n  public NoOpNewMeasure<G> withValue(Serializable value) {\n    // do nothing\n    return this;\n  }\n\n  @Override\n  public void save() {\n    // do nothing\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/noop/NoOpNewMessageFormatting.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi.noop;\n\nimport org.sonar.api.batch.sensor.issue.MessageFormatting;\nimport org.sonar.api.batch.sensor.issue.NewMessageFormatting;\n\npublic class NoOpNewMessageFormatting implements NewMessageFormatting {\n\n  @Override\n  public NoOpNewMessageFormatting start(int start) {\n    return this;\n  }\n\n  @Override\n  public NoOpNewMessageFormatting end(int end) {\n    return this;\n  }\n\n  @Override\n  public NoOpNewMessageFormatting type(MessageFormatting.Type type) {\n    return this;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/noop/NoOpNewSignificantCode.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi.noop;\n\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.fs.TextRange;\nimport org.sonar.api.batch.sensor.code.NewSignificantCode;\n\npublic class NoOpNewSignificantCode implements NewSignificantCode {\n\n  @Override\n  public void save() {\n    // no op\n  }\n\n  @Override\n  public NewSignificantCode onFile(InputFile file) {\n    // no op\n    return this;\n  }\n\n  @Override\n  public NewSignificantCode addRange(TextRange range) {\n    // no op\n    return this;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/noop/NoOpNewSymbolTable.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi.noop;\n\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.fs.TextRange;\nimport org.sonar.api.batch.sensor.symbol.NewSymbol;\nimport org.sonar.api.batch.sensor.symbol.NewSymbolTable;\n\npublic class NoOpNewSymbolTable implements NewSymbolTable, NewSymbol {\n  @Override\n  public void save() {\n    // Do nothing\n  }\n\n  @Override\n  public NoOpNewSymbolTable onFile(InputFile inputFile) {\n    // Do nothing\n    return this;\n  }\n\n  @Override\n  public NoOpNewSymbolTable newSymbol(int startLine, int startLineOffset, int endLine, int endLineOffset) {\n    // Do nothing\n    return this;\n  }\n\n  @Override\n  public NoOpNewSymbolTable newSymbol(TextRange range) {\n    // Do nothing\n    return this;\n  }\n\n  @Override\n  public NoOpNewSymbolTable newReference(int startLine, int startLineOffset, int endLine, int endLineOffset) {\n    // Do nothing\n    return this;\n  }\n\n  @Override\n  public NoOpNewSymbolTable newReference(TextRange range) {\n    // Do nothing\n    return this;\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/noop/package-info.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@javax.annotation.ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.analysis.sonarapi.noop;\n"
  },
  {
    "path": "backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/sonarapi/package-info.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.analysis.sonarapi;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/AnalysisQueueTest.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.UUID;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.analysis.api.TriggerType;\nimport org.sonarsource.sonarlint.core.analysis.command.AnalyzeCommand;\nimport org.sonarsource.sonarlint.core.analysis.command.UnregisterModuleCommand;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.commons.progress.TaskManager;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\n\nclass AnalysisQueueTest {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @Test\n  void it_should_prioritize_unregister_module_commands_over_analyses() throws InterruptedException {\n    var analysisQueue = new AnalysisQueue();\n    var taskManager = mock(TaskManager.class);\n    analysisQueue.post(new AnalyzeCommand(\"key\", UUID.randomUUID(), null, null, null, null, new SonarLintCancelMonitor(), taskManager, null, () -> true, Set.of(), Map.of()));\n    var unregisterModuleCommand = new UnregisterModuleCommand(\"key\");\n    analysisQueue.post(unregisterModuleCommand);\n\n    var command = analysisQueue.takeNextCommand();\n\n    assertThat(command).isEqualTo(unregisterModuleCommand);\n  }\n\n  @Test\n  void it_should_not_queue_a_canceled_command() throws InterruptedException {\n    var canceledProgressMonitor = new SonarLintCancelMonitor();\n    var progressMonitor = new SonarLintCancelMonitor();\n    var analysisQueue = new AnalysisQueue();\n    var taskManager = mock(TaskManager.class);\n    var canceledCommand = new AnalyzeCommand(\"1\", UUID.randomUUID(), TriggerType.FORCED, null, null, null, canceledProgressMonitor, taskManager, null, () -> true, Set.of(), Map.of());\n    var command = new AnalyzeCommand(\"2\", UUID.randomUUID(), TriggerType.FORCED, null, null, null, progressMonitor, taskManager, null, () -> true, Set.of(), Map.of());\n    canceledProgressMonitor.cancel();\n    analysisQueue.post(canceledCommand);\n    analysisQueue.post(command);\n\n    var nextCommand = analysisQueue.takeNextCommand();\n\n    assertThat(nextCommand).isEqualTo(command);\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/api/AnalysisConfigurationTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.api;\n\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Path;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport javax.annotation.CheckForNull;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonar.api.batch.rule.ActiveRule;\nimport org.sonar.api.rule.RuleKey;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport testutils.TestClientInputFile;\n\nimport static java.nio.file.Files.createDirectory;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.entry;\n\nclass AnalysisConfigurationTests {\n\n  @AfterEach\n  void init_property() {\n    System.clearProperty(\"sonarlint.debug.active.rules\");\n  }\n\n  @Test\n  void testToString_and_getters(@TempDir Path temp) throws Exception {\n    Map<String, String> props = new HashMap<>();\n    props.put(\"sonar.java.libraries\", \"foo bar\");\n\n    final var srcFile1 = createDirectory(temp.resolve(\"src1\"));\n    final var srcFile2 = createDirectory(temp.resolve(\"src2\"));\n    final var srcFile3 = createDirectory(temp.resolve(\"src3\"));\n    ClientInputFile inputFile = new TestClientInputFile(temp, srcFile1, false, StandardCharsets.UTF_8, null);\n    ClientInputFile inputFileWithLanguage = new TestClientInputFile(temp, srcFile2, false, StandardCharsets.UTF_8, SonarLanguage.JAVA);\n    ClientInputFile testInputFile = new TestClientInputFile(temp, srcFile3, true, null, SonarLanguage.PHP);\n    var baseDir = createDirectory(temp.resolve(\"baseDir\"));\n    var activeRuleWithParams = newActiveRule(\"php:S123\", Map.of(\"param1\", \"value1\"));\n    var config = AnalysisConfiguration.builder()\n      .setBaseDir(baseDir)\n      .addInputFile(inputFile)\n      .addInputFiles(inputFileWithLanguage)\n      .addInputFiles(List.of(testInputFile))\n      .putAllExtraProperties(props)\n      .putExtraProperty(\"sonar.foo\", \"bar\")\n      .addActiveRules(List.of(newActiveRule(\"java:S123\"), newActiveRule(\"java:S456\")))\n      .addActiveRule(activeRuleWithParams)\n      .addActiveRules(newActiveRule(\"python:S123\"), newActiveRule(\"python:S456\"))\n      .build();\n    assertThat(config).hasToString(\"[\\n\" +\n      \"  baseDir: \" + baseDir + \"\\n\" +\n      \"  extraProperties: {sonar.java.libraries=foo bar, sonar.foo=bar}\\n\" +\n      \"  activeRules: [2 python, 2 java, 1 php]\\n\" +\n      \"  inputFiles: [\\n\" +\n      \"    \" + srcFile1.toUri() + \" (UTF-8)\\n\" +\n      \"    \" + srcFile2.toUri() + \" (UTF-8) [java]\\n\" +\n      \"    \" + srcFile3.toUri() + \" (default) [test] [php]\\n\" +\n      \"  ]\\n\" +\n      \"]\\n\");\n    assertThat(config.baseDir()).isEqualTo(baseDir);\n    assertThat(config.inputFiles()).containsExactly(inputFile, inputFileWithLanguage, testInputFile);\n    assertThat(config.extraProperties()).containsExactly(entry(\"sonar.java.libraries\", \"foo bar\"), entry(\"sonar.foo\", \"bar\"));\n    assertThat(config.activeRules()).extracting(ActiveRule::ruleKey).map(RuleKey::toString).containsExactly(\"java:S123\", \"java:S456\", \"php:S123\", \"python:S123\", \"python:S456\");\n  }\n\n  @Test\n  void testToString_and_getters_when_empty() {\n    var config = AnalysisConfiguration.builder().build();\n    assertThat(config).hasToString(\"\"\"\n      [\n        baseDir: null\n        extraProperties: {}\n        activeRules: []\n        inputFiles: [\n        ]\n      ]\n      \"\"\");\n    assertThat(config.baseDir()).isNull();\n    assertThat(config.inputFiles()).isEmpty();\n    assertThat(config.activeRules()).isEmpty();\n  }\n\n  @Test\n  void testToString_and_getters_when_active_rules_verbose() {\n    System.setProperty(\"sonarlint.debug.active.rules\", \"true\");\n\n    var activeRuleWithParams = newActiveRule(\"php:S123\", Map.of(\"param1\", \"value1\"));\n    var config = AnalysisConfiguration.builder()\n      .addActiveRules(List.of(newActiveRule(\"java:S123\"), newActiveRule(\"java:S456\")))\n      .addActiveRules(activeRuleWithParams)\n      .addActiveRules(newActiveRule(\"python:S123\"), newActiveRule(\"python:S456\"))\n      .build();\n    assertThat(config).hasToString(\"\"\"\n      [\n        baseDir: null\n        extraProperties: {}\n        activeRules: [java:S123, java:S456, php:S123{param1=value1}, python:S123, python:S456]\n        inputFiles: [\n        ]\n      ]\n      \"\"\");\n    assertThat(config.baseDir()).isNull();\n    assertThat(config.inputFiles()).isEmpty();\n    assertThat(config.activeRules()).extracting(ActiveRule::ruleKey).map(RuleKey::toString).containsExactly(\"java:S123\", \"java:S456\", \"php:S123\", \"python:S123\", \"python:S456\");\n  }\n\n  @Test\n  void testToString_and_getters_when_active_rules_not_verbose() {\n    var activeRuleWithParams = newActiveRule(\"php:S123\", Map.of(\"param1\", \"value1\"));\n    var config = AnalysisConfiguration.builder()\n      .addActiveRules(List.of(newActiveRule(\"java:S123\"), newActiveRule(\"java:S456\")))\n      .addActiveRules(activeRuleWithParams)\n      .addActiveRules(newActiveRule(\"python:S123\"), newActiveRule(\"python:S456\"))\n      .build();\n    assertThat(config).hasToString(\"\"\"\n      [\n        baseDir: null\n        extraProperties: {}\n        activeRules: [2 python, 2 java, 1 php]\n        inputFiles: [\n        ]\n      ]\n      \"\"\");\n    assertThat(config.baseDir()).isNull();\n    assertThat(config.inputFiles()).isEmpty();\n    assertThat(config.activeRules()).extracting(ActiveRule::ruleKey).map(RuleKey::toString).containsExactly(\"java:S123\", \"java:S456\", \"php:S123\", \"python:S123\", \"python:S456\");\n  }\n\n  private static ActiveRule newActiveRule(String ruleKey) {\n    return newActiveRule(ruleKey, Map.of());\n  }\n\n  private static ActiveRule newActiveRule(String ruleKey, Map<String, String> params) {\n    return new ActiveRule() {\n\n      @Override\n      public RuleKey ruleKey() {\n        return RuleKey.parse(ruleKey);\n      }\n\n      @Override\n      public String severity() {\n        return \"\";\n      }\n\n      @Override\n      public String language() {\n        return \"\";\n      }\n\n      @CheckForNull\n      @Override\n      public String param(String key) {\n        return params().get(key);\n      }\n\n      @Override\n      public Map<String, String> params() {\n        return params;\n      }\n\n      @CheckForNull\n      @Override\n      public String internalKey() {\n        return \"\";\n      }\n\n      @CheckForNull\n      @Override\n      public String templateRuleKey() {\n        return \"\";\n      }\n\n      @Override\n      public String qpKey() {\n        return \"\";\n      }\n\n      @Override\n      public String toString() {\n        var sb = new StringBuilder();\n        sb.append(ruleKey);\n        if (!params.isEmpty()) {\n          sb.append(params);\n        }\n        return sb.toString();\n      }\n    };\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/api/AnalysisSchedulerConfigurationTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.api;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.analysis.AnalysisScheduler;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.plugin.commons.PluginsLoader;\n\nimport static java.nio.file.Files.createDirectory;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.entry;\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\n\nclass AnalysisSchedulerConfigurationTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @Test\n  void testDefaults() {\n    var config = AnalysisSchedulerConfiguration.builder()\n      .build();\n    assertThat(config.getWorkDir()).isNull();\n    assertThat(config.getEffectiveSettings()).isEmpty();\n    assertThat(config.getClientPid()).isZero();\n  }\n\n  @Test\n  void extraProps() {\n    Map<String, String> extraProperties = new HashMap<>();\n    extraProperties.put(\"foo\", \"bar\");\n    var config = AnalysisSchedulerConfiguration.builder()\n      .setExtraProperties(extraProperties)\n      .build();\n    assertThat(config.getEffectiveSettings()).containsOnly(entry(\"foo\", \"bar\"));\n  }\n\n  @Test\n  void effectiveConfig_should_add_nodejs() {\n    Map<String, String> extraProperties = new HashMap<>();\n    extraProperties.put(\"foo\", \"bar\");\n    var config = AnalysisSchedulerConfiguration.builder()\n      .setExtraProperties(extraProperties)\n      .setNodeJs(Paths.get(\"nodejsPath\"))\n      .build();\n    assertThat(config.getEffectiveSettings()).containsOnly(entry(\"foo\", \"bar\"), entry(\"sonar.nodejs.executable\", \"nodejsPath\"));\n  }\n\n  @Test\n  void overrideDirs(@TempDir Path temp) throws Exception {\n    var work = createDirectory(temp.resolve(\"work\"));\n    var config = AnalysisSchedulerConfiguration.builder()\n      .setWorkDir(work)\n      .build();\n    assertThat(config.getWorkDir()).isEqualTo(work);\n  }\n\n  @Test\n  void providePid() {\n    var config = AnalysisSchedulerConfiguration.builder().setClientPid(123).build();\n    assertThat(config.getClientPid()).isEqualTo(123);\n  }\n\n  @Test\n  void should_not_fail_if_module_supplier_is_not_provided(@TempDir Path workDir) {\n    assertDoesNotThrow(() -> {\n      var analysisGlobalConfig = AnalysisSchedulerConfiguration.builder().setClientPid(1234L).setWorkDir(workDir).build();\n      var result = new PluginsLoader().load(new PluginsLoader.Configuration(Set.of(), Set.of(), false, Optional.empty()), Set.of());\n      new AnalysisScheduler(analysisGlobalConfig, result.getLoadedPlugins(), logTester.getLogOutput());\n    });\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/api/ClientInputFileTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.api;\n\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.nio.charset.Charset;\nimport java.nio.file.Path;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ClientInputFileTests {\n\n  @Test\n  void testDefaults(@TempDir Path tempDir) {\n    var path = tempDir.resolve(\"Foo.java\");\n    var underTest = new ClientInputFile() {\n      @Override\n      public boolean isTest() {\n        return false;\n      }\n\n      @Override\n      public InputStream inputStream() {\n        return null;\n      }\n\n      @Override\n      public String getPath() {\n        return path.toAbsolutePath().toString();\n      }\n\n      @Override\n      public String relativePath() {\n        return path.getParent().toString();\n      }\n\n      @Override\n      public <G> G getClientObject() {\n        return null;\n      }\n\n      @Override\n      public Charset getCharset() {\n        return null;\n      }\n\n      @Override\n      public String contents() {\n        return null;\n      }\n\n      @Override\n      public URI uri() {\n        return path.toUri();\n      }\n    };\n\n    assertThat(underTest.language()).isNull();\n    assertThat(underTest.uri()).hasScheme(\"file\");\n    assertThat(underTest.uri().getPath()).endsWith(\"/Foo.java\");\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/api/DefaultLocationTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.api;\n\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.DefaultTextPointer;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.DefaultTextRange;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\n\nclass DefaultLocationTests {\n  @Test\n  void verify_accessors() {\n    var inputFile = mock(ClientInputFile.class);\n    var message = \"fummy\";\n    var sqApiTextRange = new DefaultTextRange(new DefaultTextPointer(1, 2), new DefaultTextPointer(3, 4));\n    var defaultLocation = new DefaultLocation(inputFile, sqApiTextRange, message);\n\n    assertThat(defaultLocation.getInputFile()).isSameAs(inputFile);\n    assertThat(defaultLocation.getMessage()).isSameAs(message);\n    assertThat(defaultLocation.getTextRange().getStartLine()).isEqualTo(1);\n    assertThat(defaultLocation.getTextRange().getStartLineOffset()).isEqualTo(2);\n    assertThat(defaultLocation.getTextRange().getEndLine()).isEqualTo(3);\n    assertThat(defaultLocation.getTextRange().getEndLineOffset()).isEqualTo(4);\n  }\n\n  @Test\n  void text_range_can_be_null() {\n    var inputFile = mock(ClientInputFile.class);\n    var message = \"fummy\";\n    var defaultLocation = new DefaultLocation(inputFile, null, message);\n\n    assertThat(defaultLocation.getTextRange()).isNull();\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/api/IssueLocationTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.api;\n\nimport javax.annotation.Nullable;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass IssueLocationTests {\n\n  @Test\n  void it_should_return_text_range_details_if_provided() {\n    var issueLocation = newIssueLocation(new TextRange(1, 2, 3, 4));\n\n    assertThat(issueLocation.getStartLine()).isEqualTo(1);\n    assertThat(issueLocation.getStartLineOffset()).isEqualTo(2);\n    assertThat(issueLocation.getEndLine()).isEqualTo(3);\n    assertThat(issueLocation.getEndLineOffset()).isEqualTo(4);\n  }\n\n  @Test\n  void it_should_return_null_details_if_no_text_range_provided() {\n    var issueLocation = newIssueLocation(null);\n\n    assertThat(issueLocation.getStartLine()).isNull();\n    assertThat(issueLocation.getStartLineOffset()).isNull();\n    assertThat(issueLocation.getEndLine()).isNull();\n    assertThat(issueLocation.getEndLineOffset()).isNull();\n  }\n\n  private static IssueLocation newIssueLocation(@Nullable TextRange textRange) {\n    return new IssueLocation() {\n      @Override\n      public ClientInputFile getInputFile() {\n        return null;\n      }\n\n      @Override\n      public TextRange getTextRange() {\n        return textRange;\n      }\n\n      @Override\n      public String getMessage() {\n        return null;\n      }\n    };\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/command/AnalyzeCommandTest.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.command;\n\nimport java.net.URI;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.UUID;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisConfiguration;\nimport org.sonarsource.sonarlint.core.analysis.api.TriggerType;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.commons.progress.TaskManager;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass AnalyzeCommandTest {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @Test\n  void it_should_cancel_posting_command() throws Exception {\n    var files = Set.of(new URI(\"file:///test1\"));\n    var props = Map.of(\"a\", \"b\");\n    var cmd1 = newAnalyzeCommand(files, props);\n    var cmd2 = newAnalyzeCommand(files, props);\n\n    assertThat(cmd1.shouldCancelPost(cmd2)).isTrue();\n  }\n\n  @Test\n  void it_should_cancel_posting_command_if_canceled() throws Exception {\n    var cmd1 = newAnalyzeCommand(Set.of(new URI(\"file:///test1\")), Map.of());\n    var cmd2 = newAnalyzeCommand(Set.of(new URI(\"file:///test2\")), Map.of());\n    cmd1.cancel();\n\n    assertThat(cmd1.shouldCancelPost(cmd2)).isTrue();\n  }\n\n  @Test\n  void it_should_not_cancel_when_files_are_different() throws Exception {\n    var cmd1 = newAnalyzeCommand(Set.of(new URI(\"file:///test1\")), Map.of());\n    var cmd2 = newAnalyzeCommand(Set.of(new URI(\"file:///test2\")), Map.of());\n\n    assertThat(cmd1.shouldCancelPost(cmd2)).isFalse();\n  }\n\n\n  @Test\n  void if_should_cancel_task_in_queue_when_canceled() {\n    var cmd = newAnalyzeCommand(Set.of(), Map.of());\n    cmd.cancel();\n\n    assertThat(cmd.shouldCancelQueue()).isTrue();\n  }\n\n  @Test\n  void it_should_not_cancel_task_in_queue_if_not_canceled() {\n    var cmd = newAnalyzeCommand(Set.of(), Map.of());\n\n    assertThat(cmd.shouldCancelQueue()).isFalse();\n  }\n\n  private static AnalyzeCommand newAnalyzeCommand(Set<URI> files, Map<String, String> extraProps) {\n    return new AnalyzeCommand(\n      \"moduleKey\",\n      UUID.randomUUID(),\n      TriggerType.FORCED,\n      () -> AnalysisConfiguration.builder().addInputFiles().build(),\n      issue -> {},\n      null,\n      new SonarLintCancelMonitor(),\n      new TaskManager(),\n      inputFiles -> {},\n      () -> true,\n      files,\n      extraProps\n    );\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/AnalysisSettingsTest.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis;\n\nimport java.util.Collections;\nimport java.util.Map;\nimport org.junit.jupiter.api.Test;\nimport org.sonar.api.config.PropertyDefinitions;\nimport org.sonar.api.utils.System2;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisConfiguration;\nimport org.sonarsource.sonarlint.core.analysis.container.global.GlobalSettings;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\n\nclass AnalysisSettingsTest {\n  private final PropertyDefinitions propertyDefinitions = new PropertyDefinitions(System2.INSTANCE, Collections.emptyList());\n  private final GlobalSettings globalSettings = mock(GlobalSettings.class);\n\n  @Test\n  void trimAnalysisPropertyKeys() {\n    AnalysisConfiguration analysisConfiguration = AnalysisConfiguration.builder()\n      .putAllExtraProperties(Map.of(\"key1   \", \"value1\", \"key1 \", \"value11\")).build();\n\n    AnalysisSettings analysisSettings = new AnalysisSettings(globalSettings, analysisConfiguration, propertyDefinitions);\n\n    assertThat(analysisSettings.getProperties().keySet())\n      .contains(\"key1\")\n      .hasSize(1);\n  }\n\n}"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/AnalysisTempFolderProviderTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\n\nclass AnalysisTempFolderProviderTests {\n\n  @Test\n  void allMethodsShouldThrow() {\n    var underTest = new AnalysisTempFolderProvider();\n    var tempFolder = underTest.provide();\n\n    assertThatExceptionOfType(UnsupportedOperationException.class)\n      .isThrownBy(tempFolder::newDir)\n      .withMessage(\"Don't create temp folders during analysis\");\n\n    assertThatExceptionOfType(UnsupportedOperationException.class)\n      .isThrownBy(() -> tempFolder.newDir(\"foo\"))\n      .withMessage(\"Don't create temp folders during analysis\");\n\n    assertThatExceptionOfType(UnsupportedOperationException.class)\n      .isThrownBy(tempFolder::newFile)\n      .withMessage(\"Don't create temp files during analysis\");\n\n    assertThatExceptionOfType(UnsupportedOperationException.class)\n      .isThrownBy(() -> tempFolder.newFile(\"foo\", \"bar\"))\n      .withMessage(\"Don't create temp files during analysis\");\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/SonarLintPathPatternTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SonarLintPathPatternTests {\n\n  @Test\n  void constructor_should_add_double_star_prefix_when_not_present() {\n    assertThat(new SonarLintPathPattern(\"*.java\")).hasToString(\"**/*.java\");\n  }\n\n  @Test\n  void constructor_should_not_add_double_star_prefix_when_already_present() {\n    assertThat(new SonarLintPathPattern(\"**/*.java\")).hasToString(\"**/*.java\");\n  }\n\n  @Test\n  void create_should_return_array_of_patterns() {\n    var patterns = SonarLintPathPattern.create(new String[]{\"*.java\", \"*.xml\"});\n    \n    assertThat(patterns).hasSize(2);\n    assertThat(patterns[0].toString()).hasToString(\"**/*.java\");\n    assertThat(patterns[1].toString()).hasToString(\"**/*.xml\");\n  }\n\n  @Test\n  void create_should_return_empty_array_when_input_is_empty() {\n    assertThat(SonarLintPathPattern.create(new String[]{})).isEmpty();\n  }\n\n  @Test\n  void match_should_match_java_files() {\n    var pattern = new SonarLintPathPattern(\"*.java\");\n    \n    assertThat(pattern.match(\"src/main/java/Test.java\")).isTrue();\n    assertThat(pattern.match(\"src/test/java/Test.java\")).isTrue();\n    assertThat(pattern.match(\"Test.java\")).isTrue();\n    assertThat(pattern.match(\"Test.txt\")).isFalse();\n  }\n\n  @Test\n  void match_should_match_xml_files() {\n    var pattern = new SonarLintPathPattern(\"*.xml\");\n    \n    assertThat(pattern.match(\"pom.xml\")).isTrue();\n    assertThat(pattern.match(\"src/main/resources/config.xml\")).isTrue();\n    assertThat(pattern.match(\"Test.java\")).isFalse();\n  }\n\n  @Test\n  void match_should_match_with_path_patterns() {\n    var pattern = new SonarLintPathPattern(\"src/**/*.java\");\n    \n    assertThat(pattern.match(\"src/main/java/Test.java\")).isTrue();\n    assertThat(pattern.match(\"src/test/java/Test.java\")).isTrue();\n    assertThat(pattern.match(\"Test.java\")).isFalse();\n  }\n\n  @Test\n  void match_should_match_test_patterns() {\n    var pattern = new SonarLintPathPattern(\"**/test/**/*.java\");\n    \n    assertThat(pattern.match(\"src/test/java/Test.java\")).isTrue();\n    assertThat(pattern.match(\"src/main/java/Test.java\")).isFalse();\n  }\n\n  @Test\n  void match_with_case_sensitive_should_respect_case() {\n    var pattern = new SonarLintPathPattern(\"*.JAVA\");\n    \n    assertThat(pattern.match(\"src/main/java/Test.java\", true)).isFalse();\n    assertThat(pattern.match(\"src/main/java/Test.JAVA\", true)).isTrue();\n  }\n\n  @Test\n  void match_should_handle_different_path_separators() {\n    var pattern = new SonarLintPathPattern(\"*.java\");\n    \n    assertThat(pattern.match(\"src\\\\main\\\\java\\\\Test.java\")).isTrue();\n    assertThat(pattern.match(\"src/main/java/Test.java\")).isTrue();\n    assertThat(pattern.match(\"src\\\\test\\\\java\\\\Test.java\")).isTrue();\n    assertThat(pattern.match(\"Test.java\")).isTrue();\n  }\n\n  @Test\n  void match_should_handle_path_without_extension() {\n    var pattern = new SonarLintPathPattern(\"*.java\");\n    \n    var result = pattern.match(\"src/main/java/Test\");\n    \n    assertThat(result).isFalse();\n  }\n\n  @Test\n  void match_should_handle_path_with_dot_but_no_extension() {\n    var pattern = new SonarLintPathPattern(\"*.java\");\n    \n    var result = pattern.match(\"src/main/java/Test.\");\n    \n    assertThat(result).isFalse();\n  }\n\n  @Test\n  void toString_should_return_pattern_string() {\n    var pattern = new SonarLintPathPattern(\"*.java\");\n    \n    var result = pattern.toString();\n    \n    assertThat(result).isEqualTo(\"**/*.java\");\n  }\n\n  @Test\n  void sanitizeExtension_should_handle_null() {\n    assertThat(SonarLintPathPattern.sanitizeExtension(null)).isNull();\n  }\n\n  @Test\n  void sanitizeExtension_should_handle_empty_string() {\n    assertThat(SonarLintPathPattern.sanitizeExtension(\"\")).isEmpty();\n  }\n\n  @Test\n  void sanitizeExtension_should_remove_leading_dot() {\n    assertThat(SonarLintPathPattern.sanitizeExtension(\".java\")).isEqualTo(\"java\");\n  }\n\n  @Test\n  void sanitizeExtension_should_convert_to_lowercase() {\n    assertThat(SonarLintPathPattern.sanitizeExtension(\"JAVA\")).isEqualTo(\"java\");\n  }\n\n  @Test\n  void sanitizeExtension_should_handle_extension_without_dot() {\n    assertThat(SonarLintPathPattern.sanitizeExtension(\"java\")).isEqualTo(\"java\");\n  }\n\n  @Test\n  void sanitizeExtension_should_handle_mixed_case_with_dot() {\n    assertThat(SonarLintPathPattern.sanitizeExtension(\".JaVa\")).isEqualTo(\"java\");\n  }\n} \n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/DefaultFilePredicatesTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonar.api.batch.fs.FilePredicate;\nimport org.sonar.api.batch.fs.FilePredicates;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.fs.InputFile.Type;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport testutils.OnDiskTestClientInputFile;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nclass DefaultFilePredicatesTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private InputFile javaFile;\n  private FilePredicates predicates;\n\n  @TempDir\n  Path baseDir;\n\n  @BeforeEach\n  void before() throws IOException {\n    predicates = new DefaultFilePredicates();\n    var filePath = baseDir.resolve(\"src/main/java/struts/Action.java\");\n    Files.createDirectories(filePath.getParent());\n    Files.write(filePath, \"foo\".getBytes(StandardCharsets.UTF_8));\n    var clientInputFile = new OnDiskTestClientInputFile(filePath, \"src/main/java/struts/Action.java\", false, StandardCharsets.UTF_8, SonarLanguage.JAVA);\n    InputStream fileInputStream = Files.newInputStream(filePath);\n    javaFile = new SonarLintInputFile(clientInputFile, f -> new FileMetadata().readMetadata(fileInputStream, StandardCharsets.UTF_8, filePath.toUri(), null))\n      .setType(Type.MAIN)\n      .setLanguage(SonarLanguage.JAVA);\n  }\n\n  @Test\n  void all() {\n    assertThat(predicates.all().apply(javaFile)).isTrue();\n  }\n\n  @Test\n  void none() {\n    assertThat(predicates.none().apply(javaFile)).isFalse();\n  }\n\n  @Test\n  void matches_inclusion_pattern() {\n    assertThat(predicates.matchesPathPattern(\"file:**/src/main/**/Action.java\").apply(javaFile)).isTrue();\n    assertThat(predicates.matchesPathPattern(\"**/src/main/**/Action.java\").apply(javaFile)).isTrue();\n    assertThat(predicates.matchesPathPattern(\"src/main/**/Action.java\").apply(javaFile)).isTrue();\n    assertThat(predicates.matchesPathPattern(\"src/**/*.php\").apply(javaFile)).isFalse();\n  }\n\n  @Test\n  void matches_inclusion_patterns() {\n    assertThat(predicates.matchesPathPatterns(new String[] {\"src/other/**.java\", \"src/main/**/Action.java\"}).apply(javaFile)).isTrue();\n    assertThat(predicates.matchesPathPatterns(new String[] {}).apply(javaFile)).isTrue();\n    assertThat(predicates.matchesPathPatterns(new String[] {\"src/other/**.java\", \"src/**/*.php\"}).apply(javaFile)).isFalse();\n  }\n\n  @Test\n  void does_not_match_exclusion_pattern() {\n    assertThat(predicates.doesNotMatchPathPattern(\"src/main/**/Action.java\").apply(javaFile)).isFalse();\n    assertThat(predicates.doesNotMatchPathPattern(\"src/**/*.php\").apply(javaFile)).isTrue();\n  }\n\n  @Test\n  void does_not_match_exclusion_patterns() {\n    assertThat(predicates.doesNotMatchPathPatterns(new String[] {}).apply(javaFile)).isTrue();\n    assertThat(predicates.doesNotMatchPathPatterns(new String[] {\"src/other/**.java\", \"src/**/*.php\"}).apply(javaFile)).isTrue();\n    assertThat(predicates.doesNotMatchPathPatterns(new String[] {\"src/other/**.java\", \"src/main/**/Action.java\"}).apply(javaFile)).isFalse();\n  }\n\n  @Test\n  void has_relative_path_unsupported() {\n    assertThrows(UnsupportedOperationException.class, () -> predicates.hasRelativePath(\"src/main/java/struts/Action.java\").apply(javaFile));\n  }\n\n  @Test\n  void has_uri() {\n    var uri = javaFile.uri();\n    assertThat(predicates.hasURI(uri).apply(javaFile)).isTrue();\n\n    assertThat(predicates.hasURI(baseDir.resolve(\"another.php\").toUri()).apply(javaFile)).isFalse();\n  }\n\n  @Test\n  void has_name() {\n    var fileName = javaFile.filename();\n    assertThat(predicates.hasFilename(fileName).apply(javaFile)).isTrue();\n\n    assertThat(predicates.hasFilename(\"another.php\").apply(javaFile)).isFalse();\n    assertThat(predicates.hasFilename(\"Action.php\").apply(javaFile)).isFalse();\n  }\n\n  @Test\n  void has_extension() {\n    var extension = \"java\";\n    assertThat(predicates.hasExtension(extension).apply(javaFile)).isTrue();\n\n    assertThat(predicates.hasExtension(\"php\").apply(javaFile)).isFalse();\n    assertThat(predicates.hasExtension(\"\").apply(javaFile)).isFalse();\n\n  }\n\n  @Test\n  void has_path() {\n    assertThrows(UnsupportedOperationException.class, () -> predicates.hasPath(\"src/main/java/struts/Action.java\").apply(javaFile));\n  }\n\n  @Test\n  void is_file() {\n    assertThat(predicates.is(javaFile.file()).apply(javaFile)).isTrue();\n    assertThat(predicates.is(new File(\"foo.php\")).apply(javaFile)).isFalse();\n  }\n\n  @Test\n  void has_language() {\n    assertThat(predicates.hasLanguage(\"java\").apply(javaFile)).isTrue();\n    assertThat(predicates.hasLanguage(\"php\").apply(javaFile)).isFalse();\n  }\n\n  @Test\n  void has_languages() {\n    assertThat(predicates.hasLanguages(Arrays.asList(\"java\", \"php\")).apply(javaFile)).isTrue();\n    assertThat(predicates.hasLanguages(\"java\", \"php\").apply(javaFile)).isTrue();\n    assertThat(predicates.hasLanguages(Arrays.asList(\"cobol\", \"php\")).apply(javaFile)).isFalse();\n    assertThat(predicates.hasLanguages(\"cobol\", \"php\").apply(javaFile)).isFalse();\n    assertThat(predicates.hasLanguages(Collections.emptyList()).apply(javaFile)).isTrue();\n  }\n\n  @Test\n  void has_type() {\n    assertThat(predicates.hasType(InputFile.Type.MAIN).apply(javaFile)).isTrue();\n    assertThat(predicates.hasType(InputFile.Type.TEST).apply(javaFile)).isFalse();\n  }\n\n  @Test\n  void has_status() {\n    assertThat(predicates.hasAnyStatus().apply(javaFile)).isTrue();\n    try {\n      predicates.hasStatus(InputFile.Status.SAME).apply(javaFile);\n      fail(\"Expected exception\");\n    } catch (Exception e) {\n      assertThat(e).isInstanceOf(UnsupportedOperationException.class);\n    }\n  }\n\n  @Test\n  void not() {\n    assertThat(predicates.not(predicates.hasType(InputFile.Type.MAIN)).apply(javaFile)).isFalse();\n    assertThat(predicates.not(predicates.hasType(InputFile.Type.TEST)).apply(javaFile)).isTrue();\n  }\n\n  @Test\n  void and() {\n    // empty\n    assertThat(predicates.and().apply(javaFile)).isTrue();\n    assertThat(predicates.and().apply(javaFile)).isTrue();\n    assertThat(predicates.and(Collections.emptyList()).apply(javaFile)).isTrue();\n\n    // two arguments\n    assertThat(predicates.and(predicates.all(), predicates.all()).apply(javaFile)).isTrue();\n    assertThat(predicates.and(predicates.all(), predicates.none()).apply(javaFile)).isFalse();\n    assertThat(predicates.and(predicates.none(), predicates.all()).apply(javaFile)).isFalse();\n\n    // collection\n    assertThat(predicates.and(Arrays.asList(predicates.all(), predicates.all())).apply(javaFile)).isTrue();\n    assertThat(predicates.and(Arrays.asList(predicates.all(), predicates.none())).apply(javaFile)).isFalse();\n\n    // array\n    assertThat(predicates.and(new FilePredicate[] {predicates.all(), predicates.all()}).apply(javaFile)).isTrue();\n    assertThat(predicates.and(new FilePredicate[] {predicates.all(), predicates.none()}).apply(javaFile)).isFalse();\n  }\n\n  @Test\n  void or() {\n    // empty\n    assertThat(predicates.or().apply(javaFile)).isTrue();\n    assertThat(predicates.or().apply(javaFile)).isTrue();\n    assertThat(predicates.or(Collections.emptyList()).apply(javaFile)).isTrue();\n\n    // two arguments\n    assertThat(predicates.or(predicates.all(), predicates.all()).apply(javaFile)).isTrue();\n    assertThat(predicates.or(predicates.all(), predicates.none()).apply(javaFile)).isTrue();\n    assertThat(predicates.or(predicates.none(), predicates.all()).apply(javaFile)).isTrue();\n    assertThat(predicates.or(predicates.none(), predicates.none()).apply(javaFile)).isFalse();\n\n    // collection\n    assertThat(predicates.or(Arrays.asList(predicates.all(), predicates.all())).apply(javaFile)).isTrue();\n    assertThat(predicates.or(Arrays.asList(predicates.all(), predicates.none())).apply(javaFile)).isTrue();\n    assertThat(predicates.or(Arrays.asList(predicates.none(), predicates.none())).apply(javaFile)).isFalse();\n\n    // array\n    assertThat(predicates.or(new FilePredicate[] {predicates.all(), predicates.all()}).apply(javaFile)).isTrue();\n    assertThat(predicates.or(new FilePredicate[] {predicates.all(), predicates.none()}).apply(javaFile)).isTrue();\n    assertThat(predicates.or(new FilePredicate[] {predicates.none(), predicates.none()}).apply(javaFile)).isFalse();\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/InputFileBuilderTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.scanner.IssueExclusionsLoader;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport testutils.FileUtils;\nimport testutils.OnDiskTestClientInputFile;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.when;\n\nclass InputFileBuilderTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private final LanguageDetection langDetection = mock(LanguageDetection.class);\n  private final IssueExclusionsLoader issueExclusionsLoader = mock(IssueExclusionsLoader.class);\n  private final FileMetadata metadata = new FileMetadata();\n\n  @TempDir\n  private Path tempDir;\n\n  @Test\n  void testCreate() throws IOException {\n    when(langDetection.language(any(InputFile.class))).thenReturn(SonarLanguage.JAVA);\n\n    var path = tempDir.resolve(\"file\");\n    Files.write(path, \"test\".getBytes(StandardCharsets.ISO_8859_1));\n    ClientInputFile file = new OnDiskTestClientInputFile(path, \"file\", true, StandardCharsets.ISO_8859_1);\n\n    var builder = new InputFileBuilder(langDetection, metadata, issueExclusionsLoader);\n    var inputFile = builder.create(file);\n\n    assertThat(inputFile.type()).isEqualTo(InputFile.Type.TEST);\n    assertThat(inputFile.file()).isEqualTo(path.toFile());\n    assertThat(inputFile.absolutePath()).isEqualTo(FileUtils.toSonarQubePath(path.toString()));\n    assertThat(inputFile.language()).isEqualTo(\"java\");\n    assertThat(inputFile.key()).isEqualTo(path.toUri().toString());\n    assertThat(inputFile.lines()).isEqualTo(1);\n\n    verify(issueExclusionsLoader).createCharHandlerFor(inputFile);\n  }\n\n  @Test\n  void testCreateWithLanguageSet() throws IOException {\n    var path = tempDir.resolve(\"file\");\n    Files.write(path, \"test\".getBytes(StandardCharsets.ISO_8859_1));\n    ClientInputFile file = new OnDiskTestClientInputFile(path, \"file\", true, StandardCharsets.ISO_8859_1, SonarLanguage.CPP);\n\n    var builder = new InputFileBuilder(langDetection, metadata, issueExclusionsLoader);\n    var inputFile = builder.create(file);\n\n    assertThat(inputFile.language()).isEqualTo(\"cpp\");\n    verifyNoInteractions(langDetection);\n  }\n\n  @Test\n  void testCreate_lazy_error() throws IOException {\n    when(langDetection.language(any(InputFile.class))).thenReturn(SonarLanguage.JAVA);\n    ClientInputFile file = new OnDiskTestClientInputFile(Paths.get(\"INVALID\"), \"INVALID\", true, StandardCharsets.ISO_8859_1);\n\n    var builder = new InputFileBuilder(langDetection, metadata, issueExclusionsLoader);\n    var slFile = builder.create(file);\n\n    // Call any method that will trigger metadata initialization\n    var thrown = assertThrows(IllegalStateException.class, () -> slFile.selectLine(1));\n    assertThat(thrown).hasMessageStartingWith(\"Failed to open a stream on file\");\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/InputFileCacheTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.sonar.api.batch.fs.InputFile;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass InputFileCacheTests {\n  private InputFileIndex cache;\n\n  @BeforeEach\n  void setUp() {\n    cache = new InputFileIndex();\n  }\n\n  @Test\n  void testFiles() {\n    var file1 = mock(InputFile.class);\n    when(file1.filename()).thenReturn(\"file1.java\");\n    when(file1.language()).thenReturn(\"lang1\");\n    var file2 = mock(InputFile.class);\n    when(file2.filename()).thenReturn(\"file2\");\n    when(file2.language()).thenReturn(\"lang2\");\n\n    cache.doAdd(file1);\n    cache.doAdd(file2);\n    assertThat(cache.inputFiles()).containsOnly(file1, file2);\n\n    assertThrows(UnsupportedOperationException.class, () -> cache.inputFile(\"file1.java\"));\n\n    assertThat(cache.getFilesByExtension(\"java\")).containsOnly(file1);\n    assertThat(cache.getFilesByExtension(\"\")).containsOnly(file2);\n    assertThat(cache.getFilesByName(\"file1.java\")).containsOnly(file1);\n\n    assertThat(cache.languages()).containsExactly(\"lang1\", \"lang2\");\n\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/LanguageDetectionTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.nio.file.Path;\nimport java.util.Map;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.resources.Language;\nimport org.sonar.api.utils.MessageException;\nimport org.sonarsource.sonarlint.core.plugin.commons.sonarapi.MapSettings;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport testutils.TestInputFileBuilder;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass LanguageDetectionTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @TempDir\n  private Path basedir;\n\n  @Test\n  void test_sanitizeExtension() {\n    assertThat(LanguageDetection.sanitizeExtension(\".cbl\")).isEqualTo(\"cbl\");\n    assertThat(LanguageDetection.sanitizeExtension(\".CBL\")).isEqualTo(\"cbl\");\n    assertThat(LanguageDetection.sanitizeExtension(\"CBL\")).isEqualTo(\"cbl\");\n    assertThat(LanguageDetection.sanitizeExtension(\"cbl\")).isEqualTo(\"cbl\");\n  }\n\n  @Test\n  void search_by_file_extension() {\n    var detection = new LanguageDetection(new MapSettings(Map.of()).asConfig());\n\n    assertThat(detection.language(newInputFile(\"Foo.java\"))).isEqualTo(SonarLanguage.JAVA);\n    assertThat(detection.language(newInputFile(\"src/Foo.java\"))).isEqualTo(SonarLanguage.JAVA);\n    assertThat(detection.language(newInputFile(\"Foo.JAVA\"))).isEqualTo(SonarLanguage.JAVA);\n    assertThat(detection.language(newInputFile(\"Foo.jav\"))).isEqualTo(SonarLanguage.JAVA);\n    assertThat(detection.language(newInputFile(\"Foo.Jav\"))).isEqualTo(SonarLanguage.JAVA);\n\n    assertThat(detection.language(newInputFile(\"abc.abap\"))).isEqualTo(SonarLanguage.ABAP);\n    assertThat(detection.language(newInputFile(\"abc.ABAP\"))).isEqualTo(SonarLanguage.ABAP);\n\n    assertThat(detection.language(newInputFile(\"abc.truc\"))).isNull();\n    assertThat(detection.language(newInputFile(\"abap\"))).isNull();\n  }\n\n  @Test\n  void recognise_yaml_files() {\n    var detection = new LanguageDetection(new MapSettings(Map.of()).asConfig());\n\n    assertThat(detection.language(newInputFile(\"lambda.yaml\"))).isEqualTo(SonarLanguage.YAML);\n    assertThat(detection.language(newInputFile(\"lambda.yml\"))).isEqualTo(SonarLanguage.YAML);\n    assertThat(detection.language(newInputFile(\"config/lambda.yml\"))).isEqualTo(SonarLanguage.YAML);\n    assertThat(detection.language(newInputFile(\"config/lambda.YAML\"))).isEqualTo(SonarLanguage.YAML);\n\n    assertThat(detection.language(newInputFile(\"wrong.ylm\"))).isNull();\n    assertThat(detection.language(newInputFile(\"config.js\"))).isNotEqualTo(SonarLanguage.YAML);\n  }\n\n  @Test\n  void recognise_kts_files() {\n    var detection = new LanguageDetection(new MapSettings(Map.of()).asConfig());\n\n    assertThat(detection.language(newInputFile(\"settings.kts\"))).isEqualTo(SonarLanguage.KOTLIN);\n\n    assertThat(detection.language(newInputFile(\"settings.kms\"))).isNull();\n    assertThat(detection.language(newInputFile(\"settings.js\"))).isNotEqualTo(SonarLanguage.KOTLIN);\n  }\n\n  @Test\n  void recognise_css_files() {\n    var detection = new LanguageDetection(new MapSettings(Map.of()).asConfig());\n\n    assertThat(detection.language(newInputFile(\"style.css\"))).isEqualTo(SonarLanguage.CSS);\n    assertThat(detection.language(newInputFile(\"style.less\"))).isEqualTo(SonarLanguage.CSS);\n    assertThat(detection.language(newInputFile(\"style.scss\"))).isEqualTo(SonarLanguage.CSS);\n\n    assertThat(detection.language(newInputFile(\"style.stylus\"))).isNull();\n  }\n\n  @Test\n  void recognise_go_file() {\n    var detection = new LanguageDetection(new MapSettings(Map.of()).asConfig());\n\n    assertThat(detection.language(newInputFile(\"myFile.go\"))).isEqualTo(SonarLanguage.GO);\n    assertThat(detection.language(newInputFile(\"folder/myFile.go\"))).isEqualTo(SonarLanguage.GO);\n\n    assertThat(detection.language(newInputFile(\"style.nogo\"))).isNull();\n  }\n\n  @Test\n  void recognise_terraform_file() {\n    var detection = new LanguageDetection(new MapSettings(Map.of()).asConfig());\n\n    assertThat(detection.language(newInputFile(\"myFile.tf\"))).isEqualTo(SonarLanguage.TERRAFORM);\n    assertThat(detection.language(newInputFile(\"folder/myFile.tf\"))).isEqualTo(SonarLanguage.TERRAFORM);\n\n    assertThat(detection.language(newInputFile(\"style.notf\"))).isNull();\n  }\n\n  @Test\n  void should_not_fail_if_no_language() {\n    var detection = new LanguageDetection(new MapSettings(Map.of()).asConfig());\n    assertThat(detection.language(newInputFile(\"Foo.blabla\"))).isNull();\n  }\n\n  @Test\n  void fail_if_conflicting_language_suffix() {\n    var settings = new MapSettings(Map.of(SonarLanguage.XML.getFileSuffixesPropKey(), \"xhtml\",\n      SonarLanguage.HTML.getFileSuffixesPropKey(), \"xhtml\"));\n    var detection = new LanguageDetection(settings.asConfig());\n    var inputFile = newInputFile(\"abc.xhtml\");\n    var e = assertThrows(MessageException.class, () -> detection.language(inputFile));\n    assertThat(e.getMessage())\n      .contains(\"Language of file \\\"file://\")\n      .contains(\"abc.xhtml\\\" can not be decided as the file extension matches both \")\n      .contains(\"HTML: xhtml\")\n      .contains(\"XML: xhtml\");\n  }\n\n  private InputFile newInputFile(String path) {\n    return new TestInputFileBuilder(path).setBaseDir(basedir).build();\n  }\n\n  static class MockLanguage implements Language {\n    private final String key;\n    private final String[] extensions;\n\n    MockLanguage(String key, String... extensions) {\n      this.key = key;\n      this.extensions = extensions;\n    }\n\n    @Override\n    public String getKey() {\n      return key;\n    }\n\n    @Override\n    public String getName() {\n      return key;\n    }\n\n    @Override\n    public String[] getFileSuffixes() {\n      return extensions;\n    }\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/ProgressReportTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\n\nclass ProgressReportTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  private static final String THREAD_NAME = \"progress\";\n\n  @Test\n  void die_on_stop() {\n    var underTest = new ProgressReport(THREAD_NAME, 100);\n    underTest.start(\"start\");\n    assertThat(isThreadAlive(THREAD_NAME)).isTrue();\n    underTest.stop(\"stop\");\n    assertThat(isThreadAlive(THREAD_NAME)).isFalse();\n  }\n\n  @Test\n  void accept_no_stop_msg() {\n    var underTest = new ProgressReport(THREAD_NAME, 100);\n    underTest.start(\"start\");\n    assertThat(isThreadAlive(THREAD_NAME)).isTrue();\n    underTest.stop(null);\n    assertThat(isThreadAlive(THREAD_NAME)).isFalse();\n  }\n\n  @Test\n  void do_not_block_app() {\n    var underTest = new ProgressReport(THREAD_NAME, 100);\n    underTest.start(\"start\");\n    assertThat(isDaemon(THREAD_NAME)).isTrue();\n    underTest.stop(\"stop\");\n  }\n\n  @Test\n  void do_log() {\n    var underTest = new ProgressReport(THREAD_NAME, 100);\n    underTest.start(\"start\");\n    underTest.message(() -> \"Some message\");\n    await().atMost(5, SECONDS).untilAsserted(() -> assertThat(logTester.logs()).contains(\"start\", \"Some message\"));\n    underTest.stop(\"stop\");\n    assertThat(logTester.logs()).contains(\"start\", \"Some message\", \"stop\");\n  }\n\n  private static boolean isDaemon(String name) {\n    var t = getThread(name);\n    return (t != null) && t.isDaemon();\n  }\n\n  private static boolean isThreadAlive(String name) {\n    var t = getThread(name);\n    return (t != null) && t.isAlive();\n  }\n\n  private static Thread getThread(String name) {\n    var threads = Thread.getAllStackTraces().keySet();\n\n    for (Thread t : threads) {\n      if (t.getName().equals(name)) {\n        return t;\n      }\n    }\n    return null;\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/SonarLintFileSystemTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.io.File;\nimport java.nio.file.Path;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisConfiguration;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport testutils.TestInputFileBuilder;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass SonarLintFileSystemTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private SonarLintFileSystem fs;\n  @TempDir\n  Path basedir;\n  private final InputFileIndex inputFileCache = new InputFileIndex();\n\n  @BeforeEach\n  void prepare() {\n    fs = new SonarLintFileSystem(AnalysisConfiguration.builder().setBaseDir(basedir).build(), inputFileCache);\n  }\n\n  @Test\n  void return_fake_workdir() {\n    assertThat(fs.workDir()).isEqualTo(basedir.toFile());\n  }\n\n  @Test\n  void add_languages() {\n    assertThat(fs.languages()).isEmpty();\n\n    inputFileCache.doAdd(new TestInputFileBuilder(\"src/Foo.php\").setLanguage(SonarLanguage.PHP).build());\n    inputFileCache.doAdd(new TestInputFileBuilder(\"src/Bar.java\").setLanguage(SonarLanguage.JAVA).build());\n\n    assertThat(fs.languages()).containsOnly(\"java\", \"php\");\n  }\n\n  @Test\n  void files() {\n    assertThat(fs.inputFiles(fs.predicates().all())).isEmpty();\n\n    var inputFile = new TestInputFileBuilder(\"src/Foo.php\").setBaseDir(basedir).setLanguage(SonarLanguage.PHP).build();\n    inputFileCache.doAdd(inputFile);\n    inputFileCache.doAdd(new TestInputFileBuilder(\"src/Bar.java\").setBaseDir(basedir).setLanguage(SonarLanguage.JAVA).build());\n    inputFileCache.doAdd(new TestInputFileBuilder(\"src/Baz.java\").setBaseDir(basedir).setLanguage(SonarLanguage.JAVA).build());\n\n    // no language\n    inputFileCache.doAdd(new TestInputFileBuilder(\"src/readme.txt\").setBaseDir(basedir).build());\n\n    // needed for CFamily\n    assertThat(fs.inputFile(fs.predicates().is(inputFile.file()))).isNotNull();\n\n    assertThat(fs.inputFile(fs.predicates().hasURI(new File(basedir.toFile(), \"src/Bar.java\").toURI()))).isNotNull();\n    assertThat(fs.inputFile(fs.predicates().hasURI(new File(basedir.toFile(), \"does/not/exist\").toURI()))).isNull();\n    assertThat(fs.inputFile(fs.predicates().hasURI(new File(basedir.toFile(), \"../src/Bar.java\").toURI()))).isNull();\n\n    assertThat(fs.files(fs.predicates().all())).hasSize(4);\n    assertThat(fs.files(fs.predicates().hasLanguage(\"java\"))).hasSize(2);\n    assertThat(fs.files(fs.predicates().hasLanguage(\"cobol\"))).isEmpty();\n\n    assertThat(fs.hasFiles(fs.predicates().all())).isTrue();\n    assertThat(fs.hasFiles(fs.predicates().hasLanguage(\"java\"))).isTrue();\n    assertThat(fs.hasFiles(fs.predicates().hasLanguage(\"cobol\"))).isFalse();\n\n    assertThat(fs.inputFiles(fs.predicates().all())).hasSize(4);\n    assertThat(fs.inputFiles(fs.predicates().hasLanguage(\"php\"))).hasSize(1);\n    assertThat(fs.inputFiles(fs.predicates().hasLanguage(\"java\"))).hasSize(2);\n    assertThat(fs.inputFiles(fs.predicates().hasLanguage(\"cobol\"))).isEmpty();\n\n    assertThat(fs.languages()).containsOnly(\"java\", \"php\");\n  }\n\n  @Test\n  void input_file_returns_null_if_file_not_found() {\n    assertThat(fs.inputFile(fs.predicates().hasLanguage(\"cobol\"))).isNull();\n  }\n\n  @Test\n  void input_file_fails_if_too_many_results() {\n    inputFileCache.doAdd(new TestInputFileBuilder(\"src/Bar.java\").setLanguage(SonarLanguage.JAVA).build());\n    inputFileCache.doAdd(new TestInputFileBuilder(\"src/Baz.java\").setLanguage(SonarLanguage.JAVA).build());\n\n    var thrown = assertThrows(IllegalArgumentException.class, () -> fs.inputFile(fs.predicates().all()));\n    assertThat(thrown).hasMessageStartingWith(\"expected one element\");\n  }\n\n  @Test\n  void input_file_supports_non_indexed_predicates() {\n    inputFileCache.doAdd(new TestInputFileBuilder(\"src/Bar.java\").setLanguage(SonarLanguage.JAVA).build());\n\n    // it would fail if more than one java file\n    assertThat(fs.inputFile(fs.predicates().hasLanguage(\"java\"))).isNotNull();\n  }\n\n  @Test\n  void unsupported_resolve_path() {\n    assertThrows(UnsupportedOperationException.class, () -> fs.resolvePath(\"foo\"));\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/SonarLintInputDirTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.sonar.api.utils.PathUtils;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\n\nclass SonarLintInputDirTests {\n  private SonarLintInputDir inputDir;\n  private Path path;\n\n  @BeforeEach\n  void setUp() {\n    path = Paths.get(\"file1\").toAbsolutePath();\n    inputDir = new SonarLintInputDir(path);\n  }\n\n  @Test\n  void testInputDir() {\n    assertThat(inputDir.absolutePath()).isEqualTo(PathUtils.canonicalPath(path.toFile()));\n    assertThat(inputDir.file()).isEqualTo(path.toFile());\n    assertThat(inputDir.key()).isEqualTo(PathUtils.canonicalPath(path.toFile()));\n    assertThat(inputDir.isFile()).isFalse();\n    assertThat(inputDir.path()).isEqualTo(path);\n    assertThat(inputDir.relativePath()).isEqualTo(inputDir.absolutePath());\n    assertThat(inputDir)\n      .hasToString(\"[path=\" + path + \"]\")\n      .isNotEqualTo(mock(SonarLintInputDir.class))\n      .isEqualTo(inputDir);\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/filesystem/SonarLintInputFileTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem;\n\nimport java.io.BufferedReader;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.net.URI;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.stream.Collectors;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonar.api.batch.fs.InputFile.Status;\nimport testutils.FileUtils;\nimport testutils.InMemoryTestClientInputFile;\nimport testutils.OnDiskTestClientInputFile;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\n\nclass SonarLintInputFileTests {\n\n  @Test\n  void testGetters(@TempDir Path path) throws IOException {\n    var filePath = path.resolve(\"foo.php\");\n    Files.write(filePath, \"test string\".getBytes(StandardCharsets.UTF_8));\n    var inputFile = new OnDiskTestClientInputFile(filePath, \"file\", false, StandardCharsets.UTF_8);\n    var fileInputStream = Files.newInputStream(filePath);\n    var file = new SonarLintInputFile(inputFile, f -> new FileMetadata().readMetadata(fileInputStream, StandardCharsets.UTF_8, filePath.toUri(), null));\n\n    assertThat(file.contents()).isEqualTo(\"test string\");\n    assertThat(file.md5Hash()).isEqualTo(\"6f8db599de986fab7a21625b7916589c\");\n    assertThat(file.charset()).isEqualByComparingTo(StandardCharsets.UTF_8);\n    assertThat(file.absolutePath()).isEqualTo(FileUtils.toSonarQubePath(inputFile.getPath()));\n    assertThat(file.file()).isEqualTo(filePath.toFile());\n    assertThat(file.path()).isEqualTo(filePath);\n    assertThat(file.getClientInputFile()).isEqualTo(inputFile);\n    assertThat(file.status()).isEqualTo(Status.ADDED);\n    assertThat(file)\n      .isEqualTo(file)\n      .isNotEqualTo(mock(SonarLintInputFile.class));\n\n    var stream = file.inputStream();\n    try (var reader = new BufferedReader(new InputStreamReader(stream))) {\n      assertThat(reader.lines().collect(Collectors.joining())).isEqualTo(\"test string\");\n    }\n  }\n\n  @Test\n  void checkValidPointer() {\n    var inputFile = new InMemoryTestClientInputFile(\"foo\", \"src/Foo.php\", null, false, null);\n    var metadata = new FileMetadata.Metadata(2, new int[] {0, 10}, 16);\n    var file = new SonarLintInputFile(inputFile, f -> metadata);\n    assertThat(file.newPointer(1, 0).line()).isEqualTo(1);\n    assertThat(file.newPointer(1, 0).lineOffset()).isZero();\n    // Don't fail\n    file.newPointer(1, 9);\n    file.newPointer(2, 0);\n    file.newPointer(2, 5);\n  }\n\n  @Test\n  void selectLine() {\n    var inputFile = new InMemoryTestClientInputFile(\"foo\", \"src/Foo.php\", null, false, null);\n    var metadata = new FileMetadata().readMetadata(new ByteArrayInputStream(\"bla bla a\\nabcde\\n\\nabc\".getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8,\n      URI.create(\"file://foo.php\"), null);\n    var file = new SonarLintInputFile(inputFile, f -> metadata);\n\n    assertThat(file.selectLine(1).start().line()).isEqualTo(1);\n    assertThat(file.selectLine(1).start().lineOffset()).isZero();\n    assertThat(file.selectLine(1).end().line()).isEqualTo(1);\n    assertThat(file.selectLine(1).end().lineOffset()).isEqualTo(9);\n\n    // Don't fail when selecting empty line\n    assertThat(file.selectLine(3).start().line()).isEqualTo(3);\n    assertThat(file.selectLine(3).start().lineOffset()).isZero();\n    assertThat(file.selectLine(3).end().line()).isEqualTo(3);\n    assertThat(file.selectLine(3).end().lineOffset()).isZero();\n  }\n\n  @Test\n  void testRangeOverlap() {\n    var inputFile = new InMemoryTestClientInputFile(\"foo\", \"src/Foo.php\", null, false, null);\n    var metadata = new FileMetadata.Metadata(2, new int[] {0, 10}, 16);\n    var file = new SonarLintInputFile(inputFile, f -> metadata);\n\n    // Don't fail\n    assertThat(file.newRange(file.newPointer(1, 0), file.newPointer(1, 1)).overlap(file.newRange(file.newPointer(1, 0), file.newPointer(1, 1)))).isTrue();\n    assertThat(file.newRange(file.newPointer(1, 0), file.newPointer(1, 1)).overlap(file.newRange(file.newPointer(1, 0), file.newPointer(1, 2)))).isTrue();\n    assertThat(file.newRange(file.newPointer(1, 0), file.newPointer(1, 1)).overlap(file.newRange(file.newPointer(1, 1), file.newPointer(1, 2)))).isFalse();\n    assertThat(file.newRange(file.newPointer(1, 2), file.newPointer(1, 3)).overlap(file.newRange(file.newPointer(1, 0), file.newPointer(1, 2)))).isFalse();\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/EnforceIssuesFilterTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore;\n\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonar.api.batch.fs.InputComponent;\nimport org.sonar.api.rule.RuleKey;\nimport org.sonar.api.scan.issue.filter.IssueFilterChain;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.FileMetadata.Metadata;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputFile;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputProject;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.pattern.IssueInclusionPatternInitializer;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.pattern.IssuePattern;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.DefaultFilterableIssue;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport testutils.OnDiskTestClientInputFile;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.when;\n\nclass EnforceIssuesFilterTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private IssueInclusionPatternInitializer exclusionPatternInitializer;\n  private EnforceIssuesFilter ignoreFilter;\n  private DefaultFilterableIssue issue;\n  private IssueFilterChain chain;\n\n  @BeforeEach\n  void init() {\n    exclusionPatternInitializer = mock(IssueInclusionPatternInitializer.class);\n    issue = mock(DefaultFilterableIssue.class);\n    chain = mock(IssueFilterChain.class);\n    when(chain.accept(issue)).thenReturn(true);\n  }\n\n  @Test\n  void shouldPassToChainIfNoConfiguredPatterns() {\n    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer);\n    assertThat(ignoreFilter.accept(issue, chain)).isTrue();\n    verify(chain).accept(issue);\n  }\n\n  @Test\n  void shouldPassToChainIfRuleDoesNotMatch() {\n    var rule = \"rule\";\n    var ruleKey = mock(RuleKey.class);\n    when(ruleKey.toString()).thenReturn(rule);\n    when(issue.ruleKey()).thenReturn(ruleKey);\n\n    var matching = mock(IssuePattern.class);\n    when(matching.matchRule(ruleKey)).thenReturn(false);\n    when(exclusionPatternInitializer.getMulticriteriaPatterns()).thenReturn(List.of(matching));\n\n    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer);\n    assertThat(ignoreFilter.accept(issue, chain)).isTrue();\n    verify(chain).accept(issue);\n  }\n\n  @Test\n  void shouldAcceptIssueIfFullyMatched() {\n    var rule = \"rule\";\n    var path = \"org/sonar/api/Issue.java\";\n    var ruleKey = mock(RuleKey.class);\n    when(ruleKey.toString()).thenReturn(rule);\n    when(issue.ruleKey()).thenReturn(ruleKey);\n\n    var matching = mock(IssuePattern.class);\n    when(matching.matchRule(ruleKey)).thenReturn(true);\n    when(matching.matchFile(path)).thenReturn(true);\n    when(exclusionPatternInitializer.getMulticriteriaPatterns()).thenReturn(List.of(matching));\n    when(issue.getComponent()).thenReturn(createComponentWithPath(path));\n\n    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer);\n    assertThat(ignoreFilter.accept(issue, chain)).isTrue();\n    verifyNoInteractions(chain);\n  }\n\n  private InputComponent createComponentWithPath(String path) {\n    return new SonarLintInputFile(new OnDiskTestClientInputFile(Paths.get(path), path, false, StandardCharsets.UTF_8),\n      f -> mock(Metadata.class));\n  }\n\n  @Test\n  void shouldRefuseIssueIfRuleMatchesButNotPath() {\n    var rule = \"rule\";\n    var path = \"org/sonar/api/Issue.java\";\n    var componentKey = \"org.sonar.api.Issue\";\n    var ruleKey = mock(RuleKey.class);\n    when(ruleKey.toString()).thenReturn(rule);\n    when(issue.ruleKey()).thenReturn(ruleKey);\n    when(issue.componentKey()).thenReturn(componentKey);\n\n    var matching = mock(IssuePattern.class);\n    when(matching.matchRule(ruleKey)).thenReturn(true);\n    when(matching.matchFile(path)).thenReturn(false);\n    when(exclusionPatternInitializer.getMulticriteriaPatterns()).thenReturn(List.of(matching));\n    when(issue.getComponent()).thenReturn(createComponentWithPath(path));\n\n    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer);\n    assertThat(ignoreFilter.accept(issue, chain)).isFalse();\n    verifyNoInteractions(chain);\n  }\n\n  @Test\n  void shouldRefuseIssueIfRuleMatchesAndNotFile() {\n    var rule = \"rule\";\n    var path = \"org/sonar/api/Issue.java\";\n    var ruleKey = mock(RuleKey.class);\n    when(ruleKey.toString()).thenReturn(rule);\n    when(issue.ruleKey()).thenReturn(ruleKey);\n\n    var matching = mock(IssuePattern.class);\n    when(matching.matchRule(ruleKey)).thenReturn(true);\n    when(matching.matchFile(path)).thenReturn(true);\n    when(exclusionPatternInitializer.getMulticriteriaPatterns()).thenReturn(List.of(matching));\n    when(issue.getComponent()).thenReturn(new SonarLintInputProject());\n\n    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer);\n    assertThat(ignoreFilter.accept(issue, chain)).isFalse();\n    verifyNoInteractions(chain);\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/IgnoreIssuesFilterTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonar.api.rule.RuleKey;\nimport org.sonar.api.scan.issue.filter.IssueFilterChain;\nimport org.sonar.api.utils.WildcardPattern;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputFile;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.DefaultFilterableIssue;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\n class IgnoreIssuesFilterTests {\n   @RegisterExtension\n   private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private final DefaultFilterableIssue issue = mock(DefaultFilterableIssue.class);\n  private final IssueFilterChain chain = mock(IssueFilterChain.class);\n  private final IgnoreIssuesFilter underTest = new IgnoreIssuesFilter();\n  private SonarLintInputFile component;\n  private final RuleKey ruleKey = RuleKey.of(\"foo\", \"bar\");\n\n  @BeforeEach\n  void prepare() {\n    component = mock(SonarLintInputFile.class);\n    when(issue.getComponent()).thenReturn(component);\n    when(issue.ruleKey()).thenReturn(ruleKey);\n  }\n\n  @Test\n  void shouldPassToChainIfMatcherHasNoPatternForIssue() {\n    when(chain.accept(issue)).thenReturn(true);\n    assertThat(underTest.accept(issue, chain)).isTrue();\n    verify(chain).accept(any());\n  }\n\n  @Test\n  void shouldRejectIfRulePatternMatches() {\n    var pattern = mock(WildcardPattern.class);\n    when(pattern.match(ruleKey.toString())).thenReturn(true);\n    underTest.addRuleExclusionPatternForComponent(component, pattern);\n\n    assertThat(underTest.accept(issue, chain)).isFalse();\n  }\n\n  @Test\n  void shouldAcceptIfRulePatternDoesNotMatch() {\n    var pattern = mock(WildcardPattern.class);\n    when(pattern.match(ruleKey.toString())).thenReturn(false);\n    underTest.addRuleExclusionPatternForComponent(component, pattern);\n\n    assertThat(underTest.accept(issue, chain)).isFalse();\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/pattern/IssueExclusionPatternInitializerTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.pattern;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.plugin.commons.sonarapi.MapSettings;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass IssueExclusionPatternInitializerTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @Test\n  void testNoConfiguration() {\n    var patternsInitializer = new IssueExclusionPatternInitializer(new MapSettings(Map.of()).asConfig());\n    assertThat(patternsInitializer.hasConfiguredPatterns()).isFalse();\n    assertThat(patternsInitializer.getMulticriteriaPatterns()).isEmpty();\n  }\n\n  @Test\n  void shouldLogInvalidResourceKey() {\n    Map<String, String> settings = new HashMap<>();\n    settings.put(\"sonar.issue.ignore\" + \".multicriteria\", \"1\");\n    settings.put(\"sonar.issue.ignore\" + \".multicriteria\" + \".1.\" + \"resourceKey\", \"\");\n    settings.put(\"sonar.issue.ignore\" + \".multicriteria\" + \".1.\" + \"ruleKey\", \"*\");\n    new IssueExclusionPatternInitializer(new MapSettings(settings).asConfig());\n\n    assertThat(logTester.logs()).containsExactly(\"Issue exclusions are misconfigured. File pattern is mandatory for each entry of 'sonar.issue.ignore.multicriteria'\");\n  }\n\n  @Test\n  void shouldLogInvalidRuleKey() {\n    Map<String, String> settings = new HashMap<>();\n    settings.put(\"sonar.issue.ignore\" + \".multicriteria\", \"1\");\n    settings.put(\"sonar.issue.ignore\" + \".multicriteria\" + \".1.\" + \"resourceKey\", \"*\");\n    settings.put(\"sonar.issue.ignore\" + \".multicriteria\" + \".1.\" + \"ruleKey\", \"\");\n    new IssueExclusionPatternInitializer(new MapSettings(settings).asConfig());\n\n    assertThat(logTester.logs()).containsExactly(\"Issue exclusions are misconfigured. Rule key pattern is mandatory for each entry of 'sonar.issue.ignore.multicriteria'\");\n  }\n\n  @Test\n  void shouldReturnBlockPattern() {\n    Map<String, String> settings = new HashMap<>();\n    settings.put(IssueExclusionPatternInitializer.PATTERNS_BLOCK_KEY, \"1,2,3\");\n    settings.put(IssueExclusionPatternInitializer.PATTERNS_BLOCK_KEY + \".1.\" + IssueExclusionPatternInitializer.BEGIN_BLOCK_REGEXP, \"// SONAR-OFF\");\n    settings.put(IssueExclusionPatternInitializer.PATTERNS_BLOCK_KEY + \".1.\" + IssueExclusionPatternInitializer.END_BLOCK_REGEXP, \"// SONAR-ON\");\n    settings.put(IssueExclusionPatternInitializer.PATTERNS_BLOCK_KEY + \".2.\" + IssueExclusionPatternInitializer.BEGIN_BLOCK_REGEXP, \"// FOO-OFF\");\n    settings.put(IssueExclusionPatternInitializer.PATTERNS_BLOCK_KEY + \".2.\" + IssueExclusionPatternInitializer.END_BLOCK_REGEXP, \"// FOO-ON\");\n    settings.put(IssueExclusionPatternInitializer.PATTERNS_BLOCK_KEY + \".3.\" + IssueExclusionPatternInitializer.BEGIN_BLOCK_REGEXP, \"// IGNORE-TO-EOF\");\n    settings.put(IssueExclusionPatternInitializer.PATTERNS_BLOCK_KEY + \".3.\" + IssueExclusionPatternInitializer.END_BLOCK_REGEXP, \"\");\n    var patternsInitializer = new IssueExclusionPatternInitializer(new MapSettings(settings).asConfig());\n\n    assertThat(patternsInitializer.hasConfiguredPatterns()).isTrue();\n    assertThat(patternsInitializer.hasFileContentPattern()).isTrue();\n    assertThat(patternsInitializer.hasMulticriteriaPatterns()).isFalse();\n    assertThat(patternsInitializer.getMulticriteriaPatterns()).isEmpty();\n    assertThat(patternsInitializer.getBlockPatterns()).hasSize(3);\n    assertThat(patternsInitializer.getAllFilePatterns()).isEmpty();\n  }\n\n  @Test\n  void shouldLogInvalidStartBlockPattern() {\n    Map<String, String> settings = new HashMap<>();\n    settings.put(IssueExclusionPatternInitializer.PATTERNS_BLOCK_KEY, \"1\");\n    settings.put(IssueExclusionPatternInitializer.PATTERNS_BLOCK_KEY + \".1.\" + IssueExclusionPatternInitializer.BEGIN_BLOCK_REGEXP, \"\");\n    settings.put(IssueExclusionPatternInitializer.PATTERNS_BLOCK_KEY + \".1.\" + IssueExclusionPatternInitializer.END_BLOCK_REGEXP, \"// SONAR-ON\");\n    new IssueExclusionPatternInitializer(new MapSettings(settings).asConfig());\n\n    assertThat(logTester.logs()).containsExactly(\"Issue exclusions are misconfigured. Start block regexp is mandatory for each entry of 'sonar.issue.ignore.block'\");\n  }\n\n  @Test\n  void shouldReturnAllFilePattern() {\n    Map<String, String> settings = new HashMap<>();\n    settings.put(IssueExclusionPatternInitializer.PATTERNS_ALLFILE_KEY, \"1,2\");\n    settings.put(IssueExclusionPatternInitializer.PATTERNS_ALLFILE_KEY + \".1.\" + IssueExclusionPatternInitializer.FILE_REGEXP, \"@SONAR-IGNORE-ALL\");\n    settings.put(IssueExclusionPatternInitializer.PATTERNS_ALLFILE_KEY + \".2.\" + IssueExclusionPatternInitializer.FILE_REGEXP, \"//FOO-IGNORE-ALL\");\n    var patternsInitializer = new IssueExclusionPatternInitializer(new MapSettings(settings).asConfig());\n\n    assertThat(patternsInitializer.hasConfiguredPatterns()).isTrue();\n    assertThat(patternsInitializer.hasFileContentPattern()).isTrue();\n    assertThat(patternsInitializer.hasMulticriteriaPatterns()).isFalse();\n    assertThat(patternsInitializer.getMulticriteriaPatterns()).isEmpty();\n    assertThat(patternsInitializer.getBlockPatterns()).isEmpty();\n    assertThat(patternsInitializer.getAllFilePatterns()).hasSize(2);\n  }\n\n  @Test\n  void shouldLogInvalidAllFilePattern() {\n    Map<String, String> settings = new HashMap<>();\n    settings.put(IssueExclusionPatternInitializer.PATTERNS_ALLFILE_KEY, \"1\");\n    settings.put(IssueExclusionPatternInitializer.PATTERNS_ALLFILE_KEY + \".1.\" + IssueExclusionPatternInitializer.FILE_REGEXP, \"\");\n    new IssueExclusionPatternInitializer(new MapSettings(settings).asConfig());\n\n    assertThat(logTester.logs()).containsExactly(\"Issue exclusions are misconfigured. Remove blank entries from 'sonar.issue.ignore.allfile'\");\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/pattern/IssueInclusionPatternInitializerTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.pattern;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.plugin.commons.sonarapi.MapSettings;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass IssueInclusionPatternInitializerTests {\n\n  @Test\n  void testNoConfiguration() {\n    var patternsInitializer = new IssueInclusionPatternInitializer(new MapSettings(Collections.emptyMap()).asConfig());\n    patternsInitializer.initPatterns();\n    assertThat(patternsInitializer.hasConfiguredPatterns()).isFalse();\n  }\n\n  @Test\n  void shouldHavePatternsBasedOnMulticriteriaPattern() {\n    Map<String, String> settings = new HashMap<>();\n    settings.put(\"sonar.issue.enforce\" + \".multicriteria\", \"1,2\");\n    settings.put(\"sonar.issue.enforce\" + \".multicriteria\" + \".1.\" + \"resourceKey\", \"org/foo/Bar.java\");\n    settings.put(\"sonar.issue.enforce\" + \".multicriteria\" + \".1.\" + \"ruleKey\", \"*\");\n    settings.put(\"sonar.issue.enforce\" + \".multicriteria\" + \".2.\" + \"resourceKey\", \"org/foo/Hello.java\");\n    settings.put(\"sonar.issue.enforce\" + \".multicriteria\" + \".2.\" + \"ruleKey\", \"checkstyle:MagicNumber\");\n    var patternsInitializer = new IssueInclusionPatternInitializer(new MapSettings(settings).asConfig());\n    patternsInitializer.initPatterns();\n\n    assertThat(patternsInitializer.hasConfiguredPatterns()).isTrue();\n    assertThat(patternsInitializer.hasMulticriteriaPatterns()).isTrue();\n    assertThat(patternsInitializer.getMulticriteriaPatterns()).hasSize(2);\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/pattern/IssuePatternTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.pattern;\n\nimport org.junit.jupiter.api.Test;\nimport org.sonar.api.rules.Rule;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass IssuePatternTests {\n\n  @Test\n  void shouldMatchJavaFile() {\n    var javaFile = \"org/foo/Bar.java\";\n    assertThat(new IssuePattern(\"org/foo/Bar.java\", \"*\").matchFile(javaFile)).isTrue();\n    assertThat(new IssuePattern(\"org/foo/*\", \"*\").matchFile(javaFile)).isTrue();\n    assertThat(new IssuePattern(\"**Bar.java\", \"*\").matchFile(javaFile)).isTrue();\n    assertThat(new IssuePattern(\"**\", \"*\").matchFile(javaFile)).isTrue();\n    assertThat(new IssuePattern(\"org/*/?ar.java\", \"*\").matchFile(javaFile)).isTrue();\n\n    assertThat(new IssuePattern(\"org/other/Hello.java\", \"*\").matchFile(javaFile)).isFalse();\n    assertThat(new IssuePattern(\"org/foo/Hello.java\", \"*\").matchFile(javaFile)).isFalse();\n    assertThat(new IssuePattern(\"org/*/??ar.java\", \"*\").matchFile(javaFile)).isFalse();\n    assertThat(new IssuePattern(\"org/*/??ar.java\", \"*\").matchFile(null)).isFalse();\n    assertThat(new IssuePattern(\"org/*/??ar.java\", \"*\").matchFile(\"plop\")).isFalse();\n  }\n\n  @Test\n  void shouldMatchRule() {\n    var rule = Rule.create(\"checkstyle\", \"IllegalRegexp\", \"\").ruleKey();\n    assertThat(new IssuePattern(\"*\", \"*\").matchRule(rule)).isTrue();\n    assertThat(new IssuePattern(\"*\", \"checkstyle:*\").matchRule(rule)).isTrue();\n    assertThat(new IssuePattern(\"*\", \"checkstyle:IllegalRegexp\").matchRule(rule)).isTrue();\n    assertThat(new IssuePattern(\"*\", \"checkstyle:Illegal*\").matchRule(rule)).isTrue();\n    assertThat(new IssuePattern(\"*\", \"*:*Illegal*\").matchRule(rule)).isTrue();\n\n    assertThat(new IssuePattern(\"*\", \"pmd:IllegalRegexp\").matchRule(rule)).isFalse();\n    assertThat(new IssuePattern(\"*\", \"pmd:*\").matchRule(rule)).isFalse();\n    assertThat(new IssuePattern(\"*\", \"*:Foo*IllegalRegexp\").matchRule(rule)).isFalse();\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/scanner/IssueExclusionsLoaderTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.scanner;\n\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.FileMetadata.Metadata;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputFile;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.IgnoreIssuesFilter;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.pattern.IssueExclusionPatternInitializer;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.pattern.IssuePattern;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport testutils.OnDiskTestClientInputFile;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\nimport static org.mockito.Mockito.when;\n\nclass IssueExclusionsLoaderTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private IssueExclusionPatternInitializer exclusionPatternInitializer;\n  private IgnoreIssuesFilter ignoreIssuesFilter;\n\n  private IssueExclusionsLoader scanner;\n\n  @BeforeEach\n  void before() {\n    exclusionPatternInitializer = mock(IssueExclusionPatternInitializer.class);\n    ignoreIssuesFilter = mock(IgnoreIssuesFilter.class);\n    scanner = new IssueExclusionsLoader(exclusionPatternInitializer, ignoreIssuesFilter);\n  }\n\n  private SonarLintInputFile createFile(String path) {\n    return new SonarLintInputFile(new OnDiskTestClientInputFile(Paths.get(path), path, false, StandardCharsets.UTF_8), f -> mock(Metadata.class));\n  }\n\n  @Test\n  void testToString() {\n    assertThat(scanner).hasToString(\"Issues Exclusions - Source Scanner\");\n  }\n\n  @Test\n  void createComputer() {\n\n    assertThat(scanner.createCharHandlerFor(createFile(\"src/main/java/Foo.java\"))).isNull();\n\n    when(exclusionPatternInitializer.getAllFilePatterns()).thenReturn(Collections.singletonList(\"pattern\"));\n    scanner = new IssueExclusionsLoader(exclusionPatternInitializer, ignoreIssuesFilter);\n    assertThat(scanner.createCharHandlerFor(createFile(\"src/main/java/Foo.java\"))).isNotNull();\n\n  }\n\n  @Test\n  void populateRuleExclusionPatterns() {\n    var pattern1 = new IssuePattern(\"org/foo/Bar*.java\", \"*\");\n    var pattern2 = new IssuePattern(\"org/foo/Hell?.java\", \"checkstyle:MagicNumber\");\n    when(exclusionPatternInitializer.getMulticriteriaPatterns()).thenReturn(Arrays.asList(pattern1, pattern2));\n\n    var loader = new IssueExclusionsLoader(exclusionPatternInitializer, ignoreIssuesFilter);\n    var file1 = createFile(\"org/foo/Bar.java\");\n    loader.addMulticriteriaPatterns(file1);\n    var file2 = createFile(\"org/foo/Baz.java\");\n    loader.addMulticriteriaPatterns(file2);\n    var file3 = createFile(\"org/foo/Hello.java\");\n    loader.addMulticriteriaPatterns(file3);\n\n    verify(ignoreIssuesFilter).addRuleExclusionPatternForComponent(file1, pattern1.getRulePattern());\n    verify(ignoreIssuesFilter).addRuleExclusionPatternForComponent(file3, pattern2.getRulePattern());\n    verifyNoMoreInteractions(ignoreIssuesFilter);\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/scanner/IssueExclusionsRegexpScannerTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.scanner;\n\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.regex.Pattern;\nimport java.util.stream.IntStream;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.FileMetadata;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.FileMetadata.Metadata;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputFile;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.scanner.IssueExclusionsLoader.DoubleRegexpMatcher;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport testutils.OnDiskTestClientInputFile;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThat;\nimport static org.mockito.Mockito.mock;\n\nclass IssueExclusionsRegexpScannerTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  private SonarLintInputFile javaFile;\n\n  private List<Pattern> allFilePatterns;\n  private List<DoubleRegexpMatcher> blockPatterns;\n  private IssueExclusionsRegexpScanner regexpScanner;\n  private final FileMetadata fileMetadata = new FileMetadata();\n\n  @BeforeEach\n  void init() {\n    blockPatterns = Arrays.asList(new DoubleRegexpMatcher(Pattern.compile(\"// SONAR-OFF\"), Pattern.compile(\"// SONAR-ON\")),\n      new DoubleRegexpMatcher(Pattern.compile(\"// FOO-OFF\"), Pattern.compile(\"// FOO-ON\")));\n    allFilePatterns = Collections.singletonList(Pattern.compile(\"@SONAR-IGNORE-ALL\"));\n\n    javaFile = new SonarLintInputFile(new OnDiskTestClientInputFile(Paths.get(\"src/Foo.java\"), \"src/Foo.java\", false, StandardCharsets.UTF_8), f -> mock(Metadata.class));\n    regexpScanner = new IssueExclusionsRegexpScanner(javaFile, allFilePatterns, blockPatterns);\n  }\n\n  @Test\n  void shouldDetectPatternLastLine() throws URISyntaxException, IOException {\n    var filePath = getResource(\"file-with-single-regexp-last-line.txt\");\n    fileMetadata.readMetadata(Files.newInputStream(filePath), UTF_8, filePath.toUri(), regexpScanner);\n\n    assertThat(javaFile.isIgnoreAllIssues()).isTrue();\n  }\n\n  @Test\n  void shouldDoNothing() throws Exception {\n    var filePath = getResource(\"file-with-no-regexp.txt\");\n    fileMetadata.readMetadata(Files.newInputStream(filePath), UTF_8, filePath.toUri(), regexpScanner);\n\n    assertThat(javaFile.isIgnoreAllIssues()).isFalse();\n  }\n\n  @Test\n  void shouldExcludeAllIssues() throws Exception {\n    var filePath = getResource(\"file-with-single-regexp.txt\");\n    fileMetadata.readMetadata(Files.newInputStream(filePath), UTF_8, filePath.toUri(), regexpScanner);\n\n    assertThat(javaFile.isIgnoreAllIssues()).isTrue();\n  }\n\n  @Test\n  void shouldExcludeAllIssuesEvenIfAlsoDoubleRegexps() throws Exception {\n    var filePath = getResource(\"file-with-single-regexp-and-double-regexp.txt\");\n    fileMetadata.readMetadata(Files.newInputStream(filePath), UTF_8, filePath.toUri(), regexpScanner);\n\n    assertThat(javaFile.isIgnoreAllIssues()).isTrue();\n  }\n\n  @Test\n  void shouldExcludeLines() throws Exception {\n    var filePath = getResource(\"file-with-double-regexp.txt\");\n    fileMetadata.readMetadata(Files.newInputStream(filePath), UTF_8, filePath.toUri(), regexpScanner);\n\n    assertThat(javaFile.isIgnoreAllIssues()).isFalse();\n    assertThat(IntStream.rangeClosed(1, 20).noneMatch(javaFile::isIgnoreAllIssuesOnLine)).isTrue();\n    assertThat(IntStream.rangeClosed(21, 25).allMatch(javaFile::isIgnoreAllIssuesOnLine)).isTrue();\n    assertThat(IntStream.rangeClosed(26, 34).noneMatch(javaFile::isIgnoreAllIssuesOnLine)).isTrue();\n  }\n\n  @Test\n  void shouldAddPatternToExcludeLinesTillTheEnd() throws Exception {\n    var filePath = getResource(\"file-with-double-regexp-unfinished.txt\");\n    fileMetadata.readMetadata(Files.newInputStream(filePath), UTF_8, filePath.toUri(), regexpScanner);\n\n    assertThat(javaFile.isIgnoreAllIssues()).isFalse();\n    assertThat(IntStream.rangeClosed(1, 20).noneMatch(javaFile::isIgnoreAllIssuesOnLine)).isTrue();\n    assertThat(IntStream.rangeClosed(21, 34).allMatch(javaFile::isIgnoreAllIssuesOnLine)).isTrue();\n  }\n\n  @Test\n  void shouldAddPatternToExcludeSeveralLineRanges() throws Exception {\n    var filePath = getResource(\"file-with-double-regexp-twice.txt\");\n    fileMetadata.readMetadata(Files.newInputStream(filePath), UTF_8, filePath.toUri(), regexpScanner);\n\n    assertThat(javaFile.isIgnoreAllIssues()).isFalse();\n    assertThat(IntStream.rangeClosed(1, 20).noneMatch(javaFile::isIgnoreAllIssuesOnLine)).isTrue();\n    assertThat(IntStream.rangeClosed(21, 25).allMatch(javaFile::isIgnoreAllIssuesOnLine)).isTrue();\n    assertThat(IntStream.rangeClosed(26, 28).noneMatch(javaFile::isIgnoreAllIssuesOnLine)).isTrue();\n    assertThat(IntStream.rangeClosed(29, 33).allMatch(javaFile::isIgnoreAllIssuesOnLine)).isTrue();\n  }\n\n  @Test\n  void shouldAddPatternToExcludeLinesWithWrongOrder() throws Exception {\n    var filePath = getResource(\"file-with-double-regexp-wrong-order.txt\");\n    fileMetadata.readMetadata(Files.newInputStream(filePath), UTF_8, filePath.toUri(), regexpScanner);\n\n    assertThat(IntStream.rangeClosed(1, 24).noneMatch(javaFile::isIgnoreAllIssuesOnLine)).isTrue();\n    assertThat(IntStream.rangeClosed(25, 35).allMatch(javaFile::isIgnoreAllIssuesOnLine)).isTrue();\n  }\n\n  @Test\n  void shouldAddPatternToExcludeLinesWithMess() throws Exception {\n    var filePath = getResource(\"file-with-double-regexp-mess.txt\");\n    fileMetadata.readMetadata(Files.newInputStream(filePath), UTF_8, filePath.toUri(), regexpScanner);\n\n    assertThat(IntStream.rangeClosed(1, 20).noneMatch(javaFile::isIgnoreAllIssuesOnLine)).isTrue();\n    assertThat(IntStream.rangeClosed(21, 29).allMatch(javaFile::isIgnoreAllIssuesOnLine)).isTrue();\n    assertThat(IntStream.rangeClosed(30, 37).noneMatch(javaFile::isIgnoreAllIssuesOnLine)).isTrue();\n  }\n\n  private Path getResource(String fileName) throws URISyntaxException {\n    return Paths.get(this.getClass().getResource(\"/IssueExclusionsRegexpScannerTests/\" + fileName).toURI());\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/issue/ignore/scanner/LineRangeTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.issue.ignore.scanner;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass LineRangeTests {\n\n  @Test\n  void lineRangeShouldBeOrdered() {\n    assertThrows(IllegalArgumentException.class, () -> new LineRange(25, 12));\n  }\n\n  @Test\n  void shouldConvertLineRangeToLines() {\n    var range = new LineRange(12, 15);\n\n    assertThat(range.toLines()).containsOnly(12, 13, 14, 15);\n  }\n\n  @Test\n  void shouldTestInclusionInRangeOfLines() {\n    var range = new LineRange(12, 15);\n\n    assertThat(range.in(3)).isFalse();\n    assertThat(range.in(12)).isTrue();\n    assertThat(range.in(13)).isTrue();\n    assertThat(range.in(14)).isTrue();\n    assertThat(range.in(15)).isTrue();\n    assertThat(range.in(16)).isFalse();\n  }\n\n  @Test\n  void testToString() {\n    assertThat(new LineRange(12, 15)).hasToString(\"[12-15]\");\n  }\n\n  @Test\n  void testEquals() {\n    var range = new LineRange(12, 15);\n    assertThat(range).isEqualTo(range)\n      .isEqualTo(new LineRange(12, 15))\n      .isNotEqualTo(new LineRange(12, 2000))\n      .isNotEqualTo(new LineRange(1000, 2000))\n      .isNotEqualTo(null)\n      .isNotEqualTo(new StringBuffer());\n  }\n\n  @Test\n  void testHashCode() {\n    assertThat(new LineRange(12, 15)).hasSameHashCodeAs(new LineRange(12, 15).hashCode());\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/sensor/SensorOptimizerTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.sensor;\n\nimport java.util.List;\nimport java.util.Map;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonar.api.batch.fs.FileSystem;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.rule.ActiveRule;\nimport org.sonar.api.batch.rule.ActiveRules;\nimport org.sonar.api.rule.RuleKey;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisConfiguration;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.InputFileIndex;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintFileSystem;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.ActiveRulesAdapter;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.DefaultSensorDescriptor;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.plugin.commons.sonarapi.MapSettings;\nimport testutils.TestInputFileBuilder;\n\nimport static java.util.Arrays.asList;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass SensorOptimizerTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private FileSystem fs;\n  private SensorOptimizer optimizer;\n  private MapSettings settings;\n  private final InputFileIndex inputFileCache = new InputFileIndex();\n\n  @BeforeEach\n  void prepare() {\n    fs = new SonarLintFileSystem(mock(AnalysisConfiguration.class), inputFileCache);\n    settings = new MapSettings(Map.of());\n    optimizer = new SensorOptimizer(fs, mock(ActiveRules.class), settings.asConfig());\n  }\n\n  @Test\n  void should_run_analyzer_with_no_metadata() {\n    var descriptor = new DefaultSensorDescriptor();\n\n    assertThat(optimizer.shouldExecute(descriptor)).isTrue();\n  }\n\n  @Test\n  void should_optimize_on_language() {\n    var descriptor = new DefaultSensorDescriptor()\n      .onlyOnLanguages(\"java\", \"php\");\n    assertThat(optimizer.shouldExecute(descriptor)).isFalse();\n\n    inputFileCache.doAdd(new TestInputFileBuilder(\"src/Foo.java\").setLanguage(SonarLanguage.JAVA).build());\n    assertThat(optimizer.shouldExecute(descriptor)).isTrue();\n  }\n\n  @Test\n  void should_optimize_on_type() {\n    var descriptor = new DefaultSensorDescriptor()\n      .onlyOnFileType(InputFile.Type.MAIN);\n    assertThat(optimizer.shouldExecute(descriptor)).isFalse();\n\n    inputFileCache.doAdd(new TestInputFileBuilder(\"tests/FooTest.java\").setType(InputFile.Type.TEST).build());\n    assertThat(optimizer.shouldExecute(descriptor)).isFalse();\n\n    inputFileCache.doAdd(new TestInputFileBuilder(\"src/Foo.java\").setType(InputFile.Type.MAIN).build());\n    assertThat(optimizer.shouldExecute(descriptor)).isTrue();\n  }\n\n  @Test\n  void should_optimize_on_both_type_and_language() {\n    var descriptor = new DefaultSensorDescriptor()\n      .onlyOnLanguages(\"java\", \"php\")\n      .onlyOnFileType(InputFile.Type.MAIN);\n    assertThat(optimizer.shouldExecute(descriptor)).isFalse();\n\n    inputFileCache.doAdd(new TestInputFileBuilder(\"tests/FooTest.java\").setLanguage(SonarLanguage.JAVA).setType(InputFile.Type.TEST).build());\n    inputFileCache.doAdd(new TestInputFileBuilder(\"src/Foo.cbl\").setLanguage(SonarLanguage.COBOL).setType(InputFile.Type.MAIN).build());\n    assertThat(optimizer.shouldExecute(descriptor)).isFalse();\n\n    inputFileCache.doAdd(new TestInputFileBuilder(\"src/Foo.java\").setLanguage(SonarLanguage.JAVA).setType(InputFile.Type.MAIN).build());\n    assertThat(optimizer.shouldExecute(descriptor)).isTrue();\n  }\n\n  @Test\n  void should_optimize_on_repository() {\n    var descriptor = new DefaultSensorDescriptor()\n      .createIssuesForRuleRepositories(\"squid\");\n    assertThat(optimizer.shouldExecute(descriptor)).isFalse();\n\n    var ruleAnotherRepo = mock(ActiveRule.class);\n    when(ruleAnotherRepo.ruleKey()).thenReturn(RuleKey.of(\"repo1\", \"foo\"));\n    ActiveRules activeRules = new ActiveRulesAdapter(List.of(ruleAnotherRepo));\n    optimizer = new SensorOptimizer(fs, activeRules, settings.asConfig());\n\n    assertThat(optimizer.shouldExecute(descriptor)).isFalse();\n\n    var ruleSquid = mock(ActiveRule.class);\n    when(ruleSquid.ruleKey()).thenReturn(RuleKey.of(\"squid\", \"rule\"));\n\n    activeRules = new ActiveRulesAdapter(asList(ruleSquid, ruleAnotherRepo));\n\n    optimizer = new SensorOptimizer(fs, activeRules, settings.asConfig());\n\n    assertThat(optimizer.shouldExecute(descriptor)).isTrue();\n  }\n\n  @Test\n  void should_optimize_on_settings() {\n    var descriptor = new DefaultSensorDescriptor().onlyWhenConfiguration(c -> c.hasKey(\"sonar.foo.reportPath\"));\n    assertThat(optimizer.shouldExecute(descriptor)).isFalse();\n\n    settings = new MapSettings(Map.of(\"sonar.foo.reportPath\", \"foo\"));\n    optimizer = new SensorOptimizer(fs, mock(ActiveRules.class), settings.asConfig());\n    assertThat(optimizer.shouldExecute(descriptor)).isTrue();\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/sensor/SensorsExecutorTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.sensor;\n\nimport java.util.List;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonar.api.batch.sensor.Sensor;\nimport org.sonar.api.batch.sensor.SensorContext;\nimport org.sonar.api.batch.sensor.SensorDescriptor;\nimport org.sonar.api.scanner.sensor.ProjectSensor;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.DefaultSensorContext;\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.ProgressIndicator;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass SensorsExecutorTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  public static final DefaultSensorContext DEFAULT_SENSOR_CONTEXT = new DefaultSensorContext(null, null, null, null, null, null, null, new ProgressIndicator() {\n    @Override\n    public void notifyProgress(@Nullable String message, @Nullable Integer percentage) {\n      // no-op\n    }\n\n    @Override\n    public boolean isCanceled() {\n      return false;\n    }\n  });\n\n  private static class MyClass {\n    @Override\n    public String toString() {\n      return null;\n    }\n  }\n\n  @Test\n  void testDescribe() {\n    Object withToString = new Object() {\n      @Override\n      public String toString() {\n        return \"desc\";\n      }\n    };\n\n    var withoutToString = new Object();\n\n    assertThat(SensorsExecutor.describe(withToString)).isEqualTo((\"desc\"));\n    assertThat(SensorsExecutor.describe(withoutToString)).isEqualTo(\"java.lang.Object\");\n    assertThat(SensorsExecutor.describe(new MyClass())).endsWith(\"MyClass\");\n  }\n\n  @Test\n  void testThrowingSensorShouldBeLogged() {\n    var sensorOptimizer = mock(SensorOptimizer.class);\n    when(sensorOptimizer.shouldExecute(any())).thenReturn(true);\n    var executor = new SensorsExecutor(DEFAULT_SENSOR_CONTEXT, sensorOptimizer, Optional.empty(), Optional.of(List.of(new ThrowingSensor())));\n\n    executor.execute();\n\n    assertThat(logTester.logs(LogOutput.Level.ERROR)).contains(\"Error executing sensor: 'Throwing sensor'\");\n  }\n\n  @Test\n  void shouldRunGlobalSensorLast() {\n    var sensorOptimizer = mock(SensorOptimizer.class);\n    when(sensorOptimizer.shouldExecute(any())).thenReturn(true);\n\n    var regularSensor = new RegularSensor();\n    var globalSensor = new GlobalSensor();\n    var oldGlobalSensor = new OldGlobalSensor();\n\n    var executor = new SensorsExecutor(DEFAULT_SENSOR_CONTEXT, sensorOptimizer, Optional.empty(), Optional.of(List.of(globalSensor, regularSensor, oldGlobalSensor)));\n\n    executor.execute();\n\n    assertThat(logTester.logs(LogOutput.Level.INFO)).containsExactly(\"Executing 'Regular sensor'\", \"Executing 'Global sensor'\", \"Executing 'Old Global sensor'\");\n  }\n\n  private static class ThrowingSensor implements Sensor {\n    @Override\n    public void describe(SensorDescriptor descriptor) {\n      descriptor.name(\"Throwing sensor\");\n    }\n\n    @Override\n    public void execute(SensorContext context) {\n      throw new Error();\n    }\n  }\n\n  private static class RegularSensor implements Sensor {\n    @Override\n    public void describe(SensorDescriptor descriptor) {\n      descriptor.name(\"Regular sensor\");\n    }\n\n    @Override\n    public void execute(SensorContext context) {\n      SonarLintLogger.get().info(\"Executing 'Regular sensor'\");\n    }\n  }\n\n  private static class GlobalSensor implements ProjectSensor {\n\n    @Override\n    public void describe(SensorDescriptor descriptor) {\n      descriptor.name(\"Global sensor\");\n    }\n\n    @Override\n    public void execute(SensorContext context) {\n      SonarLintLogger.get().info(\"Executing 'Global sensor'\");\n    }\n  }\n\n  private static class OldGlobalSensor implements Sensor {\n    @Override\n    public void describe(SensorDescriptor descriptor) {\n      descriptor.name(\"Old Global sensor\").global();\n    }\n\n    @Override\n    public void execute(SensorContext context) {\n      SonarLintLogger.get().info(\"Executing 'Old Global sensor'\");\n    }\n  }\n\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/analysis/sensor/SonarLintSensorStorageTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.analysis.sensor;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.sonar.api.batch.rule.ActiveRules;\nimport org.sonar.api.batch.sensor.code.NewSignificantCode;\nimport org.sonar.api.batch.sensor.coverage.NewCoverage;\nimport org.sonar.api.batch.sensor.cpd.NewCpdTokens;\nimport org.sonar.api.batch.sensor.error.AnalysisError;\nimport org.sonar.api.batch.sensor.highlighting.NewHighlighting;\nimport org.sonar.api.batch.sensor.issue.ExternalIssue;\nimport org.sonar.api.batch.sensor.issue.Issue;\nimport org.sonar.api.batch.sensor.measure.Measure;\nimport org.sonar.api.batch.sensor.rule.AdHocRule;\nimport org.sonar.api.batch.sensor.symbol.NewSymbolTable;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisResults;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.IssueListenerHolder;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputFile;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.issue.IssueFilters;\n\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.when;\n\n@ExtendWith(MockitoExtension.class)\nclass SonarLintSensorStorageTests {\n\n  @Mock\n  private ActiveRules activeRules;\n  @Mock\n  private IssueFilters filters;\n  @Mock\n  private IssueListenerHolder issueListener;\n  @Mock\n  private AnalysisResults analysisResult;\n  @Mock\n  private SonarLintInputFile inputFile;\n  @Mock\n  private ClientInputFile clientInputFile;\n\n  private SonarLintSensorStorage underTest;\n\n  @BeforeEach\n  void setUp() {\n    underTest = new SonarLintSensorStorage(activeRules, filters, issueListener, analysisResult);\n  }\n\n  @Test\n  void store_Measure_doesnt_interact_with_its_param() {\n    var measure = mock(Measure.class);\n    underTest.store(measure);\n    verifyNoInteractions(measure);\n  }\n\n  @Test\n  void store_ExternalIssue_doesnt_interact_with_its_param() {\n    var externalIssue = mock(ExternalIssue.class);\n    underTest.store(externalIssue);\n    verifyNoInteractions(externalIssue);\n  }\n\n  @Test\n  void store_DefaultSignificantCode_doesnt_interact_with_its_param() {\n    var significantCode = mock(NewSignificantCode.class);\n    underTest.store(significantCode);\n    verifyNoInteractions(significantCode);\n  }\n\n  @Test\n  void store_DefaultHighlighting_doesnt_interact_with_its_param() {\n    var highlighting = mock(NewHighlighting.class);\n    underTest.store(highlighting);\n    verifyNoInteractions(highlighting);\n  }\n\n  @Test\n  void store_DefaultCoverage_doesnt_interact_with_its_param() {\n    var coverage = mock(NewCoverage.class);\n    underTest.store(coverage);\n    verifyNoInteractions(coverage);\n  }\n\n  @Test\n  void store_DefaultCpdTokens_doesnt_interact_with_its_param() {\n    var cpdTokens = mock(NewCpdTokens.class);\n    underTest.store(cpdTokens);\n    verifyNoInteractions(cpdTokens);\n  }\n\n  @Test\n  void store_DefaultSymbolTable_doesnt_interact_with_its_param() {\n    var symbolTable = mock(NewSymbolTable.class);\n    underTest.store(symbolTable);\n    verifyNoInteractions(symbolTable);\n  }\n\n  @Test\n  void store_AdHocRule_doesnt_interact_with_its_param() {\n    var adHocRule = mock(AdHocRule.class);\n    underTest.store(adHocRule);\n    verifyNoInteractions(adHocRule);\n  }\n\n  @Test\n  void store_should_throw_exception_for_non_sonarlint_issue() {\n    var issue = mock(Issue.class);\n    \n    assertThatThrownBy(() -> underTest.store(issue))\n      .isInstanceOf(IllegalArgumentException.class)\n      .hasMessage(\"Trying to store a non-SonarLint issue?\");\n  }\n\n  @Test\n  void store_AnalysisError_should_add_failed_analysis_file() {\n    var analysisError = mock(AnalysisError.class);\n    when(analysisError.inputFile()).thenReturn(inputFile);\n    when(inputFile.getClientInputFile()).thenReturn(clientInputFile);\n    \n    underTest.store(analysisError);\n    \n    verify(analysisResult).addFailedAnalysisFile(clientInputFile);\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/global/AnalysisExtensionInstallerTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.global;\n\nimport java.util.Map;\nimport java.util.Set;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonar.api.Plugin;\nimport org.sonar.api.batch.sensor.Sensor;\nimport org.sonar.api.batch.sensor.SensorContext;\nimport org.sonar.api.batch.sensor.SensorDescriptor;\nimport org.sonar.api.config.Configuration;\nimport org.sonar.api.utils.Version;\nimport org.sonarsource.api.sonarlint.SonarLintSide;\nimport org.sonarsource.sonarlint.core.analysis.container.ContainerLifespan;\nimport org.sonarsource.sonarlint.core.plugin.commons.sonarapi.MapSettings;\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.plugin.commons.LoadedPlugins;\nimport org.sonarsource.sonarlint.core.plugin.commons.container.SpringComponentContainer;\nimport org.sonarsource.sonarlint.core.plugin.commons.sonarapi.SonarLintRuntimeImpl;\nimport org.sonarsource.sonarlint.plugin.api.SonarLintRuntime;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.when;\n\nclass AnalysisExtensionInstallerTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  private static final String FAKE_PLUGIN_KEY = \"foo\";\n  private static final String JAVA_PLUGIN_KEY = \"java\";\n  private static final String DBD_PLUGIN_KEY = \"dbd\";\n\n  private static final Configuration EMPTY_CONFIG = new MapSettings(Map.of()).asConfig();\n  private static final Version PLUGIN_API_VERSION = Version.create(5, 4, 0);\n  private static final long FAKE_PID = 123L;\n  private static final SonarLintRuntime RUNTIME = new SonarLintRuntimeImpl(Version.create(8, 0), PLUGIN_API_VERSION, FAKE_PID);\n  private AnalysisExtensionInstaller underTest;\n  private LoadedPlugins loadedPlugins;\n  private SpringComponentContainer container;\n\n  @BeforeEach\n  void prepare() {\n    loadedPlugins = mock(LoadedPlugins.class);\n    container = mock(SpringComponentContainer.class);\n    underTest = new AnalysisExtensionInstaller(RUNTIME, loadedPlugins, EMPTY_CONFIG);\n  }\n\n  @Test\n  void install_sonarlintside_extensions_with_default_lifespan_in_analysis_container_for_compatible_plugins() {\n    when(loadedPlugins.getAnalysisPluginInstancesByKeys()).thenReturn(Map.of(FAKE_PLUGIN_KEY, new FakePlugin()));\n\n    underTest.install(container, ContainerLifespan.ANALYSIS);\n\n    verify(container).addExtension(FAKE_PLUGIN_KEY, FakeSonarLintDefaultLifespanComponent.class);\n  }\n\n  @Test\n  void install_sonarlintside_extensions_with_single_analysis_lifespan_in_analysis_container_for_compatible_plugins() {\n    when(loadedPlugins.getAnalysisPluginInstancesByKeys()).thenReturn(Map.of(FAKE_PLUGIN_KEY, new FakePlugin(FakeSonarLintSingleAnalysisLifespanComponent.class)));\n\n    underTest.install(container, ContainerLifespan.ANALYSIS);\n\n    verify(container).addExtension(FAKE_PLUGIN_KEY, FakeSonarLintSingleAnalysisLifespanComponent.class);\n  }\n\n  @Test\n  void install_sonarlintside_extensions_with_multiple_analysis_lifespan_in_global_container_for_compatible_plugins() {\n    when(loadedPlugins.getAnalysisPluginInstancesByKeys()).thenReturn(Map.of(FAKE_PLUGIN_KEY, new FakePlugin(FakeSonarLintMultipleAnalysisLifespanComponent.class)));\n\n    underTest.install(container, ContainerLifespan.INSTANCE);\n\n    verify(container).addExtension(FAKE_PLUGIN_KEY, FakeSonarLintMultipleAnalysisLifespanComponent.class);\n  }\n\n  @Test\n  void install_sonarlintside_extensions_with_instance_lifespan_in_global_container_for_compatible_plugins() {\n    when(loadedPlugins.getAnalysisPluginInstancesByKeys()).thenReturn(Map.of(FAKE_PLUGIN_KEY, new FakePlugin(FakeSonarLintInstanceLifespanComponent.class)));\n\n    underTest.install(container, ContainerLifespan.INSTANCE);\n\n    verify(container).addExtension(FAKE_PLUGIN_KEY, FakeSonarLintInstanceLifespanComponent.class);\n  }\n\n  @Test\n  void dont_install_sonarlintside_extensions_with_multiple_analysis_lifespan_in_analysis_container_for_compatible_plugins() {\n    when(loadedPlugins.getAllPluginInstancesByKeys()).thenReturn(Map.of(FAKE_PLUGIN_KEY, new FakePlugin(FakeSonarLintMultipleAnalysisLifespanComponent.class)));\n\n    underTest.install(container, ContainerLifespan.ANALYSIS);\n\n    verifyNoInteractions(container);\n  }\n\n  @Test\n  void dont_install_sonarlintside_extensions_with_single_analysis_lifespan_in_global_container_for_compatible_plugins() {\n    when(loadedPlugins.getAllPluginInstancesByKeys()).thenReturn(Map.of(FAKE_PLUGIN_KEY, new FakePlugin(FakeSonarLintSingleAnalysisLifespanComponent.class)));\n\n    underTest.install(container, ContainerLifespan.INSTANCE);\n\n    verifyNoInteractions(container);\n  }\n\n  @Test\n  void install_sonarlintside_extensions_with_module_lifespan_in_module_container_for_compatible_plugins() {\n    when(loadedPlugins.getAnalysisPluginInstancesByKeys()).thenReturn(Map.of(FAKE_PLUGIN_KEY, new FakePlugin(FakeSonarLintModuleLifespanComponent.class)));\n\n    underTest.install(container, ContainerLifespan.MODULE);\n\n    verify(container).addExtension(FAKE_PLUGIN_KEY, FakeSonarLintModuleLifespanComponent.class);\n  }\n\n  @Test\n  void install_sensors_for_sonarsource_plugins_by_language() {\n    when(loadedPlugins.getAnalysisPluginInstancesByKeys()).thenReturn(Map.of(JAVA_PLUGIN_KEY, new FakePlugin()));\n\n    underTest.install(container, ContainerLifespan.ANALYSIS);\n\n    verify(container).addExtension(JAVA_PLUGIN_KEY, FakeSensor.class);\n  }\n\n  @Test\n  void install_sensors_for_sonarsource_plugins_by_allowlist() {\n    when(loadedPlugins.getAnalysisPluginInstancesByKeys()).thenReturn(Map.of(DBD_PLUGIN_KEY, new FakePlugin()));\n    when(loadedPlugins.getAdditionalAllowedPlugins()).thenReturn(Set.of(DBD_PLUGIN_KEY));\n\n    underTest.install(container, ContainerLifespan.ANALYSIS);\n\n    verify(container).addExtension(DBD_PLUGIN_KEY, FakeSensor.class);\n  }\n\n  @Test\n  void dont_install_sensors_for_non_sonarsource_plugins() {\n    when(loadedPlugins.getAnalysisPluginInstancesByKeys()).thenReturn(Map.of(FAKE_PLUGIN_KEY, new FakePlugin()));\n\n    underTest.install(container, ContainerLifespan.ANALYSIS);\n\n    verify(container, never()).addExtension(FAKE_PLUGIN_KEY, FakeSensor.class);\n  }\n\n  @Test\n  void provide_sonarlint_context_for_plugin_definition() {\n    var pluginInstance = new PluginStoringSonarLintPluginApiVersion();\n    when(loadedPlugins.getAnalysisPluginInstancesByKeys()).thenReturn(Map.of(FAKE_PLUGIN_KEY, pluginInstance));\n\n    underTest = new AnalysisExtensionInstaller(RUNTIME, loadedPlugins, EMPTY_CONFIG);\n\n    underTest.install(container, ContainerLifespan.ANALYSIS);\n\n    assertThat(pluginInstance.sonarLintPluginApiVersion).isEqualTo(PLUGIN_API_VERSION);\n    assertThat(pluginInstance.clientPid).isEqualTo(FAKE_PID);\n  }\n\n  @Test\n  void log_when_plugin_throws() {\n    when(loadedPlugins.getAnalysisPluginInstancesByKeys()).thenReturn(Map.of(FAKE_PLUGIN_KEY, new ThrowingPlugin()));\n\n    underTest = new AnalysisExtensionInstaller(RUNTIME, loadedPlugins, EMPTY_CONFIG);\n\n    underTest.install(container, ContainerLifespan.ANALYSIS);\n\n    assertThat(logTester.logs(LogOutput.Level.ERROR)).contains(\"Error loading components for plugin 'foo'\");\n  }\n\n  private static class FakePlugin implements Plugin {\n    private final Object component;\n\n    private FakePlugin() {\n      this(FakeSonarLintDefaultLifespanComponent.class);\n    }\n\n    public FakePlugin(Object component) {\n      this.component = component;\n    }\n\n    @Override\n    public void define(Context context) {\n      context.addExtension(component);\n      context.addExtension(FakeSensor.class);\n    }\n\n  }\n\n  private static class ThrowingPlugin implements Plugin {\n    @Override\n    public void define(Context context) {\n      throw new Error();\n    }\n\n  }\n\n  private static class PluginStoringSonarLintPluginApiVersion implements Plugin {\n    Version sonarLintPluginApiVersion;\n    long clientPid;\n\n    @Override\n    public void define(Context context) {\n      if (context.getRuntime() instanceof SonarLintRuntime) {\n        sonarLintPluginApiVersion = ((SonarLintRuntime) context.getRuntime()).getSonarLintPluginApiVersion();\n        clientPid = ((SonarLintRuntime) context.getRuntime()).getClientPid();\n      }\n    }\n\n  }\n\n  @SonarLintSide\n  private static class FakeSonarLintDefaultLifespanComponent {\n  }\n\n  @SonarLintSide(lifespan = SonarLintSide.SINGLE_ANALYSIS)\n  private static class FakeSonarLintSingleAnalysisLifespanComponent {\n  }\n\n  @SonarLintSide(lifespan = SonarLintSide.MULTIPLE_ANALYSES)\n  private static class FakeSonarLintMultipleAnalysisLifespanComponent {\n  }\n\n  @SonarLintSide(lifespan = \"MODULE\")\n  private static class FakeSonarLintModuleLifespanComponent {\n  }\n\n  @SonarLintSide(lifespan = \"INSTANCE\")\n  private static class FakeSonarLintInstanceLifespanComponent {\n  }\n\n  private static class FakeSensor implements Sensor {\n\n    @Override\n    public void describe(SensorDescriptor descriptor) {\n\n    }\n\n    @Override\n    public void execute(SensorContext context) {\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/global/GlobalSettingsTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.global;\n\nimport java.nio.file.Paths;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonar.api.config.PropertyDefinitions;\nimport org.sonar.api.utils.System2;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisSchedulerConfiguration;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass GlobalSettingsTests {\n\n  @RegisterExtension\n  SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @Test\n  void emptyNodePathPropertyForSonarJS() {\n    var underTest = new GlobalSettings(AnalysisSchedulerConfiguration.builder().build(), new PropertyDefinitions(System2.INSTANCE));\n\n    var nodeJsExecutableValue = underTest.getString(\"sonar.nodejs.executable\");\n\n    assertThat(nodeJsExecutableValue).isNull();\n  }\n\n  @Test\n  void customNodePathPropertyForSonarJS() {\n    var providedNodePath = Paths.get(\"foo/bar/node\");\n    var underTest = new GlobalSettings(AnalysisSchedulerConfiguration.builder().setNodeJs(providedNodePath).build(),\n      new PropertyDefinitions(System2.INSTANCE));\n\n    var nodeJsExecutableValue = underTest.getString(\"sonar.nodejs.executable\");\n\n    assertThat(nodeJsExecutableValue).isEqualTo(providedNodePath.toString());\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/global/GlobalTempFolderProviderTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.global;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.attribute.BasicFileAttributeView;\nimport java.nio.file.attribute.FileTime;\nimport java.util.concurrent.TimeUnit;\nimport org.apache.commons.io.FileUtils;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonar.api.utils.TempFolder;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisSchedulerConfiguration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass GlobalTempFolderProviderTests {\n\n  @TempDir\n  private Path workingDir;\n\n  private final GlobalTempFolderProvider tempFolderProvider = new GlobalTempFolderProvider();\n\n  @Test\n  void createTempFolderProps() throws Exception {\n\n    TempFolder tempFolder = tempFolderProvider.provide(AnalysisSchedulerConfiguration.builder().setWorkDir(workingDir).build());\n    tempFolder.newDir();\n    tempFolder.newFile();\n    assertThat(getCreatedTempDir(workingDir)).exists();\n    assertThat(getCreatedTempDir(workingDir).list()).hasSize(2);\n\n    FileUtils.deleteQuietly(workingDir.toFile());\n  }\n\n  @Test\n  @Disabled(\"SLCORE-821\")\n  void cleanUpOld() throws IOException {\n    var creationTime = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(100);\n\n    for (var i = 0; i < 3; i++) {\n      var tmp = new File(workingDir.toFile(), \".sonarlinttmp_\" + i);\n      tmp.mkdirs();\n      setFileCreationDate(tmp, creationTime);\n    }\n\n    tempFolderProvider.provide(AnalysisSchedulerConfiguration.builder().setWorkDir(workingDir).build());\n    // this also checks that all other temps were deleted\n    assertThat(getCreatedTempDir(workingDir)).exists();\n\n    FileUtils.deleteQuietly(workingDir.toFile());\n  }\n\n  private File getCreatedTempDir(Path workingDir) {\n    assertThat(workingDir).isDirectory();\n    assertThat(workingDir.toFile().listFiles()).hasSize(1);\n    return workingDir.toFile().listFiles()[0];\n  }\n\n  private void setFileCreationDate(File f, long time) throws IOException {\n    var attributes = Files.getFileAttributeView(f.toPath(), BasicFileAttributeView.class);\n    var creationTime = FileTime.fromMillis(time);\n    attributes.setTimes(creationTime, creationTime, creationTime);\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/container/module/ModuleInputFileBuilderTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.container.module;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.FileMetadata;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.LanguageDetection;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport testutils.FileUtils;\nimport testutils.OnDiskTestClientInputFile;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.when;\n\nclass ModuleInputFileBuilderTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private final LanguageDetection langDetection = mock(LanguageDetection.class);\n  private final FileMetadata metadata = new FileMetadata();\n\n  @TempDir\n  private Path tempDir;\n\n  @Test\n  void testCreate() throws IOException {\n    when(langDetection.language(any(InputFile.class))).thenReturn(SonarLanguage.JAVA);\n\n    var path = tempDir.resolve(\"file\");\n    Files.write(path, \"test\".getBytes(StandardCharsets.ISO_8859_1));\n    ClientInputFile file = new OnDiskTestClientInputFile(path, \"file\", true, StandardCharsets.ISO_8859_1);\n\n    var builder = new ModuleInputFileBuilder(langDetection, metadata);\n    var inputFile = builder.create(file);\n\n    assertThat(inputFile.type()).isEqualTo(InputFile.Type.TEST);\n    assertThat(inputFile.file()).isEqualTo(path.toFile());\n    assertThat(inputFile.absolutePath()).isEqualTo(FileUtils.toSonarQubePath(path.toString()));\n    assertThat(inputFile.language()).isEqualTo(\"java\");\n    assertThat(inputFile.key()).isEqualTo(path.toUri().toString());\n    assertThat(inputFile.lines()).isEqualTo(1);\n  }\n\n  @Test\n  void testCreateWithLanguageSet() throws IOException {\n    var path = tempDir.resolve(\"file\");\n    Files.write(path, \"test\".getBytes(StandardCharsets.ISO_8859_1));\n    ClientInputFile file = new OnDiskTestClientInputFile(path, \"file\", true, StandardCharsets.ISO_8859_1, SonarLanguage.CPP);\n\n    var builder = new ModuleInputFileBuilder(langDetection, metadata);\n    var inputFile = builder.create(file);\n\n    assertThat(inputFile.language()).isEqualTo(\"cpp\");\n    verifyNoInteractions(langDetection);\n  }\n\n  @Test\n  void testCreate_lazy_error() throws IOException {\n    when(langDetection.language(any(InputFile.class))).thenReturn(SonarLanguage.JAVA);\n    ClientInputFile file = new OnDiskTestClientInputFile(Paths.get(\"INVALID\"), \"INVALID\", true, StandardCharsets.ISO_8859_1);\n\n    var builder = new ModuleInputFileBuilder(langDetection, metadata);\n    var slFile = builder.create(file);\n\n    // Call any method that will trigger metadata initialization\n    var thrown = assertThrows(IllegalStateException.class, () -> slFile.selectLine(1));\n    assertThat(thrown).hasMessageStartingWith(\"Failed to open a stream on file\");\n\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/mediumtests/AnalysisSchedulerMediumTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.mediumtests;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\nimport java.util.stream.Stream;\nimport javax.annotation.CheckForNull;\nimport org.apache.commons.io.FileUtils;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.rule.ActiveRule;\nimport org.sonar.api.rule.RuleKey;\nimport org.sonarsource.sonarlint.core.analysis.AnalysisScheduler;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisConfiguration;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisSchedulerConfiguration;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientModuleFileSystem;\nimport org.sonarsource.sonarlint.core.analysis.api.Issue;\nimport org.sonarsource.sonarlint.core.analysis.api.TriggerType;\nimport org.sonarsource.sonarlint.core.analysis.command.AnalyzeCommand;\nimport org.sonarsource.sonarlint.core.commons.LogTestStartAndEnd;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.commons.progress.TaskManager;\nimport org.sonarsource.sonarlint.core.plugin.commons.PluginsLoader;\nimport testutils.OnDiskTestClientInputFile;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\n\n@ExtendWith(LogTestStartAndEnd.class)\nclass AnalysisSchedulerMediumTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester(true);\n  private static final Consumer<List<ClientInputFile>> NO_OP_ANALYSIS_STARTED_CONSUMER = inputFiles -> {\n  };\n  private static final Supplier<Boolean> ANALYSIS_READY_SUPPLIER = () -> true;\n  private static final Consumer<Issue> NO_OP_ISSUE_LISTENER = issue -> {\n  };\n  public static final TaskManager TASK_MANAGER = new TaskManager();\n\n  private AnalysisScheduler analysisScheduler;\n  private volatile boolean engineStopped = true;\n  private final SonarLintCancelMonitor progressMonitor = new SonarLintCancelMonitor();\n\n  @BeforeEach\n  void prepare(@TempDir Path workDir) throws IOException {\n    var enabledLanguages = Set.of(SonarLanguage.PYTHON);\n    var analysisGlobalConfig = AnalysisSchedulerConfiguration.builder()\n      .setClientPid(1234L)\n      .setWorkDir(workDir)\n      .setFileSystemProvider(this::provideFileSystem)\n      .build();\n    var result = new PluginsLoader().load(new PluginsLoader.Configuration(Set.of(findPythonJarPath()), enabledLanguages, false, Optional.empty()), Set.of());\n    this.analysisScheduler = new AnalysisScheduler(analysisGlobalConfig, result.getLoadedPlugins(), logTester.getLogOutput());\n    engineStopped = false;\n  }\n\n  private ClientModuleFileSystem provideFileSystem(String moduleKey) {\n    return aModuleFileSystem();\n  }\n\n  @AfterEach\n  void cleanUp() {\n    if (!engineStopped) {\n      this.analysisScheduler.stop();\n    }\n  }\n\n  @Test\n  void should_analyze_a_file_inside_a_module(@TempDir Path baseDir) throws Exception {\n    var content = \"\"\"\n      def foo():\n        x = 9; # trailing comment\n      \"\"\";\n    ClientInputFile inputFile = preparePythonInputFile(baseDir, content);\n\n    AnalysisConfiguration analysisConfig = AnalysisConfiguration.builder()\n      .addInputFiles(inputFile)\n      .addActiveRules(trailingCommentRule())\n      .setBaseDir(baseDir)\n      .build();\n    List<Issue> issues = new ArrayList<>();\n    var analyzeCommand = new AnalyzeCommand(\"moduleKey\", UUID.randomUUID(), TriggerType.FORCED, () -> analysisConfig, issues::add, null, progressMonitor, TASK_MANAGER,\n      NO_OP_ANALYSIS_STARTED_CONSUMER, ANALYSIS_READY_SUPPLIER, Set.of(), Map.of());\n    analysisScheduler.post(analyzeCommand);\n    analyzeCommand.getFutureResult().get();\n    assertThat(issues).hasSize(1);\n    assertThat(issues)\n      .extracting(\"ruleKey\", \"message\", \"inputFile\", \"flows\", \"textRange.startLine\", \"textRange.startLineOffset\", \"textRange.endLine\", \"textRange.endLineOffset\")\n      .containsOnly(tuple(RuleKey.parse(\"python:S139\"), \"Move this trailing comment on the previous empty line.\", inputFile, List.of(), 2, 9, 2, 27));\n    assertThat(issues.get(0).quickFixes()).hasSize(1);\n  }\n\n  @Test\n  void should_fail_the_future_if_the_analyze_command_execution_fails() {\n    var command = new AnalyzeCommand(\"moduleKey\", UUID.randomUUID(), TriggerType.FORCED, () -> {\n      throw new RuntimeException(\"Kaboom\");\n    }, issue -> {\n    }, null, progressMonitor, TASK_MANAGER, NO_OP_ANALYSIS_STARTED_CONSUMER, ANALYSIS_READY_SUPPLIER, Set.of(), Map.of());\n    analysisScheduler.post(command);\n\n    assertThat(command.getFutureResult()).failsWithin(300, TimeUnit.MILLISECONDS)\n      .withThrowableOfType(ExecutionException.class)\n      .havingCause()\n      .isInstanceOf(RuntimeException.class)\n      .withMessage(\"Kaboom\");\n  }\n\n  @Test\n  void should_cancel_progress_monitor_of_executing_analyze_command_when_stopping(@TempDir Path baseDir) throws IOException, InterruptedException {\n    var content = \"\"\"\n      def foo():\n        x = 9; # trailing comment\n      \"\"\";\n    ClientInputFile inputFile = preparePythonInputFile(baseDir, content);\n\n    AnalysisConfiguration analysisConfig = AnalysisConfiguration.builder()\n      .addInputFiles(inputFile)\n      .addActiveRules(trailingCommentRule())\n      .setBaseDir(baseDir)\n      .build();\n    var analyzeCommand = new AnalyzeCommand(\"moduleKey\", UUID.randomUUID(), TriggerType.FORCED, () -> analysisConfig, NO_OP_ISSUE_LISTENER, null, progressMonitor, TASK_MANAGER,\n      inputFiles -> pause(300), ANALYSIS_READY_SUPPLIER, Set.of(), Map.of());\n    analysisScheduler.post(analyzeCommand);\n    // let the engine run the first command\n    Thread.sleep(100);\n    analysisScheduler.stop();\n    engineStopped = true;\n\n    await().until(analyzeCommand.getFutureResult()::isDone);\n    assertThat(analyzeCommand.getFutureResult())\n      .isCancelled();\n    assertThat(progressMonitor.isCanceled()).isTrue();\n  }\n\n  @Test\n  void should_cancel_pending_commands_when_stopping(@TempDir Path baseDir) throws IOException, InterruptedException {\n    var content = \"\"\"\n      def foo():\n        x = 9; # trailing comment\n      \"\"\";\n    ClientInputFile inputFile = preparePythonInputFile(baseDir, content);\n\n    AnalysisConfiguration analysisConfig = AnalysisConfiguration.builder()\n      .addInputFiles(inputFile)\n      .addActiveRules(trailingCommentRule())\n      .setBaseDir(baseDir)\n      .build();\n    var analyzeCommand = new AnalyzeCommand(\"moduleKey\", UUID.randomUUID(), TriggerType.FORCED, () -> analysisConfig, NO_OP_ISSUE_LISTENER, null, progressMonitor, TASK_MANAGER,\n      inputFiles -> pause(300), ANALYSIS_READY_SUPPLIER, Set.of(), Map.of());\n    var secondAnalyzeCommand = new AnalyzeCommand(\"moduleKey\", UUID.randomUUID(), TriggerType.FORCED, () -> analysisConfig, NO_OP_ISSUE_LISTENER, null, progressMonitor,\n      TASK_MANAGER, NO_OP_ANALYSIS_STARTED_CONSUMER, ANALYSIS_READY_SUPPLIER, Set.of(), Map.of());\n    analysisScheduler.post(analyzeCommand);\n    analysisScheduler.post(secondAnalyzeCommand);\n    // let the engine run the first command\n    Thread.sleep(100);\n\n    analysisScheduler.stop();\n    engineStopped = true;\n\n    await().until(analyzeCommand.getFutureResult()::isDone);\n    assertThat(analyzeCommand.getFutureResult())\n      .isCancelled();\n    assertThat(secondAnalyzeCommand.getFutureResult())\n      .isCancelled();\n    assertThat(progressMonitor.isCanceled()).isTrue();\n  }\n\n  @Test\n  void should_not_fail_next_analysis_on_exception_from_command(@TempDir Path baseDir) throws IOException {\n    Supplier<Boolean> throwingSupplier = () -> {\n      throw new RuntimeException(\"Kaboom\");\n    };\n    var content = \"\"\"\n      def foo():\n        x = 9; # trailing comment\n      \"\"\";\n    var inputFile = preparePythonInputFile(baseDir, content);\n\n    var analysisConfig = AnalysisConfiguration.builder()\n      .addInputFiles(inputFile)\n      .addActiveRules(trailingCommentRule())\n      .setBaseDir(baseDir)\n      .build();\n    var issues1 = new ArrayList<>();\n    var issues2 = new ArrayList<>();\n    var analyzeCommand1 = new AnalyzeCommand(\"moduleKey\", UUID.randomUUID(), TriggerType.FORCED,\n      () -> analysisConfig, issues1::add, null, progressMonitor, TASK_MANAGER,\n      NO_OP_ANALYSIS_STARTED_CONSUMER, ANALYSIS_READY_SUPPLIER, Set.of(), Map.of(\"a\", \"1\"));\n    var throwingCommand = new AnalyzeCommand(\"moduleKey\", UUID.randomUUID(), TriggerType.FORCED,\n      () -> analysisConfig, NO_OP_ISSUE_LISTENER, null, progressMonitor, TASK_MANAGER,\n      NO_OP_ANALYSIS_STARTED_CONSUMER, throwingSupplier, Set.of(), Map.of(\"b\", \"2\"));\n    var analyzeCommand2 = new AnalyzeCommand(\"moduleKey\", UUID.randomUUID(), TriggerType.FORCED,\n      () -> analysisConfig, issues2::add, null, progressMonitor, TASK_MANAGER,\n      NO_OP_ANALYSIS_STARTED_CONSUMER, ANALYSIS_READY_SUPPLIER, Set.of(), Map.of(\"c\", \"3\"));\n\n    analysisScheduler.post(analyzeCommand1);\n    analysisScheduler.post(throwingCommand);\n    analysisScheduler.post(analyzeCommand2);\n\n    await().untilAsserted(() -> assertThat(logTester.logs()).contains(\"Analysis command failed\"));\n    await().atMost(3, TimeUnit.SECONDS)\n      .until(() -> analyzeCommand2.getFutureResult().isDone());\n    assertThat(issues2).hasSize(1);\n  }\n\n  @Test\n  void should_not_queue_command_if_already_canceled(@TempDir Path baseDir) {\n    var analysisConfig = AnalysisConfiguration.builder()\n      .addActiveRules(trailingCommentRule())\n      .setBaseDir(baseDir)\n      .build();\n    var analyzeCommand = new AnalyzeCommand(\"moduleKey\", UUID.randomUUID(), TriggerType.FORCED,\n      () -> analysisConfig, i -> {\n      }, null, progressMonitor, TASK_MANAGER,\n      NO_OP_ANALYSIS_STARTED_CONSUMER, ANALYSIS_READY_SUPPLIER, Set.of(), Map.of(\"a\", \"1\"));\n    progressMonitor.cancel();\n\n    analysisScheduler.post(analyzeCommand);\n\n    await().untilAsserted(() -> assertThat(logTester.logs()).contains(\"Not picking next command \" + analyzeCommand + \", is canceled\"));\n  }\n\n  @Test\n  void should_interrupt_executing_thread_when_stopping(@TempDir Path baseDir) throws IOException {\n    var content = \"\"\"\n      def foo():\n        x = 9; # trailing comment\n      \"\"\";\n    ClientInputFile inputFile = preparePythonInputFile(baseDir, content);\n\n    AnalysisConfiguration analysisConfig = AnalysisConfiguration.builder()\n      .addInputFiles(inputFile)\n      .addActiveRules(trailingCommentRule())\n      .setBaseDir(baseDir)\n      .build();\n    var threadTermination = new AtomicReference<String>();\n    var analyzeCommand = new AnalyzeCommand(\"moduleKey\", UUID.randomUUID(), TriggerType.FORCED, () -> analysisConfig, NO_OP_ISSUE_LISTENER, null, progressMonitor, TASK_MANAGER,\n      inputFiles -> {\n        try {\n          Thread.sleep(3000);\n        } catch (InterruptedException e) {\n          threadTermination.set(\"INTERRUPTED\");\n          return;\n        }\n        threadTermination.set(\"FINISHED\");\n      }, ANALYSIS_READY_SUPPLIER, Set.of(), Map.of());\n    analysisScheduler.post(analyzeCommand);\n    // let the engine run the first command\n    pause(200);\n\n    analysisScheduler.stop();\n    engineStopped = true;\n\n    await().until(analyzeCommand.getFutureResult()::isDone);\n    assertThat(threadTermination).hasValue(\"INTERRUPTED\");\n  }\n\n  @Test\n  void should_not_log_any_error_when_stopping() {\n    // let the engine block waiting for the first command\n    pause(500);\n\n    analysisScheduler.stop();\n\n    // let the engine stop properly\n    pause(1000);\n    assertThat(logTester.logs(LogOutput.Level.ERROR)).isEmpty();\n  }\n\n  private ClientInputFile preparePythonInputFile(Path baseDir, String content) throws IOException {\n    final var file = new File(baseDir.toFile(), \"file.py\");\n    FileUtils.write(file, content, StandardCharsets.UTF_8);\n    return new OnDiskTestClientInputFile(file.toPath(), \"file.py\", false, StandardCharsets.UTF_8, SonarLanguage.PYTHON);\n  }\n\n  private static Path findPythonJarPath() throws IOException {\n    var pluginsFolderPath = Paths.get(\"target/plugins/\");\n    try (var files = Files.list(pluginsFolderPath)) {\n      return files.filter(x -> x.getFileName().toString().endsWith(\".jar\"))\n        .filter(x -> x.getFileName().toString().contains(\"python\"))\n        .findFirst().orElseThrow(() -> new RuntimeException(\"Unable to locate the python plugin\"));\n    }\n  }\n\n  private static ActiveRule trailingCommentRule() {\n    return new ActiveRule() {\n      @Override\n      public RuleKey ruleKey() {\n        return RuleKey.parse(\"python:S139\");\n      }\n\n      @Override\n      public String severity() {\n        return \"\";\n      }\n\n      @Override\n      public String language() {\n        return \"py\";\n      }\n\n      @CheckForNull\n      @Override\n      public String param(String key) {\n        return params().get(key);\n      }\n\n      @Override\n      public Map<String, String> params() {\n        return Map.of(\"legalTrailingCommentPattern\", \"^#\\\\s*+[^\\\\s]++$\");\n      }\n\n      @Override\n      public String internalKey() {\n        return \"\";\n      }\n\n      @CheckForNull\n      @Override\n      public String templateRuleKey() {\n        return null;\n      }\n\n      @Override\n      public String qpKey() {\n        return \"\";\n      }\n    };\n  }\n\n  private static ClientModuleFileSystem aModuleFileSystem() {\n    return new ClientModuleFileSystem() {\n      @Override\n      public Stream<ClientInputFile> files(String suffix, InputFile.Type type) {\n        return Stream.of();\n      }\n\n      @Override\n      public Stream<ClientInputFile> files() {\n        return Stream.of();\n      }\n    };\n  }\n\n  private static void pause(long period) {\n    try {\n      Thread.sleep(period);\n    } catch (InterruptedException e) {\n      e.printStackTrace();\n    }\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/sonarapi/DefaultAnalysisErrorTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.fs.TextPointer;\nimport org.sonar.api.batch.sensor.internal.SensorStorage;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.DefaultTextPointer;\nimport testutils.TestInputFileBuilder;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\n\nclass DefaultAnalysisErrorTests {\n  private InputFile inputFile;\n  private SensorStorage storage;\n  private TextPointer textPointer;\n\n  @BeforeEach\n  void setUp() {\n    inputFile = new TestInputFileBuilder(\"src/File.java\").build();\n    textPointer = new DefaultTextPointer(5, 2);\n    storage = mock(SensorStorage.class);\n  }\n\n  @Test\n  void test_analysis_error() {\n    var analysisError = new DefaultAnalysisError(storage);\n    analysisError.onFile(inputFile)\n      .at(textPointer)\n      .message(\"msg\");\n\n    assertThat(analysisError.location()).isEqualTo(textPointer);\n    assertThat(analysisError.message()).isEqualTo(\"msg\");\n    assertThat(analysisError.inputFile()).isEqualTo(inputFile);\n  }\n\n  @Test\n  void test_save() {\n    var analysisError = new DefaultAnalysisError(storage);\n    analysisError.onFile(inputFile).save();\n\n    verify(storage).store(analysisError);\n    verifyNoMoreInteractions(storage);\n  }\n\n  @Test\n  void test_no_storage() {\n    var analysisError = new DefaultAnalysisError();\n    assertThrows(NullPointerException.class, () -> analysisError.onFile(inputFile).save());\n  }\n\n  @Test\n  void test_validation() {\n    assertThrows(IllegalArgumentException.class, () -> new DefaultAnalysisError(storage).onFile(null));\n    assertThrows(IllegalStateException.class, () -> new DefaultAnalysisError(storage).onFile(inputFile).onFile(inputFile));\n    assertThrows(IllegalStateException.class, () -> new DefaultAnalysisError(storage).at(textPointer).at(textPointer));\n    assertThrows(NullPointerException.class, () -> new DefaultAnalysisError(storage).save());\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/sonarapi/DefaultFilterableIssueTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi;\n\nimport java.util.Map;\nimport java.util.Optional;\nimport org.junit.jupiter.api.Test;\nimport org.sonar.api.batch.fs.InputComponent;\nimport org.sonar.api.batch.fs.TextRange;\nimport org.sonar.api.batch.rule.ActiveRule;\nimport org.sonar.api.rule.RuleKey;\nimport org.sonarsource.sonarlint.core.analysis.api.Issue;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.DefaultTextPointer;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.DefaultTextRange;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass DefaultFilterableIssueTests {\n\n  @Test\n  void delegate_textRange_to_rawIssue() {\n    TextRange textRange = new DefaultTextRange(new DefaultTextPointer(0, 1), new DefaultTextPointer(2, 3));\n    var activeRule = mock(ActiveRule.class);\n    when(activeRule.ruleKey()).thenReturn(RuleKey.of(\"foo\", \"S123\"));\n    var rawIssue = new Issue(activeRule, null, Map.of(), textRange, null, null, null, Optional.empty());\n    var underTest = new DefaultFilterableIssue(rawIssue, mock(InputComponent.class));\n    assertThat(underTest.textRange()).usingRecursiveComparison().isEqualTo(textRange);\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/sonarapi/DefaultSensorContextTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi;\n\nimport javax.annotation.Nullable;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.sonar.api.SonarRuntime;\nimport org.sonar.api.batch.fs.FileSystem;\nimport org.sonar.api.batch.rule.ActiveRules;\nimport org.sonar.api.batch.sensor.internal.SensorStorage;\nimport org.sonar.api.config.Configuration;\nimport org.sonar.api.config.Settings;\nimport org.sonar.api.utils.Version;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputProject;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.noop.NoOpNewCoverage;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.noop.NoOpNewCpdTokens;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.noop.NoOpNewHighlighting;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.noop.NoOpNewMeasure;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.noop.NoOpNewSignificantCode;\nimport org.sonarsource.sonarlint.core.analysis.sonarapi.noop.NoOpNewSymbolTable;\nimport org.sonarsource.sonarlint.core.commons.progress.ProgressIndicator;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\nimport static org.mockito.Mockito.when;\n\n@ExtendWith(MockitoExtension.class)\nclass DefaultSensorContextTests {\n  @Mock\n  private SonarLintInputProject module;\n  @Mock\n  private Settings settings;\n  @Mock\n  private Configuration config;\n  @Mock\n  private FileSystem fs;\n  @Mock\n  private ActiveRules activeRules;\n  @Mock\n  private SensorStorage sensorStorage;\n  @Mock\n  private SonarRuntime sqRuntime;\n\n  private DefaultSensorContext ctx;\n  private ProgressIndicator progressIndicator;\n  private boolean canceled;\n\n  @BeforeEach\n  void setUp() {\n    canceled = false;\n    progressIndicator = new ProgressIndicator() {\n      @Override\n      public void notifyProgress(@Nullable String message, @Nullable Integer percentage) {\n        // no-op\n      }\n\n      @Override\n      public boolean isCanceled() {\n        return canceled;\n      }\n    };\n    ctx = new DefaultSensorContext(module, settings, config, fs, activeRules, sensorStorage, sqRuntime, progressIndicator);\n  }\n\n  @Test\n  void testGetters() {\n    when(sqRuntime.getApiVersion()).thenReturn(Version.create(6, 1));\n\n    assertThat(ctx.activeRules()).isEqualTo(activeRules);\n    assertThat(ctx.settings()).isEqualTo(settings);\n    assertThat(ctx.config()).isEqualTo(config);\n    assertThat(ctx.fileSystem()).isEqualTo(fs);\n    assertThat(ctx.module()).isEqualTo(module);\n    assertThat(ctx.runtime()).isEqualTo(sqRuntime);\n\n    assertThat(ctx.getSonarQubeVersion()).isEqualTo(Version.create(6, 1));\n    assertThat(ctx.isCancelled()).isFalse();\n\n    // no ops\n    assertThat(ctx.newCpdTokens()).isInstanceOf(NoOpNewCpdTokens.class);\n    assertThat(ctx.newSymbolTable()).isInstanceOf(NoOpNewSymbolTable.class);\n    assertThat(ctx.newHighlighting()).isInstanceOf(NoOpNewHighlighting.class);\n    assertThat(ctx.newMeasure()).isInstanceOf(NoOpNewMeasure.class);\n    assertThat(ctx.newCoverage()).isInstanceOf(NoOpNewCoverage.class);\n    assertThat(ctx.newSignificantCode()).isInstanceOf(NoOpNewSignificantCode.class);\n    ctx.addContextProperty(null, null);\n    ctx.markForPublishing(null);\n    assertThat(ctx.canSkipUnchangedFiles()).isFalse();\n    assertThat(ctx.isCacheEnabled()).isFalse();\n    assertThat(ctx.isFeatureAvailable(\"any\")).isFalse();\n    assertThrows(UnsupportedOperationException.class, () -> ctx.newExternalIssue());\n    assertThrows(UnsupportedOperationException.class, () -> ctx.previousCache());\n    assertThrows(UnsupportedOperationException.class, () -> ctx.nextCache());\n    ctx.addTelemetryProperty(\"not\", \"applicable\");\n\n    verify(sqRuntime).getApiVersion();\n\n    verifyNoMoreInteractions(sqRuntime);\n    verifyNoInteractions(module);\n    verifyNoInteractions(settings);\n    verifyNoInteractions(fs);\n    verifyNoInteractions(activeRules);\n    verifyNoInteractions(sensorStorage);\n  }\n\n  @Test\n  void testCancellation() {\n    assertThat(ctx.isCancelled()).isFalse();\n\n    canceled = true;\n\n    assertThat(ctx.isCancelled()).isTrue();\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/sonarapi/DefaultSensorDescriptorTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi;\n\nimport java.util.Map;\nimport org.junit.jupiter.api.Test;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonarsource.sonarlint.core.plugin.commons.sonarapi.MapSettings;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass DefaultSensorDescriptorTests {\n\n  @Test\n  void describe() {\n    var descriptor = new DefaultSensorDescriptor();\n    descriptor\n      .name(\"Foo\")\n      .onlyOnLanguage(\"java\")\n      .onlyOnFileType(InputFile.Type.MAIN)\n      .onlyWhenConfiguration(c -> c.hasKey(\"sonar.foo.reportPath\") && c.hasKey(\"sonar.foo.reportPath2\"))\n      .createIssuesForRuleRepository(\"squid-java\");\n\n    assertThat(descriptor.name()).isEqualTo(\"Foo\");\n    assertThat(descriptor.languages()).containsOnly(\"java\");\n    assertThat(descriptor.type()).isEqualTo(InputFile.Type.MAIN);\n    var settings = new MapSettings(Map.of(\"sonar.foo.reportPath\", \"foo\"));\n    assertThat(descriptor.configurationPredicate().test(settings.asConfig())).isFalse();\n    settings = new MapSettings(Map.of(\"sonar.foo.reportPath\", \"foo\", \"sonar.foo.reportPath2\", \"foo\"));\n    assertThat(descriptor.configurationPredicate().test(settings.asConfig())).isTrue();\n    assertThat(descriptor.ruleRepositories()).containsOnly(\"squid-java\");\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/sonarapi/DefaultSonarLintIssueTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi;\n\nimport java.nio.file.Path;\nimport java.util.List;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.rule.Severity;\nimport org.sonar.api.batch.sensor.internal.SensorStorage;\nimport org.sonar.api.batch.sensor.issue.MessageFormatting;\nimport org.sonar.api.batch.sensor.issue.fix.NewInputFileEdit;\nimport org.sonar.api.batch.sensor.issue.fix.NewQuickFix;\nimport org.sonar.api.batch.sensor.issue.fix.NewTextEdit;\nimport org.sonar.api.issue.impact.SoftwareQuality;\nimport org.sonar.api.rule.RuleKey;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputDir;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputProject;\nimport testutils.TestInputFileBuilder;\n\nimport static org.apache.commons.lang3.StringUtils.repeat;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\nimport static org.assertj.core.api.Assertions.entry;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\n\nclass DefaultSonarLintIssueTests {\n\n  private SonarLintInputProject project;\n\n  private final InputFile inputFile = new TestInputFileBuilder(\"src/Foo.php\")\n    .initMetadata(\"Foo\\nBar\\n\")\n    .build();\n\n  @TempDir\n  private Path baseDir;\n\n  @BeforeEach\n  void prepare() {\n    project = new SonarLintInputProject();\n  }\n\n  @Test\n  void build_file_issue() {\n    var storage = mock(SensorStorage.class);\n    var range = inputFile.selectLine(1);\n    var issue = new DefaultSonarLintIssue(project, baseDir, storage)\n      .at(new DefaultSonarLintIssueLocation()\n        .on(inputFile)\n        .at(range)\n        .message(\"Wrong way!\"))\n      .forRule(RuleKey.of(\"repo\", \"rule\"))\n      .gap(10.0)\n      .overrideImpact(SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.HIGH);\n\n    assertThat(issue.primaryLocation().inputComponent()).isEqualTo(inputFile);\n    assertThat(issue.ruleKey()).isEqualTo(RuleKey.of(\"repo\", \"rule\"));\n    assertThat(issue.primaryLocation().textRange().start().line()).isEqualTo(1);\n    assertThat(issue.primaryLocation().message()).isEqualTo(\"Wrong way!\");\n    assertThat(issue.overridenImpacts()).containsExactly(entry(SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.HIGH));\n\n    assertThatExceptionOfType(UnsupportedOperationException.class)\n      .isThrownBy(issue::gap)\n      .withMessage(\"No gap in SonarLint\");\n\n    var newQuickFix = issue.newQuickFix().message(\"Fix this issue\");\n    var newInputFileEdit = newQuickFix.newInputFileEdit().on(inputFile);\n    newInputFileEdit.addTextEdit((NewTextEdit) newInputFileEdit.newTextEdit().at(range).withNewText(\"// Fixed!\"));\n    newQuickFix.addInputFileEdit((NewInputFileEdit) newInputFileEdit);\n    issue.addQuickFix((NewQuickFix) newQuickFix);\n\n    var quickFixes = issue.quickFixes();\n    assertThat(quickFixes).hasSize(1);\n    var quickFix = quickFixes.get(0);\n    assertThat(quickFix.message()).isEqualTo(\"Fix this issue\");\n    var inputFileEdits = quickFix.inputFileEdits();\n    assertThat(inputFileEdits).hasSize(1);\n    var inputFileEdit = inputFileEdits.get(0);\n    assertThat(inputFileEdit.target()).isEqualTo(inputFile);\n    assertThat(inputFileEdit.textEdits()).hasSize(1);\n    var textEdit = inputFileEdit.textEdits().get(0);\n    assertThat(textEdit.range().start().line()).isEqualTo(range.start().line());\n    assertThat(textEdit.range().start().lineOffset()).isEqualTo(range.start().lineOffset());\n    assertThat(textEdit.range().end().line()).isEqualTo(range.end().line());\n    assertThat(textEdit.range().end().lineOffset()).isEqualTo(range.end().lineOffset());\n    assertThat(textEdit.newText()).isEqualTo(\"// Fixed!\");\n\n    issue.save();\n\n    verify(storage).store(issue);\n  }\n\n  @Test\n  void replace_null_characters() {\n    var storage = mock(SensorStorage.class);\n    var issue = new DefaultSonarLintIssue(project, baseDir, storage)\n      .at(new DefaultSonarLintIssueLocation()\n        .on(inputFile)\n        .message(\"Wrong \\u0000 use of NULL\\u0000\"))\n      .forRule(RuleKey.of(\"repo\", \"rule\"));\n\n    assertThat(issue.primaryLocation().message()).isEqualTo(\"Wrong [NULL] use of NULL[NULL]\");\n\n    issue.save();\n\n    verify(storage).store(issue);\n  }\n\n  @Test\n  void truncate_and_trim() {\n    var storage = mock(SensorStorage.class);\n    var prefix = \"prefix: \";\n    var issue = new DefaultSonarLintIssue(project, baseDir, storage)\n      .at(new DefaultSonarLintIssueLocation()\n        .on(inputFile)\n        .message(\"   \" + prefix + repeat(\"a\", 4000)))\n      .forRule(RuleKey.of(\"repo\", \"rule\"));\n\n    var ellipse = \"...\";\n    assertThat(issue.primaryLocation().message()).isEqualTo(prefix + repeat(\"a\", 4000 - prefix.length() - ellipse.length()) + ellipse);\n\n    issue.save();\n\n    verify(storage).store(issue);\n  }\n\n  @Test\n  void ignore_formatting_and_keep_unformatted_message() {\n    var storage = mock(SensorStorage.class);\n    var location = new DefaultSonarLintIssueLocation();\n    var issue = new DefaultSonarLintIssue(project, baseDir, storage)\n      .at(location\n        .on(inputFile)\n        .message(\"formattedMessage\", List.of(location.newMessageFormatting()\n          .start(1)\n          .end(2)\n          .type(MessageFormatting.Type.CODE))))\n      .forRule(RuleKey.of(\"repo\", \"rule\"));\n\n    assertThat(issue.primaryLocation().message()).isEqualTo(\"formattedMessage\");\n    assertThat(issue.primaryLocation().messageFormattings()).isEmpty();\n  }\n\n  @Test\n  void move_directory_issue_to_project_root() {\n    var storage = mock(SensorStorage.class);\n    var issue = new DefaultSonarLintIssue(project, baseDir, storage)\n      .at(new DefaultSonarLintIssueLocation()\n        .on(new SonarLintInputDir(baseDir.resolve(\"src/main\")))\n        .message(\"Wrong way!\"))\n      .forRule(RuleKey.of(\"repo\", \"rule\"))\n      .overrideSeverity(Severity.BLOCKER);\n\n    assertThat(issue.primaryLocation().inputComponent()).isEqualTo(project);\n    assertThat(issue.ruleKey()).isEqualTo(RuleKey.of(\"repo\", \"rule\"));\n    assertThat(issue.primaryLocation().textRange()).isNull();\n    assertThat(issue.primaryLocation().message()).isEqualTo(\"[src/main] Wrong way!\");\n    assertThat(issue.overriddenSeverity()).isEqualTo(Severity.BLOCKER);\n\n    issue.save();\n\n    verify(storage).store(issue);\n  }\n\n  @Test\n  void build_project_issue() {\n    var storage = mock(SensorStorage.class);\n    var issue = new DefaultSonarLintIssue(project, baseDir, storage)\n      .at(new DefaultSonarLintIssueLocation()\n        .on(project)\n        .message(\"Wrong way!\"))\n      .forRule(RuleKey.of(\"repo\", \"rule\"))\n      .gap(10.0);\n\n    assertThat(issue.primaryLocation().inputComponent()).isEqualTo(project);\n    assertThat(issue.ruleKey()).isEqualTo(RuleKey.of(\"repo\", \"rule\"));\n    assertThat(issue.primaryLocation().textRange()).isNull();\n    assertThat(issue.primaryLocation().message()).isEqualTo(\"Wrong way!\");\n\n    issue.save();\n\n    verify(storage).store(issue);\n  }\n\n  @Test\n  void does_not_support_variants() {\n    var storage = mock(SensorStorage.class);\n    var issue = (DefaultSonarLintIssue) new DefaultSonarLintIssue(project, baseDir, storage)\n      .at(new DefaultSonarLintIssueLocation()\n        .on(project)\n        .message(\"Wrong way!\"))\n      .forRule(RuleKey.of(\"repo\", \"rule\"))\n      .setCodeVariants(List.of(\"variant1\", \"variant2\"))\n      .gap(10.0);\n\n    assertThat(issue.codeVariants()).isEmpty();\n  }\n\n  @Test\n  void supports_adding_internal_tags_one_by_one() {\n    var storage = mock(SensorStorage.class);\n    var issue = (DefaultSonarLintIssue) new DefaultSonarLintIssue(project, baseDir, storage)\n      .at(new DefaultSonarLintIssueLocation()\n        .on(project)\n        .message(\"Wrong way!\"))\n      .forRule(RuleKey.of(\"repo\", \"rule\"))\n      .addInternalTag(\"tag1\")\n      .addInternalTag(\"tag2\");\n\n    assertThat(issue.internalTags()).containsExactly(\"tag1\", \"tag2\");\n  }\n\n  @Test\n  void supports_adding_many_internal_tags() {\n    var storage = mock(SensorStorage.class);\n    var issue = (DefaultSonarLintIssue) new DefaultSonarLintIssue(project, baseDir, storage)\n      .at(new DefaultSonarLintIssueLocation()\n        .on(project)\n        .message(\"Wrong way!\"))\n      .forRule(RuleKey.of(\"repo\", \"rule\"))\n      .addInternalTags(List.of(\"tag1\", \"tag2\"))\n      .addInternalTags(List.of(\"tag3\"));\n\n    assertThat(issue.internalTags()).containsExactly(\"tag1\", \"tag2\", \"tag3\");\n  }\n\n  @Test\n  void supports_setting_many_internal_tags() {\n    var storage = mock(SensorStorage.class);\n    var issue = (DefaultSonarLintIssue) new DefaultSonarLintIssue(project, baseDir, storage)\n      .at(new DefaultSonarLintIssueLocation()\n        .on(project)\n        .message(\"Wrong way!\"))\n      .forRule(RuleKey.of(\"repo\", \"rule\"))\n      .setInternalTags(List.of(\"tag1\", \"tag2\"));\n\n    assertThat(issue.internalTags()).containsExactly(\"tag1\", \"tag2\");\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/sonarapi/DefaultTempFolderTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi;\n\nimport java.io.File;\nimport org.apache.commons.io.FileUtils;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput.Level;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass DefaultTempFolderTests {\n\n  @RegisterExtension\n  SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @Test\n  void createTempFolderAndFile(@TempDir File rootTempFolder) {\n    var underTest = new DefaultTempFolder(rootTempFolder);\n    var dir = underTest.newDir();\n    assertThat(dir).exists().isDirectory();\n    var file = underTest.newFile();\n    assertThat(file).exists().isFile();\n\n    underTest.clean();\n    assertThat(rootTempFolder).doesNotExist();\n  }\n\n  @Test\n  void createTempFolderWithName(@TempDir File rootTempFolder) {\n    var underTest = new DefaultTempFolder(rootTempFolder);\n    var dir = underTest.newDir(\"sample\");\n    assertThat(dir).exists().isDirectory();\n    assertThat(new File(rootTempFolder, \"sample\")).isEqualTo(dir);\n\n    underTest.clean();\n    assertThat(rootTempFolder).doesNotExist();\n  }\n\n  @Test\n  void newDir_throws_ISE_if_name_is_not_valid(@TempDir File rootTempFolder) {\n    var underTest = new DefaultTempFolder(rootTempFolder);\n    var tooLong = new StringBuilder(\"tooooolong\");\n    for (var i = 0; i < 50; i++) {\n      tooLong.append(\"tooooolong\");\n    }\n\n    var tooLongString = tooLong.toString();\n\n    var thrown = assertThrows(IllegalStateException.class, () -> underTest.newDir(tooLongString));\n    assertThat(thrown).hasMessageStartingWith(\"Failed to create temp directory\");\n  }\n\n  @Test\n  void newFile_throws_ISE_if_name_is_not_valid(@TempDir File rootTempFolder) {\n    var underTest = new DefaultTempFolder(rootTempFolder);\n    var tooLong = new StringBuilder(\"tooooolong\");\n    for (var i = 0; i < 50; i++) {\n      tooLong.append(\"tooooolong\");\n    }\n\n    var tooLongString = tooLong.toString();\n\n    var thrown = assertThrows(IllegalStateException.class, () -> underTest.newFile(tooLongString, \".txt\"));\n    assertThat(thrown).hasMessage(\"Failed to create temp file\");\n  }\n\n  @Test\n  void clean_deletes_non_empty_directory(@TempDir File dir) throws Exception {\n    FileUtils.touch(new File(dir, \"foo.txt\"));\n\n    var underTest = new DefaultTempFolder(dir);\n    underTest.clean();\n\n    assertThat(dir).doesNotExist();\n  }\n\n  @Test\n  void clean_does_not_fail_if_directory_has_already_been_deleted(@TempDir File dir) {\n    var underTest = new DefaultTempFolder(dir);\n    underTest.clean();\n    assertThat(dir).doesNotExist();\n\n    // second call does not fail, nor log ERROR logs\n    underTest.clean();\n\n    assertThat(logTester.logs(Level.ERROR)).isEmpty();\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/sonarapi/noop/NoOpNewCoverageTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi.noop;\n\nimport org.junit.jupiter.api.Test;\n\nclass NoOpNewCoverageTests {\n\n  @Test\n  void test() {\n    new NoOpNewCoverage()\n      .onFile(null)\n      .conditions(0, 0, 0)\n      .lineHits(0, 0)\n      .save();\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/sonarapi/noop/NoOpNewCpdTokensTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi.noop;\n\nimport org.junit.jupiter.api.Test;\n\n class NoOpNewCpdTokensTests {\n\n  @Test\n   void improve_coverage() {\n    new NoOpNewCpdTokens()\n      .onFile(null)\n      .addToken(null, null)\n      .addToken(0, 0, 0, 0, null)\n      .save();\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/sonarapi/noop/NoOpNewHighlightingTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi.noop;\n\nimport org.junit.jupiter.api.Test;\n\nclass NoOpNewHighlightingTests {\n\n  @Test\n  void improve_coverage() {\n    new NoOpNewHighlighting().onFile(null)\n      .highlight(null, null)\n      .highlight(0, 0, 0, 0, null)\n      .save();\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/sonarapi/noop/NoOpNewMeasureTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi.noop;\n\nimport org.junit.jupiter.api.Test;\n\n class NoOpNewMeasureTests {\n  @Test\n   void test() {\n    new NoOpNewMeasure<>()\n      .on(null)\n      .forMetric(null)\n      .withValue(null)\n      .save();\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/sonarapi/noop/NoOpNewMessageFormattingTest.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi.noop;\n\nimport org.junit.jupiter.api.Test;\nimport org.sonar.api.batch.sensor.issue.MessageFormatting;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass NoOpNewMessageFormattingTest {\n  @Test\n  void should_do_nothing_and_return_same_instance() {\n    var originalMessageFormatting = new NoOpNewMessageFormatting();\n    var messageFormatting = originalMessageFormatting\n      .start(1)\n      .end(4)\n      .type(MessageFormatting.Type.CODE);\n\n    assertThat(messageFormatting).isEqualTo(originalMessageFormatting);\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/sonarapi/noop/NoOpNewSignificantCodeTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi.noop;\n\nimport org.junit.jupiter.api.Test;\n\n class NoOpNewSignificantCodeTests {\n  @Test\n   void visit_all_builder_fields() {\n    new NoOpNewSignificantCode()\n      .onFile(null)\n      .addRange(null)\n      .save();\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/sonarapi/noop/NoOpNewSymbolTableTests.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis.sonarapi.noop;\n\nimport org.junit.jupiter.api.Test;\n\nclass NoOpNewSymbolTableTests {\n\n  @Test\n  void improve_coverage() {\n    new NoOpNewSymbolTable()\n      .onFile(null)\n      .newReference(null)\n      .newReference(0, 0, 0, 0)\n      .newSymbol(null)\n      .newSymbol(0, 0, 0, 0)\n      .save();\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/testutils/FileUtils.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage testutils;\n\nimport java.io.File;\nimport java.util.regex.Pattern;\n\npublic class FileUtils {\n\n  private static final String PATH_SEPARATOR_PATTERN = Pattern.quote(File.separator);\n\n  /**\n   * Converts path to format used by SonarQube\n   *\n   * @param path path string in the local OS\n   * @return SonarQube path\n   */\n  public static String toSonarQubePath(String path) {\n    if (File.separatorChar != '/') {\n      return path.replaceAll(PATH_SEPARATOR_PATTERN, \"/\");\n    }\n    return path;\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/testutils/InMemoryTestClientInputFile.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage testutils;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Path;\nimport javax.annotation.Nullable;\nimport org.sonar.api.utils.PathUtils;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\n\npublic class InMemoryTestClientInputFile implements ClientInputFile {\n  private final boolean isTest;\n  private final SonarLanguage language;\n  private final String relativePath;\n  private final String contents;\n  private final Path path;\n\n  public InMemoryTestClientInputFile(String contents, String relativePath, @Nullable Path path, final boolean isTest, @Nullable SonarLanguage language) {\n    this.contents = contents;\n    this.relativePath = relativePath;\n    this.path = path;\n    this.isTest = isTest;\n    this.language = language;\n  }\n\n  @Override\n  public String getPath() {\n    if (path == null) {\n      throw new UnsupportedOperationException(\"getPath\");\n    }\n    return PathUtils.sanitize(path.toString());\n  }\n\n  @Override\n  public String relativePath() {\n    return relativePath;\n  }\n\n  @Override\n  public boolean isTest() {\n    return isTest;\n  }\n\n  @Override\n  public SonarLanguage language() {\n    return language;\n  }\n\n  @Override\n  public Charset getCharset() {\n    return StandardCharsets.UTF_8;\n  }\n\n  @Override\n  public <G> G getClientObject() {\n    return null;\n  }\n\n  @Override\n  public InputStream inputStream() throws IOException {\n    return new ByteArrayInputStream(relativePath.getBytes(StandardCharsets.UTF_8));\n  }\n\n  @Override\n  public String contents() throws IOException {\n    return contents;\n  }\n\n  @Override\n  public URI uri() {\n    if (path == null) {\n      return URI.create(\"file://\" + relativePath);\n    }\n    return path.toUri();\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/testutils/OnDiskTestClientInputFile.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage testutils;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.nio.charset.Charset;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\n\npublic class OnDiskTestClientInputFile implements ClientInputFile {\n  private final Path path;\n  private final boolean isTest;\n  private final Charset encoding;\n  private final SonarLanguage language;\n  private final String relativePath;\n\n  public OnDiskTestClientInputFile(final Path path, String relativePath, final boolean isTest, final Charset encoding) {\n    this(path, relativePath, isTest, encoding, null);\n  }\n\n  public OnDiskTestClientInputFile(final Path path, String relativePath, final boolean isTest, final Charset encoding, @Nullable SonarLanguage language) {\n    this.path = path;\n    this.relativePath = relativePath;\n    this.isTest = isTest;\n    this.encoding = encoding;\n    this.language = language;\n  }\n\n  @Override\n  public String getPath() {\n    return path.toString();\n  }\n\n  @Override\n  public String relativePath() {\n    return relativePath;\n  }\n\n  @Override\n  public boolean isTest() {\n    return isTest;\n  }\n\n  @Override\n  public SonarLanguage language() {\n    return language;\n  }\n\n  @Override\n  public Charset getCharset() {\n    return encoding;\n  }\n\n  @Override\n  public <G> G getClientObject() {\n    return null;\n  }\n\n  @Override\n  public InputStream inputStream() throws IOException {\n    return Files.newInputStream(path);\n  }\n\n  @Override\n  public String contents() throws IOException {\n    return new String(Files.readAllBytes(path), encoding);\n  }\n\n  @Override\n  public URI uri() {\n    return path.toUri();\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/testutils/TestClientInputFile.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage testutils;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.nio.charset.Charset;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\n\npublic class TestClientInputFile implements ClientInputFile {\n  private final Path path;\n  private final boolean isTest;\n  private final Charset encoding;\n  private final SonarLanguage language;\n  private final Path baseDir;\n\n  public TestClientInputFile(final Path baseDir, final Path path, final boolean isTest, final Charset encoding, @Nullable SonarLanguage language) {\n    this.baseDir = baseDir;\n    this.path = path;\n    this.isTest = isTest;\n    this.encoding = encoding;\n    this.language = language;\n  }\n\n  @Override\n  public String getPath() {\n    return path.toString();\n  }\n\n  @Override\n  public String relativePath() {\n    return baseDir.relativize(path).toString();\n  }\n\n  @Override\n  public boolean isTest() {\n    return isTest;\n  }\n\n  @Override\n  public Charset getCharset() {\n    return encoding;\n  }\n\n  @Override\n  public <G> G getClientObject() {\n    return null;\n  }\n\n  @Override\n  public InputStream inputStream() throws IOException {\n    return Files.newInputStream(path);\n  }\n\n  @Override\n  public String contents() throws IOException {\n    return new String(Files.readAllBytes(path), encoding);\n  }\n\n  @Override\n  public SonarLanguage language() {\n    return language;\n  }\n\n  @Override\n  public URI uri() {\n    return path.toUri();\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/java/testutils/TestInputFileBuilder.java",
    "content": "/*\n * SonarLint Core - Analysis Engine\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage testutils;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.File;\nimport java.net.URI;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Path;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.fs.InputFile.Type;\nimport org.sonar.api.utils.PathUtils;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.FileMetadata;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputFile;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\n\n/**\n * Intended to be used in unit tests that need to create {@link InputFile}s.\n * An InputFile is unambiguously identified by a <b>module key</b> and a <b>relative path</b>, so these parameters are mandatory.\n * <p>\n * A module base directory is only needed to construct absolute paths.\n * <p>\n * Examples of usage of the constructors:\n *\n * <pre>\n * InputFile file1 = TestInputFileBuilder.create(\"module1\", \"myfile.java\").build();\n * InputFile file2 = TestInputFileBuilder.create(\"\", fs.baseDir(), myfile).build();\n * </pre>\n * <p>\n * file1 will have the \"module1\" as both module key and module base directory.\n * file2 has an empty string as module key, and a relative path which is the path from the filesystem base directory to myfile.\n */\npublic class TestInputFileBuilder {\n  private final String relativePath;\n  @CheckForNull\n  private Path baseDir;\n  private SonarLanguage language;\n  private InputFile.Type type = InputFile.Type.MAIN;\n  private int lines = -1;\n  private int[] originalLineStartOffsets = new int[0];\n  private int lastValidOffset = -1;\n  private String contents;\n\n  /**\n   * Create a InputFile with a given module key and module base directory.\n   * The relative path is generated comparing the file path to the module base directory.\n   * filePath must point to a file that is within the module base directory.\n   */\n  public TestInputFileBuilder(File baseDir, File filePath) {\n    var relativePathStr = baseDir.toPath().relativize(filePath.toPath()).toString();\n    setBaseDir(baseDir.toPath());\n    this.relativePath = PathUtils.sanitize(relativePathStr);\n  }\n\n  public TestInputFileBuilder(String relativePath) {\n    this.relativePath = PathUtils.sanitize(relativePath);\n  }\n\n  public static TestInputFileBuilder create(File moduleBaseDir, File filePath) {\n    return new TestInputFileBuilder(moduleBaseDir, filePath);\n  }\n\n  public static TestInputFileBuilder create(String relativePath) {\n    return new TestInputFileBuilder(relativePath);\n  }\n\n  public TestInputFileBuilder setBaseDir(Path baseDir) {\n    this.baseDir = baseDir;\n    return this;\n  }\n\n  public TestInputFileBuilder setLanguage(@Nullable SonarLanguage language) {\n    this.language = language;\n    return this;\n  }\n\n  public TestInputFileBuilder setType(InputFile.Type type) {\n    this.type = type;\n    return this;\n  }\n\n  public TestInputFileBuilder setLines(int lines) {\n    this.lines = lines;\n    return this;\n  }\n\n  /**\n   * Set contents of the file and calculates metadata from it.\n   * The contents will be returned by {@link InputFile#contents()} and {@link InputFile#inputStream()} and can be\n   * inconsistent with the actual physical file pointed by {@link InputFile#path()}, {@link InputFile#absolutePath()}, etc.\n   */\n  public TestInputFileBuilder setContents(String content) {\n    this.contents = content;\n    initMetadata(content);\n    return this;\n  }\n\n  public TestInputFileBuilder setLastValidOffset(int lastValidOffset) {\n    this.lastValidOffset = lastValidOffset;\n    return this;\n  }\n\n  public TestInputFileBuilder setOriginalLineStartOffsets(int[] originalLineStartOffsets) {\n    this.originalLineStartOffsets = originalLineStartOffsets;\n    return this;\n  }\n\n  public TestInputFileBuilder setMetadata(FileMetadata.Metadata metadata) {\n    this.setLines(metadata.lines());\n    this.setLastValidOffset(metadata.lastValidOffset());\n    this.setOriginalLineStartOffsets(metadata.originalLineOffsets());\n    return this;\n  }\n\n  public TestInputFileBuilder initMetadata(String content) {\n    return setMetadata(\n      new FileMetadata().readMetadata(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8, URI.create(\"file://test\"), null));\n  }\n\n  public SonarLintInputFile build() {\n    ClientInputFile clientInputFile = new InMemoryTestClientInputFile(contents, relativePath, baseDir != null ? baseDir.resolve(relativePath) : null, type == Type.TEST,\n      language);\n    return new SonarLintInputFile(clientInputFile, f -> new FileMetadata.Metadata(lines, originalLineStartOffsets, lastValidOffset))\n      .setType(type)\n      .setLanguage(language);\n  }\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/resources/IssueExclusionsRegexpScannerTests/file-with-double-regexp-mess.txt",
    "content": "package org.sonar.plugins.switchoffviolations.pattern;\n\nimport com.google.common.collect.Sets;\n\nimport java.util.Set;\n\n/**\n * \n */\npublic class LineRange {\n  int from, to;\n\n  public LineRange(int from, int to) {\n    if (to < from) {\n      throw new IllegalArgumentException(\"Line range is not valid: \" + from + \" must be greater than \" + to);\n    }\n    this.from = from;\n    this.to = to;\n  }\n\n  // SONAR-OFF\n  public boolean in(int lineId) {\n    return from <= lineId && lineId <= to;\n  }\n  // FOO-OFF\n\n  public Set<Integer> toLines() {\n    Set<Integer> lines = Sets.newLinkedHashSet();\n    // SONAR-ON\n    for (int index = from; index <= to; index++) {\n      lines.add(index);\n    }\n    // FOO-ON\n    return lines;\n  }\n\n}"
  },
  {
    "path": "backend/analysis-engine/src/test/resources/IssueExclusionsRegexpScannerTests/file-with-double-regexp-twice.txt",
    "content": "package org.sonar.plugins.switchoffviolations.pattern;\n\nimport com.google.common.collect.Sets;\n\nimport java.util.Set;\n\n/**\n * \n */\npublic class LineRange {\n  int from, to;\n\n  public LineRange(int from, int to) {\n    if (to < from) {\n      throw new IllegalArgumentException(\"Line range is not valid: \" + from + \" must be greater than \" + to);\n    }\n    this.from = from;\n    this.to = to;\n  }\n\n  // SONAR-OFF\n  public boolean in(int lineId) {\n    return from <= lineId && lineId <= to;\n  }\n  // SONAR-ON\n\n  public Set<Integer> toLines() {\n    Set<Integer> lines = Sets.newLinkedHashSet();\n    // FOO-OFF\n    for (int index = from; index <= to; index++) {\n      lines.add(index);\n    }\n    // FOO-ON\n    return lines;\n  }\n\n}"
  },
  {
    "path": "backend/analysis-engine/src/test/resources/IssueExclusionsRegexpScannerTests/file-with-double-regexp-unfinished.txt",
    "content": "package org.sonar.plugins.switchoffviolations.pattern;\n\nimport com.google.common.collect.Sets;\n\nimport java.util.Set;\n\n/**\n * \n */\npublic class LineRange {\n  int from, to;\n\n  public LineRange(int from, int to) {\n    if (to < from) {\n      throw new IllegalArgumentException(\"Line range is not valid: \" + from + \" must be greater than \" + to);\n    }\n    this.from = from;\n    this.to = to;\n  }\n\n  // SONAR-OFF\n  public boolean in(int lineId) {\n    return from <= lineId && lineId <= to;\n  }\n\n  public Set<Integer> toLines() {\n    Set<Integer> lines = Sets.newLinkedHashSet();\n    for (int index = from; index <= to; index++) {\n      lines.add(index);\n    }\n    return lines;\n  }\n\n}"
  },
  {
    "path": "backend/analysis-engine/src/test/resources/IssueExclusionsRegexpScannerTests/file-with-double-regexp-wrong-order.txt",
    "content": "package org.sonar.plugins.switchoffviolations.pattern;\n\nimport com.google.common.collect.Sets;\n\nimport java.util.Set;\n\n/**\n * \n */\npublic class LineRange {\n  int from, to;\n\n  public LineRange(int from, int to) {\n    if (to < from) {\n      throw new IllegalArgumentException(\"Line range is not valid: \" + from + \" must be greater than \" + to);\n    }\n    this.from = from;\n    this.to = to;\n  }\n\n  // SONAR-ON\n  public boolean in(int lineId) {\n    return from <= lineId && lineId <= to;\n  }\n  // SONAR-OFF\n\n  public Set<Integer> toLines() {\n    Set<Integer> lines = Sets.newLinkedHashSet();\n    for (int index = from; index <= to; index++) {\n      lines.add(index);\n    }\n    return lines;\n  }\n\n}"
  },
  {
    "path": "backend/analysis-engine/src/test/resources/IssueExclusionsRegexpScannerTests/file-with-double-regexp.txt",
    "content": "package org.sonar.plugins.switchoffviolations.pattern;\n\nimport com.google.common.collect.Sets;\n\nimport java.util.Set;\n\n/**\n * \n */\npublic class LineRange {\n  int from, to;\n\n  public LineRange(int from, int to) {\n    if (to < from) {\n      throw new IllegalArgumentException(\"Line range is not valid: \" + from + \" must be greater than \" + to);\n    }\n    this.from = from;\n    this.to = to;\n  }\n\n  // SONAR-OFF\n  public boolean in(int lineId) {\n    return from <= lineId && lineId <= to;\n  }\n  // SONAR-ON\n\n  public Set<Integer> toLines() {\n    Set<Integer> lines = Sets.newLinkedHashSet();\n    for (int index = from; index <= to; index++) {\n      lines.add(index);\n    }\n    return lines;\n  }\n\n}"
  },
  {
    "path": "backend/analysis-engine/src/test/resources/IssueExclusionsRegexpScannerTests/file-with-no-regexp.txt",
    "content": "package org.sonar.plugins.switchoffviolations.pattern;\n\nimport com.google.common.collect.Sets;\n\nimport java.util.Set;\n\n/**\n * \n */\npublic class LineRange {\n  int from, to;\n\n  public LineRange(int from, int to) {\n    if (to < from) {\n      throw new IllegalArgumentException(\"Line range is not valid: \" + from + \" must be greater than \" + to);\n    }\n    this.from = from;\n    this.to = to;\n  }\n\n  public boolean in(int lineId) {\n    return from <= lineId && lineId <= to;\n  }\n\n  public Set<Integer> toLines() {\n    Set<Integer> lines = Sets.newLinkedHashSet();\n    for (int index = from; index <= to; index++) {\n      lines.add(index);\n    }\n    return lines;\n  }\n\n}"
  },
  {
    "path": "backend/analysis-engine/src/test/resources/IssueExclusionsRegexpScannerTests/file-with-single-regexp-and-double-regexp.txt",
    "content": "package org.sonar.plugins.switchoffviolations.pattern;\n\nimport com.google.common.collect.Sets;\n\n  // SONAR-OFF\n\nimport java.util.Set;\n\n/**\n * @SONAR-IGNORE-ALL\n */\npublic class LineRange {\n  int from, to;\n\n  public LineRange(int from, int to) {\n    if (to < from) {\n      throw new IllegalArgumentException(\"Line range is not valid: \" + from + \" must be greater than \" + to);\n    }\n    this.from = from;\n    this.to = to;\n  }\n\n  public boolean in(int lineId) {\n    return from <= lineId && lineId <= to;\n  }\n  // SONAR-ON\n\n  public Set<Integer> toLines() {\n    Set<Integer> lines = Sets.newLinkedHashSet();\n    for (int index = from; index <= to; index++) {\n      lines.add(index);\n    }\n    return lines;\n  }\n\n}"
  },
  {
    "path": "backend/analysis-engine/src/test/resources/IssueExclusionsRegexpScannerTests/file-with-single-regexp-last-line.txt",
    "content": "package org.sonar.plugins.switchoffviolations.pattern;\n\nimport com.google.common.collect.Sets;\n\nimport java.util.Set;\n\npublic class LineRange {\n  int from, to;\n\n  public LineRange(int from, int to) {\n    if (to < from) {\n      throw new IllegalArgumentException(\"Line range is not valid: \" + from + \" must be greater than \" + to);\n    }\n    this.from = from;\n    this.to = to;\n  }\n\n  public boolean in(int lineId) {\n    return from <= lineId && lineId <= to;\n  }\n\n  public Set<Integer> toLines() {\n    Set<Integer> lines = Sets.newLinkedHashSet();\n    for (int index = from; index <= to; index++) {\n      lines.add(index);\n    }\n    return lines;\n  }\n\n}\n// @SONAR-IGNORE-ALL"
  },
  {
    "path": "backend/analysis-engine/src/test/resources/IssueExclusionsRegexpScannerTests/file-with-single-regexp.txt",
    "content": "package org.sonar.plugins.switchoffviolations.pattern;\n\nimport com.google.common.collect.Sets;\n\nimport java.util.Set;\n\n/**\n * @SONAR-IGNORE-ALL\n */\npublic class LineRange {\n  int from, to;\n\n  public LineRange(int from, int to) {\n    if (to < from) {\n      throw new IllegalArgumentException(\"Line range is not valid: \" + from + \" must be greater than \" + to);\n    }\n    this.from = from;\n    this.to = to;\n  }\n\n  public boolean in(int lineId) {\n    return from <= lineId && lineId <= to;\n  }\n\n  public Set<Integer> toLines() {\n    Set<Integer> lines = Sets.newLinkedHashSet();\n    for (int index = from; index <= to; index++) {\n      lines.add(index);\n    }\n    return lines;\n  }\n\n}\n"
  },
  {
    "path": "backend/analysis-engine/src/test/resources/logback-test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE configuration>\n\n<configuration>\n  <include resource=\"logback-shared.xml\"/>\n</configuration>"
  },
  {
    "path": "backend/cli/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-backend-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../pom.xml</relativePath>\n  </parent>\n  <artifactId>sonarlint-backend-cli</artifactId>\n  <name>SonarLint Core - Backend CLI</name>\n  <description>SonarLint backend as a standalone CLI</description>\n  <properties>\n    <unpack.dir>${project.build.directory}/unpack</unpack.dir>\n    <jre.dirname.linux>jdk-21.0.10+7-jre</jre.dirname.linux>\n    <jre.dirname.windows>jdk-21.0.10+7-jre</jre.dirname.windows>\n    <jre.dirname.macosx>jdk-21.0.10+7-jre/Contents/Home</jre.dirname.macosx>\n  </properties>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-jar-plugin</artifactId>\n        <configuration>\n          <archive>\n            <manifest>\n              <mainClass>org.sonarsource.sonarlint.core.backend.cli.SonarLintServerCli</mainClass>\n              <addClasspath>true</addClasspath>\n            </manifest>\n          </archive>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n\n  <profiles>\n      <profile>\n        <id>dist-no-arch</id>\n          <build>\n            <plugins>\n              <plugin>\n                <artifactId>maven-assembly-plugin</artifactId>\n                <executions>\n                  <execution>\n                    <id>assemble-no-arch</id>\n                    <phase>package</phase>\n                    <goals>\n                      <goal>single</goal>\n                    </goals>\n                    <configuration>\n                      <escapeString>\\</escapeString>\n                      <descriptors>\n                        <descriptor>src/main/assembly/dist-no-arch.xml</descriptor>\n                      </descriptors>\n                    </configuration>\n                  </execution>\n                </executions>\n              </plugin>\n            </plugins>\n          </build>\n      </profile>\n\n      <profile>\n        <activation>\n          <os>\n            <family>windows</family>\n          </os>\n        </activation>\n        <id>dist-windows_x64</id>\n          <build>\n            <plugins>\n              <plugin>\n                <groupId>com.googlecode.maven-download-plugin</groupId>\n                <artifactId>download-maven-plugin</artifactId>\n                <executions>\n                  <execution>\n                    <id>unpack-windows_x64</id>\n                    <phase>package</phase>\n                    <goals>\n                      <goal>wget</goal>\n                    </goals>\n                    <configuration>\n                      <url>https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.10%2B7/OpenJDK21U-jre_x64_windows_hotspot_21.0.10_7.zip</url>\n                      <unpack>true</unpack>\n                      <outputDirectory>${unpack.dir}/windows_x64</outputDirectory>\n                      <sha256>a6ac6789e51a2c245f41430c42e72b39ec706a449812fc5e4cbfc55ceed1e5ae</sha256>\n                    </configuration>\n                  </execution>\n                </executions>\n              </plugin>\n              <plugin>\n                <artifactId>maven-assembly-plugin</artifactId>\n                <executions>\n                  <execution>\n                    <id>assemble-windows_x64</id>\n                    <phase>package</phase>\n                    <goals>\n                      <goal>single</goal>\n                    </goals>\n                    <configuration>\n                      <escapeString>\\</escapeString>\n                      <descriptors>\n                        <descriptor>src/main/assembly/dist-windows_x64.xml</descriptor>\n                      </descriptors>\n                    </configuration>\n                  </execution>\n                </executions>\n              </plugin>\n            </plugins>\n          </build>\n      </profile>\n\n      <profile>\n        <activation>\n          <os>\n            <family>unix</family>\n          </os>\n        </activation>\n        <id>dist-linux_x64</id>\n          <build>\n            <plugins>\n              <plugin>\n                <groupId>com.googlecode.maven-download-plugin</groupId>\n                <artifactId>download-maven-plugin</artifactId>\n                <executions>\n                  <execution>\n                    <id>unpack-linux_x64</id>\n                    <phase>package</phase>\n                    <goals>\n                      <goal>wget</goal>\n                    </goals>\n                    <configuration>\n                      <url>https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.10%2B7/OpenJDK21U-jre_x64_linux_hotspot_21.0.10_7.tar.gz</url>\n                      <unpack>true</unpack>\n                      <outputDirectory>${unpack.dir}/linux_x64</outputDirectory>\n                      <sha256>991be6ac6725e76109ecbd131d658f992dcbeacba3a8b4b6650302c8012b52fb</sha256>\n                    </configuration>\n                  </execution>\n                </executions>\n              </plugin>\n              <plugin>\n                <artifactId>maven-assembly-plugin</artifactId>\n                <executions>\n                  <execution>\n                    <id>assemble-linux_x64</id>\n                    <phase>package</phase>\n                    <goals>\n                      <goal>single</goal>\n                    </goals>\n                    <configuration>\n                      <escapeString>\\</escapeString>\n                      <descriptors>\n                        <descriptor>src/main/assembly/dist-linux_x64.xml</descriptor>\n                      </descriptors>\n                    </configuration>\n                  </execution>\n                </executions>\n              </plugin>\n            </plugins>\n          </build>\n      </profile>\n\n      <profile>\n        <id>dist-linux_aarch64</id>\n          <build>\n            <plugins>\n              <plugin>\n                <groupId>com.googlecode.maven-download-plugin</groupId>\n                <artifactId>download-maven-plugin</artifactId>\n                <executions>\n                  <execution>\n                    <id>unpack-linux_aarch64</id>\n                    <phase>package</phase>\n                    <goals>\n                      <goal>wget</goal>\n                    </goals>\n                    <configuration>\n                      <url>https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.10%2B7/OpenJDK21U-jre_aarch64_linux_hotspot_21.0.10_7.tar.gz</url>\n                      <unpack>true</unpack>\n                      <outputDirectory>${unpack.dir}/linux_aarch64</outputDirectory>\n                      <sha256>3ca84da7c4f57eee8d7e7f0645dc904a3a06456d32b37a4dd57a5e7527245250</sha256>\n                    </configuration>\n                  </execution>\n                </executions>\n              </plugin>\n              <plugin>\n                <artifactId>maven-assembly-plugin</artifactId>\n                <executions>\n                  <execution>\n                    <id>assemble-linux_aarch64</id>\n                    <phase>package</phase>\n                    <goals>\n                      <goal>single</goal>\n                    </goals>\n                    <configuration>\n                      <escapeString>\\</escapeString>\n                      <descriptors>\n                        <descriptor>src/main/assembly/dist-linux_aarch64.xml</descriptor>\n                      </descriptors>\n                    </configuration>\n                  </execution>\n                </executions>\n              </plugin>\n            </plugins>\n          </build>\n      </profile>\n\n      <profile>\n      <id>dist-macosx_x64</id>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>com.googlecode.maven-download-plugin</groupId>\n            <artifactId>download-maven-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>unpack-macosx_x64</id>\n                <phase>package</phase>\n                <goals>\n                  <goal>wget</goal>\n                </goals>\n                <configuration>\n                  <url>https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.10%2B7/OpenJDK21U-jre_x64_mac_hotspot_21.0.10_7.tar.gz</url>\n                  <unpack>true</unpack>\n                  <outputDirectory>${unpack.dir}/macosx_x64</outputDirectory>\n                  <sha256>008d2bb904c0e07500b92bf4b0f8d434d694b13d5189f06358a52d46b1351f37</sha256>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <artifactId>maven-assembly-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>assemble-macosx_x64</id>\n                <phase>package</phase>\n                <goals>\n                  <goal>single</goal>\n                </goals>\n                <configuration>\n                  <escapeString>\\</escapeString>\n                  <descriptors>\n                    <descriptor>src/main/assembly/dist-macosx_x64.xml</descriptor>\n                  </descriptors>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n\n    <profile>\n      <id>dist-macosx_aarch64</id>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>com.googlecode.maven-download-plugin</groupId>\n            <artifactId>download-maven-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>unpack-macosx_aarch64</id>\n                <phase>package</phase>\n                <goals>\n                  <goal>wget</goal>\n                </goals>\n                <configuration>\n                  <url>https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.10%2B7/OpenJDK21U-jre_aarch64_mac_hotspot_21.0.10_7.tar.gz</url>\n                  <unpack>true</unpack>\n                  <outputDirectory>${unpack.dir}/macosx_aarch64</outputDirectory>\n                  <sha256>c3be8c87f1a5cdc727903546eb810e112f94cd7222dac6a9d3f3146ee932008d</sha256>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <artifactId>maven-assembly-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>assemble-macosx_aarch64</id>\n                <phase>package</phase>\n                <goals>\n                  <goal>single</goal>\n                </goals>\n                <configuration>\n                  <escapeString>\\</escapeString>\n                  <descriptors>\n                    <descriptor>src/main/assembly/dist-macosx_aarch64.xml</descriptor>\n                  </descriptors>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n  </profiles>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.code.findbugs</groupId>\n      <artifactId>jsr305</artifactId>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>info.picocli</groupId>\n      <artifactId>picocli</artifactId>\n      <version>4.7.7</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-rpc-impl</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n\n    <!-- unit tests -->\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-engine</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.assertj</groupId>\n      <artifactId>assertj-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n</project>\n"
  },
  {
    "path": "backend/cli/src/main/assembly/dist-linux_aarch64.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--    Generate a distribution archive which contains executable application with dependencies and supporting files\n        - The actual jar\n        - the dependencies\n        - the JRE\n        - the launcher script\n          -->\n<assembly>\n  <id>linux_aarch64</id>\n  <includeBaseDirectory>false</includeBaseDirectory>\n  <formats>\n    <format>tar.gz</format>\n  </formats>\n\n  <!-- The dependencies -->\n  <dependencySets>\n    <dependencySet>\n      <outputDirectory>/lib</outputDirectory>\n      <unpack>false</unpack>\n      <excludes>\n        <exclude>${artifact}</exclude>\n      </excludes>\n    </dependencySet>\n  </dependencySets>\n\n  <fileSets>\n    <fileSet>\n      <!-- The sloop jar-->\n      <directory>${project.build.directory}</directory>\n      <outputDirectory>lib</outputDirectory>\n      <fileMode>0644</fileMode>\n      <includes>\n        <include>**/${project.artifactId}-${project.version}.jar</include>\n      </includes>\n    </fileSet>\n\n    <!-- jre basic, except bin/ -->\n    <fileSet>\n      <directory>${unpack.dir}/linux_aarch64/${jre.dirname.linux}</directory>\n      <outputDirectory>jre</outputDirectory>\n      <excludes>\n        <exclude>bin/**</exclude>\n        <exclude>man/**</exclude>\n        <exclude>lib/jspawnhelper</exclude>\n        <exclude>lib/jexec</exclude>\n        <exclude>plugin/**</exclude>\n      </excludes>\n    </fileSet>\n\n    <!-- jre/bin/java -->\n    <fileSet>\n      <directory>${unpack.dir}/linux_aarch64/${jre.dirname.linux}/bin</directory>\n      <outputDirectory>jre/bin</outputDirectory>\n      <includes>\n        <include>java</include>\n      </includes>\n      <fileMode>0755</fileMode>\n    </fileSet>\n\n    <!-- jre lib executable files -->\n    <fileSet>\n      <directory>${unpack.dir}/linux_aarch64/${jre.dirname.linux}/lib</directory>\n      <outputDirectory>jre/lib</outputDirectory>\n      <includes>\n        <include>jspawnhelper</include>\n        <include>jexec</include>\n      </includes>\n      <fileMode>0755</fileMode>\n    </fileSet>\n  </fileSets>\n</assembly>\n"
  },
  {
    "path": "backend/cli/src/main/assembly/dist-linux_x64.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--    Generate a distribution archive which contains executable application with dependencies and supporting files\n        - The actual jar\n        - the dependencies\n        - the JRE\n        - the launcher script\n          -->\n<assembly>\n  <id>linux_x64</id>\n  <includeBaseDirectory>false</includeBaseDirectory>\n  <formats>\n    <format>tar.gz</format>\n  </formats>\n\n  <!-- The dependencies -->\n  <dependencySets>\n    <dependencySet>\n      <outputDirectory>/lib</outputDirectory>\n      <unpack>false</unpack>\n      <excludes>\n        <exclude>${artifact}</exclude>\n      </excludes>\n    </dependencySet>\n  </dependencySets>\n\n  <fileSets>\n    <fileSet>\n      <!-- The sloop jar-->\n      <directory>${project.build.directory}</directory>\n      <outputDirectory>lib</outputDirectory>\n      <fileMode>0644</fileMode>\n      <includes>\n        <include>**/${project.artifactId}-${project.version}.jar</include>\n      </includes>\n    </fileSet>\n\n    <!-- jre basic, except bin/ -->\n    <fileSet>\n      <directory>${unpack.dir}/linux_x64/${jre.dirname.linux}</directory>\n      <outputDirectory>jre</outputDirectory>\n      <excludes>\n        <exclude>bin/**</exclude>\n        <exclude>man/**</exclude>\n        <exclude>lib/jspawnhelper</exclude>\n        <exclude>lib/jexec</exclude>\n        <exclude>plugin/**</exclude>\n      </excludes>\n    </fileSet>\n\n    <!-- jre/bin/java -->\n    <fileSet>\n      <directory>${unpack.dir}/linux_x64/${jre.dirname.linux}/bin</directory>\n      <outputDirectory>jre/bin</outputDirectory>\n      <includes>\n        <include>java</include>\n      </includes>\n      <fileMode>0755</fileMode>\n    </fileSet>\n\n    <!-- jre lib executable files -->\n    <fileSet>\n      <directory>${unpack.dir}/linux_x64/${jre.dirname.linux}/lib</directory>\n      <outputDirectory>jre/lib</outputDirectory>\n      <includes>\n        <include>jspawnhelper</include>\n        <include>jexec</include>\n      </includes>\n      <fileMode>0755</fileMode>\n    </fileSet>\n  </fileSets>\n</assembly>\n"
  },
  {
    "path": "backend/cli/src/main/assembly/dist-macosx_aarch64.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--    Generate a distribution archive which contains executable application with dependencies and supporting files\n        - The actual jar\n        - the dependencies\n        - the JRE\n        - the launcher script\n          -->\n<assembly>\n  <id>macosx_aarch64</id>\n  <includeBaseDirectory>false</includeBaseDirectory>\n  <formats>\n    <format>tar.gz</format>\n  </formats>\n\n  <!-- The dependencies -->\n  <dependencySets>\n    <dependencySet>\n      <outputDirectory>/lib</outputDirectory>\n      <unpack>false</unpack>\n      <excludes>\n        <exclude>${artifact}</exclude>\n      </excludes>\n    </dependencySet>\n  </dependencySets>\n\n  <fileSets>\n    <fileSet>\n      <!-- The sloop jar-->\n      <directory>${project.build.directory}</directory>\n      <outputDirectory>lib</outputDirectory>\n      <fileMode>0644</fileMode>\n      <includes>\n        <include>**/${project.artifactId}-${project.version}.jar</include>\n      </includes>\n    </fileSet>\n\n    <!-- jre basic, except bin/ and misc -->\n    <fileSet>\n      <directory>${unpack.dir}/macosx_aarch64/${jre.dirname.macosx}</directory>\n      <outputDirectory>jre</outputDirectory>\n      <excludes>\n        <exclude>bin/**</exclude>\n        <exclude>man/**</exclude>\n        <exclude>lib/jspawnhelper</exclude>\n      </excludes>\n    </fileSet>\n\n    <!-- jre/bin/java -->\n    <fileSet>\n      <directory>${unpack.dir}/macosx_aarch64/${jre.dirname.macosx}/bin</directory>\n      <outputDirectory>jre/bin</outputDirectory>\n      <includes>\n        <include>java</include>\n      </includes>\n      <fileMode>0755</fileMode>\n    </fileSet>\n\n    <!-- jre lib executable files -->\n    <fileSet>\n      <directory>${unpack.dir}/macosx_aarch64/${jre.dirname.macosx}/lib</directory>\n      <outputDirectory>jre/lib</outputDirectory>\n      <includes>\n        <include>jspawnhelper</include>\n      </includes>\n      <fileMode>0755</fileMode>\n    </fileSet>\n\n  </fileSets>\n</assembly>\n"
  },
  {
    "path": "backend/cli/src/main/assembly/dist-macosx_x64.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--    Generate a distribution archive which contains executable application with dependencies and supporting files\n        - The actual jar\n        - the dependencies\n        - the JRE\n        - the launcher script\n          -->\n<assembly>\n  <id>macosx_x64</id>\n  <includeBaseDirectory>false</includeBaseDirectory>\n  <formats>\n    <format>tar.gz</format>\n  </formats>\n\n  <!-- The dependencies -->\n  <dependencySets>\n    <dependencySet>\n      <outputDirectory>/lib</outputDirectory>\n      <unpack>false</unpack>\n      <excludes>\n        <exclude>${artifact}</exclude>\n      </excludes>\n    </dependencySet>\n  </dependencySets>\n\n  <fileSets>\n    <fileSet>\n      <!-- The sloop jar-->\n      <directory>${project.build.directory}</directory>\n      <outputDirectory>lib</outputDirectory>\n      <fileMode>0644</fileMode>\n      <includes>\n        <include>**/${project.artifactId}-${project.version}.jar</include>\n      </includes>\n    </fileSet>\n\n    <!-- jre basic, except bin/ and misc -->\n    <fileSet>\n      <directory>${unpack.dir}/macosx_x64/${jre.dirname.macosx}</directory>\n      <outputDirectory>jre</outputDirectory>\n      <excludes>\n        <exclude>bin/**</exclude>\n        <exclude>man/**</exclude>\n        <exclude>lib/jspawnhelper</exclude>\n      </excludes>\n    </fileSet>\n\n    <!-- jre bin -->\n    <fileSet>\n      <directory>${unpack.dir}/macosx_x64/${jre.dirname.macosx}/bin</directory>\n      <outputDirectory>jre/bin</outputDirectory>\n      <includes>\n        <include>java</include>\n      </includes>\n      <fileMode>0755</fileMode>\n    </fileSet>\n\n    <!-- jre lib executable files -->\n    <fileSet>\n      <directory>${unpack.dir}/macosx_x64/${jre.dirname.macosx}/lib</directory>\n      <outputDirectory>jre/lib</outputDirectory>\n      <includes>\n        <include>jspawnhelper</include>\n      </includes>\n      <fileMode>0755</fileMode>\n    </fileSet>\n  </fileSets>\n</assembly>\n"
  },
  {
    "path": "backend/cli/src/main/assembly/dist-no-arch.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--    Generate a distribution archive which contains executable application with dependencies and supporting files\n        - The actual jar\n        - the dependencies\n        - the launcher scripts\n          -->\n<assembly>\n  <id>no-arch</id>\n  <includeBaseDirectory>false</includeBaseDirectory>\n  <formats>\n    <!-- ZIP archive as Windows cannot work with tar.gz out of the box but Unix can work with zip out of the box  -->\n    <format>zip</format>\n  </formats>\n\n  <!-- The dependencies -->\n  <dependencySets>\n    <dependencySet>\n      <outputDirectory>/lib</outputDirectory>\n      <unpack>false</unpack>\n      <excludes>\n        <exclude>${artifact}</exclude>\n      </excludes>\n    </dependencySet>\n  </dependencySets>\n\n  <fileSets>\n    <fileSet>\n      <!-- The sloop jar-->\n      <directory>${project.build.directory}</directory>\n      <outputDirectory>lib</outputDirectory>\n      <fileMode>0644</fileMode>\n      <includes>\n        <include>**/${project.artifactId}-${project.version}.jar</include>\n      </includes>\n    </fileSet>\n  </fileSets>\n</assembly>\n"
  },
  {
    "path": "backend/cli/src/main/assembly/dist-windows_x64.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--    Generate a distribution archive which contains executable application with dependencies and supporting files\n        - The actual jar\n        - the dependencies\n        - the JRE\n        - the launcher script\n          -->\n<assembly>\n  <id>windows_x64</id>\n  <includeBaseDirectory>false</includeBaseDirectory>\n  <formats>\n    <format>zip</format>\n  </formats>\n\n  <!-- The dependencies -->\n  <dependencySets>\n    <dependencySet>\n      <outputDirectory>/lib</outputDirectory>\n      <unpack>false</unpack>\n      <excludes>\n        <exclude>${artifact}</exclude>\n      </excludes>\n    </dependencySet>\n  </dependencySets>\n\n  <fileSets>\n    <fileSet>\n      <!-- The sloop jar-->\n      <directory>${project.build.directory}</directory>\n      <outputDirectory>lib</outputDirectory>\n      <fileMode>0644</fileMode>\n      <includes>\n        <include>**/${project.artifactId}-${project.version}.jar</include>\n      </includes>\n    </fileSet>\n\n    <!-- jre basic, except bin/ -->\n    <fileSet>\n      <directory>${unpack.dir}/windows_x64/${jre.dirname.windows}</directory>\n      <outputDirectory>jre</outputDirectory>\n      <excludes>\n        <exclude>bin/**</exclude>\n        <exclude>man/**</exclude>\n        <exclude>plugin/**</exclude>\n      </excludes>\n    </fileSet>\n\n    <!-- jre bin -->\n    <fileSet>\n      <directory>${unpack.dir}/windows_x64/${jre.dirname.windows}/bin</directory>\n      <outputDirectory>jre/bin</outputDirectory>\n      <fileMode>0755</fileMode>\n    </fileSet>\n  </fileSets>\n</assembly>\n"
  },
  {
    "path": "backend/cli/src/main/java/org/sonarsource/sonarlint/core/backend/cli/EndOfStreamAwareInputStream.java",
    "content": "/*\n * SonarLint Core - Backend CLI\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.backend.cli;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.concurrent.CompletableFuture;\n\npublic class EndOfStreamAwareInputStream extends InputStream {\n  private final InputStream delegate;\n  private final CompletableFuture<Void> onExit = new CompletableFuture<>();\n\n  public EndOfStreamAwareInputStream(InputStream delegate) {\n    this.delegate = delegate;\n  }\n\n  public CompletableFuture<Void> onExit() {\n    return onExit;\n  }\n\n  @Override\n  public int read() throws IOException {\n    return exitIfNegative(delegate::read);\n  }\n\n  @Override\n  public int read(byte[] b) throws IOException {\n    return exitIfNegative(() -> delegate.read(b));\n  }\n\n  @Override\n  public int read(byte[] b, int off, int len) throws IOException {\n    return exitIfNegative(() -> delegate.read(b, off, len));\n  }\n\n  private int exitIfNegative(SupplierWithIOException<Integer> call) throws IOException {\n    int result = call.get();\n\n    if (result < 0) {\n      onExit.complete(null);\n    }\n\n    return result;\n  }\n\n  @FunctionalInterface\n  private interface SupplierWithIOException<T> {\n    /**\n     * @return result\n     */\n    T get() throws IOException;\n  }\n}\n"
  },
  {
    "path": "backend/cli/src/main/java/org/sonarsource/sonarlint/core/backend/cli/SonarLintServerCli.java",
    "content": "/*\n * SonarLint Core - Backend CLI\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.backend.cli;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.InputStream;\nimport java.io.PrintStream;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.CancellationException;\nimport org.sonarsource.sonarlint.core.rpc.impl.BackendJsonRpcLauncher;\nimport picocli.CommandLine;\n\n@CommandLine.Command(name = \"slcore\", mixinStandardHelpOptions = true, description = \"The SonarLint Core backend\")\npublic class SonarLintServerCli implements Callable<Integer> {\n\n  @Override\n  public Integer call() {\n    return run(System.in, System.out);\n  }\n\n  int run(InputStream originalStdIn, PrintStream originalStdOut) {\n    var inputStream = new EndOfStreamAwareInputStream(originalStdIn);\n    System.setIn(new ByteArrayInputStream(new byte[0]));\n    // Redirect all logs to stderr for now, would be better to go to a file later\n    System.setOut(System.err);\n\n    try {\n      var rpcLauncher = new BackendJsonRpcLauncher(inputStream, originalStdOut);\n      var rpcServer = rpcLauncher.getServer();\n      inputStream.onExit().thenRun(() -> {\n        if (!rpcServer.isReaderShutdown()) {\n          System.err.println(\"Input stream has closed, exiting...\");\n          rpcServer.shutdown();\n        }\n      });\n      rpcServer.getClientListener().get();\n    } catch (CancellationException shutdown) {\n      System.err.println(\"Server is shutting down...\");\n    } catch (InterruptedException e) {\n      e.printStackTrace();\n      Thread.currentThread().interrupt();\n      return -1;\n    } catch (Exception e) {\n      e.printStackTrace();\n      return -1;\n    }\n\n    return 0;\n  }\n\n  public static void main(String... args) {\n    var exitCode = new CommandLine(new SonarLintServerCli()).execute(args);\n    System.exit(exitCode);\n  }\n}\n"
  },
  {
    "path": "backend/cli/src/main/java/org/sonarsource/sonarlint/core/backend/cli/package-info.java",
    "content": "/*\n * SonarLint Core - Backend CLI\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.backend.cli;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "backend/cli/src/test/java/org/sonarsource/sonarlint/core/backend/cli/EndOfStreamAwareInputStreamTest.java",
    "content": "/*\n * SonarLint Core - Backend CLI\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.backend.cli;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass EndOfStreamAwareInputStreamTest {\n  @Test\n  void it_should_complete_onExit_when_reading_single_byte_and_stream_is_empty() throws IOException {\n    var stream = new EndOfStreamAwareInputStream(new ByteArrayInputStream(new byte[0]));\n\n    var bytesRead = stream.read();\n\n    assertThat(bytesRead).isEqualTo(-1);\n    assertThat(stream.onExit()).isCompleted();\n  }\n\n  @Test\n  void it_should_complete_onExit_when_reading_byte_array_and_stream_is_empty() throws IOException {\n    var stream = new EndOfStreamAwareInputStream(new ByteArrayInputStream(new byte[0]));\n\n    var bytesRead = stream.read(new byte[5]);\n\n    assertThat(bytesRead).isEqualTo(-1);\n    assertThat(stream.onExit()).isCompleted();\n  }\n\n  @Test\n  void it_should_complete_onExit_when_reading_byte_array_slice_and_stream_is_empty() throws IOException {\n    var stream = new EndOfStreamAwareInputStream(new ByteArrayInputStream(new byte[0]));\n\n    var bytesRead = stream.read(new byte[5], 0, 3);\n\n    assertThat(bytesRead).isEqualTo(-1);\n    assertThat(stream.onExit()).isCompleted();\n  }\n\n  @Test\n  void it_should_not_complete_onExit_if_stream_is_not_empty() throws IOException {\n    var stream = new EndOfStreamAwareInputStream(new ByteArrayInputStream(new byte[] {0b01}));\n\n    var bytesRead = stream.read();\n\n    assertThat(bytesRead).isEqualTo(1);\n    assertThat(stream.onExit()).isNotCompleted();\n  }\n}\n"
  },
  {
    "path": "backend/cli/src/test/java/org/sonarsource/sonarlint/core/backend/cli/SonarLintServerCliTest.java",
    "content": "/*\n * SonarLint Core - Backend CLI\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.backend.cli;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Future;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.rpc.impl.BackendJsonRpcLauncher;\nimport org.sonarsource.sonarlint.core.rpc.impl.SonarLintRpcServerImpl;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.mockConstructionWithAnswer;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.when;\n\nclass SonarLintServerCliTest {\n  @Test\n  void it_should_return_success_exit_code_when_parent_stream_ends() {\n    var exitCode = new SonarLintServerCli().run(new ByteArrayInputStream(new byte[0]), new PrintStream(new ByteArrayOutputStream()));\n\n    assertThat(exitCode).isZero();\n  }\n\n  @Test\n  void log_when_client_is_closed() throws IOException {\n    var outContent = new ByteArrayOutputStream();\n    System.setErr(new PrintStream(outContent));\n\n    var inputStream = spy(new ByteArrayInputStream(new byte[0]));\n    when(inputStream.available()).thenReturn(1);\n    var exitCode = new SonarLintServerCli().run(inputStream, new PrintStream(new ByteArrayOutputStream()));\n\n    assertThat(outContent.toString()).isEqualToIgnoringNewLines(\"Input stream has closed, exiting...\");\n\n    assertThat(exitCode).isZero();\n    outContent.close();\n  }\n\n  @Test\n  void log_when_connection_canceled() {\n    var outContent = new ByteArrayOutputStream();\n    System.setErr(new PrintStream(outContent));\n\n    var mockServer = mock(SonarLintRpcServerImpl.class);\n    doThrow(CancellationException.class).when(mockServer).getClientListener();\n    try (var ignored = mockConstructionWithAnswer(BackendJsonRpcLauncher.class, invocationOnMock -> mockServer)) {\n      var exitCode = new SonarLintServerCli().run(new ByteArrayInputStream(new byte[0]), new PrintStream(new ByteArrayOutputStream()));\n\n      assertThat(outContent.toString()).isEqualToIgnoringNewLines(\"Server is shutting down...\");\n      assertThat(exitCode).isZero();\n    }\n  }\n\n  @Test\n  void log_interrupted_exception() throws ExecutionException, InterruptedException {\n    var outContent = new ByteArrayOutputStream();\n    System.setErr(new PrintStream(outContent));\n\n    var mockServer = mock(SonarLintRpcServerImpl.class);\n    var mockFuture = mock(Future.class);\n    when(mockServer.getClientListener()).thenReturn(mockFuture);\n    doThrow(new InterruptedException(\"interrupted exc\")).when(mockFuture).get();\n    try (var ignored = mockConstructionWithAnswer(BackendJsonRpcLauncher.class, invocationOnMock -> mockServer)) {\n      var exitCode = new SonarLintServerCli().run(new ByteArrayInputStream(new byte[0]), new PrintStream(new ByteArrayOutputStream()));\n\n      assertThat(outContent.toString()).contains(\"java.lang.InterruptedException: interrupted exc\");\n      assertThat(exitCode).isEqualTo(-1);\n    }\n  }\n\n  @Test\n  void log_other_exceptions() {\n    var outContent = new ByteArrayOutputStream();\n    System.setErr(new PrintStream(outContent));\n\n    var mockServer = mock(SonarLintRpcServerImpl.class);\n    doThrow(new RuntimeException(\"an exc\")).when(mockServer).getClientListener();\n    try (var ignored = mockConstructionWithAnswer(BackendJsonRpcLauncher.class, invocationOnMock -> mockServer)) {\n      var exitCode = new SonarLintServerCli().run(new ByteArrayInputStream(new byte[0]), new PrintStream(new ByteArrayOutputStream()));\n\n      assertThat(outContent.toString()).contains(\"java.lang.RuntimeException: an exc\");\n      assertThat(exitCode).isEqualTo(-1);\n    }\n  }\n\n}"
  },
  {
    "path": "backend/commons/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-backend-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../pom.xml</relativePath>\n  </parent>\n  <artifactId>sonarlint-commons</artifactId>\n  <name>SonarLint Core - Commons</name>\n  <description>Common code for all SonarLint modules</description>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.code.findbugs</groupId>\n      <artifactId>jsr305</artifactId>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>com.h2database</groupId>\n      <artifactId>h2</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.jooq</groupId>\n      <artifactId>jooq</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.flywaydb</groupId>\n      <artifactId>flyway-core</artifactId>\n    </dependency>\n    <dependency>\n        <groupId>org.sonarsource.git.blame</groupId>\n        <artifactId>git-files-blame</artifactId>\n      <version>2.0.2.54</version>\n    </dependency>\n    <dependency>\n      <groupId>org.eclipse.jgit</groupId>\n      <artifactId>org.eclipse.jgit</artifactId>\n      <version>${jgit7.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>commons-io</groupId>\n      <artifactId>commons-io</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.google.code.gson</groupId>\n      <artifactId>gson</artifactId>\n    </dependency>\n    <!-- unit tests -->\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-engine</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-params</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.assertj</groupId>\n      <artifactId>assertj-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.awaitility</groupId>\n      <artifactId>awaitility</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>com.squareup.okhttp3</groupId>\n      <artifactId>mockwebserver3</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>ch.qos.logback</groupId>\n      <artifactId>logback-classic</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>io.sentry</groupId>\n      <artifactId>sentry</artifactId>\n      <version>${sentry.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>jakarta.inject</groupId>\n      <artifactId>jakarta.inject-api</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-lang3</artifactId>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <resources>\n      <resource>\n        <directory>src/main/resources</directory>\n        <includes>\n          <include>sl_core_version.txt</include>\n        </includes>\n        <filtering>true</filtering>\n      </resource>\n      <resource>\n        <directory>src/main/resources</directory>\n        <excludes>\n          <exclude>sl_core_version.txt</exclude>\n        </excludes>\n        <filtering>false</filtering>\n      </resource>\n    </resources>\n    <plugins>\n      <plugin>\n        <groupId>org.flywaydb</groupId>\n        <artifactId>flyway-maven-plugin</artifactId>\n          <executions>\n            <execution>\n              <phase>generate-sources</phase>\n              <goals>\n                <goal>migrate</goal>\n              </goals>\n            </execution>\n          </executions>\n            <configuration>\n            <url>jdbc:h2:${project.build.directory}/db/for-schema-generation.db</url>\n            <user>user</user>\n            <locations>\n                <location>filesystem:src/main/resources/db/migration/</location>\n            </locations>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.jooq</groupId>\n        <artifactId>jooq-codegen-maven</artifactId>\n        <executions>\n          <execution>\n            <phase>generate-sources</phase>\n            <goals>\n              <goal>generate</goal>\n            </goals>\n          </execution>\n        </executions>\n        <configuration>\n          <jdbc>\n            <driver>org.h2.Driver</driver>\n            <url>jdbc:h2:${project.build.directory}/db/for-schema-generation.db</url>\n            <user>user</user>\n          </jdbc>\n          <generator>\n            <database>\n              <name>org.jooq.meta.h2.H2Database</name>\n              <inputSchema>PUBLIC</inputSchema>\n            </database>\n            <target>\n              <packageName>org.sonarsource.sonarlint.core.commons.storage.model</packageName>\n              <directory>${project.build.directory}/generated-sources/jooq</directory>\n            </target>\n          </generator>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-jar-plugin</artifactId>\n        <executions>\n          <execution>\n            <goals>\n              <goal>test-jar</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/CleanCodeAttribute.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport static org.sonarsource.sonarlint.core.commons.CleanCodeAttributeCategory.ADAPTABLE;\nimport static org.sonarsource.sonarlint.core.commons.CleanCodeAttributeCategory.CONSISTENT;\nimport static org.sonarsource.sonarlint.core.commons.CleanCodeAttributeCategory.INTENTIONAL;\nimport static org.sonarsource.sonarlint.core.commons.CleanCodeAttributeCategory.RESPONSIBLE;\n\npublic enum CleanCodeAttribute {\n\n  CONVENTIONAL(CONSISTENT),\n  FORMATTED(CONSISTENT),\n  IDENTIFIABLE(CONSISTENT),\n\n  CLEAR(INTENTIONAL),\n  COMPLETE(INTENTIONAL),\n  EFFICIENT(INTENTIONAL),\n  LOGICAL(INTENTIONAL),\n\n  DISTINCT(ADAPTABLE),\n  FOCUSED(ADAPTABLE),\n  MODULAR(ADAPTABLE),\n  TESTED(ADAPTABLE),\n\n  LAWFUL(RESPONSIBLE),\n  RESPECTFUL(RESPONSIBLE),\n  TRUSTWORTHY(RESPONSIBLE);\n\n  private final CleanCodeAttributeCategory attributeCategory;\n\n\n  CleanCodeAttribute(CleanCodeAttributeCategory attributeCategory) {\n    this.attributeCategory = attributeCategory;\n  }\n\n\n  public CleanCodeAttributeCategory getAttributeCategory() {\n    return attributeCategory;\n  }\n\n  public static CleanCodeAttribute defaultCleanCodeAttribute() {\n    return CONVENTIONAL;\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/CleanCodeAttributeCategory.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\npublic enum CleanCodeAttributeCategory {\n  ADAPTABLE,\n  CONSISTENT,\n  INTENTIONAL,\n  RESPONSIBLE\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/ConnectionKind.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\npublic enum ConnectionKind {\n  SONARQUBE, SONARCLOUD\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/HotspotReviewStatus.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Set;\nimport javax.annotation.Nullable;\n\nimport static org.sonarsource.sonarlint.core.commons.ConnectionKind.SONARCLOUD;\nimport static org.sonarsource.sonarlint.core.commons.ConnectionKind.SONARQUBE;\n\npublic enum HotspotReviewStatus {\n  TO_REVIEW(Set.of(SONARQUBE, SONARCLOUD)),\n  SAFE(Set.of(SONARQUBE, SONARCLOUD)),\n  FIXED(Set.of(SONARQUBE, SONARCLOUD)),\n  ACKNOWLEDGED(Set.of(SONARQUBE));\n  private final Set<ConnectionKind> allowedConnectionKinds;\n\n  HotspotReviewStatus(Set<ConnectionKind> allowedConnectionKinds) {\n    this.allowedConnectionKinds = allowedConnectionKinds;\n  }\n\n  public boolean isReviewed() {\n    return !equals(TO_REVIEW);\n  }\n\n  public boolean isResolved() {\n    // ACKNOWLEDGED is considered as non-resolved because the hotspot is confirmed\n    return equals(SAFE) || equals(FIXED);\n  }\n\n  public static HotspotReviewStatus fromStatusAndResolution(String status, @Nullable String resolution) {\n    if (\"REVIEWED\".equals(status)) {\n      if (resolution == null) {\n        return HotspotReviewStatus.SAFE;\n      }\n      return switch (resolution) {\n        case \"SAFE\" -> HotspotReviewStatus.SAFE;\n        case \"FIXED\" -> HotspotReviewStatus.FIXED;\n        case \"ACKNOWLEDGED\" -> HotspotReviewStatus.ACKNOWLEDGED;\n        default -> HotspotReviewStatus.TO_REVIEW;\n      };\n    }\n    return HotspotReviewStatus.TO_REVIEW;\n  }\n\n  private boolean isAllowedOn(ConnectionKind kind) {\n    return allowedConnectionKinds.contains(kind);\n  }\n\n  public static List<HotspotReviewStatus> allowedStatusesOn(ConnectionKind kind) {\n    return Arrays.stream(HotspotReviewStatus.values()).filter(status -> status.isAllowedOn(kind))\n      .toList();\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/IOExceptionUtils.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport java.io.IOException;\nimport java.util.Queue;\n\npublic class IOExceptionUtils {\n\n  public static void tryAndCollectIOException(IORunnable runnable, Queue<IOException> exceptions) {\n    try {\n      runnable.run();\n    } catch (IOException e) {\n      exceptions.add(e);\n    }\n  }\n\n  public static void throwFirstWithOtherSuppressed(Queue<IOException> exceptions) throws IOException {\n    if (!exceptions.isEmpty()) {\n      var first = exceptions.poll();\n      exceptions.forEach(first::addSuppressed);\n      throw first;\n    }\n  }\n\n  public interface IORunnable {\n    void run() throws IOException;\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/ImpactSeverity.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\npublic enum ImpactSeverity {\n  INFO,\n  LOW,\n  MEDIUM,\n  HIGH,\n  BLOCKER;\n\n  public static ImpactSeverity mapSeverity(String severity) {\n    if (\"BLOCKER\".equals(severity) || \"ImpactSeverity_BLOCKER\".equals(severity)) {\n      return ImpactSeverity.BLOCKER;\n    } else if (\"INFO\".equals(severity) || \"ImpactSeverity_INFO\".equals(severity)) {\n      return ImpactSeverity.INFO;\n    } else {\n      return ImpactSeverity.valueOf(severity);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/IssueSeverity.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\npublic enum IssueSeverity {\n\n  INFO,\n  MINOR,\n  MAJOR,\n  CRITICAL,\n  BLOCKER\n\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/IssueStatus.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport javax.annotation.CheckForNull;\n\n/**\n * Represents Issue resolution status. Not the status of the issue itself.\n */\npublic enum IssueStatus {\n  ACCEPT,\n  WONT_FIX,\n  FALSE_POSITIVE;\n\n  @CheckForNull\n  public static IssueStatus parse(String stringRepresentation) {\n    return switch (stringRepresentation) {\n      // ACCEPTED transition leads to WONTFIX status on server so we are not making difference between them.\n      case \"WONTFIX\", \"ACCEPT\" -> IssueStatus.ACCEPT;\n      case \"FALSE-POSITIVE\" -> IssueStatus.FALSE_POSITIVE;\n      default -> null;\n    };\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/KnownFinding.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport java.time.Instant;\nimport java.util.UUID;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\n\npublic class KnownFinding {\n  private final UUID id;\n  private final String serverKey;\n  private final TextRangeWithHash textRangeWithHash;\n  private final LineWithHash lineWithHash;\n  private final String ruleKey;\n  private final String message;\n  private final Instant introductionDate;\n\n  public KnownFinding(UUID id, @Nullable String serverKey, @Nullable TextRangeWithHash textRangeWithHash, @Nullable LineWithHash lineWithHash, String ruleKey, String message,\n    Instant introductionDate) {\n    this.id = id;\n    this.serverKey = serverKey;\n    this.textRangeWithHash = textRangeWithHash;\n    this.lineWithHash = lineWithHash;\n    this.ruleKey = ruleKey;\n    this.message = message;\n    this.introductionDate = introductionDate;\n  }\n\n  public UUID getId() {\n    return id;\n  }\n\n  @CheckForNull\n  public String getServerKey() {\n    return serverKey;\n  }\n\n  @CheckForNull\n  public TextRangeWithHash getTextRangeWithHash() {\n    return textRangeWithHash;\n  }\n\n  @CheckForNull\n  public LineWithHash getLineWithHash() {\n    return lineWithHash;\n  }\n\n  public String getRuleKey() {\n    return ruleKey;\n  }\n\n  public String getMessage() {\n    return message;\n  }\n\n  public Instant getIntroductionDate() {\n    return introductionDate;\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/KnownFindingType.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\npublic enum KnownFindingType {\n  ISSUE,\n  HOTSPOT\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/LineWithHash.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\npublic class LineWithHash {\n\n  private final int number;\n  private final String hash;\n\n  public LineWithHash(int number, String hash) {\n    this.number = number;\n    this.hash = hash;\n  }\n\n  public int getNumber() {\n    return number;\n  }\n\n  public String getHash() {\n    return hash;\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/LocalOnlyIssue.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.UUID;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\n\npublic class LocalOnlyIssue {\n  private final UUID id;\n  private final Path serverRelativePath;\n  private final TextRangeWithHash textRangeWithHash;\n  private final LineWithHash lineWithHash;\n  private final String ruleKey;\n  private final String message;\n  private LocalOnlyIssueResolution resolution;\n\n  /**\n   * @param resolution is null when the issue is not resolved\n   */\n  public LocalOnlyIssue(UUID id, Path serverRelativePath, @Nullable TextRangeWithHash textRangeWithHash, @Nullable LineWithHash lineWithHash, String ruleKey,\n    String message, @Nullable LocalOnlyIssueResolution resolution) {\n    this.id = id;\n    this.serverRelativePath = serverRelativePath;\n    this.textRangeWithHash = textRangeWithHash;\n    this.lineWithHash = lineWithHash;\n    this.ruleKey = ruleKey;\n    this.message = message;\n    this.resolution = resolution;\n  }\n\n  public UUID getId() {\n    return id;\n  }\n\n  public Path getServerRelativePath() {\n    return serverRelativePath;\n  }\n\n  @CheckForNull\n  public TextRangeWithHash getTextRangeWithHash() {\n    return textRangeWithHash;\n  }\n\n  @CheckForNull\n  public LineWithHash getLineWithHash() {\n    return lineWithHash;\n  }\n\n  public String getRuleKey() {\n    return ruleKey;\n  }\n\n  public String getMessage() {\n    return message;\n  }\n\n  @CheckForNull\n  public LocalOnlyIssueResolution getResolution() {\n    return resolution;\n  }\n\n  public void resolve(IssueStatus newStatus) {\n    resolution = new LocalOnlyIssueResolution(newStatus, Instant.now(), null);\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/LocalOnlyIssueResolution.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport java.time.Instant;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class LocalOnlyIssueResolution {\n  private final IssueStatus resolutionStatus;\n  private final Instant resolutionDate;\n  private String comment;\n\n  public LocalOnlyIssueResolution(IssueStatus status, Instant resolutionDate, @Nullable String comment) {\n    this.resolutionStatus = status;\n    this.resolutionDate = resolutionDate;\n    this.comment = comment;\n  }\n\n  public IssueStatus getStatus() {\n    return resolutionStatus;\n  }\n\n  public Instant getResolutionDate() {\n    return resolutionDate;\n  }\n\n  @CheckForNull\n  public String getComment() {\n    return comment;\n  }\n\n  public void setComment(String comment) {\n    this.comment = comment;\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/MultiFileBlameResult.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.Collection;\nimport java.util.Map;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\nimport org.apache.commons.io.FilenameUtils;\nimport org.sonarsource.sonarlint.core.commons.util.git.BlameResult;\n\nimport static java.util.Objects.isNull;\n\npublic class MultiFileBlameResult {\n\n  private final Map<String, BlameResult> blameResultPerFile;\n  private final Path gitRepoRelativeProjectBaseDir;\n\n  public MultiFileBlameResult(Map<String, BlameResult> blameResultPerFile, Path gitRepoRelativeProjectBaseDir) {\n    this.blameResultPerFile = blameResultPerFile;\n    this.gitRepoRelativeProjectBaseDir = gitRepoRelativeProjectBaseDir;\n  }\n\n  public static MultiFileBlameResult empty(Path gitRepoRelativeProjectBaseDir) {\n    return new MultiFileBlameResult(Map.of(), gitRepoRelativeProjectBaseDir);\n  }\n\n  /**\n   * @param projectDirRelativeFilePath A path relative to the Git repository root\n   * @param lineNumbers Line numbers for which to check the latest change date. Numbering starts from `1`!\n   * @return The latest changed date or an empty optional if the date couldn't be determined or any of the lines is modified\n   */\n  public Optional<Instant> getLatestChangeDateForLinesInFile(Path projectDirRelativeFilePath, Collection<Integer> lineNumbers) {\n    validateLineNumbersArgument(lineNumbers);\n\n    return Optional.of(projectDirRelativeFilePath.toString())\n      .map(gitRepoRelativeProjectBaseDir::resolve)\n      .map(Path::toString)\n      .map(FilenameUtils::separatorsToUnix)\n      .map(blameResultPerFile::get)\n      .map(fileBlame -> getTheLatestChange(fileBlame, lineNumbers));\n  }\n\n  private static Instant getTheLatestChange(BlameResult blameForFile, Collection<Integer> lineNumbers) {\n    Instant latestDate = null;\n    for (var lineNumber : lineNumbers) {\n      if (lineNumber > blameForFile.lineCommitDates().size()) {\n        continue;\n      }\n      var dateForLine = blameForFile.lineCommitDates().get(lineNumber - 1);\n      if (isLineModified(dateForLine)) {\n        return null;\n      }\n      latestDate = isNull(latestDate) || latestDate.isBefore(dateForLine) ? dateForLine : latestDate;\n    }\n    return latestDate;\n  }\n\n  private static void validateLineNumbersArgument(Collection<Integer> lineNumbers) {\n    if (lineNumbers.stream().anyMatch(i -> i < 1)) {\n      throw new IllegalArgumentException(\"Line numbers must be greater than 0. The numbering starts from 1 (i.e. the \" +\n        \"first line of a file should be `1`)\");\n    }\n  }\n\n  private static boolean isLineModified(@Nullable Instant dateForLine) {\n    return dateForLine == null;\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/NewCodeDefinition.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport java.time.Instant;\nimport java.time.ZoneId;\nimport java.time.ZonedDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.time.temporal.ChronoUnit;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic interface NewCodeDefinition {\n\n  String DATETIME_FORMAT = \"MM/dd/yyyy HH:mm:ss\";\n  DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern(DATETIME_FORMAT);\n\n  NewCodeMode getMode();\n\n  boolean isOnNewCode(long creationDate);\n\n  default boolean isOnNewCode(Instant introductionDate) {\n    return isOnNewCode(introductionDate.toEpochMilli());\n  }\n\n  boolean isSupported();\n\n  static String formatEpochToDate(long epoch) {\n    return ZonedDateTime.ofInstant(Instant.ofEpochMilli(epoch), ZoneId.systemDefault()).format(DATETIME_FORMATTER);\n  }\n\n  static NewCodeDefinition withAlwaysNew() {\n    return new NewCodeAlwaysNew();\n  }\n\n  static NewCodeDefinition withExactNumberOfDays(int days) {\n    return new NewCodeExactNumberOfDays(days);\n  }\n\n  /**\n   * @param days the theoretical number of days\n   * @param thresholdDate the actual date in the past that serves for the comparison. Can be different from the number of days as it is updated after analysis on the server side\n   */\n  static NewCodeDefinition withNumberOfDaysWithDate(int days, long thresholdDate) {\n    return new NewCodeNumberOfDaysWithDate(days, thresholdDate);\n  }\n\n  static NewCodeDefinition withPreviousVersion(long thresholdDate, @Nullable String version) {\n    return new NewCodePreviousVersion(thresholdDate, version);\n  }\n\n  static NewCodeDefinition withReferenceBranch(String referenceBranch) {\n    return new NewCodeReferenceBranch(referenceBranch);\n  }\n\n  static NewCodeDefinition withSpecificAnalysis(long thresholdDate) {\n    return new NewCodeSpecificAnalysis(thresholdDate);\n  }\n\n  Instant getThresholdDate();\n\n  abstract class NewCodeDefinitionWithDate implements NewCodeDefinition {\n    protected final long thresholdDate;\n\n    protected NewCodeDefinitionWithDate(long thresholdDate) {\n      this.thresholdDate = thresholdDate;\n    }\n\n    public boolean isOnNewCode(long creationDate) {\n      return creationDate > thresholdDate;\n    }\n\n    public boolean isSupported() {\n      return true;\n    }\n\n    public Instant getThresholdDate() {\n      return Instant.ofEpochMilli(thresholdDate);\n    }\n  }\n\n  class NewCodeExactNumberOfDays implements NewCodeDefinition {\n    private final int days;\n\n    public NewCodeExactNumberOfDays(int days) {\n      this.days = days;\n    }\n\n    @Override\n    public NewCodeMode getMode() {\n      return NewCodeMode.NUMBER_OF_DAYS;\n    }\n\n    @Override\n    public boolean isOnNewCode(long creationDate) {\n      return creationDate > Instant.now().minus(days, ChronoUnit.DAYS).toEpochMilli();\n    }\n\n    @Override\n    public boolean isSupported() {\n      return true;\n    }\n\n    @Override\n    public Instant getThresholdDate() {\n      return Instant.now().minus(days, ChronoUnit.DAYS);\n    }\n\n    // Text used by IDEs in the UI. Communicate changes to IDE squad prior to changing the wording.\n    @Override\n    public String toString() {\n      return String.format(\"From last %s days\", days);\n    }\n  }\n\n  class NewCodeNumberOfDaysWithDate extends NewCodeDefinitionWithDate {\n    Integer days;\n\n    private NewCodeNumberOfDaysWithDate(Integer days, long thresholdDate) {\n      super(thresholdDate);\n      this.days = days;\n    }\n\n    // Text used by IDEs in the UI. Communicate changes to IDE squad prior to changing the wording.\n    @Override\n    public String toString() {\n      return String.format(\"From last %s days\", days);\n    }\n\n    @Override\n    public NewCodeMode getMode() {\n      return NewCodeMode.NUMBER_OF_DAYS;\n    }\n\n    public Integer getDays() {\n      return days;\n    }\n  }\n\n  class NewCodePreviousVersion extends NewCodeDefinitionWithDate {\n    private final String version;\n\n    private NewCodePreviousVersion(long thresholdDate, @Nullable String version) {\n      super(thresholdDate);\n      this.version = version;\n    }\n\n    // Text used by IDEs in the UI. Communicate changes to IDE squad prior to changing the wording.\n    @Override\n    public String toString() {\n      var versionQualifier = (version == null) ? formatEpochToDate(this.thresholdDate) : (\"version \" + version);\n      return String.format(\"Since %s\", versionQualifier);\n    }\n\n    @Override\n    public NewCodeMode getMode() {\n      return NewCodeMode.PREVIOUS_VERSION;\n    }\n\n    @CheckForNull\n    public String getVersion() {\n      return version;\n    }\n  }\n\n  class NewCodeSpecificAnalysis extends NewCodeDefinitionWithDate {\n    private NewCodeSpecificAnalysis(long thresholdDate) {\n      super(thresholdDate);\n    }\n\n    // Text used by IDEs in the UI. Communicate changes to IDE squad prior to changing the wording.\n    @Override\n    public String toString() {\n      return String.format(\"Since analysis from %s\", formatEpochToDate(this.thresholdDate));\n    }\n\n    @Override\n    public NewCodeMode getMode() {\n      return NewCodeMode.SPECIFIC_ANALYSIS;\n    }\n  }\n\n  class NewCodeReferenceBranch implements NewCodeDefinition {\n    private final String branchName;\n\n    private NewCodeReferenceBranch(String branchName) {\n      this.branchName = branchName;\n    }\n\n    @Override\n    public NewCodeMode getMode() {\n      return NewCodeMode.REFERENCE_BRANCH;\n    }\n\n    @Override\n    public boolean isOnNewCode(long creationDate) {\n      return true;\n    }\n\n    @Override\n    public boolean isSupported() {\n      return false;\n    }\n\n    public String getBranchName() {\n      return branchName;\n    }\n\n    @Override\n    public Instant getThresholdDate() {\n      // instead of Long.MAX_VALUE it's set for Instant.now() in case it will be used for git blame limit\n      return Instant.now();\n    }\n\n    // Text used by IDEs in the UI. Communicate changes to IDE squad prior to changing the wording.\n    @Override\n    public String toString() {\n      return \"Current new code definition (reference branch) is not supported\";\n    }\n  }\n\n  class NewCodeAlwaysNew implements NewCodeDefinition {\n\n    private NewCodeAlwaysNew() {\n      // NOP\n    }\n\n    @Override\n    public NewCodeMode getMode() {\n      throw new UnsupportedOperationException(\"Mode shouldn't be called for this new code definition\");\n    }\n\n    @Override\n    public boolean isOnNewCode(long creationDate) {\n      return true;\n    }\n\n    @Override\n    public Instant getThresholdDate() {\n      // instead of 0L it's set for Instant.now() in case it will be used for git blame limit (which shouldn't normally happen)\n      return Instant.now();\n    }\n\n    @Override\n    public boolean isSupported() {\n      return true;\n    }\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/NewCodeMode.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\npublic enum NewCodeMode {\n\n  REFERENCE_BRANCH, NUMBER_OF_DAYS, PREVIOUS_VERSION, SPECIFIC_ANALYSIS\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/RuleKey.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport java.util.Objects;\nimport javax.annotation.concurrent.Immutable;\n\n@Immutable\npublic class RuleKey {\n\n  private static final char SEPARATOR = ':';\n\n  private final String repository;\n  private final String rule;\n\n  public RuleKey(String repository, String rule) {\n    this.repository = repository;\n    this.rule = rule;\n  }\n\n  public String repository() {\n    return repository;\n  }\n\n  public String rule() {\n    return rule;\n  }\n\n  public static RuleKey parse(String s) {\n    var separatorIndex = s.indexOf(SEPARATOR);\n    if (separatorIndex < 0) {\n      throw new IllegalArgumentException(\"Invalid rule key: \" + s);\n    }\n\n    var key = s.substring(0, separatorIndex);\n    var repo = s.substring(separatorIndex + 1);\n    return new RuleKey(key, repo);\n  }\n\n  @Override\n  public String toString() {\n    return repository + SEPARATOR + rule;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) {\n      return true;\n    }\n    if (o == null || getClass() != o.getClass()) {\n      return false;\n    }\n    var ruleKey = (RuleKey) o;\n    return Objects.equals(repository, ruleKey.repository) &&\n      Objects.equals(rule, ruleKey.rule);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(repository, rule);\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/RuleType.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\npublic enum RuleType {\n\n  CODE_SMELL,\n  BUG,\n  VULNERABILITY,\n  SECURITY_HOTSPOT\n\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/SoftwareQuality.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\npublic enum SoftwareQuality {\n  MAINTAINABILITY,\n  RELIABILITY,\n  SECURITY\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/SonarLintCoreVersion.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\n\npublic class SonarLintCoreVersion {\n\n  private SonarLintCoreVersion() {\n  }\n\n  public static String get() {\n    String version;\n    var packageInfo = SonarLintCoreVersion.class.getPackage();\n    if (packageInfo != null && packageInfo.getImplementationVersion() != null) {\n      version = packageInfo.getImplementationVersion();\n    } else {\n      version = getLibraryVersion();\n    }\n    return version;\n  }\n\n  public static String getLibraryVersion() {\n    var version = \"unknown\";\n    var resource = SonarLintCoreVersion.class.getResourceAsStream(\"/sl_core_version.txt\");\n    if (resource != null) {\n      try {\n        version = new String(resource.readAllBytes(), StandardCharsets.UTF_8);\n      } catch (IOException e) {\n        return version;\n      }\n    }\n\n    return version;\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/SonarLintException.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport javax.annotation.Nullable;\n\npublic class SonarLintException extends RuntimeException {\n\n  public SonarLintException() {\n    super();\n  }\n\n  public SonarLintException(String msg) {\n    super(msg);\n  }\n\n  public SonarLintException(String msg, @Nullable Throwable cause) {\n    super(msg, cause);\n  }\n\n  public SonarLintException(String msg, @Nullable Throwable cause, boolean withStackTrace) {\n    super(msg, cause, !withStackTrace, withStackTrace);\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/SonarLintGitIgnore.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport java.nio.file.Path;\nimport org.eclipse.jgit.ignore.IgnoreNode;\n\npublic class SonarLintGitIgnore {\n  private final IgnoreNode ignoreNode;\n\n  public SonarLintGitIgnore(IgnoreNode ignoreNode) {\n    this.ignoreNode = ignoreNode;\n  }\n\n  public boolean isIgnored(Path clientRelativeFilePath) {\n    var normalizedUnixPath = clientRelativeFilePath.toString().replace(\"\\\\\", \"/\");\n    var rules = ignoreNode.getRules();\n    // Parse rules in the reverse order that they were read because later rules have higher priority\n    for (var i = rules.size() - 1; i > -1; i--) {\n      var rule = rules.get(i);\n      if (rule.isMatch(normalizedUnixPath, false)) {\n        return rule.getResult();\n      }\n    }\n    return false;\n  }\n\n  public boolean isFileIgnored(Path clientFileRelativePath) {\n    return isIgnored(clientFileRelativePath);\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/SonarLintUserHome.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport javax.annotation.Nullable;\n\npublic class SonarLintUserHome {\n\n  public static final String SONARLINT_USER_HOME_ENV = \"SONARLINT_USER_HOME\";\n\n  private SonarLintUserHome() {\n    // utility class, forbidden constructor\n  }\n\n  public static Path get() {\n    return home(System.getenv(SONARLINT_USER_HOME_ENV));\n  }\n\n  static Path home(@Nullable String slHome) {\n    if (slHome != null) {\n      return Paths.get(slHome);\n    }\n    return Paths.get(System.getProperty(\"user.home\")).resolve(\".sonarlint\");\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/Transition.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\npublic enum Transition {\n\n  ACCEPT(\"accept\"),\n  WONT_FIX(\"wontfix\"),\n  FALSE_POSITIVE(\"falsepositive\"),\n  REOPEN(\"reopen\");\n\n  private final String status;\n\n  Transition(String status) {\n    this.status = status;\n  }\n\n  public String getStatus() {\n    return status;\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/Version.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport java.util.Arrays;\n\npublic class Version implements Comparable<Version> {\n\n  private final String name;\n  private final String nameWithoutQualifier;\n  private final int[] numbers;\n  private final String qualifier;\n\n  private Version(String version) {\n    this.name = version.trim();\n    var qualifierPosition = name.indexOf(\"-\");\n    if (qualifierPosition != -1) {\n      this.qualifier = name.substring(qualifierPosition + 1);\n      this.nameWithoutQualifier = name.substring(0, qualifierPosition);\n    } else {\n      this.qualifier = \"\";\n      this.nameWithoutQualifier = this.name;\n    }\n    final var split = this.nameWithoutQualifier.split(\"\\\\.\");\n    numbers = new int[split.length];\n    for (var i = 0; i < split.length; i++) {\n      numbers[i] = Integer.parseInt(split[i]);\n    }\n  }\n\n  private Version(String name, String nameWithoutQualifier, int[] numbers, String qualifier) {\n    this.name = name;\n    this.nameWithoutQualifier = nameWithoutQualifier;\n    this.numbers = Arrays.copyOf(numbers, numbers.length);\n    this.qualifier = qualifier;\n  }\n\n  public int getMajor() {\n    return numbers.length > 0 ? numbers[0] : 0;\n  }\n\n  public int getMinor() {\n    return numbers.length > 1 ? numbers[1] : 0;\n  }\n\n  public int getPatch() {\n    return numbers.length > 2 ? numbers[2] : 0;\n  }\n\n  public int getBuild() {\n    return numbers.length > 3 ? numbers[3] : 0;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public String getQualifier() {\n    return qualifier;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) {\n      return true;\n    }\n    if (!(o instanceof Version other)) {\n      return false;\n    }\n    return getMajor() == other.getMajor()\n      && getMinor() == other.getMinor()\n      && getPatch() == other.getPatch()\n      && getBuild() == other.getBuild()\n      && qualifier.equals(other.qualifier);\n  }\n\n  @Override\n  public int hashCode() {\n    var result = Integer.hashCode(getMajor());\n    result = 31 * result + Integer.hashCode(getMinor());\n    result = 31 * result + Integer.hashCode(getPatch());\n    result = 31 * result + Integer.hashCode(getBuild());\n    result = 31 * result + qualifier.hashCode();\n    return result;\n  }\n\n  @Override\n  public int compareTo(Version other) {\n    var c = compareToIgnoreQualifier(other);\n    if (c == 0) {\n      if (\"\".equals(qualifier)) {\n        c = \"\".equals(other.qualifier) ? 0 : 1;\n      } else if (\"\".equals(other.qualifier)) {\n        c = -1;\n      } else {\n        c = qualifier.compareTo(other.qualifier);\n      }\n    }\n    return c;\n  }\n\n  public int compareToIgnoreQualifier(Version other) {\n    var maxNumbers = Math.max(numbers.length, other.numbers.length);\n    var myNumbers = Arrays.copyOf(numbers, maxNumbers);\n    var otherNumbers = Arrays.copyOf(other.numbers, maxNumbers);\n    for (var i = 0; i < maxNumbers; i++) {\n      var compare = Integer.compare(myNumbers[i], otherNumbers[i]);\n      if (compare != 0) {\n        return compare;\n      }\n    }\n    return 0;\n  }\n\n  @Override\n  public String toString() {\n    return name;\n  }\n\n  public static Version create(String version) {\n    return new Version(version);\n  }\n\n  public Version removeQualifier() {\n    return new Version(nameWithoutQualifier, nameWithoutQualifier, numbers, \"\");\n  }\n\n  public boolean satisfiesMinRequirement(Version minRequirement) {\n    return this.compareToIgnoreQualifier(minRequirement) >= 0;\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/VulnerabilityProbability.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport java.util.Arrays;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\n\npublic enum VulnerabilityProbability {\n  HIGH(3),\n  MEDIUM(2),\n  LOW(1);\n\n  private final int score;\n\n  VulnerabilityProbability(int index) {\n    this.score = index;\n  }\n\n  public int getScore() {\n    return score;\n  }\n\n  public static Optional<VulnerabilityProbability> byScore(@Nullable Integer score) {\n    if (score == null) {\n      return Optional.empty();\n    }\n    return Arrays.stream(values())\n      .filter(t -> t.score == score)\n      .findFirst();\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/api/SonarLanguage.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.api;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\n\npublic enum SonarLanguage {\n\n  ABAP(\"abap\", SonarPlugin.ABAP, \"Abap\", new String[]{\".abap\", \".ab4\", \".flow\", \".asprog\"}, \"sonar.abap.file.suffixes\"),\n  APEX(\"apex\", SonarPlugin.APEX, \"Apex\", new String[]{\".cls\", \".trigger\"}, \"sonar.apex.file.suffixes\"),\n  C(\"c\", SonarPlugin.C_FAMILY, \"C\", new String[]{\".c\", \".h\"}, \"sonar.c.file.suffixes\"),\n  CPP(\"cpp\", SonarPlugin.C_FAMILY, \"C++\", new String[]{\".cc\", \".cpp\", \".cxx\", \".c++\", \".hh\", \".hpp\", \".hxx\", \".h++\", \".ipp\"}, \"sonar.cpp.file.suffixes\"),\n  CS(\"cs\", SonarPlugin.CS_OSS, \"C#\", new String[]{\".cs\", \".razor\"}, \"sonar.cs.file.suffixes\"),\n  CSS(\"css\", SonarPlugin.JS, \"CSS\", new String[]{\".css\", \".less\", \".scss\"}, \"sonar.css.file.suffixes\"),\n  OBJC(\"objc\", SonarPlugin.C_FAMILY, \"Objective-C\", new String[]{\".m\"}, \"sonar.objc.file.suffixes\"),\n  COBOL(\"cobol\", SonarPlugin.COBOL, \"COBOL\", new String[0], \"sonar.cobol.file.suffixes\"),\n  HTML(\"web\", SonarPlugin.WEB, \"HTML\", new String[]{\".html\", \".xhtml\", \".cshtml\", \".vbhtml\", \".aspx\", \".ascx\", \".rhtml\", \".erb\", \".shtm\", \".shtml\"}, \"sonar.html.file.suffixes\"),\n  IPYTHON(\"ipynb\", SonarPlugin.PYTHON, \"IPython Notebook\", new String[]{\".ipynb\"}, \"sonar.ipython.file.suffixes\"),\n  JAVA(\"java\", SonarPlugin.JAVA, \"Java\", new String[]{\".java\", \".jav\"}, \"sonar.java.file.suffixes\"),\n  JCL(\"jcl\", SonarPlugin.JCL, \"JCL\", new String[]{\".jcl\"}, \"sonar.jcl.file.suffixes\"),\n  JS(\"js\", SonarPlugin.JS, \"JavaScript\", new String[]{\".js\", \".jsx\", \".vue\"}, \"sonar.javascript.file.suffixes\"),\n  KOTLIN(\"kotlin\", SonarPlugin.KOTLIN, \"Kotlin\", new String[]{\".kt\", \".kts\"}, \"sonar.kotlin.file.suffixes\"),\n  PHP(\"php\", SonarPlugin.PHP, \"PHP\", new String[]{\"php\", \"php3\", \"php4\", \"php5\", \"phtml\", \"inc\"}, \"sonar.php.file.suffixes\"),\n  PLI(\"pli\", SonarPlugin.PLI, \"PL/I\", new String[]{\".pli\"}, \"sonar.pli.file.suffixes\"),\n  PLSQL(\"plsql\", SonarPlugin.PLSQL, \"PL/SQL\", new String[]{\".sql\", \".pks\", \".pkb\"}, \"sonar.plsql.file.suffixes\"),\n  PYTHON(\"py\", SonarPlugin.PYTHON, \"Python\", new String[]{\".py\"}, \"sonar.python.file.suffixes\"),\n  RPG(\"rpg\", SonarPlugin.RPG, \"RPG\", new String[]{\".rpg\", \".rpgle\"}, \"sonar.rpg.file.suffixes\"),\n  RUBY(\"ruby\", SonarPlugin.RUBY, \"Ruby\", new String[]{\".rb\"}, \"sonar.ruby.file.suffixes\"),\n  SCALA(\"scala\", SonarPlugin.SCALA, \"Scala\", new String[]{\".scala\"}, \"sonar.scala.file.suffixes\"),\n  SECRETS(\"secrets\", SonarPlugin.TEXT, \"Secrets\", new String[0], \"sonar.secrets.file.suffixes\"),\n  TEXT(\"text\", SonarPlugin.TEXT, \"Text\", new String[0], \"sonar.text.file.suffixes\"),\n  SWIFT(\"swift\", SonarPlugin.SWIFT, \"Swift\", new String[]{\".swift\"}, \"sonar.swift.file.suffixes\"),\n  TSQL(\"tsql\", SonarPlugin.TSQL, \"T-SQL\", new String[]{\".tsql\"}, \"sonar.tsql.file.suffixes\"),\n  TS(\"ts\", SonarPlugin.JS, \"TypeScript\", new String[]{\".ts\", \".tsx\"},\n    \"sonar.typescript.file.suffixes\"),\n  JSP(\"jsp\", SonarPlugin.WEB, \"JSP\", new String[]{\".jsp\", \".jspf\", \".jspx\"}, \"sonar.jsp.file.suffixes\"),\n  VBNET(\"vbnet\", SonarPlugin.VBNET_OSS, \"VB.NET\", new String[]{\".vb\"}, \"sonar.vbnet.file.suffixes\"),\n  XML(\"xml\", SonarPlugin.XML, \"XML\", new String[]{\".xml\", \".xsd\", \".xsl\"}, \"sonar.xml.file.suffixes\"),\n  YAML(\"yaml\", SonarPlugin.JS, \"YAML\", new String[]{\".yml\", \"yaml\"}, Constants.NO_PUBLIC_PROPERTY_PROVIDED_FOR_THIS_LANGUAGE),\n  JSON(\"json\", SonarPlugin.JS, \"JSON\", new String[]{\".json\"}, Constants.NO_PUBLIC_PROPERTY_PROVIDED_FOR_THIS_LANGUAGE),\n  GO(\"go\", SonarPlugin.GO, \"Go\", new String[]{\".go\"}, \"sonar.go.file.suffixes\"),\n  CLOUDFORMATION(\"cloudformation\", SonarPlugin.IAC, \"CloudFormation\", new String[0], Constants.NO_PUBLIC_PROPERTY_PROVIDED_FOR_THIS_LANGUAGE),\n  DOCKER(\"docker\", SonarPlugin.IAC, \"Docker\", new String[0], Constants.NO_PUBLIC_PROPERTY_PROVIDED_FOR_THIS_LANGUAGE),\n  KUBERNETES(\"kubernetes\", SonarPlugin.IAC, \"Kubernetes\", new String[0], Constants.NO_PUBLIC_PROPERTY_PROVIDED_FOR_THIS_LANGUAGE),\n  TERRAFORM(\"terraform\", SonarPlugin.IAC, \"Terraform\", new String[]{\".tf\"}, \"sonar.terraform.file.suffixes\"),\n  AZURERESOURCEMANAGER(\"azureresourcemanager\", SonarPlugin.IAC, \"Azure Resource Manager\", new String[]{\".bicep\"}, Constants.NO_PUBLIC_PROPERTY_PROVIDED_FOR_THIS_LANGUAGE),\n  ANSIBLE(\"ansible\", SonarPlugin.IAC, \"Ansible\", new String[0], Constants.NO_PUBLIC_PROPERTY_PROVIDED_FOR_THIS_LANGUAGE),\n  GITHUBACTIONS(\"githubactions\", SonarPlugin.IAC, \"GitHub Actions\", new String[0], Constants.NO_PUBLIC_PROPERTY_PROVIDED_FOR_THIS_LANGUAGE);\n\n  private final String sonarLanguageKey;\n\n  /**\n   * The Sonar Plugin declaring this language\n   */\n  private final SonarPlugin plugin;\n  private final String name;\n  private final String[] defaultFileSuffixes;\n  private final String fileSuffixesPropKey;\n\n  private static final Map<String, SonarLanguage> mMap = Collections.unmodifiableMap(initializeMapping());\n\n  private static Map<String, SonarLanguage> initializeMapping() {\n    Map<String, SonarLanguage> mMap = new HashMap<>();\n    for (SonarLanguage l : SonarLanguage.values()) {\n      mMap.put(l.sonarLanguageKey, l);\n    }\n    return mMap;\n  }\n\n  SonarLanguage(String sonarLanguageKey, SonarPlugin plugin, String name, String[] defaultFileSuffixes, String fileSuffixesPropKey) {\n    this.sonarLanguageKey = sonarLanguageKey;\n    this.plugin = plugin;\n    this.name = name;\n    this.defaultFileSuffixes = defaultFileSuffixes;\n    this.fileSuffixesPropKey = fileSuffixesPropKey;\n  }\n\n  public String getSonarLanguageKey() {\n    return sonarLanguageKey;\n  }\n\n  public SonarPlugin getPlugin() {\n    return plugin;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public String[] getDefaultFileSuffixes() {\n    return defaultFileSuffixes;\n  }\n\n  public String getFileSuffixesPropKey() {\n    return fileSuffixesPropKey;\n  }\n\n  public boolean shouldSyncInConnectedMode() {\n    return !equals(SonarLanguage.IPYTHON);\n  }\n\n  public static Optional<SonarLanguage> getLanguageByLanguageKey(String languageKey) {\n    var languages = Stream.of(values()).filter(l -> l.getSonarLanguageKey().equals(languageKey)).collect(Collectors.toCollection(ArrayList::new));\n    return languages.isEmpty() ? Optional.empty() : Optional.of(languages.get(0));\n  }\n\n  public static Optional<SonarLanguage> forKey(String languageKey) {\n    return Optional.ofNullable(mMap.get(languageKey));\n  }\n\n  public static class Constants {\n    private static final String NO_PUBLIC_PROPERTY_PROVIDED_FOR_THIS_LANGUAGE = \"<no public property provided for this language>\";\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/api/TextRange.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.api;\n\nimport java.util.Objects;\n\npublic class TextRange {\n\n  private final int startLine;\n  private final int startLineOffset;\n  private final int endLine;\n  private final int endLineOffset;\n\n  public TextRange(int startLine, int startLineOffset, int endLine, int endLineOffset) {\n    this.startLine = startLine;\n    this.startLineOffset = startLineOffset;\n    this.endLine = endLine;\n    this.endLineOffset = endLineOffset;\n  }\n\n  public int getStartLine() {\n    return startLine;\n  }\n\n  public int getStartLineOffset() {\n    return startLineOffset;\n  }\n\n  public int getEndLine() {\n    return endLine;\n  }\n\n  public int getEndLineOffset() {\n    return endLineOffset;\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(endLine, endLineOffset, startLine, startLineOffset);\n  }\n\n  @Override\n  public boolean equals(Object obj) {\n    if (this == obj) {\n      return true;\n    }\n    if (!(obj instanceof TextRange other)) {\n      return false;\n    }\n    return endLine == other.endLine && endLineOffset == other.endLineOffset && startLine == other.startLine && startLineOffset == other.startLineOffset;\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/api/TextRangeWithHash.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.api;\n\nimport java.util.Objects;\n\npublic class TextRangeWithHash extends TextRange {\n\n  private final String hash;\n\n  public TextRangeWithHash(int startLine, int startLineOffset, int endLine, int endLineOffset, String hash) {\n    super(startLine, startLineOffset, endLine, endLineOffset);\n    this.hash = hash;\n  }\n\n  public String getHash() {\n    return hash;\n  }\n\n  @Override\n  public int hashCode() {\n    final var prime = 31;\n    int result = super.hashCode();\n    result = prime * result + Objects.hash(hash);\n    return result;\n  }\n\n  @Override\n  public boolean equals(Object obj) {\n    if (this == obj) {\n      return true;\n    }\n    if (!super.equals(obj)) {\n      return false;\n    }\n    if (!(obj instanceof TextRangeWithHash other)) {\n      return false;\n    }\n    return Objects.equals(hash, other.hash);\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/api/package-info.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.commons.api;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/api/progress/CanceledException.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.api.progress;\n\nimport org.sonarsource.sonarlint.core.commons.SonarLintException;\n\npublic class CanceledException extends SonarLintException {\n\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/api/progress/package-info.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.commons.api.progress;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/dogfood/DogfoodEnvironmentDetectionService.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.dogfood;\n\nimport org.apache.commons.lang3.SystemUtils;\n\npublic class DogfoodEnvironmentDetectionService {\n  public static final String SONARSOURCE_DOGFOODING_ENV_VAR_KEY = \"SONARSOURCE_DOGFOODING\";\n\n  public boolean isDogfoodEnvironment() {\n    return \"1\".equals(SystemUtils.getEnvironmentVariable(SONARSOURCE_DOGFOODING_ENV_VAR_KEY, \"0\"));\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/dogfood/package-info.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.commons.dogfood;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/log/FormattingTuple.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.log;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\nclass FormattingTuple {\n\n  private final String message;\n  private final Throwable throwable;\n\n  public FormattingTuple(@Nullable String message) {\n    this(message, null);\n  }\n\n  public FormattingTuple(@Nullable String message, @Nullable Throwable throwable) {\n    this.message = message;\n    this.throwable = throwable;\n  }\n\n  @CheckForNull\n  public String getMessage() {\n    return message;\n  }\n\n  @CheckForNull\n  public Throwable getThrowable() {\n    return throwable;\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/log/LogOutput.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.log;\n\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport javax.annotation.Nullable;\n\n/**\n * Allow to redirect SonarLint logs to a custom output on client side\n */\npublic interface LogOutput {\n\n  /**\n   * @deprecated please implement {@link #log(String, Level, String)} instead\n   */\n  @Deprecated(since = \"10.0\")\n  default void log(String formattedMessage, Level level) {\n    log(formattedMessage, level, null);\n  }\n\n  default void log(@Nullable String formattedMessage, Level level, @Nullable String stacktrace) {\n    if (formattedMessage != null) {\n      log(formattedMessage, level);\n    }\n    if (stacktrace != null) {\n      log(stacktrace, level);\n    }\n  }\n\n  enum Level {\n    OFF, ERROR, WARN, INFO, DEBUG, TRACE;\n\n    public boolean isMoreVerboseOrEqual(Level targetLevel) {\n      return this.ordinal() >= targetLevel.ordinal();\n    }\n  }\n\n  static String stackTraceToString(Throwable t) {\n    var stringWriter = new StringWriter();\n    var printWriter = new PrintWriter(stringWriter);\n    t.printStackTrace(printWriter);\n    return stringWriter.toString();\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/log/MessageFormatter.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.log;\n\nimport java.text.MessageFormat;\nimport java.util.HashMap;\nimport java.util.Map;\nimport javax.annotation.Nullable;\n\n// Inspired by https://github.com/qos-ch/slf4j/blob/98f1f2f46533eba4945dda995225cf3c4017a075/slf4j-api/src/main/java/org/slf4j/helpers/MessageFormatter.java\n// contributors: lizongbo: proposed special treatment of array parameter values\n// Joern Huxhorn: pointed out double[] omission, suggested deep array copy\n/**\n * Formats messages according to very simple substitution rules. Substitutions\n * can be made 1, 2 or more arguments.\n *\n * <p>\n * For example,\n *\n * <pre>\n * MessageFormatter.format(&quot;Hi {}.&quot;, &quot;there&quot;)\n * </pre>\n *\n * will return the string \"Hi there.\".\n * <p>\n * The {} pair is called the <em>formatting anchor</em>. It serves to designate\n * the location where arguments need to be substituted within the message\n * pattern.\n * <p>\n * In case your message contains the '{' or the '}' character, you do not have\n * to do anything special unless the '}' character immediately follows '{'. For\n * example,\n *\n * <pre>\n * MessageFormatter.format(&quot;Set {1,2,3} is not equal to {}.&quot;, &quot;1,2&quot;);\n * </pre>\n *\n * will return the string \"Set {1,2,3} is not equal to 1,2.\".\n *\n * <p>\n * If for whatever reason you need to place the string \"{}\" in the message\n * without its <em>formatting anchor</em> meaning, then you need to escape the\n * '{' character with '\\', that is the backslash character. Only the '{'\n * character should be escaped. There is no need to escape the '}' character.\n * For example,\n *\n * <pre>\n * MessageFormatter.format(&quot;Set \\\\{} is not equal to {}.&quot;, &quot;1,2&quot;);\n * </pre>\n *\n * will return the string \"Set {} is not equal to 1,2.\".\n *\n * <p>\n * The escaping behavior just described can be overridden by escaping the escape\n * character '\\'. Calling\n *\n * <pre>\n * MessageFormatter.format(&quot;File name is C:\\\\\\\\{}.&quot;, &quot;file.zip&quot;);\n * </pre>\n *\n * will return the string \"File name is C:\\file.zip\".\n *\n * <p>\n * The formatting conventions are different than those of {@link MessageFormat}\n * which ships with the Java platform. This is justified by the fact that\n * SLF4J's implementation is 10 times faster than that of {@link MessageFormat}.\n * This local performance difference is both measurable and significant in the\n * larger context of the complete logging processing chain.\n *\n * <p>\n * See also {@link #format(String, Object)},\n * {@link #format(String, Object, Object)} and\n * {@link #arrayFormat(String, Object[])} methods for more details.\n *\n * @author Ceki G&uuml;lc&uuml;\n * @author Joern Huxhorn\n */\nfinal class MessageFormatter {\n  static final char DELIM_START = '{';\n  static final char DELIM_STOP = '}';\n  static final String DELIM_STR = \"{}\";\n  private static final char ESCAPE_CHAR = '\\\\';\n\n  private MessageFormatter() {\n  }\n\n  /**\n   * Performs single argument substitution for the 'messagePattern' passed as\n   * parameter.\n   * <p>\n   * For example,\n   *\n   * <pre>\n   * MessageFormatter.format(&quot;Hi {}.&quot;, &quot;there&quot;);\n   * </pre>\n   *\n   * will return the string \"Hi there.\".\n   * <p>\n   *\n   * @param messagePattern\n   *          The message pattern which will be parsed and formatted\n   * @param arg\n   *          The argument to be substituted in place of the formatting anchor\n   * @return The formatted message\n   */\n  public static FormattingTuple format(String messagePattern, Object arg) {\n    return arrayFormat(messagePattern, new Object[] {arg});\n  }\n\n  /**\n   *\n   * Performs a two argument substitution for the 'messagePattern' passed as\n   * parameter.\n   * <p>\n   * For example,\n   *\n   * <pre>\n   * MessageFormatter.format(&quot;Hi {}. My name is {}.&quot;, &quot;Alice&quot;, &quot;Bob&quot;);\n   * </pre>\n   *\n   * will return the string \"Hi Alice. My name is Bob.\".\n   *\n   * @param messagePattern\n   *          The message pattern which will be parsed and formatted\n   * @param arg1\n   *          The argument to be substituted in place of the first formatting\n   *          anchor\n   * @param arg2\n   *          The argument to be substituted in place of the second formatting\n   *          anchor\n   * @return The formatted message\n   */\n  public static FormattingTuple format(final String messagePattern, Object arg1, Object arg2) {\n    return arrayFormat(messagePattern, new Object[] {arg1, arg2});\n  }\n\n  public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray) {\n    var throwableCandidate = MessageFormatter.getThrowableCandidate(argArray);\n    var args = argArray;\n    if (throwableCandidate != null) {\n      args = MessageFormatter.trimmedCopy(argArray);\n    }\n    return arrayFormat(messagePattern, args, throwableCandidate);\n  }\n\n  public static FormattingTuple arrayFormat(@Nullable final String messagePattern, @Nullable final Object[] argArray, @Nullable Throwable throwable) {\n\n    if (messagePattern == null) {\n      return new FormattingTuple(null, throwable);\n    }\n\n    if (argArray == null) {\n      return new FormattingTuple(messagePattern);\n    }\n\n    var i = 0;\n    int j;\n    // use string builder for better multicore performance\n    var sbuf = new StringBuilder(messagePattern.length() + 50);\n\n    int L;\n    for (L = 0; L < argArray.length; L++) {\n\n      j = messagePattern.indexOf(DELIM_STR, i);\n\n      if (j == -1) {\n        // no more variables\n        if (i == 0) { // this is a simple string\n          return new FormattingTuple(messagePattern, throwable);\n        } else { // add the tail string which contains no variables and return\n          // the result.\n          sbuf.append(messagePattern, i, messagePattern.length());\n          return new FormattingTuple(sbuf.toString(), throwable);\n        }\n      } else {\n        if (isEscapedDelimiter(messagePattern, j)) {\n          if (!isDoubleEscaped(messagePattern, j)) {\n            L--; // DELIM_START was escaped, thus should not be incremented\n            sbuf.append(messagePattern, i, j - 1);\n            sbuf.append(DELIM_START);\n            i = j + 1;\n          } else {\n            // The escape character preceding the delimiter start is\n            // itself escaped: \"abc x:\\\\{}\"\n            // we have to consume one backward slash\n            sbuf.append(messagePattern, i, j - 1);\n            deeplyAppendParameter(sbuf, argArray[L], new HashMap<>());\n            i = j + 2;\n          }\n        } else {\n          // normal case\n          sbuf.append(messagePattern, i, j);\n          deeplyAppendParameter(sbuf, argArray[L], new HashMap<>());\n          i = j + 2;\n        }\n      }\n    }\n    // append the characters following the last {} pair.\n    sbuf.append(messagePattern, i, messagePattern.length());\n    return new FormattingTuple(sbuf.toString(), throwable);\n  }\n\n  static boolean isEscapedDelimiter(String messagePattern, int delimiterStartIndex) {\n    if (delimiterStartIndex == 0) {\n      return false;\n    }\n    var potentialEscape = messagePattern.charAt(delimiterStartIndex - 1);\n    return potentialEscape == ESCAPE_CHAR;\n  }\n\n  static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) {\n    return delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR;\n  }\n\n  // special treatment of array values was suggested by 'lizongbo'\n  private static void deeplyAppendParameter(StringBuilder sbuf, @Nullable Object o, Map<Object[], Object> seenMap) {\n    if (o == null) {\n      sbuf.append(\"null\");\n      return;\n    }\n    if (!o.getClass().isArray()) {\n      safeObjectAppend(sbuf, o);\n    } else {\n      // check for primitive array types because they\n      // unfortunately cannot be cast to Object[]\n      switch (o) {\n        case boolean[] booleans -> booleanArrayAppend(sbuf, booleans);\n        case byte[] bytes -> byteArrayAppend(sbuf, bytes);\n        case char[] chars -> charArrayAppend(sbuf, chars);\n        case short[] shorts -> shortArrayAppend(sbuf, shorts);\n        case int[] ints -> intArrayAppend(sbuf, ints);\n        case long[] longs -> longArrayAppend(sbuf, longs);\n        case float[] floats -> floatArrayAppend(sbuf, floats);\n        case double[] doubles -> doubleArrayAppend(sbuf, doubles);\n        default -> objectArrayAppend(sbuf, (Object[]) o, seenMap);\n      }\n    }\n  }\n\n  private static void safeObjectAppend(StringBuilder sbuf, Object o) {\n    try {\n      var oAsString = o.toString();\n      sbuf.append(oAsString);\n    } catch (Exception e) {\n      sbuf.append(\"[FAILED toString()]\");\n    }\n  }\n\n  private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Map<Object[], Object> seenMap) {\n    sbuf.append('[');\n    if (!seenMap.containsKey(a)) {\n      seenMap.put(a, null);\n      final var len = a.length;\n      for (var i = 0; i < len; i++) {\n        deeplyAppendParameter(sbuf, a[i], seenMap);\n        if (i != len - 1) {\n          sbuf.append(\", \");\n        }\n      }\n      // allow repeats in siblings\n      seenMap.remove(a);\n    } else {\n      sbuf.append(\"...\");\n    }\n    sbuf.append(']');\n  }\n\n  private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) {\n    sbuf.append('[');\n    final var len = a.length;\n    for (var i = 0; i < len; i++) {\n      sbuf.append(a[i]);\n      if (i != len - 1) {\n        sbuf.append(\", \");\n      }\n    }\n    sbuf.append(']');\n  }\n\n  private static void byteArrayAppend(StringBuilder sbuf, byte[] a) {\n    sbuf.append('[');\n    final var len = a.length;\n    for (var i = 0; i < len; i++) {\n      sbuf.append(a[i]);\n      if (i != len - 1) {\n        sbuf.append(\", \");\n      }\n    }\n    sbuf.append(']');\n  }\n\n  private static void charArrayAppend(StringBuilder sbuf, char[] a) {\n    sbuf.append('[');\n    final var len = a.length;\n    for (var i = 0; i < len; i++) {\n      sbuf.append(a[i]);\n      if (i != len - 1) {\n        sbuf.append(\", \");\n      }\n    }\n    sbuf.append(']');\n  }\n\n  private static void shortArrayAppend(StringBuilder sbuf, short[] a) {\n    sbuf.append('[');\n    final var len = a.length;\n    for (var i = 0; i < len; i++) {\n      sbuf.append(a[i]);\n      if (i != len - 1) {\n        sbuf.append(\", \");\n      }\n    }\n    sbuf.append(']');\n  }\n\n  private static void intArrayAppend(StringBuilder sbuf, int[] a) {\n    sbuf.append('[');\n    final var len = a.length;\n    for (var i = 0; i < len; i++) {\n      sbuf.append(a[i]);\n      if (i != len - 1) {\n        sbuf.append(\", \");\n      }\n    }\n    sbuf.append(']');\n  }\n\n  private static void longArrayAppend(StringBuilder sbuf, long[] a) {\n    sbuf.append('[');\n    final var len = a.length;\n    for (var i = 0; i < len; i++) {\n      sbuf.append(a[i]);\n      if (i != len - 1) {\n        sbuf.append(\", \");\n      }\n    }\n    sbuf.append(']');\n  }\n\n  private static void floatArrayAppend(StringBuilder sbuf, float[] a) {\n    sbuf.append('[');\n    final var len = a.length;\n    for (var i = 0; i < len; i++) {\n      sbuf.append(a[i]);\n      if (i != len - 1) {\n        sbuf.append(\", \");\n      }\n    }\n    sbuf.append(']');\n  }\n\n  private static void doubleArrayAppend(StringBuilder sbuf, double[] a) {\n    sbuf.append('[');\n    final var len = a.length;\n    for (var i = 0; i < len; i++) {\n      sbuf.append(a[i]);\n      if (i != len - 1) {\n        sbuf.append(\", \");\n      }\n    }\n    sbuf.append(']');\n  }\n\n  /**\n   * Helper method to determine if an {@link Object} array contains a {@link Throwable} as last element\n   *\n   * @param argArray\n   *          The arguments off which we want to know if it contains a {@link Throwable} as last element\n   * @return if the last {@link Object} in argArray is a {@link Throwable} this method will return it,\n   *          otherwise it returns null\n   */\n  public static Throwable getThrowableCandidate(final Object[] argArray) {\n    return NormalizedParameters.getThrowableCandidate(argArray);\n  }\n\n  /**\n   * Helper method to get all but the last element of an array\n   *\n   * @param argArray\n   *          The arguments from which we want to remove the last element\n   *\n   * @return a copy of the array without the last element\n   */\n  public static Object[] trimmedCopy(final Object[] argArray) {\n    return NormalizedParameters.trimmedCopy(argArray);\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/log/NormalizedParameters.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.log;\n\nimport javax.annotation.Nullable;\n\n/**\n * Holds normalized calling call parameters.\n *\n * Includes utility methods such as {@link #normalize(String, Object[], Throwable)} to help the normalization of parameters.\n *\n * @author ceki\n * @since 2.0\n */\nclass NormalizedParameters {\n\n  private NormalizedParameters() {\n  }\n\n  /**\n   * Helper method to determine if an {@link Object} array contains a\n   * {@link Throwable} as last element\n   *\n   * @param argArray The arguments off which we want to know if it contains a\n   *                 {@link Throwable} as last element\n   * @return if the last {@link Object} in argArray is a {@link Throwable} this\n   *         method will return it, otherwise it returns null\n   */\n  public static Throwable getThrowableCandidate(@Nullable final Object[] argArray) {\n    if (argArray == null || argArray.length == 0) {\n      return null;\n    }\n\n    final var lastEntry = argArray[argArray.length - 1];\n    if (lastEntry instanceof Throwable throwable) {\n      return throwable;\n    }\n\n    return null;\n  }\n\n  /**\n   * Helper method to get all but the last element of an array\n   *\n   * @param argArray The arguments from which we want to remove the last element\n   *\n   * @return a copy of the array without the last element\n   */\n  public static Object[] trimmedCopy(@Nullable final Object[] argArray) {\n    if (argArray == null || argArray.length == 0) {\n      throw new IllegalStateException(\"non-sensical empty or null argument array\");\n    }\n\n    final var trimmedLen = argArray.length - 1;\n\n    var trimmed = new Object[trimmedLen];\n\n    if (trimmedLen > 0) {\n      System.arraycopy(argArray, 0, trimmed, 0, trimmedLen);\n    }\n\n    return trimmed;\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/log/SonarLintLogger.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.log;\n\nimport io.sentry.Sentry;\nimport io.sentry.SentryLogLevel;\nimport java.util.Optional;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput.Level;\n\n/**\n * This is the logging facade to be used in SonarLint core.\n */\npublic class SonarLintLogger {\n  private static final SonarLintLogger logger = new SonarLintLogger();\n  private Level currentLevel = Level.OFF;\n\n  public static SonarLintLogger get() {\n    return logger;\n  }\n\n  private final InheritableThreadLocal<LogOutput> target = new InheritableThreadLocal<>();\n\n  SonarLintLogger() {\n    // singleton class\n  }\n\n  public void setTarget(@Nullable LogOutput target) {\n    this.target.set(target);\n  }\n\n  /**\n   * In some cases, the log output is not properly inherited by the \"child\" threads (especially when using shared thread pools).\n   * We have to copy the log output manually, in a similar way to https://logback.qos.ch/manual/mdc.html#managedThreads\n   */\n  @CheckForNull\n  public LogOutput getTargetForCopy() {\n    return this.target.get();\n  }\n\n  public void trace(String msg) {\n    log(msg, Level.TRACE, (Throwable) null);\n  }\n\n  public void trace(String msg, @Nullable Object arg) {\n    doLogExtractingThrowable(Level.TRACE, msg, new Object[]{arg});\n  }\n\n  public void trace(String msg, @Nullable Object arg1, @Nullable Object arg2) {\n    doLogExtractingThrowable(Level.TRACE, msg, new Object[]{arg1, arg2});\n  }\n\n  public void trace(String msg, Object... args) {\n    doLogExtractingThrowable(Level.TRACE, msg, args);\n  }\n\n  public void debug(String msg) {\n    log(msg, Level.DEBUG, (Throwable) null);\n  }\n\n  public void debug(String msg, @Nullable Object arg) {\n    doLogExtractingThrowable(Level.DEBUG, msg, new Object[]{arg});\n  }\n\n  public void debug(String msg, @Nullable Object arg1, @Nullable Object arg2) {\n    doLogExtractingThrowable(Level.DEBUG, msg, new Object[]{arg1, arg2});\n  }\n\n  public void debug(String msg, Object... args) {\n    doLogExtractingThrowable(Level.DEBUG, msg, args);\n  }\n\n  public void info(String msg) {\n    log(msg, Level.INFO, (Throwable) null);\n  }\n\n  public void info(String msg, @Nullable Object arg) {\n    doLogExtractingThrowable(Level.INFO, msg, new Object[]{arg});\n  }\n\n  public void info(String msg, @Nullable Object arg1, @Nullable Object arg2) {\n    doLogExtractingThrowable(Level.INFO, msg, new Object[]{arg1, arg2});\n  }\n\n  public void info(String msg, Object... args) {\n    doLogExtractingThrowable(Level.INFO, msg, args);\n  }\n\n  public void warn(String msg) {\n    log(msg, Level.WARN, (Throwable) null);\n  }\n\n  public void warn(String msg, Throwable thrown) {\n    log(msg, Level.WARN, thrown);\n  }\n\n  public void warn(String msg, @Nullable Object arg) {\n    doLogExtractingThrowable(Level.WARN, msg, new Object[]{arg});\n  }\n\n  public void warn(String msg, @Nullable Object arg1, @Nullable Object arg2) {\n    doLogExtractingThrowable(Level.WARN, msg, new Object[]{arg1, arg2});\n  }\n\n  public void warn(String msg, Object... args) {\n    doLogExtractingThrowable(Level.WARN, msg, args);\n  }\n\n  public void error(String msg) {\n    log(msg, Level.ERROR, (Throwable) null);\n  }\n\n  public void error(String msg, @Nullable Object arg) {\n    doLogExtractingThrowable(Level.ERROR, msg, new Object[]{arg});\n  }\n\n  public void error(String msg, @Nullable Object arg1, @Nullable Object arg2) {\n    doLogExtractingThrowable(Level.ERROR, msg, new Object[]{arg1, arg2});\n  }\n\n  public void error(String msg, Object... args) {\n    doLogExtractingThrowable(Level.ERROR, msg, args);\n  }\n\n  public void error(String msg, Throwable thrown) {\n    log(msg, Level.ERROR, thrown);\n  }\n\n  private void doLogExtractingThrowable(Level level, String msg, Object[] argArray) {\n    var tuple = MessageFormatter.arrayFormat(msg, argArray);\n    log(tuple.getMessage(), level, tuple.getThrowable());\n  }\n\n  private void log(@Nullable String formattedMessage, Level level, @Nullable Throwable t) {\n    if (currentLevel.isMoreVerboseOrEqual(level) && (formattedMessage != null || t != null)) {\n      var stacktrace = t == null ? null : LogOutput.stackTraceToString(t);\n      log(formattedMessage, level, stacktrace);\n    }\n  }\n\n  private void log(@Nullable String formattedMessage, Level level, @Nullable String stackTrace) {\n    var output = Optional.ofNullable(target.get()).orElseThrow(() -> {\n      var noLogOutputConfigured = new IllegalStateException(\"No log output configured\");\n      noLogOutputConfigured.printStackTrace(System.err);\n      return noLogOutputConfigured;\n    });\n    if (output != null) {\n      output.log(formattedMessage, level, stackTrace);\n      Sentry.logger().log(getSentryLogLevel(level), formattedMessage);\n    }\n  }\n\n  private static SentryLogLevel getSentryLogLevel(Level level) {\n    try {\n      return SentryLogLevel.valueOf(level.name());\n    } catch (IllegalArgumentException notSupported) {\n      // Current log levels map nicely almost 1:1, but this may change later\n      return SentryLogLevel.ERROR;\n    }\n  }\n\n  /**\n   * Append an 's' at the end of the word\n   */\n  public static String singlePlural(int count, String singular) {\n    return count == 1 ? singular : (singular + \"s\");\n  }\n\n  public void setLevel(Level newLevel) {\n    this.currentLevel = newLevel;\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/log/package-info.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.commons.log;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/package-info.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.commons;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/plugins/Dependency.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.plugins;\n\npublic record Dependency(SonarArtifact artifact, boolean optional) {\n  public static Dependency optional(SonarArtifact artifact) {\n    return new Dependency(artifact, true);\n  }\n\n  public static Dependency required(SonarArtifact key) {\n    return new Dependency(key, false);\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/plugins/EnterpriseReplacement.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.plugins;\n\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.Version;\n\n/**\n * Describes when a plugin is served as its enterprise edition.\n */\npublic record EnterpriseReplacement(boolean onSonarQubeCloud, @Nullable Version startingSonarQubeServerVersion) {\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/plugins/SonarArtifact.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.plugins;\n\nimport java.util.Set;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\n\npublic interface SonarArtifact {\n  /* A key that uniquely identifies the artifact */\n  String getKey();\n\n  /* The list of languages that the artifact supports */\n  Set<SonarLanguage> getLanguages();\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/plugins/SonarPlugin.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.plugins;\n\nimport java.util.Arrays;\nimport java.util.EnumSet;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\n\npublic enum SonarPlugin implements SonarArtifact {\n  ABAP(\"abap\"),\n  APEX(\"sonarapex\"),\n  C_FAMILY(\"cpp\"),\n  CSHARP_ENTERPRISE(\"csharpenterprise\"),\n  CS_OSS(\"csharp\", CSHARP_ENTERPRISE),\n  COBOL(\"cobol\"),\n  GO(\"go\", new EnterpriseReplacement(true, Version.create(\"2025.2\"))),\n  IAC(\"iac\", new EnterpriseReplacement(true, Version.create(\"2025.1\"))),\n  JAVA(\"java\"),\n  JCL(\"jcl\"),\n  JS(\"javascript\"),\n  KOTLIN(\"kotlin\"),\n  PHP(\"php\"),\n  PLI(\"pli\"),\n  PLSQL(\"plsql\"),\n  PYTHON(\"python\"),\n  RPG(\"rpg\"),\n  RUBY(\"ruby\"),\n  SCALA(\"sonarscala\"),\n  SONARLINT_OMNISHARP(\"omnisharp\", Set.of(\n    Dependency.required(CS_OSS),\n    Dependency.optional(CSHARP_ENTERPRISE),\n    Dependency.required(SonarPluginDependency.OMNISHARP_MONO),\n    Dependency.required(SonarPluginDependency.OMNISHARP_NET472),\n    Dependency.required(SonarPluginDependency.OMNISHARP_NET6))),\n  SWIFT(\"swift\"),\n  TEXT_DEVELOPER(\"textdeveloper\"),\n  TEXT_ENTERPRISE(\"textenterprise\"),\n  TEXT(\"text\", TEXT_DEVELOPER, TEXT_ENTERPRISE),\n  TSQL(\"tsql\"),\n  VBNET_ENTERPRISE(\"vbnetenterprise\"),\n  VBNET_OSS(\"vbnet\", VBNET_ENTERPRISE),\n  WEB(\"web\"),\n  XML(\"xml\");\n\n  public static Optional<SonarPlugin> findByKey(String key) {\n    return Arrays.stream(values()).filter(p -> p.key.equals(key)).findFirst();\n  }\n\n  /**\n   * Returns {@code true} if the given key is a known enterprise variant with a <em>different</em>\n   * plugin key than its base plugin (e.g. {@code \"csharpenterprise\"} for CS,\n   * {@code \"vbnetenterprise\"} for VB.NET).\n   *\n   * <p>Enterprise editions that share the base plugin key (GO, IAC, TEXT) are <em>not</em>\n   * considered enterprise variants in this sense — they are represented by\n   * {@link #getEnterpriseReplacement()} on the base plugin entry.</p>\n   */\n  public static boolean isEnterpriseVariant(String key) {\n    return Arrays.stream(values())\n      .flatMap(p -> p.enterpriseVariants.stream())\n      .anyMatch(ev -> ev.getKey().equals(key));\n  }\n\n  /**\n   * Returns the base plugin key for a different-key enterprise variant\n   * (e.g. {@code \"csharp\"} for {@code \"csharpenterprise\"}), or empty if the key is not a\n   * known enterprise variant.\n   */\n  public static Optional<SonarPlugin> basePluginFor(String enterpriseKey) {\n    return Arrays.stream(values())\n      .filter(p -> p.enterpriseVariants.stream().map(SonarPlugin::getKey).anyMatch(key -> key.equals(enterpriseKey)))\n      .findFirst();\n  }\n\n  /**\n   * Returns the base plugin key for a different-key enterprise variant\n   * (e.g. {@code \"csharp\"} for {@code \"csharpenterprise\"}), or empty if the key is not a\n   * known enterprise variant.\n   */\n  public static Optional<String> baseKeyFor(String enterpriseKey) {\n    return basePluginFor(enterpriseKey)\n      .map(SonarPlugin::getKey);\n  }\n\n  private final String key;\n  /**\n   * Non-empty for plugins that have at least one enterprise variant plugin that uses a\n   * different server key (e.g. {@code CSHARP_ENTERPRISE}). There can be more than one variant.\n   */\n  private final Set<SonarPlugin> enterpriseVariants;\n  /**\n   * Non-null for plugins whose enterprise edition is a drop-in replacement served under the\n   * <em>same</em> plugin key (GO, IAC, TEXT).\n   */\n  @Nullable\n  private final EnterpriseReplacement enterpriseReplacement;\n  private final Set<Dependency> dependencies;\n\n  SonarPlugin(String key) {\n    this.key = key;\n    this.enterpriseVariants = Set.of();\n    this.enterpriseReplacement = null;\n    this.dependencies = Set.of();\n  }\n\n  /** Constructor for plugins with a different-key enterprise variant (CS, VBNET). */\n  SonarPlugin(String key, SonarPlugin... enterpriseVariants) {\n    this.key = key;\n    this.enterpriseVariants = Set.of(enterpriseVariants);\n    this.enterpriseReplacement = null;\n    this.dependencies = Set.of();\n  }\n\n  /** Constructor for same-key enterprise plugins (GO, IAC, TEXT). */\n  SonarPlugin(String key, EnterpriseReplacement enterpriseReplacement) {\n    this.key = key;\n    this.enterpriseVariants = Set.of();\n    this.enterpriseReplacement = enterpriseReplacement;\n    this.dependencies = Set.of();\n  }\n\n  /** Constructor for plugins with dependencies (e.g. SONARLINT_OMNISHARP). */\n  SonarPlugin(String key, Set<Dependency> dependencies) {\n    this.key = key;\n    this.enterpriseVariants = Set.of();\n    this.enterpriseReplacement = null;\n    this.dependencies = dependencies;\n  }\n\n  @Override\n  public String getKey() {\n    return key;\n  }\n\n  /**\n   * Returns the enterprise variant plugins (with different keys) for this plugin, if any. Never null.\n   */\n  public Set<SonarPlugin> getEnterpriseVariants() {\n    return enterpriseVariants;\n  }\n\n  /**\n   * Returns the enterprise replacement metadata for same-key enterprise plugins (GO, IAC, TEXT),\n   * or empty for all other plugins.\n   */\n  public Optional<EnterpriseReplacement> getEnterpriseReplacement() {\n    return Optional.ofNullable(enterpriseReplacement);\n  }\n\n  public Set<Dependency> getDependencies() {\n    return dependencies;\n  }\n\n  @Override\n  public Set<SonarLanguage> getLanguages() {\n    var sonarLanguages = EnumSet.noneOf(SonarLanguage.class);\n    sonarLanguages.addAll(Arrays.stream(SonarLanguage.values()).filter(l -> l.getPlugin().getKey().equals(key)).collect(Collectors.toSet()));\n    basePluginFor(key).ifPresent(sonarPlugin -> sonarLanguages.addAll(sonarPlugin.getLanguages()));\n    return sonarLanguages;\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/plugins/SonarPluginDependency.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.plugins;\n\nimport java.util.Arrays;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\n\npublic enum SonarPluginDependency implements SonarArtifact {\n  OMNISHARP_MONO(\"omnisharp-mono\"),\n  OMNISHARP_NET472(\"omnisharp-net472\"),\n  OMNISHARP_NET6(\"omnisharp-net6\");\n\n  public static Optional<SonarPluginDependency> findByKey(String key) {\n    return Arrays.stream(values()).filter(p -> p.key.equals(key)).findFirst();\n  }\n\n  private final String key;\n\n  SonarPluginDependency(String key) {\n    this.key = key;\n  }\n\n  @Override\n  public String getKey() {\n    return key;\n  }\n\n  /**\n   * All current dependency artifacts are Omnisharp-related and support C# only.\n   * Computed lazily to avoid circular static initialization between SonarLanguage,\n   * SonarPlugin, and SonarPluginDependency.\n   */\n  @Override\n  public Set<SonarLanguage> getLanguages() {\n    return Set.of(SonarLanguage.CS);\n  }\n\n  public Set<SonarPlugin> getDependents() {\n    return Arrays.stream(SonarPlugin.values())\n      .filter(plugin -> plugin.getDependencies().stream().anyMatch(dep -> dep.artifact().equals(this)))\n      .collect(Collectors.toSet());\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/plugins/package-info.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.commons.plugins;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/progress/ExecutorServiceShutdownWatchable.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.progress;\n\nimport java.lang.ref.WeakReference;\nimport java.util.Collection;\nimport java.util.Deque;\nimport java.util.List;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ConcurrentLinkedDeque;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic class ExecutorServiceShutdownWatchable<E extends ExecutorService> implements ExecutorService {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final E wrapped;\n\n  private final Deque<WeakReference<SonarLintCancelMonitor>> monitorsToCancelOnShutdown = new ConcurrentLinkedDeque<>();\n\n  public ExecutorServiceShutdownWatchable(E wrapped) {\n    this.wrapped = wrapped;\n  }\n\n  public E getWrapped() {\n    return wrapped;\n  }\n\n  public void cancelOnShutdown(SonarLintCancelMonitor monitor) {\n    if (wrapped.isShutdown()) {\n      monitor.cancel();\n    } else {\n      monitorsToCancelOnShutdown.add(new WeakReference<>(monitor));\n      cleanGoneMonitors();\n    }\n  }\n\n  private void cleanGoneMonitors() {\n    monitorsToCancelOnShutdown.removeIf(ref -> ref.get() == null);\n  }\n\n  @Override\n  public void shutdown() {\n    wrapped.shutdown();\n    cancelMonitors();\n  }\n\n  @Override\n  public List<Runnable> shutdownNow() {\n    var result = wrapped.shutdownNow();\n    cancelMonitors();\n    return result;\n  }\n\n  private void cancelMonitors() {\n    monitorsToCancelOnShutdown.forEach(w -> {\n      var monitor = w.get();\n      if (monitor != null) {\n        try {\n          monitor.cancel();\n        } catch (Exception e) {\n          LOG.error(\"Failed to cancel on shutdown\", e);\n        }\n      }\n    });\n  }\n\n  @Override\n  public boolean isShutdown() {\n    return wrapped.isShutdown();\n  }\n\n  @Override\n  public boolean isTerminated() {\n    return wrapped.isTerminated();\n  }\n\n  @Override\n  public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {\n    return wrapped.awaitTermination(timeout, unit);\n  }\n\n  @Override\n  public <T> Future<T> submit(Callable<T> task) {\n    return wrapped.submit(task);\n  }\n\n\n  @Override\n  public <T> Future<T> submit(Runnable task, T result) {\n    return wrapped.submit(task, result);\n  }\n\n\n  @Override\n  public Future<?> submit(Runnable task) {\n    return wrapped.submit(task);\n  }\n\n\n  @Override\n  public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {\n    return wrapped.invokeAll(tasks);\n  }\n\n\n  @Override\n  public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException {\n    return wrapped.invokeAll(tasks, timeout, unit);\n  }\n\n\n  @Override\n  public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {\n    return wrapped.invokeAny(tasks);\n  }\n\n  @Override\n  public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {\n    return wrapped.invokeAny(tasks, timeout, unit);\n  }\n\n  @Override\n  public void execute(Runnable command) {\n    wrapped.execute(command);\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/progress/NoOpProgressMonitor.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.progress;\n\nimport javax.annotation.Nullable;\n\npublic class NoOpProgressMonitor implements ProgressMonitor {\n  @Override\n  public void notifyProgress(@Nullable String message, @Nullable Integer percentage) {\n    // no-op\n  }\n\n  @Override\n  public boolean isCanceled() {\n    // no-op\n    return false;\n  }\n\n  @Override\n  public void complete() {\n    // no-op\n  }\n\n  @Override\n  public void cancel() {\n    // no-op\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/progress/ProgressIndicator.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.progress;\n\nimport javax.annotation.Nullable;\n\npublic interface ProgressIndicator {\n  void notifyProgress(@Nullable String message, @Nullable Integer percentage);\n  boolean isCanceled();\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/progress/ProgressMonitor.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.progress;\n\npublic interface ProgressMonitor extends ProgressIndicator {\n  void complete();\n  void cancel();\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/progress/SonarLintCancelMonitor.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.progress;\n\nimport java.util.Deque;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ConcurrentLinkedDeque;\n\npublic class SonarLintCancelMonitor {\n\n  private boolean canceled;\n  private final Deque<Runnable> downstreamCancelAction = new ConcurrentLinkedDeque<>();\n\n  public synchronized void cancel() {\n    canceled = true;\n    downstreamCancelAction.forEach(Runnable::run);\n    downstreamCancelAction.clear();\n  }\n\n  public boolean isCanceled() {\n    return canceled;\n  }\n\n  public void checkCanceled() {\n    if (canceled) {\n      throw new CancellationException();\n    }\n  }\n\n  public synchronized void onCancel(Runnable action) {\n    if (canceled) {\n      action.run();\n    } else {\n      this.downstreamCancelAction.add(action);\n    }\n  }\n\n  public void watchForShutdown(ExecutorServiceShutdownWatchable<?> executorService) {\n    executorService.cancelOnShutdown(this);\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/progress/Task.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.progress;\n\npublic interface Task {\n  void run(ProgressIndicator indicator);\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/progress/TaskManager.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.progress;\n\nimport java.util.UUID;\nimport java.util.concurrent.ConcurrentHashMap;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic class TaskManager {\n  private static final ProgressMonitor NO_OP = new NoOpProgressMonitor();\n  private final ConcurrentHashMap<String, ProgressMonitor> progressMonitorsByTaskId = new ConcurrentHashMap<>();\n\n  public final void createAndRunTask(@Nullable String configurationScopeId, UUID taskId, String title, @Nullable String message,\n    boolean indeterminate, boolean cancellable, Task task,\n    SonarLintCancelMonitor cancelMonitor) {\n    trackNewTask(taskId, cancelMonitor);\n    runExistingTask(configurationScopeId, taskId, title, message, indeterminate, cancellable, task, cancelMonitor);\n  }\n\n  public final void runExistingTask(@Nullable String configurationScopeId, UUID taskId, String title, @Nullable String message,\n    boolean indeterminate, boolean cancellable, Task task, SonarLintCancelMonitor cancelMonitor) {\n    var progressMonitor = progressMonitorsByTaskId.get(taskId.toString());\n    if (progressMonitor == null) {\n      SonarLintLogger.get().debug(\"Cannot run unknown task '{}'\", taskId);\n      return;\n    }\n    startProgress(configurationScopeId, taskId, title, message, indeterminate, cancellable, cancelMonitor);\n    try {\n      task.run(progressMonitor);\n    } finally {\n      progressMonitor.complete();\n      progressMonitorsByTaskId.remove(taskId.toString());\n    }\n  }\n\n  public final void trackNewTask(UUID taskId, SonarLintCancelMonitor cancelMonitor) {\n    var progressMonitor = createProgress(taskId, cancelMonitor);\n    progressMonitorsByTaskId.put(taskId.toString(), progressMonitor);\n  }\n\n  public void cancel(String taskId) {\n    SonarLintLogger.get().debug(\"Cancelling task from RPC request {}\", taskId);\n    var progressMonitor = progressMonitorsByTaskId.remove(taskId);\n    if (progressMonitor != null) {\n      progressMonitor.cancel();\n    }\n  }\n\n  protected void startProgress(@Nullable String configurationScopeId, UUID taskId, String title, @Nullable String message, boolean indeterminate, boolean cancellable,\n    SonarLintCancelMonitor cancelMonitor) {\n    // can be overridden\n  }\n\n  protected ProgressMonitor createProgress(UUID taskId, SonarLintCancelMonitor cancelMonitor) {\n    // can be overridden\n    return NO_OP;\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/progress/package-info.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.commons.progress;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/storage/DatabaseExceptionReporter.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.storage;\n\nimport io.sentry.Sentry;\nimport java.sql.SQLException;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\n/**\n * Central utility for reporting database exceptions to Sentry with relevant context.\n * Includes simple message-hash deduplication (60 min window) to avoid flooding Sentry.\n */\npublic final class DatabaseExceptionReporter {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  static final String DEDUP_WINDOW_PROPERTY = \"sonarlint.internal.db.dedupWindowMs\";\n  private static final long DEFAULT_DEDUP_WINDOW_MS = 60 * 60 * 1000L; // 60 minutes\n\n  private static final Map<Integer, Long> recentMessageHashes = new ConcurrentHashMap<>();\n\n  private DatabaseExceptionReporter() {\n  }\n\n  /**\n   * Captures a database exception and reports it to Sentry with contextual tags.\n   *\n   * @param exception the exception to report\n   * @param phase     the phase where the exception occurred (e.g., \"startup\", \"runtime\", \"shutdown\")\n   * @param operation the specific operation (e.g., \"h2.pool.create\", \"flyway.migrate\", \"jooq.execute\")\n   * @param sql       optional SQL statement that caused the exception\n   */\n  public static void capture(Throwable exception, String phase, String operation, @Nullable String sql) {\n    var message = exception.getMessage();\n\n    if (message != null && isDuplicate(message.hashCode())) {\n      LOG.debug(\"Skipping duplicate database exception report: {} / {}\", phase, operation);\n      return;\n    }\n\n    LOG.debug(\"Reporting database exception to Sentry: {} / {}\", phase, operation);\n\n    Sentry.captureException(exception, scope -> {\n      scope.setTag(\"component\", \"database\");\n      scope.setTag(\"db.phase\", phase);\n      scope.setTag(\"db.operation\", operation);\n\n      if (exception instanceof SQLException sqlException) {\n        var sqlState = sqlException.getSQLState();\n        var errorCode = sqlException.getErrorCode();\n        if (sqlState != null) {\n          scope.setTag(\"db.sqlState\", sqlState);\n        }\n        scope.setTag(\"db.errorCode\", String.valueOf(errorCode));\n      }\n\n      if (sql != null && !sql.isEmpty()) {\n        scope.setExtra(\"db.sql\", truncateSql(sql));\n      }\n    });\n\n    if (message != null) {\n      recordException(message.hashCode());\n    }\n  }\n\n  public static void capture(Throwable exception, String phase, String operation) {\n    capture(exception, phase, operation, null);\n  }\n\n  private static boolean isDuplicate(int messageHash) {\n    var now = System.currentTimeMillis();\n    cleanupOldEntries(now);\n    var lastReported = recentMessageHashes.get(messageHash);\n    return lastReported != null;\n  }\n\n  private static void recordException(int messageHash) {\n    recentMessageHashes.put(messageHash, System.currentTimeMillis());\n  }\n\n  private static void cleanupOldEntries(long now) {\n    recentMessageHashes.entrySet().removeIf(entry -> (now - entry.getValue()) > getDedupWindowMs());\n  }\n\n  private static long getDedupWindowMs() {\n    var property = System.getProperty(DEDUP_WINDOW_PROPERTY);\n    if (property != null) {\n      try {\n        return Long.parseLong(property);\n      } catch (NumberFormatException e) {\n        // ignore, use default\n      }\n    }\n    return DEFAULT_DEDUP_WINDOW_MS;\n  }\n\n  private static String truncateSql(String sql) {\n    var maxLength = 1000;\n    if (sql.length() <= maxLength) {\n      return sql;\n    }\n    return sql.substring(0, maxLength) + \"... [truncated]\";\n  }\n\n  // Visible for testing\n  static void clearRecentExceptions() {\n    recentMessageHashes.clear();\n  }\n\n  // Visible for testing\n  static int getRecentExceptionsCount() {\n    return recentMessageHashes.size();\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/storage/JooqDatabaseExceptionListener.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.storage;\n\nimport org.jooq.ExecuteContext;\nimport org.jooq.ExecuteListener;\n\n/**\n * A jOOQ ExecuteListener that intercepts SQL execution exceptions and reports them\n * to Sentry via {@link DatabaseExceptionReporter}.\n */\npublic class JooqDatabaseExceptionListener implements ExecuteListener {\n\n  @Override\n  public void exception(ExecuteContext ctx) {\n    var exception = ctx.exception();\n    if (exception == null) {\n      return;\n    }\n\n    var sqlException = ctx.sqlException();\n    var exceptionToReport = sqlException != null ? sqlException : exception;\n    var sql = ctx.sql();\n\n    DatabaseExceptionReporter.capture(exceptionToReport, \"runtime\", \"jooq.execute\", sql);\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/storage/SonarLintDatabase.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.storage;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.flywaydb.core.Flyway;\nimport org.h2.jdbcx.JdbcConnectionPool;\nimport org.jooq.DSLContext;\nimport org.jooq.SQLDialect;\nimport org.jooq.conf.Settings;\nimport org.jooq.impl.DSL;\nimport org.jooq.impl.DefaultConfiguration;\nimport org.jooq.impl.DefaultExecuteListenerProvider;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic final class SonarLintDatabase {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  public static final String SQ_IDE_DB_FILENAME = \"sq-ide\";\n\n  private final JdbcConnectionPool dataSource;\n  private final DSLContext dsl;\n\n  public SonarLintDatabase(Path storageRoot) {\n    JdbcConnectionPool ds;\n    try {\n      var baseDir = storageRoot.resolve(\"h2\");\n      deleteLegacyDatabase(baseDir);\n      Files.createDirectories(baseDir);\n      var dbBasePath = baseDir.toRealPath().resolve(SQ_IDE_DB_FILENAME).toAbsolutePath();\n      var url = \"jdbc:h2:\" + dbBasePath + \";AUTO_SERVER=TRUE\";\n      // Ensure H2 AUTO_SERVER binds and advertises loopback to allow local cross-process connections reliably\n      var bindAddressProperty = \"h2.bindAddress\";\n      if (StringUtils.isEmpty(System.getProperty(bindAddressProperty))) {\n        System.setProperty(bindAddressProperty, \"127.0.0.1\");\n      }\n      LOG.debug(\"Initializing H2Database with URL {}\", url);\n      ds = JdbcConnectionPool.create(url, \"sa\", \"\");\n    } catch (Exception e) {\n      DatabaseExceptionReporter.capture(e, \"startup\", \"h2.pool.create\");\n      throw new IllegalStateException(\"Failed to initialize H2Database\", e);\n    }\n    this.dataSource = ds;\n\n    var flyway = Flyway.configure()\n      .dataSource(this.dataSource)\n      .locations(\"classpath:db/migration\")\n      .defaultSchema(\"PUBLIC\")\n      .schemas(\"PUBLIC\")\n      .createSchemas(true)\n      .baselineOnMigrate(true)\n      .failOnMissingLocations(false)\n      .load();\n    try {\n      flyway.migrate();\n    } catch (Exception e) {\n      // We are catching the exception for Sentry and rethrowing here to fail starting the backend\n      DatabaseExceptionReporter.capture(e, \"startup\", \"flyway.migrate\");\n      throw e;\n    }\n\n    System.setProperty(\"org.jooq.no-tips\", \"true\");\n    System.setProperty(\"org.jooq.no-logo\", \"true\");\n\n    var jooqConfig = new DefaultConfiguration()\n      .set(this.dataSource)\n      .set(SQLDialect.H2)\n      .set(new Settings().withExecuteLogging(false))\n      .set(new DefaultExecuteListenerProvider(new JooqDatabaseExceptionListener()));\n    this.dsl = DSL.using(jooqConfig);\n  }\n\n  private static void deleteLegacyDatabase(Path baseDir) {\n    // see SLCORE-1847\n    var legacyDb = baseDir.resolve(\"sonarlint\");\n    if (Files.exists(legacyDb)) {\n      FileUtils.deleteQuietly(legacyDb.toFile());\n    }\n  }\n\n  public DSLContext dsl() {\n    return dsl;\n  }\n\n  public void shutdown() {\n    try {\n      dataSource.dispose();\n      LOG.debug(\"H2Database disposed\");\n    } catch (Exception e) {\n      DatabaseExceptionReporter.capture(e, \"shutdown\", \"h2.pool.dispose\");\n      LOG.debug(\"Error while disposing H2Database: {}\", e.getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/storage/XodusPurgeUtils.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.storage;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport org.apache.commons.io.FileUtils;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic class XodusPurgeUtils {\n\n  private XodusPurgeUtils() {\n    // Static class\n  }\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  public static void deleteInFolderWithPattern(Path folder, String pattern) {\n    if (Files.exists(folder)) {\n      try (var stream = Files.newDirectoryStream(folder, pattern)) {\n        for (var path : stream) {\n          FileUtils.deleteQuietly(path.toFile());\n        }\n      } catch (Exception e) {\n        LOG.error(\"Unable to remove files in {} for pattern {}\", folder, pattern, e);\n      }\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/storage/adapter/LocalDateAdapter.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.storage.adapter;\n\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonWriter;\nimport java.io.IOException;\nimport java.time.LocalDate;\n\npublic class LocalDateAdapter extends TypeAdapter<LocalDate> {\n\n  @Override\n  public void write(JsonWriter jsonWriter, LocalDate localDate) throws IOException {\n    jsonWriter.beginObject()\n      .name(\"year\").value(localDate.getYear())\n      .name(\"month\").value(localDate.getMonthValue())\n      .name(\"day\").value(localDate.getDayOfMonth())\n      .endObject();\n  }\n\n  @Override\n  public LocalDate read(JsonReader jsonReader) throws IOException {\n    var year = 0;\n    var month = 0;\n    var day = 0;\n    jsonReader.beginObject();\n    while(jsonReader.hasNext()) {\n      switch(jsonReader.nextName()) {\n        case \"year\":\n          year = jsonReader.nextInt();\n          break;\n        case \"month\":\n          month = jsonReader.nextInt();\n          break;\n        case \"day\":\n          day = jsonReader.nextInt();\n          break;\n        default:\n          break;\n      }\n    }\n    jsonReader.endObject();\n    return LocalDate.of(year, month, day);\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/storage/adapter/LocalDateTimeAdapter.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.storage.adapter;\n\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonWriter;\nimport java.io.IOException;\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.LocalTime;\n\npublic class LocalDateTimeAdapter extends TypeAdapter<LocalDateTime> {\n\n  @Override\n  public void write(JsonWriter jsonWriter, LocalDateTime localDateTime) throws IOException {\n    jsonWriter.beginObject()\n      .name(\"date\");\n    new LocalDateAdapter().nullSafe().write(jsonWriter, localDateTime.toLocalDate());\n    jsonWriter.name(\"time\").beginObject()\n        .name(\"hour\").value(localDateTime.getHour())\n        .name(\"minute\").value(localDateTime.getMinute())\n        .name(\"second\").value(localDateTime.getSecond())\n        .name(\"nano\").value(localDateTime.getNano())\n      .endObject()\n    .endObject();\n  }\n\n  @Override\n  public LocalDateTime read(JsonReader jsonReader) throws IOException {\n    LocalDate localDate = null;\n    LocalTime localTime = null;\n    jsonReader.beginObject();\n    while(jsonReader.hasNext()) {\n      switch(jsonReader.nextName()) {\n        case \"date\":\n          localDate = new LocalDateAdapter().read(jsonReader);\n          break;\n        case \"time\":\n          localTime = readTime(jsonReader);\n          break;\n        default:\n          break;\n      }\n    }\n    jsonReader.endObject();\n    if (localDate == null || localTime == null) {\n      throw new IllegalStateException(\"Unable to parse LocalDateTime\");\n    }\n    return LocalDateTime.of(localDate, localTime);\n  }\n\n  private static LocalTime readTime(JsonReader jsonReader) throws IOException {\n    var hour = 0;\n    var minute = 0;\n    var second = 0;\n    var nano = 0;\n    jsonReader.beginObject();\n    while(jsonReader.hasNext()) {\n      switch(jsonReader.nextName()) {\n        case \"hour\":\n          hour = jsonReader.nextInt();\n          break;\n        case \"minute\":\n          minute = jsonReader.nextInt();\n          break;\n        case \"second\":\n          second = jsonReader.nextInt();\n          break;\n        case \"nano\":\n          nano = jsonReader.nextInt();\n          break;\n        default:\n          break;\n      }\n    }\n    jsonReader.endObject();\n    return LocalTime.of(hour, minute, second, nano);\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/storage/adapter/OffsetDateTimeAdapter.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.storage.adapter;\n\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonWriter;\nimport java.io.IOException;\nimport java.time.OffsetDateTime;\nimport java.time.format.DateTimeFormatter;\n\npublic class OffsetDateTimeAdapter extends TypeAdapter<OffsetDateTime> {\n\n  private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(\"yyyy-MM-dd'T'HH:mm:ss.SSSXXX\");\n\n  @Override\n  public void write(JsonWriter jsonWriter, OffsetDateTime offsetDateTime) throws IOException {\n    jsonWriter.value(FORMATTER.format(offsetDateTime));\n  }\n\n  @Override\n  public OffsetDateTime read(JsonReader jsonReader) throws IOException {\n    return OffsetDateTime.parse(jsonReader.nextString(), FORMATTER);\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/storage/adapter/package-info.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.commons.storage.adapter;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/storage/local/FileStorageManager.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.storage.local;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardOpenOption;\nimport java.nio.file.attribute.FileTime;\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.OffsetDateTime;\nimport java.util.Base64;\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.storage.adapter.LocalDateAdapter;\nimport org.sonarsource.sonarlint.core.commons.storage.adapter.LocalDateTimeAdapter;\nimport org.sonarsource.sonarlint.core.commons.storage.adapter.OffsetDateTimeAdapter;\n\npublic class FileStorageManager<T extends LocalStorage> {\n\n  public static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final Path path;\n  private final Gson gson;\n  private final Class<T> localStorageType;\n  private final Supplier<T> defaultSupplier;\n  private T inMemoryStorage;\n  private FileTime lastModified;\n\n  public FileStorageManager(Path path, Supplier<T> defaultSupplier, Class<T> localStorageType) {\n    this.path = path;\n    this.gson = new GsonBuilder()\n      .registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter().nullSafe())\n      .registerTypeAdapter(LocalDate.class, new LocalDateAdapter().nullSafe())\n      .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter().nullSafe())\n      .create();\n    this.localStorageType = localStorageType;\n    this.defaultSupplier = defaultSupplier;\n    this.inMemoryStorage = defaultSupplier.get();\n  }\n\n  public T getStorage() {\n    if (!path.toFile().exists()) {\n      inMemoryStorage = defaultSupplier.get();\n      invalidateCache();\n    } else if (isCacheInvalid()) {\n      refreshInMemoryStorage();\n    }\n    return inMemoryStorage;\n  }\n\n  public boolean isCacheInvalid() {\n    try {\n      return lastModified == null || !lastModified.equals(Files.getLastModifiedTime(path));\n    } catch (IOException e) {\n      LOG.warn(\"Error checking if cache is invalid\", e);\n      return true;\n    }\n  }\n\n  public void invalidateCache() {\n    lastModified = null;\n  }\n\n  public synchronized void refreshInMemoryStorage() {\n    try {\n      if (isCacheInvalid()) {\n        inMemoryStorage = read();\n        updateLastModified();\n      }\n    } catch (Exception e) {\n      LOG.warn(\"Error loading data from the file\", e);\n    }\n  }\n\n  public void updateLastModified() throws IOException {\n    lastModified = Files.getLastModifiedTime(path);\n  }\n\n  public T read() throws IOException {\n    try (var fileChannel = FileChannel.open(path, StandardOpenOption.READ)) {\n      return read(fileChannel);\n    }\n  }\n\n  public void tryUpdateAtomically(Consumer<T> updater) {\n    try {\n      updateAtomically(updater);\n    } catch (Exception e) {\n      invalidateCache();\n      LOG.warn(\"Error updating data in the file\", e);\n    }\n  }\n\n  private synchronized void updateAtomically(Consumer<T> updater) throws IOException {\n    Files.createDirectories(path.getParent());\n    try (var fileChannel = FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.SYNC);\n         var ignored = fileChannel.lock()) {\n      var storageData = read(fileChannel);\n      updater.accept(storageData);\n      storageData.validateAndMigrate();\n      writeAtomically(fileChannel, storageData);\n      inMemoryStorage = storageData;\n    }\n    updateLastModified();\n  }\n\n  private T read(FileChannel fileChannel) {\n    try {\n      if (fileChannel.size() == 0) {\n        return defaultSupplier.get();\n      }\n      final var buf = ByteBuffer.allocate((int) fileChannel.size());\n      fileChannel.read(buf);\n      var decoded = Base64.getDecoder().decode(buf.array());\n      var oldJson = new String(decoded, StandardCharsets.UTF_8);\n      var localStorage = gson.fromJson(oldJson, localStorageType);\n      localStorage.validateAndMigrate();\n\n      return localStorage;\n    } catch (Exception e) {\n      LOG.warn(\"Error reading data from file\", e);\n      return defaultSupplier.get();\n    }\n  }\n\n  private void writeAtomically(FileChannel fileChannel, T newData) throws IOException {\n    fileChannel.truncate(0);\n\n    var newJson = gson.toJson(newData);\n    var encoded = Base64.getEncoder().encode(newJson.getBytes(StandardCharsets.UTF_8));\n\n    fileChannel.write(ByteBuffer.wrap(encoded));\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/storage/local/LocalStorage.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.storage.local;\n\npublic interface LocalStorage {\n  default void validateAndMigrate() {\n\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/storage/local/package-info.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.commons.storage.local;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/storage/package-info.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.commons.storage;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/tracing/Span.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.tracing;\n\nimport io.sentry.ISpan;\nimport io.sentry.SpanStatus;\n\npublic class Span {\n\n  private final ISpan sentrySpan;\n\n  Span(ISpan sentrySpan) {\n    this.sentrySpan = sentrySpan;\n  }\n\n  public void finishExceptionally(Throwable throwable) {\n    this.sentrySpan.setThrowable(throwable);\n    this.sentrySpan.finish(SpanStatus.INTERNAL_ERROR);\n  }\n\n  public void finishSuccessfully() {\n    this.sentrySpan.finish(SpanStatus.OK);\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/tracing/Step.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.tracing;\n\nimport io.sentry.ITransaction;\nimport javax.annotation.Nullable;\n\npublic class Step {\n\n  private final String task;\n  private final Runnable operation;\n\n  public Step(String task, Runnable operation) {\n    this.task = task;\n    this.operation = operation;\n  }\n\n  public void execute() {\n    operation.run();\n  }\n\n  public void executeTransaction(ITransaction transaction, @Nullable String description) {\n    var span = new Span(transaction.startChild(task, description));\n    try {\n      operation.run();\n      span.finishSuccessfully();\n    } catch (Exception exception) {\n      span.finishExceptionally(exception);\n      throw exception;\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/tracing/Trace.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.tracing;\n\nimport io.sentry.ITransaction;\nimport io.sentry.Sentry;\nimport io.sentry.SpanStatus;\nimport java.util.function.Supplier;\nimport java.util.stream.Stream;\nimport javax.annotation.Nullable;\n\npublic class Trace {\n\n  private final ITransaction transaction;\n\n  private Trace(ITransaction transaction) {\n    this.transaction = transaction;\n  }\n\n  public static Trace begin(String name, String operation) {\n    return new Trace(Sentry.startTransaction(name, operation));\n  }\n\n  public static <T> T startChild(@Nullable Trace trace, String task, @Nullable String description, Supplier<T> operation) {\n    if (trace == null) {\n      return operation.get();\n    }\n    var span = new Span(trace.transaction.startChild(task, description));\n    try {\n      var result = operation.get();\n      span.finishSuccessfully();\n      return result;\n    } catch (Exception exception) {\n      span.finishExceptionally(exception);\n      throw exception;\n    }\n  }\n\n  public static void startChild(@Nullable Trace trace, String task, @Nullable String description, Runnable operation) {\n    if (trace == null) {\n      operation.run();\n      return;\n    }\n    var span = new Span(trace.transaction.startChild(task, description));\n    try {\n      operation.run();\n      span.finishSuccessfully();\n    } catch (Exception exception) {\n      span.finishExceptionally(exception);\n      throw exception;\n    }\n  }\n\n  public static void startChildren(@Nullable Trace trace, @Nullable String description, Step... steps) {\n    if (trace == null) {\n      Stream.of(steps).forEach(Step::execute);\n      return;\n    }\n    Stream.of(steps).forEach(step -> step.executeTransaction(trace.transaction,  description));\n  }\n\n  public void setData(String key, Object value) {\n    this.transaction.setData(key, value);\n  }\n\n  public void setThrowable(Throwable throwable) {\n    this.transaction.setThrowable(throwable);\n  }\n\n  public void finishExceptionally(Throwable throwable) {\n    this.transaction.setThrowable(throwable);\n    this.transaction.setStatus(SpanStatus.INTERNAL_ERROR);\n    this.transaction.finish();\n  }\n\n  public void finishSuccessfully() {\n    this.transaction.setStatus(SpanStatus.OK);\n    this.transaction.finish();\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/tracing/package-info.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.commons.tracing;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/util/FailSafeExecutors.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.util;\n\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.FutureTask;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.ScheduledThreadPoolExecutor;\nimport java.util.concurrent.SynchronousQueue;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\n/**\n * This class should always be preferred to {@link java.util.concurrent.Executors}, except for a few cases regarding RPC read/write threads.\n */\npublic class FailSafeExecutors {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private FailSafeExecutors() {\n    // utility class\n  }\n\n  public static ExecutorService newSingleThreadExecutor(String threadName) {\n    return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), r -> new Thread(r, threadName)) {\n      @Override\n      protected void afterExecute(Runnable task, @Nullable Throwable throwable) {\n        var extractedThrowable = extractThrowable(task, throwable);\n        if (extractedThrowable != null) {\n          LOG.error(\"An error occurred while executing a task in \" + threadName, extractedThrowable);\n        }\n        super.afterExecute(task, throwable);\n      }\n    };\n  }\n\n  public static ScheduledExecutorService newSingleThreadScheduledExecutor(String threadName) {\n    return new ScheduledThreadPoolExecutor(1, r -> new Thread(r, threadName)) {\n      @Override\n      protected void afterExecute(Runnable task, @Nullable Throwable throwable) {\n        var extractedThrowable = extractThrowable(task, throwable);\n        if (extractedThrowable != null) {\n          LOG.error(\"An error occurred while executing a scheduled task in \" + threadName, extractedThrowable);\n        }\n        super.afterExecute(task, throwable);\n      }\n    };\n  }\n\n  public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {\n    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), threadFactory) {\n      @Override\n      protected void afterExecute(Runnable task, @Nullable Throwable throwable) {\n        var extractedThrowable = extractThrowable(task, throwable);\n        if (extractedThrowable != null) {\n          LOG.error(\"An error occurred while executing a task in \" + Thread.currentThread(), extractedThrowable);\n        }\n        super.afterExecute(task, throwable);\n      }\n    };\n  }\n\n  @CheckForNull\n  private static Throwable extractThrowable(Runnable task, @Nullable Throwable throwable) {\n    if (throwable != null) {\n      return throwable;\n    }\n    if (task instanceof FutureTask<?> futureTask) {\n      try {\n        if (futureTask.isDone() && !futureTask.isCancelled()) {\n          futureTask.get();\n        }\n      } catch (InterruptedException e) {\n        Thread.currentThread().interrupt();\n      } catch (ExecutionException e) {\n        return e.getCause();\n      } catch (CancellationException e) {\n        // nothing to do\n      }\n    }\n    return null;\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/util/FileUtils.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.util;\n\nimport java.net.URI;\nimport java.nio.file.Path;\n\npublic class FileUtils {\n\n  public static Path getFilePathFromUri(URI uri) {\n    try {\n      // In case the path contains non-ASCII characters, Path.of() will fail, we should first try to encode the path via toURL()\n      return Path.of(uri.toURL().getPath());\n    } catch (Exception e) {\n      return Path.of(uri);\n    }\n  }\n\n  private FileUtils() {\n    // utility class\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/util/StringUtils.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.util;\n\nimport javax.annotation.Nullable;\n\npublic class StringUtils {\n\n  private static final char RTLO = '\\u202E';\n\n  public static String pluralize(long count, String word) {\n    var pluralizedWord = count == 1 ? word : (word + 's');\n    return count + \" \" + pluralizedWord;\n  }\n\n  public static String sanitizeAgainstRTLO(@Nullable String input) {\n    if (input == null) {\n      return null;\n    }\n    return input.replaceAll(String.valueOf(RTLO), \"\");\n  }\n\n  private StringUtils() {\n    // utility class\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/util/git/BlameResult.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.util.git;\n\nimport java.time.Instant;\nimport java.util.List;\n\npublic record BlameResult(List<Instant> lineCommitDates) {\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/util/git/GitBlameReader.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.util.git;\n\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class GitBlameReader {\n  private static final String COMMITTER_MAIL = \"committer-mail \";\n  private static final String COMMITTER_TIME = \"committer-time \";\n  private static final String NOT_COMMITTED = \"<not.committed.yet>\";\n\n  private final List<Instant> commitDates = new ArrayList<>();\n  private boolean isCurrentLineCommitted;\n\n  public void readLine(String line) {\n    // committer-mail comes before committer-time\n    if (line.startsWith(COMMITTER_MAIL)) {\n      var committerEmail = line.substring(COMMITTER_MAIL.length());\n      isCurrentLineCommitted = !committerEmail.equals(NOT_COMMITTED);\n    } else if (line.startsWith(COMMITTER_TIME)) {\n      commitDates.add(isCurrentLineCommitted ? Instant.ofEpochSecond(Long.parseLong(line.substring(COMMITTER_TIME.length()))).truncatedTo(ChronoUnit.SECONDS) : null);\n    }\n  }\n\n  public BlameResult getResult() {\n    return new BlameResult(commitDates);\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/util/git/GitService.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.util.git;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.function.UnaryOperator;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.apache.commons.io.FilenameUtils;\nimport org.eclipse.jgit.api.Git;\nimport org.eclipse.jgit.api.errors.GitAPIException;\nimport org.eclipse.jgit.api.errors.NoHeadException;\nimport org.eclipse.jgit.diff.RawTextComparator;\nimport org.eclipse.jgit.ignore.IgnoreNode;\nimport org.eclipse.jgit.lib.Constants;\nimport org.eclipse.jgit.lib.ObjectLoader;\nimport org.eclipse.jgit.lib.Repository;\nimport org.eclipse.jgit.lib.RepositoryBuilder;\nimport org.eclipse.jgit.revwalk.RevWalk;\nimport org.eclipse.jgit.treewalk.TreeWalk;\nimport org.sonar.scm.git.blame.RepositoryBlameCommand;\nimport org.sonarsource.sonarlint.core.commons.MultiFileBlameResult;\nimport org.sonarsource.sonarlint.core.commons.SonarLintGitIgnore;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.util.git.exceptions.GitException;\nimport org.sonarsource.sonarlint.core.commons.util.git.exceptions.GitRepoNotFoundException;\n\nimport static java.util.Optional.ofNullable;\nimport static org.eclipse.jgit.lib.Constants.GITIGNORE_FILENAME;\n\npublic class GitService {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final int FILES_GIT_BLAME_TRIGGER_THRESHOLD = 10;\n\n  private final NativeGitLocator nativeGitLocator;\n\n  GitService(NativeGitLocator nativeGitLocator) {\n    this.nativeGitLocator = nativeGitLocator;\n  }\n\n  public static GitService create() {\n    return new GitService(new NativeGitLocator());\n  }\n\n  public MultiFileBlameResult getBlameResult(Path projectBaseDir, Set<Path> projectBaseRelativeFilePaths, Set<URI> fileUris, @Nullable UnaryOperator<String> fileContentProvider,\n    Instant thresholdDate) {\n\n    var nativeGitExecutable = nativeGitLocator.getNativeGitExecutable();\n    if (nativeGitExecutable.isEmpty() || fileUris.size() >= FILES_GIT_BLAME_TRIGGER_THRESHOLD) {\n      return blameWithGitFilesBlameLibrary(projectBaseDir, projectBaseRelativeFilePaths, fileContentProvider);\n    }\n    return nativeGitExecutable.get().blame(projectBaseDir, fileUris, thresholdDate);\n  }\n\n  // Could be optimized to only fetch VCS changed files matching the base dir\n  // Currently, it finds all the files of the git repo, even when called against a subfolder\n  public static Set<URI> getVCSChangedFiles(@Nullable Path baseDir) {\n    if (baseDir == null) {\n      return Set.of();\n    }\n    try (var repo = buildGitRepository(baseDir);\n         var git = new Git(repo)) {\n      var workTreePath = repo.getWorkTree().toPath();\n      var status = git.status().call();\n      var uncommitted = status.getUncommittedChanges().stream();\n      var untracked = status.getUntracked().stream().filter(f -> !f.equals(GITIGNORE_FILENAME));\n      return Stream.concat(uncommitted, untracked)\n        .map(workTreePath::resolve)\n        .filter(path -> path.normalize().startsWith(baseDir.normalize()))\n        .map(Path::toUri)\n        .collect(Collectors.toSet());\n    } catch (GitAPIException | GitException e) {\n      LOG.debug(\"Git repository access error: \", e);\n      return Set.of();\n    }\n  }\n\n  /**\n   * Retrieves the Git remote URL for the origin remote from the repository.\n   *\n   * @param baseDir the base directory of the project\n   * @return Optional containing the remote URL if found, empty otherwise\n   */\n  @CheckForNull\n  public static String getRemoteUrl(@Nullable Path baseDir) {\n    if (baseDir == null) {\n      return null;\n    }\n\n    try (var gitRepo = buildGitRepository(baseDir)) {\n      var config = gitRepo.getConfig();\n      return config.getString(\"remote\", \"origin\", \"url\");\n    } catch (GitRepoNotFoundException e) {\n      LOG.debug(\"Git repository not found for {}\", baseDir);\n      return null;\n    } catch (Exception e) {\n      LOG.debug(\"Error retrieving remote URL for {}: {}\", baseDir, e.getMessage());\n      return null;\n    }\n  }\n\n  public static MultiFileBlameResult blameWithGitFilesBlameLibrary(Path projectBaseDir, Set<Path> projectBaseRelativeFilePaths,\n    @Nullable UnaryOperator<String> fileContentProvider) {\n    LOG.debug(\"Falling back to JGit\");\n    var startTime = System.currentTimeMillis();\n    try (var gitRepo = buildGitRepository(projectBaseDir)) {\n      var gitRepoRelativeProjectBaseDir = getRelativePath(gitRepo, projectBaseDir);\n\n      var gitRepoRelativeFilePaths = projectBaseRelativeFilePaths.stream()\n        .map(gitRepoRelativeProjectBaseDir::resolve)\n        .map(Path::toString)\n        .map(FilenameUtils::separatorsToUnix)\n        .collect(Collectors.toSet());\n\n      var blameCommand = new RepositoryBlameCommand(gitRepo)\n        .setTextComparator(RawTextComparator.WS_IGNORE_ALL)\n        .setMultithreading(true)\n        .setFilePaths(gitRepoRelativeFilePaths);\n      ofNullable(fileContentProvider)\n        .ifPresent(provider -> blameCommand.setFileContentProvider(adaptToPlatformBasedPath(provider)));\n\n      try {\n        var blameResult = blameCommand.call();\n        var multiFileBlameResult = new MultiFileBlameResult(\n          blameResult.getFileBlameByPath().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> new BlameResult(Arrays.asList(e.getValue().getCommitDates())))),\n          gitRepoRelativeProjectBaseDir);\n        LOG.debug(\"Blamed {} files in {}ms\", projectBaseRelativeFilePaths.size(), System.currentTimeMillis() - startTime);\n        return multiFileBlameResult;\n      } catch (NoHeadException e) {\n        // it means that the repository has no commits, so we can't get any blame information\n        return MultiFileBlameResult.empty(gitRepoRelativeProjectBaseDir);\n      } catch (GitAPIException e) {\n        throw new IllegalStateException(\"Failed to blame repository files\", e);\n      }\n    }\n  }\n\n  private static Path getRelativePath(Repository gitRepo, Path projectBaseDir) {\n    var repoDir = gitRepo.isBare() ? gitRepo.getDirectory() : gitRepo.getWorkTree();\n    return repoDir.toPath().relativize(projectBaseDir);\n  }\n\n  private static Repository buildGitRepository(Path basedir) {\n    try {\n      var repositoryBuilder = new RepositoryBuilder()\n        .findGitDir(basedir.toFile());\n      if (ofNullable(repositoryBuilder.getGitDir()).isEmpty()) {\n        throw new GitRepoNotFoundException(basedir.toString());\n      }\n\n      var repository = repositoryBuilder.build();\n      try (var objReader = repository.getObjectDatabase().newReader()) {\n        // SONARSCGIT-2 Force initialization of shallow commits to avoid later concurrent modification issue\n        objReader.getShallowCommits();\n        return repository;\n      }\n    } catch (IOException e) {\n      throw new IllegalStateException(\"Unable to open Git repository\", e);\n    }\n  }\n\n  /**\n   * Assumes the supplied {@param baseDir} or some of its parents is a git repository.\n   * If error occurs during parsing .gitignore file then an ignore node with no rules is created -> Files checked against this node will be considered as not ignored.\n   */\n  public static SonarLintGitIgnore createSonarLintGitIgnore(@Nullable Path baseDir) {\n    if (baseDir == null) {\n      return new SonarLintGitIgnore(new IgnoreNode());\n    }\n    try (var gitRepo = buildGitRepository(baseDir)) {\n      var ignoreNode = buildIgnoreNode(gitRepo);\n      return new SonarLintGitIgnore(ignoreNode);\n    } catch (GitRepoNotFoundException e) {\n      LOG.info(\"Git Repository not found for {}. The path {} is not in a Git repository\", baseDir, e.getPath());\n    } catch (FileNotFoundException e) {\n      LOG.info(\".gitignore file was not found for {}\", baseDir);\n    } catch (Exception e) {\n      LOG.warn(\"Error occurred while reading .gitignore file: \", e);\n      LOG.warn(\"Building empty ignore node with no rules. Files checked against this node will be considered as not ignored.\");\n    }\n    return new SonarLintGitIgnore(new IgnoreNode());\n  }\n\n  private static IgnoreNode buildIgnoreNode(Repository repository) throws IOException {\n    var ignoreNode = new IgnoreNode();\n    if (repository.isBare()) {\n      readGitIgnoreFileFromBareRepo(repository, ignoreNode);\n    } else {\n      readIgnoreFileFromNonBareRepo(repository, ignoreNode);\n    }\n    return ignoreNode;\n  }\n\n  private static void readGitIgnoreFileFromBareRepo(Repository repository, IgnoreNode ignoreNode) throws IOException {\n    var loader = readFileContentFromGitRepo(repository, GITIGNORE_FILENAME);\n    if (loader.isPresent()) {\n      try (InputStream inputStream = loader.get().openStream()) {\n        ignoreNode.parse(inputStream);\n      }\n    }\n  }\n\n  private static void readIgnoreFileFromNonBareRepo(Repository repository, IgnoreNode ignoreNode) throws IOException {\n    var rootDir = repository.getWorkTree();\n    var gitIgnoreFile = new File(rootDir, GITIGNORE_FILENAME);\n    try (var inputStream = new FileInputStream(gitIgnoreFile)) {\n      ignoreNode.parse(inputStream);\n    }\n  }\n\n  private static UnaryOperator<String> adaptToPlatformBasedPath(UnaryOperator<String> provider) {\n    return unixPath -> {\n      var platformBasedPath = Path.of(unixPath).toString();\n      return provider.apply(platformBasedPath);\n    };\n  }\n\n  private static Optional<ObjectLoader> readFileContentFromGitRepo(Repository repository, String fileName) throws IOException {\n    var headId = repository.resolve(Constants.HEAD);\n    if (headId == null) {\n      // No commits in the repository\n      return Optional.empty();\n    }\n\n    try (var revWalk = new RevWalk(repository)) {\n      var commit = revWalk.parseCommit(headId);\n\n      try (var treeWalk = new TreeWalk(repository)) {\n        treeWalk.addTree(commit.getTree());\n        treeWalk.setRecursive(true);\n        treeWalk.setFilter(org.eclipse.jgit.treewalk.filter.PathFilter.create(fileName));\n\n        if (!treeWalk.next()) {\n          return Optional.empty();\n        }\n\n        return Optional.of(repository.open(treeWalk.getObjectId(0)));\n      }\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/util/git/NativeGit.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.util.git;\n\nimport java.net.URI;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.time.Period;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.function.Consumer;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.MultiFileBlameResult;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.util.FileUtils;\n\nimport static java.lang.String.format;\n\npublic class NativeGit {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final Version MINIMUM_REQUIRED_GIT_VERSION = Version.create(\"2.24\");\n  private static final String GIT_VERSION_OUTPUT_PREFIX = \"git version\";\n  private static final Period BLAME_HISTORY_WINDOW = Period.ofDays(365);\n  private final String executable;\n\n  public NativeGit(String executable) {\n    this.executable = executable;\n  }\n\n  public boolean isSupportedVersion() {\n    return version()\n      .filter(version -> version.satisfiesMinRequirement(MINIMUM_REQUIRED_GIT_VERSION))\n      .isPresent();\n  }\n\n  private Optional<Version> version() {\n    var lines = new ArrayList<String>();\n    var success = executeGitCommand(null, lines::add, executable, \"--version\");\n    return success ? parseGitVersionOutput(lines) : Optional.empty();\n  }\n\n  static Optional<Version> parseGitVersionOutput(List<String> lines) {\n    var version = lines.stream().findFirst()\n      .map(String::trim)\n      .filter(line -> line.startsWith(GIT_VERSION_OUTPUT_PREFIX))\n      .map(line -> line.substring(GIT_VERSION_OUTPUT_PREFIX.length()))\n      .map(String::trim)\n      .map(actualVersion -> actualVersion.split(\"\\\\.\", 3))\n      .filter(versionParts -> versionParts.length > 1)\n      .flatMap(NativeGit::tryCreateVersion);\n    if (version.isEmpty()) {\n      LOG.debug(\"Cannot parse git --version output: {}\", String.join(\"\\n\", lines));\n    }\n    return version;\n  }\n\n  private static Optional<Version> tryCreateVersion(String[] versionParts) {\n    try {\n      // keep only MAJOR and MINOR numbers, it's sufficient for checking support\n      return Optional.of(Version.create(versionParts[0] + \".\" + versionParts[1]));\n    } catch (Exception e) {\n      // error will be logged above\n    }\n    return Optional.empty();\n  }\n\n  public MultiFileBlameResult blame(Path projectBaseDir, Set<URI> fileUris, Instant thresholdDateFromNewCodeDefinition) {\n    LOG.debug(\"Using native git blame\");\n    var startTime = System.currentTimeMillis();\n    var blamePerFile = new HashMap<String, BlameResult>();\n    for (var fileUri : fileUris) {\n      var filePath = FileUtils.getFilePathFromUri(fileUri).toAbsolutePath().toString();\n      var filePathUnix = filePath.replace(\"\\\\\", \"/\");\n      var yearAgo = Instant.now().minus(BLAME_HISTORY_WINDOW);\n      var thresholdDate = thresholdDateFromNewCodeDefinition.isAfter(yearAgo) ? yearAgo : thresholdDateFromNewCodeDefinition;\n      var blameHistoryThresholdCondition = \"--since='\" + thresholdDate + \"'\";\n      var command = new String[] {executable, \"blame\", blameHistoryThresholdCondition, filePath, \"--line-porcelain\", \"--encoding=UTF-8\"};\n      var blameReader = new GitBlameReader();\n      var success = executeGitCommand(projectBaseDir, blameReader::readLine, command);\n      if (success) {\n        blamePerFile.put(filePathUnix, blameReader.getResult());\n      }\n    }\n    LOG.debug(\"Blamed {} files in {}ms\", fileUris.size(), System.currentTimeMillis() - startTime);\n    return new MultiFileBlameResult(blamePerFile, projectBaseDir);\n  }\n\n  private static boolean executeGitCommand(@Nullable Path workingDir, Consumer<String> lineConsumer, String... command) {\n    var output = new ProcessWrapperFactory()\n      .create(workingDir, lineConsumer, command)\n      .execute();\n    if (output.exitCode() == 0) {\n      return true;\n    }\n    LOG.debug(format(\"Command failed with code: %d\", output.exitCode()));\n    return false;\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/util/git/NativeGitLocator.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.util.git;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Optional;\nimport java.util.function.Consumer;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic class NativeGitLocator {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  // So we only have to make the expensive call once (or at most twice) to get the native Git executable\n  private boolean checkedForNativeGitExecutable = false;\n  private NativeGit nativeGitExecutable = null;\n\n  /**\n   * Get the native Git executable by checking for the version of both `git` and `git.exe`. We cache this information\n   * to not run these expensive processes more than once (or twice in case of Windows).\n   */\n  public Optional<NativeGit> getNativeGitExecutable() {\n    if (checkedForNativeGitExecutable) {\n      return Optional.ofNullable(nativeGitExecutable);\n    }\n\n    var nativeGit = getGitExecutable()\n      .map(NativeGit::new)\n      .filter(NativeGit::isSupportedVersion);\n    checkedForNativeGitExecutable = true;\n    nativeGitExecutable = nativeGit.orElse(null);\n    return nativeGit;\n  }\n\n  Optional<String> getGitExecutable() {\n    return SystemUtils.IS_OS_WINDOWS ? locateGitOnWindows() : Optional.of(\"git\");\n  }\n\n  private static Optional<String> locateGitOnWindows() {\n    var lines = new ArrayList<String>();\n    var result = callWhereTool(lines::add);\n    return locateGitOnWindows(result, String.join(\"\\n\", lines));\n  }\n\n  static Optional<String> locateGitOnWindows(ProcessWrapperFactory.ProcessExecutionResult result, String lines) {\n    // Windows will search current directory in addition to the PATH variable, which is unsecure.\n    // To avoid it we use where.exe to find git binary only in PATH.\n\n    if (result.exitCode() == 0 && lines.contains(\"git.exe\")) {\n      var out = Arrays.stream(lines.split(System.lineSeparator())).map(String::trim).findFirst();\n      LOG.debug(\"Found git.exe at {}\", out);\n      return out;\n    }\n    LOG.debug(\"git.exe not found in PATH. PATH value was: \" + System.getProperty(\"PATH\"));\n    return Optional.empty();\n  }\n\n  private static ProcessWrapperFactory.ProcessExecutionResult callWhereTool(Consumer<String> lineConsumer) {\n    LOG.debug(\"Looking for git command in the PATH using where.exe (Windows)\");\n    return new ProcessWrapperFactory()\n      .create(null, lineConsumer, \"C:\\\\Windows\\\\System32\\\\where.exe\", \"$PATH:git.exe\")\n      .execute();\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/util/git/ProcessWrapperFactory.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.util.git;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.nio.file.Path;\nimport java.util.function.Consumer;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\nimport static java.lang.String.format;\nimport static java.lang.String.join;\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\npublic class ProcessWrapperFactory {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  public ProcessWrapperFactory() {\n    // nothing to do\n  }\n\n  public ProcessWrapper create(@Nullable Path baseDir, Consumer<String> lineConsumer, String... command) {\n    return new ProcessWrapper(baseDir, lineConsumer, command);\n  }\n\n  public static class ProcessWrapper {\n\n    private final Path baseDir;\n    private final Consumer<String> lineConsumer;\n    private final String[] command;\n\n    ProcessWrapper(@Nullable Path baseDir, Consumer<String> lineConsumer, String... command) {\n      this.baseDir = baseDir;\n      this.lineConsumer = lineConsumer;\n      this.command = command;\n    }\n\n    void processInputStream(InputStream inputStream, Consumer<String> stringConsumer) throws IOException {\n      try (var reader = new BufferedReader(new InputStreamReader(inputStream, UTF_8))) {\n        String line;\n        while ((line = reader.readLine()) != null) {\n          stringConsumer.accept(line);\n        }\n      }\n    }\n\n    public ProcessExecutionResult execute() {\n      Process p;\n      try {\n        p = createProcess();\n      } catch (IOException e) {\n        LOG.warn(format(\"Could not execute command: [%s]\", join(\" \", command)), e);\n        return new ProcessExecutionResult(-2);\n      }\n      try {\n        return runProcessAndGetOutput(p);\n      } catch (InterruptedException e) {\n        LOG.warn(format(\"Command [%s] interrupted\", join(\" \", command)), e);\n        Thread.currentThread().interrupt();\n      } catch (Exception e) {\n        LOG.warn(format(\"Command failed: [%s]\", join(\" \", command)), e);\n      } finally {\n        p.destroy();\n      }\n      return new ProcessExecutionResult(-1);\n    }\n\n    Process createProcess() throws IOException {\n      return new ProcessBuilder()\n        .command(command)\n        .directory(baseDir != null ? baseDir.toFile() : null)\n        .start();\n    }\n\n    ProcessExecutionResult runProcessAndGetOutput(Process p) throws InterruptedException, IOException {\n      processInputStream(p.getInputStream(), lineConsumer);\n      processInputStream(p.getErrorStream(), line -> {\n        if (!line.isBlank()) {\n          LOG.debug(line);\n        }\n      });\n      int exit = p.waitFor();\n      return new ProcessExecutionResult(exit);\n    }\n  }\n\n  public record ProcessExecutionResult(int exitCode) {\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/util/git/exceptions/GitException.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.util.git.exceptions;\n\npublic class GitException extends RuntimeException {\n  private final String path;\n\n  public GitException(String path) {\n    this.path = path;\n  }\n\n  public String getPath() {\n    return path;\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/util/git/exceptions/GitRepoNotFoundException.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.util.git.exceptions;\n\npublic class GitRepoNotFoundException extends GitException {\n\n  public GitRepoNotFoundException(String path) {\n    super(path);\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/util/git/exceptions/package-info.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.commons.util.git.exceptions;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/util/git/package-info.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.commons.util.git;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/util/package-info.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.commons.util;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/validation/InvalidFields.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.validation;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class InvalidFields {\n\n  private final List<String> names = new ArrayList<>();\n\n  public void add(String name) {\n    names.add(name);\n  }\n\n  public String[] getNames() {\n    return names.toArray(new String[0]);\n  }\n\n  public boolean hasInvalidFields() {\n    return !names.isEmpty();\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/validation/RegexpValidator.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.validation;\n\nimport java.util.Map;\nimport java.util.regex.Pattern;\n\npublic class RegexpValidator {\n\n  private final Pattern pattern;\n\n  public RegexpValidator(String regexp) {\n    this.pattern = Pattern.compile(regexp);\n  }\n\n  public InvalidFields validateAll(Map<String, String> namedValues) {\n    var invalidFields = new InvalidFields();\n    namedValues.entrySet().stream()\n      .filter(this::isInvalid)\n      .map(Map.Entry::getKey)\n      .forEach(invalidFields::add);\n    return invalidFields;\n  }\n\n  private boolean isInvalid(Map.Entry<String, String> nameValue) {\n    return !pattern.matcher(nameValue.getValue()).matches();\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/validation/package-info.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.commons.validation;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/commons/src/main/resources/db/migration/README_BEFORE_TOUCHING_THIS_FOLDER.md",
    "content": "Flyway computes checksums of migration files and saves them in the DB.\n\nOnce a migration has been shipped and applied by some users, it is forbidden to modify a migration file, as the checksum would differ.\n\nThis would fail the migration validation at startup, preventing SQ-IDE from starting.\n\nAs a rule of thumb, please do not modify a migration file after it has been merged to master, because we cannot know if it has been applied by users.\n\nInstead, please add a new migration file, by respecting the pattern VX__description.sql, and the numbering (+1 compared to the latest migration)."
  },
  {
    "path": "backend/commons/src/main/resources/db/migration/V1__init_database.sql",
    "content": "CREATE TABLE IF NOT EXISTS AI_CODEFIX_SETTINGS (\n  connection_id VARCHAR(255) NOT NULL PRIMARY KEY,\n  supported_rules VARCHAR(200) ARRAY,\n  organization_eligible BOOLEAN,\n  enablement VARCHAR(64),\n  enabled_project_keys VARCHAR(400) ARRAY,\n  CONSTRAINT pk_ai_codefix_settings PRIMARY KEY (connection_id)\n);\n\nCREATE TABLE IF NOT EXISTS KNOWN_FINDINGS (\n    -- UUID\n    id UUID NOT NULL PRIMARY KEY,\n    configuration_scope_id VARCHAR(255) NOT NULL,\n    ide_relative_file_path VARCHAR(255) NOT NULL,\n    server_key VARCHAR(255),\n    rule_key VARCHAR(255) NOT NULL,\n    message VARCHAR(255) NOT NULL,\n    introduction_date TIMESTAMP NOT NULL,\n    finding_type VARCHAR(255) NOT NULL,\n    -- TextRangeWithHash\n    start_line INT,\n    start_line_offset INT,\n    end_line INT,\n    end_line_offset INT,\n    text_range_hash VARCHAR(255),\n    -- LineWithHash\n    line INT,\n    line_hash VARCHAR(255)\n);\n\nCREATE TABLE IF NOT EXISTS SERVER_FINDINGS (\n    id UUID,\n    connection_id VARCHAR(255) NOT NULL,\n    sonar_project_key VARCHAR(255) NOT NULL,\n    server_key VARCHAR(255) NOT NULL PRIMARY KEY,\n    rule_id VARCHAR(255),\n    rule_key VARCHAR(255) NOT NULL,\n    message VARCHAR(10000) NOT NULL,\n    file_path VARCHAR(4096) NOT NULL, -- default Linux path length limit\n    creation_date TIMESTAMP NOT NULL,\n    user_severity VARCHAR(255),\n    rule_type VARCHAR(255),\n    rule_description_context_key VARCHAR(255),\n    clean_code_attribute VARCHAR(255),\n    finding_type VARCHAR(255) NOT NULL,\n    branch_name VARCHAR(255),\n    vulnerability_probability VARCHAR(255),\n    assignee VARCHAR(255),\n    impacts JSON(1000),\n    flows JSON(10000000),\n    -- Resolution\n    resolved BOOLEAN,\n    issue_resolution_status VARCHAR(255),\n    hotspot_review_status VARCHAR(255),\n    -- TextRangeWithHash\n    start_line INT,\n    start_line_offset INT,\n    end_line INT,\n    end_line_offset INT,\n    text_range_hash VARCHAR(255),\n    -- LineWithHash\n    line INT,\n    line_hash VARCHAR(255)\n);\n\nCREATE TABLE IF NOT EXISTS SERVER_BRANCHES (\n    branch_name VARCHAR(255) NOT NULL,\n    connection_id VARCHAR(255) NOT NULL,\n    sonar_project_key VARCHAR(255) NOT NULL,\n    last_issue_sync_ts TIMESTAMP,\n    last_taint_sync_ts TIMESTAMP,\n    last_hotspot_sync_ts TIMESTAMP,\n    last_issue_enabled_langs VARCHAR(64) ARRAY,\n    last_taint_enabled_langs VARCHAR(64) ARRAY,\n    last_hotspot_enabled_langs VARCHAR(64) ARRAY,\n    PRIMARY KEY (branch_name, connection_id, sonar_project_key)\n);\n\nCREATE TABLE IF NOT EXISTS SERVER_DEPENDENCY_RISKS (\n    id UUID NOT NULL PRIMARY KEY,\n    connection_id VARCHAR(255) NOT NULL,\n    sonar_project_key VARCHAR(255) NOT NULL,\n    branch_name VARCHAR(255) NOT NULL,\n    type VARCHAR(255) NOT NULL,\n    severity VARCHAR(255) NOT NULL,\n    software_quality VARCHAR(255) NOT NULL,\n    status VARCHAR(255) NOT NULL,\n    package_name VARCHAR(255) NOT NULL,\n    package_version VARCHAR(255) NOT NULL,\n    vulnerability_id VARCHAR(255),\n    cvss_score VARCHAR(255),\n    transitions JSON(10000) NOT NULL\n);\n"
  },
  {
    "path": "backend/commons/src/main/resources/db/migration/V2__create_local_only_issues_table.sql",
    "content": "-- Flyway migration: create LOCAL_ONLY_ISSUES table for H2\n-- This table stores local-only issues (issues detected locally but not yet on the server)\n\nCREATE TABLE IF NOT EXISTS LOCAL_ONLY_ISSUES (\n  id UUID NOT NULL PRIMARY KEY,\n  configuration_scope_id VARCHAR(255) NOT NULL,\n  server_relative_path VARCHAR(1000) NOT NULL,\n  rule_key VARCHAR(255) NOT NULL,\n  message VARCHAR(255) NOT NULL,\n  -- Resolution fields (nullable when issue is not resolved)\n  resolution_status VARCHAR(50),\n  resolution_date TIMESTAMP,\n  comment VARCHAR(255),\n  -- TextRangeWithHash fields (nullable)\n  start_line INT,\n  start_line_offset INT,\n  end_line INT,\n  end_line_offset INT,\n  text_range_hash VARCHAR(255),\n  -- LineWithHash fields (nullable)\n  line INT,\n  line_hash VARCHAR(255)\n);\n\nCREATE INDEX IF NOT EXISTS idx_local_only_issues_config_scope_file\n    ON LOCAL_ONLY_ISSUES(configuration_scope_id, server_relative_path);\n\nCREATE INDEX IF NOT EXISTS idx_local_only_issues_resolution_date\n    ON LOCAL_ONLY_ISSUES(resolution_date);\n\n"
  },
  {
    "path": "backend/commons/src/main/resources/db/migration/V3__allow_longer_messages_for_known_findings_table.sql",
    "content": "ALTER TABLE KNOWN_FINDINGS\n    ALTER COLUMN message SET DATA TYPE VARCHAR(10000);\nALTER TABLE LOCAL_ONLY_ISSUES\n    ALTER COLUMN message SET DATA TYPE VARCHAR(10000);"
  },
  {
    "path": "backend/commons/src/main/resources/db/migration/V4__allow_longer_file_paths.sql",
    "content": "ALTER TABLE KNOWN_FINDINGS\n    ALTER COLUMN ide_relative_file_path SET DATA TYPE VARCHAR;\nALTER TABLE SERVER_FINDINGS\n    ALTER COLUMN file_path SET DATA TYPE VARCHAR;\nALTER TABLE LOCAL_ONLY_ISSUES\n    ALTER COLUMN server_relative_path SET DATA TYPE VARCHAR;\n"
  },
  {
    "path": "backend/commons/src/main/resources/db/migration/V5__allow_longer_configuration_scope_ids.sql",
    "content": "ALTER TABLE KNOWN_FINDINGS\n    ALTER COLUMN configuration_scope_id SET DATA TYPE VARCHAR;\nALTER TABLE LOCAL_ONLY_ISSUES\n    ALTER COLUMN configuration_scope_id SET DATA TYPE VARCHAR;\n"
  },
  {
    "path": "backend/commons/src/main/resources/logback-shared.xml",
    "content": "<!-- This file is shared between the production and medium test code -->\n<included>\n  <root level=\"trace\"/>\n  <logger name=\"org.springframework\" level=\"warn\" />\n  <logger name=\"org.sonarsource.sonarlint.core.plugin.commons.container.PriorityBeanFactory\" level=\"warn\" />\n  <logger name=\"nl.altindag.ssl\" level=\"warn\" />\n  <logger name=\"jetbrains.exodus\" level=\"warn\" />\n  <logger name=\"org.apache.hc\" level=\"warn\" />\n  <logger name=\"org.eclipse.lsp4j.jsonrpc\" level=\"error\" />\n  <logger name=\"org.eclipse.jgit\" level=\"info\"/>\n  <logger name=\"org.sonar.scm.git.blame\" level=\"info\"/>\n</included>"
  },
  {
    "path": "backend/commons/src/main/resources/sl_core_version.txt",
    "content": "${project.version}"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/HotspotReviewStatusTest.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass HotspotReviewStatusTest {\n  @Test\n  void should_be_resolved_when_fixed_or_safe() {\n    assertThat(HotspotReviewStatus.SAFE.isResolved()).isTrue();\n    assertThat(HotspotReviewStatus.FIXED.isResolved()).isTrue();\n    assertThat(HotspotReviewStatus.ACKNOWLEDGED.isResolved()).isFalse();\n    assertThat(HotspotReviewStatus.TO_REVIEW.isResolved()).isFalse();\n  }\n  @Test\n  void should_be_reviewed_when_fixed_or_safe_or_acknowledged() {\n    assertThat(HotspotReviewStatus.SAFE.isReviewed()).isTrue();\n    assertThat(HotspotReviewStatus.FIXED.isReviewed()).isTrue();\n    assertThat(HotspotReviewStatus.ACKNOWLEDGED.isReviewed()).isTrue();\n    assertThat(HotspotReviewStatus.TO_REVIEW.isReviewed()).isFalse();\n  }\n}"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/IOExceptionUtilsTests.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport java.io.IOException;\nimport java.util.ArrayDeque;\nimport java.util.List;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.sonarsource.sonarlint.core.commons.IOExceptionUtils.throwFirstWithOtherSuppressed;\nimport static org.sonarsource.sonarlint.core.commons.IOExceptionUtils.tryAndCollectIOException;\n\nclass IOExceptionUtilsTests {\n\n  @Test\n  void test_tryAndCollectIOException_no_exceptions() {\n    var list = new ArrayDeque<IOException>();\n    tryAndCollectIOException(() -> {\n    }, list);\n    assertThat(list).isEmpty();\n  }\n\n  @Test\n  void test_tryAndCollectIOException_one_exception() {\n    var list = new ArrayDeque<IOException>();\n    tryAndCollectIOException(() -> {\n      throw new IOException(\"e1\");\n    }, list);\n    assertThat(list).hasSize(1);\n  }\n\n  @Test\n  void test_tryAndCollectIOException_multiple_exceptions() {\n    var list = new ArrayDeque<IOException>();\n    tryAndCollectIOException(() -> {\n      throw new IOException(\"e1\");\n    }, list);\n    tryAndCollectIOException(() -> {\n      throw new IOException(\"e2\");\n    }, list);\n    assertThat(list).extracting(IOException::getMessage).containsExactlyInAnyOrder(\"e1\", \"e2\");\n  }\n\n  @Test\n  void test_throwFirstWithOtherSuppressed_no_exceptions() {\n    assertDoesNotThrow(() -> throwFirstWithOtherSuppressed(new ArrayDeque<>(List.of())));\n  }\n\n  @Test\n  void test_throwFirstWithOtherSuppressed_one_exception() {\n    var thrown = assertThrows(IOException.class, () -> throwFirstWithOtherSuppressed(new ArrayDeque<>(List.of(new IOException(\"e1\")))));\n    assertThat(thrown).hasMessage(\"e1\").hasNoSuppressedExceptions();\n  }\n\n  @Test\n  void test_throwFirstWithOtherSuppressed_multiple_exceptions() {\n    var thrown = assertThrows(IOException.class, () -> throwFirstWithOtherSuppressed(new ArrayDeque<>(List.of(new IOException(\"e1\"), new IOException(\"e2\"), new IOException(\"e3\")))));\n    assertThat(thrown).hasMessage(\"e1\").hasSuppressedException(new IOException(\"e2\")).hasSuppressedException(new IOException(\"e3\"));\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/LogTestStartAndEnd.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport org.junit.jupiter.api.extension.AfterEachCallback;\nimport org.junit.jupiter.api.extension.BeforeEachCallback;\nimport org.junit.jupiter.api.extension.ExtensionContext;\n\npublic class LogTestStartAndEnd implements BeforeEachCallback, AfterEachCallback {\n  @Override\n  public void beforeEach(ExtensionContext extensionContext) {\n    extensionContext.getTestMethod().ifPresent(method -> System.out.printf(\">>> Before test %s%n\", method.getName()));\n  }\n\n  @Override\n  public void afterEach(ExtensionContext extensionContext) {\n    extensionContext.getTestMethod().ifPresent(method -> System.out.printf(\"<<< After test %s%n\", method.getName()));\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/MultiFileBlameResultTest.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.IntStream;\nimport org.eclipse.jgit.api.Git;\nimport org.eclipse.jgit.api.errors.GitAPIException;\nimport org.eclipse.jgit.util.FileUtils;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.eclipse.jgit.util.FileUtils.RECURSIVE;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.sonarsource.sonarlint.core.commons.testutils.GitUtils.appendFile;\nimport static org.sonarsource.sonarlint.core.commons.testutils.GitUtils.commit;\nimport static org.sonarsource.sonarlint.core.commons.testutils.GitUtils.commitAtDate;\nimport static org.sonarsource.sonarlint.core.commons.testutils.GitUtils.createFile;\nimport static org.sonarsource.sonarlint.core.commons.testutils.GitUtils.createRepository;\nimport static org.sonarsource.sonarlint.core.commons.testutils.GitUtils.modifyFile;\nimport static org.sonarsource.sonarlint.core.commons.util.git.GitService.blameWithGitFilesBlameLibrary;\n\nclass MultiFileBlameResultTest {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  private Git git;\n  @TempDir\n  private Path projectDir;\n\n  @BeforeEach\n  void prepare() throws IOException, GitAPIException {\n    git = createRepository(projectDir);\n  }\n\n  @AfterEach\n  void cleanup() throws IOException {\n    FileUtils.delete(projectDir.toFile(), RECURSIVE);\n  }\n\n  @Test\n  void it_should_return_correct_latest_changed_date_for_file_lines() throws IOException, GitAPIException, InterruptedException {\n    createFile(projectDir, \"fileA\", \"line1\", \"line2\", \"line3\");\n    var c1 = commit(git, \"fileA\");\n\n    // Wait for one second to achieve different commit time\n    TimeUnit.MILLISECONDS.sleep(10);\n    appendFile(projectDir.resolve(\"fileA\"), \"new line 4\");\n    var c2 = commit(git, \"fileA\");\n\n    // Wait for one second to achieve different commit time\n    TimeUnit.MILLISECONDS.sleep(10);\n    createFile(projectDir, \"fileB\", \"line1\", \"line2\", \"line3\");\n    var c3 = commit(git, \"fileB\");\n\n    createFile(projectDir, \"fileC\", \"line1\", \"line2\", \"line3\");\n    commit(git, \"fileC\");\n\n    var results = blameWithGitFilesBlameLibrary(projectDir, Set.of(Path.of(\"fileA\"), Path.of(\"fileB\")), null);\n\n    assertThat(results.getLatestChangeDateForLinesInFile(Path.of(\"fileA\"), List.of(1, 2))).isPresent().contains(c1);\n    assertThat(results.getLatestChangeDateForLinesInFile(Path.of(\"fileA\"), List.of(2, 3))).isPresent().contains(c1);\n    assertThat(results.getLatestChangeDateForLinesInFile(Path.of(\"fileA\"), List.of(3, 4))).isPresent().contains(c2);\n    assertThat(results.getLatestChangeDateForLinesInFile(Path.of(\"fileB\"), List.of(1, 2))).isPresent().contains(c3);\n    assertThat(results.getLatestChangeDateForLinesInFile(Path.of(\"fileC\"), List.of(1, 2))).isEmpty();\n  }\n\n  @Test\n  void it_should_handle_all_line_modified() throws IOException, GitAPIException {\n    createFile(projectDir, \"fileA\", \"line1\", \"line2\", \"line3\");\n    var c1 = commit(git, \"fileA\");\n\n    var results = blameWithGitFilesBlameLibrary(projectDir, Set.of(Path.of(\"fileA\")), null);\n    assertThat(results.getLatestChangeDateForLinesInFile(Path.of(\"fileA\"), List.of(1, 2, 3))).isPresent().contains(c1);\n\n    modifyFile(projectDir.resolve(\"fileA\"), \"new line1\", \"new line2\", \"new line3\");\n\n    results = blameWithGitFilesBlameLibrary(projectDir, Set.of(Path.of(\"fileA\")), null);\n    assertThat(results.getLatestChangeDateForLinesInFile(Path.of(\"fileA\"), List.of(1, 2, 3))).isEmpty();\n  }\n\n  @Test\n  void it_should_return_latest_change_date() throws IOException, GitAPIException {\n    createFile(projectDir, \"fileA\", \"line1\", \"line2\", \"line3\");\n    var now = Instant.now();\n    var c1 = commitAtDate(git, now.minus(1, ChronoUnit.DAYS), \"fileA\");\n\n    var results = blameWithGitFilesBlameLibrary(projectDir, Set.of(Path.of(\"fileA\")), null);\n    assertThat(results.getLatestChangeDateForLinesInFile(Path.of(\"fileA\"), List.of(1, 2, 3))).isPresent().contains(c1);\n    modifyFile(projectDir.resolve(\"fileA\"), \"line1\", \"line2\", \"new line3\");\n    commitAtDate(git, now, \"fileA\");\n\n    results = blameWithGitFilesBlameLibrary(projectDir, Set.of(Path.of(\"fileA\")), null);\n    var result = results.getLatestChangeDateForLinesInFile(Path.of(\"fileA\"), List.of(1, 2, 3));\n\n    assertThat(result).isPresent();\n    assertThat(ChronoUnit.MINUTES.between(result.get(), now)).isZero();\n  }\n\n  @Test\n  void it_should_handle_end_of_line_modified() throws IOException, GitAPIException {\n    createFile(projectDir, \"fileA\", \"line1\", \"line2\");\n    var c1 = commit(git, \"fileA\");\n\n    var results = blameWithGitFilesBlameLibrary(projectDir, Set.of(Path.of(\"fileA\")), null);\n    assertThat(results.getLatestChangeDateForLinesInFile(Path.of(\"fileA\"), List.of(1, 2))).isPresent().contains(c1);\n\n    appendFile(projectDir.resolve(\"fileA\"), \"new line3\", \"new line4\");\n\n    results = blameWithGitFilesBlameLibrary(projectDir, Set.of(Path.of(\"fileA\")), null);\n    assertThat(results.getLatestChangeDateForLinesInFile(Path.of(\"fileA\"), List.of(1, 2, 3))).isEmpty();\n  }\n\n  @Test\n  void it_should_handle_dodgy_input() throws IOException, GitAPIException {\n    createFile(projectDir, \"fileA\", \"line1\", \"line2\", \"line3\");\n    var c1 = commit(git, \"fileA\");\n\n    var results = blameWithGitFilesBlameLibrary(projectDir, Set.of(Path.of(\"fileA\"), Path.of(\"fileB\")), null);\n\n    assertThat(results.getLatestChangeDateForLinesInFile(Path.of(\"fileA\"),\n      IntStream.rangeClosed(1, 100).boxed().toList())).isPresent().contains(c1);\n    assertThat(results.getLatestChangeDateForLinesInFile(Path.of(\"fileA\"),\n      IntStream.rangeClosed(100, 1000).boxed().toList())).isEmpty();\n  }\n\n  @Test\n  void it_should_raise_exception_if_wrong_line_numbering_provided() throws IOException, GitAPIException {\n    createFile(projectDir, \"fileA\", \"line1\", \"line2\", \"line3\");\n    commit(git, \"fileA\");\n\n    var fileA = Path.of(\"fileA\");\n    var results = blameWithGitFilesBlameLibrary(projectDir, Set.of(fileA), null);\n    var invalidLineNumbers = List.of(0, 1, 2);\n    assertThrows(IllegalArgumentException.class, () -> results.getLatestChangeDateForLinesInFile(fileA, invalidLineNumbers));\n  }\n\n  @Test\n  void it_should_handle_files_within_inner_dir() throws IOException, GitAPIException {\n    var deepFilePath = Path.of(\"innerDir\").resolve(\"fileA\").toString();\n    createFile(projectDir, deepFilePath, \"line1\", \"line2\", \"line3\");\n    var c1 = commit(git, deepFilePath);\n\n    var results = blameWithGitFilesBlameLibrary(projectDir, Set.of(Path.of(deepFilePath)), null);\n    assertThat(results.getLatestChangeDateForLinesInFile(\n      Path.of(deepFilePath),\n      IntStream.rangeClosed(1, 100).boxed().toList()))\n      .isPresent().contains(c1);\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/NewCodeDefinitionTests.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport java.time.Instant;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass NewCodeDefinitionTests {\n  @Test\n  void isOnNewCodeTest() {\n    var analysisDate = Instant.parse(\"2023-09-12T10:15:30.00Z\");\n    var issueCreationDateBeforeAnalysis = Instant.parse(\"2023-09-10T10:15:30.00Z\");\n    var issueCreationDateAfterAnalysis = Instant.parse(\"2023-09-14T10:15:30.00Z\");\n    var newCodeDefinitionWithoutDate = NewCodeDefinition.withNumberOfDaysWithDate(30, 0);\n    var newCodeDefinitionWithDate = NewCodeDefinition.withNumberOfDaysWithDate(30, analysisDate.toEpochMilli());\n\n    assertThat(newCodeDefinitionWithoutDate.isOnNewCode(analysisDate.toEpochMilli())).isTrue();\n    assertThat(newCodeDefinitionWithDate.isOnNewCode(issueCreationDateAfterAnalysis.toEpochMilli())).isTrue();\n    assertThat(newCodeDefinitionWithDate.isOnNewCode(issueCreationDateBeforeAnalysis.toEpochMilli())).isFalse();\n  }\n\n  @Test\n  void toStringTest() {\n    var analysisEpochDate = Instant.parse(\"2023-09-12T10:15:30.00Z\").toEpochMilli();\n    var numberOfDays = NewCodeDefinition.withNumberOfDaysWithDate(30, analysisEpochDate);\n    var previousVersionNull = NewCodeDefinition.withPreviousVersion(analysisEpochDate, null);\n    var previousVersion = NewCodeDefinition.withPreviousVersion(analysisEpochDate, \"version\");\n    var specificAnalysis = NewCodeDefinition.withSpecificAnalysis(analysisEpochDate);\n    var referenceBranch = NewCodeDefinition.withReferenceBranch(\"referenceBranch\");\n\n    var analysisDate = NewCodeDefinition.formatEpochToDate(analysisEpochDate);\n    assertThat(numberOfDays).hasToString(\"From last 30 days\");\n    assertThat(previousVersionNull).hasToString(\"Since \" + analysisDate);\n    assertThat(previousVersion).hasToString(\"Since version version\");\n    assertThat(specificAnalysis).hasToString(\"Since analysis from \" + analysisDate);\n    assertThat(referenceBranch).hasToString(\"Current new code definition (reference branch) is not supported\");\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/RuleKeyTests.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass RuleKeyTests {\n\n  @Test\n  void test_ruleKey_accessors() {\n    var repository = \"squid\";\n    var rule = \"1181\";\n\n    var ruleKey = new RuleKey(repository, rule);\n    assertThat(ruleKey.repository()).isEqualTo(repository);\n    assertThat(ruleKey.rule()).isEqualTo(rule);\n    assertThat(ruleKey).hasToString(repository + \":\" + rule);\n  }\n\n  @Test\n  void ruleKey_equals_and_hashcode() {\n    var repository = \"squid\";\n    var rule = \"1181\";\n\n    var ruleKey1 = new RuleKey(repository, rule);\n    var ruleKey2 = new RuleKey(repository, rule);\n    assertThat(ruleKey1)\n      .isEqualTo(ruleKey1)\n      .isEqualTo(ruleKey2)\n      .hasSameHashCodeAs(ruleKey2)\n      .isNotEqualTo(null)\n      .isNotEqualTo(new RuleKey(repository, rule + \"x\"))\n      .isNotEqualTo(new RuleKey(repository + \"x\", rule));\n  }\n\n  @Test\n  void ruleKey_equals_to_its_parsed_from_toString() {\n    var repository = \"squid\";\n    var rule = \"1181\";\n\n    var ruleKey1 = new RuleKey(repository, rule);\n    var ruleKey2 = RuleKey.parse(ruleKey1.toString());\n    assertThat(ruleKey2).isEqualTo(ruleKey1);\n  }\n\n  @Test\n  void parse_throws_for_illegal_format() {\n    assertThrows(IllegalArgumentException.class, () -> {\n      RuleKey.parse(\"foo\");\n    });\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/SonarLintCoreVersionTests.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport java.util.regex.Pattern;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SonarLintCoreVersionTests {\n\n  @Test\n  void testVersionFallback() {\n    var version = SonarLintCoreVersion.getLibraryVersion();\n    assertThat(isVersion(version)).isTrue();\n  }\n\n  @Test\n  void testVersion() {\n    var version = SonarLintCoreVersion.get();\n    assertThat(isVersion(version)).isTrue();\n  }\n\n  @Test\n  void testVersionAssert() {\n    assertThat(isVersion(\"2.1\")).isTrue();\n    assertThat(isVersion(\"2.0-SNAPSHOT\")).isTrue();\n    assertThat(isVersion(\"2.0.0-SNAPSHOT\")).isTrue();\n    assertThat(isVersion(\"2-SNAPSHOT\")).isFalse();\n    assertThat(isVersion(\"unknown\")).isFalse();\n    assertThat(isVersion(null)).isFalse();\n  }\n\n  private boolean isVersion(String version) {\n    if (version == null) {\n      return false;\n    }\n    var regex = \"(\\\\d+\\\\.\\\\d+(?:\\\\.\\\\d+)*).*\";\n    var pattern = Pattern.compile(regex);\n    var matcher = pattern.matcher(version);\n\n    return matcher.find();\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/SonarLintUserHomeTests.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport java.nio.file.Paths;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SonarLintUserHomeTests {\n\n  @Test\n  void env_setting_should_override_default_home() {\n    var customHome = \"/custom/home\";\n    assertThat(SonarLintUserHome.home(customHome)).isEqualTo(Paths.get(customHome));\n  }\n\n  @Test\n  void default_home_should_be_in_user_home() {\n    assertThat(SonarLintUserHome.get()).isEqualTo(Paths.get(System.getProperty(\"user.home\")).resolve(\".sonarlint\"));\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/StringUtilsTests.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.sonarsource.sonarlint.core.commons.util.StringUtils.pluralize;\nimport static org.sonarsource.sonarlint.core.commons.util.StringUtils.sanitizeAgainstRTLO;\n\nclass StringUtilsTests {\n\n  @Test\n  void should_pluralize_words() {\n    assertThat(pluralize(0, \"word\")).isEqualTo(\"0 words\");\n    assertThat(pluralize(1, \"word\")).isEqualTo(\"1 word\");\n    assertThat(pluralize(2, \"word\")).isEqualTo(\"2 words\");\n  }\n\n  @Test\n  void should_sanitize_against_rtlo() {\n    assertThat(sanitizeAgainstRTLO(\"This is a \\u202eegassem\")).isEqualTo(\"This is a egassem\");\n  }\n\n  @Test\n  void should_sanitize_with_null() {\n    assertThat(sanitizeAgainstRTLO(null)).isNull();\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/TextRangeTests.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass TextRangeTests {\n\n  @Test\n  void test_getters() {\n    var textRange = new TextRange(1, 2, 3, 4);\n    assertThat(textRange.getStartLine()).isEqualTo(1);\n    assertThat(textRange.getStartLineOffset()).isEqualTo(2);\n    assertThat(textRange.getEndLine()).isEqualTo(3);\n    assertThat(textRange.getEndLineOffset()).isEqualTo(4);\n  }\n\n  @Test\n  void test_equals_hashcode() {\n    var textRange = new TextRange(1, 2, 3, 4);\n    assertThat(textRange).hasSameHashCodeAs(new TextRange(1, 2, 3, 4))\n      .isEqualTo(textRange)\n      .isEqualTo(new TextRange(1, 2, 3, 4))\n      .isNotEqualTo(new TextRange(11, 2, 3, 4))\n      .isNotEqualTo(new TextRange(1, 22, 3, 4))\n      .isNotEqualTo(new TextRange(1, 2, 33, 4))\n      .isNotEqualTo(new TextRange(1, 2, 3, 44))\n      .isNotEqualTo(\"foo\");\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/TextRangeWithHashTests.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass TextRangeWithHashTests {\n\n  @Test\n  void test_getters() {\n    var textRange = new TextRangeWithHash(1, 2, 3, 4, \"md5\");\n    assertThat(textRange.getHash()).isEqualTo(\"md5\");\n  }\n\n  @Test\n  void test_equals_hashcode() {\n    var textRange = new TextRangeWithHash(1, 2, 3, 4, \"md5\");\n    assertThat(textRange).hasSameHashCodeAs(new TextRangeWithHash(1, 2, 3, 4, \"md5\"))\n      .isEqualTo(textRange)\n      .isEqualTo(new TextRangeWithHash(1, 2, 3, 4, \"md5\"))\n      .isNotEqualTo(new TextRange(1, 2, 3, 4))\n      .isNotEqualTo(new TextRangeWithHash(11, 2, 3, 4, \"md5\"))\n      .isNotEqualTo(new TextRangeWithHash(1, 22, 3, 4, \"md5\"))\n      .isNotEqualTo(new TextRangeWithHash(1, 2, 33, 4, \"md5\"))\n      .isNotEqualTo(new TextRangeWithHash(1, 2, 3, 44, \"md5\"))\n      .isNotEqualTo(new TextRangeWithHash(1, 2, 3, 4, \"md55\"))\n      .isNotEqualTo(\"foo\");\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/VersionTests.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass VersionTests {\n\n  @Test\n  void test_fields_of_snapshot_versions() {\n    var version = Version.create(\"1.2.3-SNAPSHOT\");\n    assertThat(version.getMajor()).isEqualTo(1);\n    assertThat(version.getMinor()).isEqualTo(2);\n    assertThat(version.getPatch()).isEqualTo(3);\n    assertThat(version.getBuild()).isEqualTo(0);\n    assertThat(version.getQualifier()).isEqualTo(\"SNAPSHOT\");\n  }\n\n  @Test\n  void test_fields_of_releases() {\n    var version = Version.create(\"1.2\");\n    assertThat(version.getMajor()).isEqualTo(1);\n    assertThat(version.getMinor()).isEqualTo(2);\n    assertThat(version.getPatch()).isEqualTo(0);\n    assertThat(version.getBuild()).isEqualTo(0);\n    assertThat(version.getQualifier()).isEmpty();\n  }\n\n  @Test\n  void compare_releases() {\n    var version12 = Version.create(\"1.2\");\n    var version121 = Version.create(\"1.2.1\");\n\n    assertThat(version12)\n      .hasToString(\"1.2\")\n      .isEqualByComparingTo(version12);\n    assertThat(version121)\n      .isEqualByComparingTo(version121)\n      .isGreaterThan(version12);\n  }\n\n  @Test\n  void compare_snapshots() {\n    var version12 = Version.create(\"1.2\");\n    var version12Snapshot = Version.create(\"1.2-SNAPSHOT\");\n    var version121Snapshot = Version.create(\"1.2.1-SNAPSHOT\");\n    var version12RC = Version.create(\"1.2-RC1\");\n\n    assertThat(version12).isGreaterThan(version12Snapshot);\n    assertThat(version12Snapshot).isEqualByComparingTo(version12Snapshot);\n    assertThat(version121Snapshot).isGreaterThan(version12Snapshot);\n    assertThat(version12Snapshot).isGreaterThan(version12RC);\n  }\n\n  @Test\n  void compare_release_candidates() {\n    var version12 = Version.create(\"1.2\");\n    var version12Snapshot = Version.create(\"1.2-SNAPSHOT\");\n    var version12RC1 = Version.create(\"1.2-RC1\");\n    var version12RC2 = Version.create(\"1.2-RC2\");\n\n    assertThat(version12RC1)\n      .isLessThan(version12Snapshot)\n      .isEqualByComparingTo(version12RC1)\n      .isLessThan(version12RC2)\n      .isLessThan(version12);\n  }\n\n  @Test\n  void testTrim() {\n    var version12 = Version.create(\"   1.2  \");\n\n    assertThat(version12.getName()).isEqualTo(\"1.2\");\n    assertThat(version12).isEqualTo(Version.create(\"1.2\"));\n  }\n\n  @Test\n  void testDefaultNumberIsZero() {\n    var version12 = Version.create(\"1.2\");\n    var version120 = Version.create(\"1.2.0\");\n\n    assertThat(version12).isEqualTo(version120);\n    assertThat(version120).isEqualTo(version12);\n  }\n\n  @Test\n  void testCompareOnTwoDigits() {\n    var version1dot10 = Version.create(\"1.10\");\n    var version1dot1 = Version.create(\"1.1\");\n    var version1dot9 = Version.create(\"1.9\");\n\n    assertThat(version1dot10)\n      .isGreaterThan(version1dot1)\n      .isGreaterThan(version1dot9);\n  }\n\n  @Test\n  void testFields() {\n    var version = Version.create(\"1.10.2\");\n\n    assertThat(version.getName()).isEqualTo(\"1.10.2\");\n    assertThat(version).hasToString(\"1.10.2\");\n    assertThat(version.getMajor()).isEqualTo(1);\n    assertThat(version.getMinor()).isEqualTo(10);\n    assertThat(version.getPatch()).isEqualTo(2);\n    assertThat(version.getBuild()).isZero();\n  }\n\n  @Test\n  void testPatchFieldsEquals() {\n    var version = Version.create(\"1.2.3.4\");\n\n    assertThat(version.getPatch()).isEqualTo(3);\n    assertThat(version.getBuild()).isEqualTo(4);\n\n    assertThat(version)\n      .isEqualTo(version)\n      .isEqualTo(Version.create(\"1.2.3.4\"))\n      .isNotEqualTo(Version.create(\"1.2.3.5\"));\n  }\n\n  @Test\n  void removeQualifier() {\n    var version = Version.create(\"1.2.3-SNAPSHOT\").removeQualifier();\n\n    assertThat(version.getMajor()).isEqualTo(1);\n    assertThat(version.getMinor()).isEqualTo(2);\n    assertThat(version.getPatch()).isEqualTo(3);\n    assertThat(version.getQualifier()).isEmpty();\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/log/ConcurrentListAppender.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.log;\n\nimport ch.qos.logback.core.AppenderBase;\nimport java.util.Queue;\nimport java.util.concurrent.ConcurrentLinkedQueue;\n\npublic class ConcurrentListAppender<E> extends AppenderBase<E> {\n  public final Queue<E> list = new ConcurrentLinkedQueue<E>();\n\n  protected void append(E e) {\n    list.add(e);\n  }\n}"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/log/MessageFormatterTests.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.log;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\n/**\n * @author Ceki Gulcu\n */\nclass MessageFormatterTests {\n\n  Integer i1 = 1;\n  Integer i2 = 2;\n  Integer i3 = 3;\n  Integer[] ia0 = new Integer[] {i1, i2, i3};\n  Integer[] ia1 = new Integer[] {10, 20, 30};\n\n  String result;\n\n  @Test\n  void testNull() {\n    result = MessageFormatter.format(null, i1).getMessage();\n    assertNull(result);\n  }\n\n  @Test\n  void testParamaterContainingAnAnchor() {\n    result = MessageFormatter.format(\"Value is {}.\", \"[{}]\").getMessage();\n    assertEquals(\"Value is [{}].\", result);\n\n    result = MessageFormatter.format(\"Values are {} and {}.\", i1, \"[{}]\").getMessage();\n    assertEquals(\"Values are 1 and [{}].\", result);\n  }\n\n  @Test\n  void nullParametersShouldBeHandledWithoutBarfing() {\n    result = MessageFormatter.format(\"Value is {}.\", null).getMessage();\n    assertEquals(\"Value is null.\", result);\n\n    result = MessageFormatter.format(\"Val1 is {}, val2 is {}.\", null, null).getMessage();\n    assertEquals(\"Val1 is null, val2 is null.\", result);\n\n    result = MessageFormatter.format(\"Val1 is {}, val2 is {}.\", i1, null).getMessage();\n    assertEquals(\"Val1 is 1, val2 is null.\", result);\n\n    result = MessageFormatter.format(\"Val1 is {}, val2 is {}.\", null, i2).getMessage();\n    assertEquals(\"Val1 is null, val2 is 2.\", result);\n\n    result = MessageFormatter.arrayFormat(\"Val1 is {}, val2 is {}, val3 is {}\", new Integer[] {null, null, null}).getMessage();\n    assertEquals(\"Val1 is null, val2 is null, val3 is null\", result);\n\n    result = MessageFormatter.arrayFormat(\"Val1 is {}, val2 is {}, val3 is {}\", new Integer[] {null, i2, i3}).getMessage();\n    assertEquals(\"Val1 is null, val2 is 2, val3 is 3\", result);\n\n    result = MessageFormatter.arrayFormat(\"Val1 is {}, val2 is {}, val3 is {}\", new Integer[] {null, null, i3}).getMessage();\n    assertEquals(\"Val1 is null, val2 is null, val3 is 3\", result);\n  }\n\n  @Test\n  void verifyOneParameterIsHandledCorrectly() {\n    result = MessageFormatter.format(\"Value is {}.\", i3).getMessage();\n    assertEquals(\"Value is 3.\", result);\n\n    result = MessageFormatter.format(\"Value is {\", i3).getMessage();\n    assertEquals(\"Value is {\", result);\n\n    result = MessageFormatter.format(\"{} is larger than 2.\", i3).getMessage();\n    assertEquals(\"3 is larger than 2.\", result);\n\n    result = MessageFormatter.format(\"No subst\", i3).getMessage();\n    assertEquals(\"No subst\", result);\n\n    result = MessageFormatter.format(\"Incorrect {subst\", i3).getMessage();\n    assertEquals(\"Incorrect {subst\", result);\n\n    result = MessageFormatter.format(\"Value is {bla} {}\", i3).getMessage();\n    assertEquals(\"Value is {bla} 3\", result);\n\n    result = MessageFormatter.format(\"Escaped \\\\{} subst\", i3).getMessage();\n    assertEquals(\"Escaped {} subst\", result);\n\n    result = MessageFormatter.format(\"{Escaped\", i3).getMessage();\n    assertEquals(\"{Escaped\", result);\n\n    result = MessageFormatter.format(\"\\\\{}Escaped\", i3).getMessage();\n    assertEquals(\"{}Escaped\", result);\n\n    result = MessageFormatter.format(\"File name is {{}}.\", \"App folder.zip\").getMessage();\n    assertEquals(\"File name is {App folder.zip}.\", result);\n\n    // escaping the escape character\n    result = MessageFormatter.format(\"File name is C:\\\\\\\\{}.\", \"App folder.zip\").getMessage();\n    assertEquals(\"File name is C:\\\\App folder.zip.\", result);\n  }\n\n  @Test\n  void testTwoParameters() {\n    result = MessageFormatter.format(\"Value {} is smaller than {}.\", i1, i2).getMessage();\n    assertEquals(\"Value 1 is smaller than 2.\", result);\n\n    result = MessageFormatter.format(\"Value {} is smaller than {}\", i1, i2).getMessage();\n    assertEquals(\"Value 1 is smaller than 2\", result);\n\n    result = MessageFormatter.format(\"{}{}\", i1, i2).getMessage();\n    assertEquals(\"12\", result);\n\n    result = MessageFormatter.format(\"Val1={}, Val2={\", i1, i2).getMessage();\n    assertEquals(\"Val1=1, Val2={\", result);\n\n    result = MessageFormatter.format(\"Value {} is smaller than \\\\{}\", i1, i2).getMessage();\n    assertEquals(\"Value 1 is smaller than {}\", result);\n\n    result = MessageFormatter.format(\"Value {} is smaller than \\\\{} tail\", i1, i2).getMessage();\n    assertEquals(\"Value 1 is smaller than {} tail\", result);\n\n    result = MessageFormatter.format(\"Value {} is smaller than \\\\{\", i1, i2).getMessage();\n    assertEquals(\"Value 1 is smaller than \\\\{\", result);\n\n    result = MessageFormatter.format(\"Value {} is smaller than {tail\", i1, i2).getMessage();\n    assertEquals(\"Value 1 is smaller than {tail\", result);\n\n    result = MessageFormatter.format(\"Value \\\\{} is smaller than {}\", i1, i2).getMessage();\n    assertEquals(\"Value {} is smaller than 1\", result);\n  }\n\n  @Test\n  void testExceptionIn_toString() {\n    Object o = new Object() {\n      @Override\n      public String toString() {\n        throw new IllegalStateException(\"a\");\n      }\n    };\n    result = MessageFormatter.format(\"Troublesome object {}\", o).getMessage();\n    assertEquals(\"Troublesome object [FAILED toString()]\", result);\n\n  }\n\n  @Test\n  void testNullArray() {\n    var msg0 = \"msg0\";\n    var msg1 = \"msg1 {}\";\n    var msg2 = \"msg2 {} {}\";\n    var msg3 = \"msg3 {} {} {}\";\n\n    Object[] args = null;\n\n    result = MessageFormatter.arrayFormat(msg0, args).getMessage();\n    assertEquals(msg0, result);\n\n    result = MessageFormatter.arrayFormat(msg1, args).getMessage();\n    assertEquals(msg1, result);\n\n    result = MessageFormatter.arrayFormat(msg2, args).getMessage();\n    assertEquals(msg2, result);\n\n    result = MessageFormatter.arrayFormat(msg3, args).getMessage();\n    assertEquals(msg3, result);\n  }\n\n  // tests the case when the parameters are supplied in a single array\n  @Test\n  void testArrayFormat() {\n    result = MessageFormatter.arrayFormat(\"Value {} is smaller than {} and {}.\", ia0).getMessage();\n    assertEquals(\"Value 1 is smaller than 2 and 3.\", result);\n\n    result = MessageFormatter.arrayFormat(\"{}{}{}\", ia0).getMessage();\n    assertEquals(\"123\", result);\n\n    result = MessageFormatter.arrayFormat(\"Value {} is smaller than {}.\", ia0).getMessage();\n    assertEquals(\"Value 1 is smaller than 2.\", result);\n\n    result = MessageFormatter.arrayFormat(\"Value {} is smaller than {}\", ia0).getMessage();\n    assertEquals(\"Value 1 is smaller than 2\", result);\n\n    result = MessageFormatter.arrayFormat(\"Val={}, {, Val={}\", ia0).getMessage();\n    assertEquals(\"Val=1, {, Val=2\", result);\n\n    result = MessageFormatter.arrayFormat(\"Val={}, {, Val={}\", ia0).getMessage();\n    assertEquals(\"Val=1, {, Val=2\", result);\n\n    result = MessageFormatter.arrayFormat(\"Val1={}, Val2={\", ia0).getMessage();\n    assertEquals(\"Val1=1, Val2={\", result);\n  }\n\n  @Test\n  void testArrayValues() {\n    var p0 = i1;\n    var p1 = new Integer[] {i2, i3};\n\n    result = MessageFormatter.format(\"{}{}\", p0, p1).getMessage();\n    assertEquals(\"1[2, 3]\", result);\n\n    // Integer[]\n    result = MessageFormatter.arrayFormat(\"{}{}\", new Object[] {\"a\", p1}).getMessage();\n    assertEquals(\"a[2, 3]\", result);\n\n    // byte[]\n    result = MessageFormatter.arrayFormat(\"{}{}\", new Object[] {\"a\", new byte[] {1, 2}}).getMessage();\n    assertEquals(\"a[1, 2]\", result);\n\n    // int[]\n    result = MessageFormatter.arrayFormat(\"{}{}\", new Object[] {\"a\", new int[] {1, 2}}).getMessage();\n    assertEquals(\"a[1, 2]\", result);\n\n    // float[]\n    result = MessageFormatter.arrayFormat(\"{}{}\", new Object[] {\"a\", new float[] {1, 2}}).getMessage();\n    assertEquals(\"a[1.0, 2.0]\", result);\n\n    // double[]\n    result = MessageFormatter.arrayFormat(\"{}{}\", new Object[] {\"a\", new double[] {1, 2}}).getMessage();\n    assertEquals(\"a[1.0, 2.0]\", result);\n\n    // boolean[]\n    result = MessageFormatter.arrayFormat(\"{}{}\", new Object[] {\"a\", new boolean[] {true, false}}).getMessage();\n    assertEquals(\"a[true, false]\", result);\n\n    // short[]\n    result = MessageFormatter.arrayFormat(\"{}{}\", new Object[] {\"a\", new short[] {1, 2}}).getMessage();\n    assertEquals(\"a[1, 2]\", result);\n\n    // char[]\n    result = MessageFormatter.arrayFormat(\"{}{}\", new Object[] {\"a\", new char[] {'a', 'b'}}).getMessage();\n    assertEquals(\"a[a, b]\", result);\n\n    // long[]\n    result = MessageFormatter.arrayFormat(\"{}{}\", new Object[] {\"a\", new long[] {1, 2}}).getMessage();\n    assertEquals(\"a[1, 2]\", result);\n\n  }\n\n  @Test\n  void testMultiDimensionalArrayValues() {\n    var multiIntegerA = new Integer[][] {ia0, ia1};\n    result = MessageFormatter.arrayFormat(\"{}{}\", new Object[] {\"a\", multiIntegerA}).getMessage();\n    assertEquals(\"a[[1, 2, 3], [10, 20, 30]]\", result);\n\n    var multiIntA = new int[][] {{1, 2}, {10, 20}};\n    result = MessageFormatter.arrayFormat(\"{}{}\", new Object[] {\"a\", multiIntA}).getMessage();\n    assertEquals(\"a[[1, 2], [10, 20]]\", result);\n\n    var multiFloatA = new float[][] {{1, 2}, {10, 20}};\n    result = MessageFormatter.arrayFormat(\"{}{}\", new Object[] {\"a\", multiFloatA}).getMessage();\n    assertEquals(\"a[[1.0, 2.0], [10.0, 20.0]]\", result);\n\n    var multiOA = new Object[][] {ia0, ia1};\n    result = MessageFormatter.arrayFormat(\"{}{}\", new Object[] {\"a\", multiOA}).getMessage();\n    assertEquals(\"a[[1, 2, 3], [10, 20, 30]]\", result);\n\n    var _3DOA = new Object[][][] {multiOA, multiOA};\n    result = MessageFormatter.arrayFormat(\"{}{}\", new Object[] {\"a\", _3DOA}).getMessage();\n    assertEquals(\"a[[[1, 2, 3], [10, 20, 30]], [[1, 2, 3], [10, 20, 30]]]\", result);\n  }\n\n  @Test\n  void testCyclicArrays() {\n    {\n      var cyclicA = new Object[1];\n      cyclicA[0] = cyclicA;\n      assertEquals(\"[[...]]\", MessageFormatter.arrayFormat(\"{}\", cyclicA).getMessage());\n    }\n    {\n      var a = new Object[2];\n      a[0] = i1;\n      var c = new Object[] {i3, a};\n      var b = new Object[] {i2, c};\n      a[1] = b;\n      assertEquals(\"1[2, [3, [1, [...]]]]\", MessageFormatter.arrayFormat(\"{}{}\", a).getMessage());\n    }\n  }\n\n  @Test\n  void testArrayThrowable() {\n    FormattingTuple ft;\n    var t = new Throwable();\n    var ia = new Object[] {i1, i2, i3, t};\n\n    ft = MessageFormatter.arrayFormat(\"Value {} is smaller than {} and {}.\", ia);\n    assertEquals(\"Value 1 is smaller than 2 and 3.\", ft.getMessage());\n    assertEquals(t, ft.getThrowable());\n\n    ft = MessageFormatter.arrayFormat(\"{}{}{}\", ia);\n    assertEquals(\"123\", ft.getMessage());\n    assertEquals(t, ft.getThrowable());\n\n    ft = MessageFormatter.arrayFormat(\"Value {} is smaller than {}.\", ia);\n    assertEquals(\"Value 1 is smaller than 2.\", ft.getMessage());\n    assertEquals(t, ft.getThrowable());\n\n    ft = MessageFormatter.arrayFormat(\"Value {} is smaller than {}\", ia);\n    assertEquals(\"Value 1 is smaller than 2\", ft.getMessage());\n    assertEquals(t, ft.getThrowable());\n\n    ft = MessageFormatter.arrayFormat(\"Val={}, {, Val={}\", ia);\n    assertEquals(\"Val=1, {, Val=2\", ft.getMessage());\n    assertEquals(t, ft.getThrowable());\n\n    ft = MessageFormatter.arrayFormat(\"Val={}, \\\\{, Val={}\", ia);\n    assertEquals(\"Val=1, \\\\{, Val=2\", ft.getMessage());\n    assertEquals(t, ft.getThrowable());\n\n    ft = MessageFormatter.arrayFormat(\"Val1={}, Val2={\", ia);\n    assertEquals(\"Val1=1, Val2={\", ft.getMessage());\n    assertEquals(t, ft.getThrowable());\n\n    ft = MessageFormatter.arrayFormat(\"Value {} is smaller than {} and {}.\", ia);\n    assertEquals(\"Value 1 is smaller than 2 and 3.\", ft.getMessage());\n    assertEquals(t, ft.getThrowable());\n\n    ft = MessageFormatter.arrayFormat(\"{}{}{}{}\", ia);\n    assertEquals(\"123{}\", ft.getMessage());\n    assertEquals(t, ft.getThrowable());\n\n    ft = MessageFormatter.arrayFormat(\"1={}\", new Object[] {i1}, t);\n    assertEquals(\"1=1\", ft.getMessage());\n    assertEquals(t, ft.getThrowable());\n\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/log/SonarLintLogTester.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.log;\n\nimport ch.qos.logback.classic.spi.ILoggingEvent;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Queue;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentLinkedQueue;\nimport javax.annotation.Nullable;\nimport org.junit.jupiter.api.extension.AfterAllCallback;\nimport org.junit.jupiter.api.extension.AfterTestExecutionCallback;\nimport org.junit.jupiter.api.extension.BeforeAllCallback;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.slf4j.LoggerFactory;\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput.Level;\n\n/**\n * <b>For tests only</b>\n * <br>\n * This JUnit 5 extension allows to access logs in tests.\n * <br>\n * Warning - not compatible with parallel execution of tests in the same JVM fork.\n * <br>\n * Example:\n * <pre>\n * public class MyClass {\n *   private final SonarLintLogger logger = SonarLintLogger.get();\n *\n *   public void doSomething() {\n *     logger.info(\"foo\");\n *   }\n * }\n *\n * class MyClassTests {\n *   &#064;org.junit.jupiter.api.extension.RegisterExtension\n *   SonarLintLogTester logTester = new SonarLintLogTester();\n *\n *   &#064;org.junit.jupiter.api.Test\n *   public void test_log() {\n *     new MyClass().doSomething();\n *\n *     assertThat(logTester.logs()).containsOnly(\"foo\");\n *   }\n * }\n * </pre>\n */\npublic class SonarLintLogTester implements AfterTestExecutionCallback, BeforeAllCallback, AfterAllCallback {\n\n  private final Queue<String> logs = new ConcurrentLinkedQueue<>();\n  private final Map<Level, Queue<String>> logsByLevel = new ConcurrentHashMap<>();\n  private final LogOutput logOutput;\n\n  private final ConcurrentListAppender<ILoggingEvent> listAppender = new ConcurrentListAppender<>();\n\n  public SonarLintLogTester(boolean writeToStdOut) {\n\n    logOutput = new LogOutput() {\n      @Override\n      public void log(@Nullable String formattedMessage, Level level, @Nullable String stacktrace) {\n        if (formattedMessage != null) {\n          logs.add(formattedMessage);\n          logsByLevel.computeIfAbsent(level, l -> new ConcurrentLinkedQueue<>()).add(formattedMessage);\n        }\n        if (stacktrace != null) {\n          logs.add(stacktrace);\n          logsByLevel.computeIfAbsent(level, l -> new ConcurrentLinkedQueue<>()).add(stacktrace);\n        }\n        if (writeToStdOut) {\n          System.out.println(level + \" \" + (formattedMessage != null ? formattedMessage : \"\"));\n          if (stacktrace != null) {\n            System.out.println(stacktrace);\n          }\n        }\n      }\n    };\n  }\n\n  public SonarLintLogTester() {\n    this(false);\n  }\n\n  @Override\n  public void afterTestExecution(ExtensionContext context) {\n    clear();\n  }\n\n  public void clear() {\n    logs.clear();\n    logsByLevel.clear();\n    listAppender.list.clear();\n  }\n\n  public LogOutput getLogOutput() {\n    return logOutput;\n  }\n\n  /**\n   * Logs in chronological order (item at index 0 is the oldest one)\n   */\n  public List<String> logs() {\n    return List.copyOf(logs);\n  }\n\n  /**\n   * Logs in chronological order (item at index 0 is the oldest one) for\n   * a given level\n   */\n  public List<String> logs(Level level) {\n    return Optional.ofNullable(logsByLevel.get(level)).map(List::copyOf).orElse(List.of());\n  }\n\n  @Override\n  public void afterAll(ExtensionContext context) {\n    SonarLintLogger.get().setTarget(null);\n    listAppender.stop();\n    listAppender.list.clear();\n    getRootLogger().detachAppender(listAppender);\n  }\n\n  @Override\n  public void beforeAll(ExtensionContext context) {\n    SonarLintLogger.get().setLevel(Level.DEBUG);\n    SonarLintLogger.get().setTarget(logOutput);\n    getRootLogger().addAppender(listAppender);\n    listAppender.start();\n  }\n\n  private static ch.qos.logback.classic.Logger getRootLogger() {\n    return (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/log/SonarLintLoggerTests.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.log;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput.Level;\n\nimport static org.mockito.ArgumentMatchers.argThat;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\n\nclass SonarLintLoggerTests {\n  private static final NullPointerException THROWN = new NullPointerException();\n  private final LogOutput output = mock(LogOutput.class);\n  private final SonarLintLogger logger = new SonarLintLogger();\n\n  @BeforeEach\n  void prepare() {\n    logger.setLevel(Level.TRACE);\n    logger.setTarget(output);\n  }\n\n  @Test\n  void should_log_error() {\n    logger.error(\"msg1\");\n    logger.error(\"msg\", (Object) null);\n    // Keep a separate variable to avoid Eclipse refactoring into a non varargs method\n    var emptyArgs = new Object[0];\n    logger.error(\"msg\", emptyArgs);\n    logger.error(\"msg {}\", \"a\");\n    logger.error(\"msg {} {}\", \"a\", \"a\");\n    // Keep a separate variable to avoid Eclipse refactoring into a non varargs method\n    var args = new Object[] {\"b\"};\n    logger.error(\"msg {}\", args);\n    logger.error(\"msg with ex\", THROWN);\n\n    var inOrder = Mockito.inOrder(output);\n    inOrder.verify(output).log(\"msg1\", Level.ERROR, null);\n    inOrder.verify(output, times(2)).log(\"msg\", Level.ERROR, null);\n    inOrder.verify(output).log(\"msg a\", Level.ERROR, null);\n    inOrder.verify(output).log(\"msg a a\", Level.ERROR, null);\n    inOrder.verify(output).log(\"msg b\", Level.ERROR, null);\n    inOrder.verify(output).log(eq(\"msg with ex\"), eq(Level.ERROR), argThat(arg -> arg.contains(\"NullPointerException\")));\n  }\n\n  @Test\n  void should_log_warn() {\n    logger.warn(\"msg1\");\n    logger.warn(\"msg\", (Object) null);\n    // Keep a separate variable to avoid Eclipse refactoring into a non varargs method\n    var emptyArgs = new Object[0];\n    logger.warn(\"msg\", emptyArgs);\n    logger.warn(\"msg {}\", \"a\");\n    logger.warn(\"msg {} {}\", \"a\", \"a\");\n    // Keep a separate variable to avoid Eclipse refactoring into a non varargs method\n    var args = new Object[] {\"b\"};\n    logger.warn(\"msg {}\", args);\n    logger.warn(\"msg with ex\", THROWN);\n\n    var inOrder = Mockito.inOrder(output);\n    inOrder.verify(output).log(\"msg1\", Level.WARN, null);\n    inOrder.verify(output, times(2)).log(\"msg\", Level.WARN, null);\n    inOrder.verify(output).log(\"msg a\", Level.WARN, null);\n    inOrder.verify(output).log(\"msg a a\", Level.WARN, null);\n    inOrder.verify(output).log(\"msg b\", Level.WARN, null);\n    inOrder.verify(output).log(eq(\"msg with ex\"), eq(Level.WARN), argThat(arg -> arg.contains(\"NullPointerException\")));\n  }\n\n  @Test\n  void should_log_info() {\n    logger.info(\"msg1\");\n    logger.info(\"msg\", (Object) null);\n    // Keep a separate variable to avoid Eclipse refactoring into a non varargs method\n    var emptyArgs = new Object[0];\n    logger.info(\"msg\", emptyArgs);\n    logger.info(\"msg {}\", \"a\");\n    logger.info(\"msg {} {}\", \"a\", \"a\");\n    // Keep a separate variable to avoid Eclipse refactoring into a non varargs method\n    var args = new Object[] {\"b\"};\n    logger.info(\"msg {}\", args);\n\n    var inOrder = Mockito.inOrder(output);\n    inOrder.verify(output).log(\"msg1\", Level.INFO, null);\n    inOrder.verify(output, times(2)).log(\"msg\", Level.INFO, null);\n    inOrder.verify(output).log(\"msg a\", Level.INFO, null);\n    inOrder.verify(output).log(\"msg a a\", Level.INFO, null);\n    inOrder.verify(output).log(\"msg b\", Level.INFO, null);\n  }\n\n  @Test\n  void should_log_debug() {\n    logger.debug(\"msg1\");\n    logger.debug(\"msg\", (Object) null);\n    // Keep a separate variable to avoid Eclipse refactoring into a non varargs method\n    var emptyArgs = new Object[0];\n    logger.debug(\"msg\", emptyArgs);\n    logger.debug(\"msg {}\", \"a\");\n    logger.debug(\"msg {} {}\", \"a\", \"a\");\n    // Keep a separate variable to avoid Eclipse refactoring into a non varargs method\n    var args = new Object[] {\"b\"};\n    logger.debug(\"msg {}\", args);\n\n    var inOrder = Mockito.inOrder(output);\n    inOrder.verify(output).log(\"msg1\", Level.DEBUG, null);\n    inOrder.verify(output, times(2)).log(\"msg\", Level.DEBUG, null);\n    inOrder.verify(output).log(\"msg a\", Level.DEBUG, null);\n    inOrder.verify(output).log(\"msg a a\", Level.DEBUG, null);\n    inOrder.verify(output).log(\"msg b\", Level.DEBUG, null);\n  }\n\n  @Test\n  void should_log_trace() {\n    logger.trace(\"msg1\");\n    logger.trace(\"msg\", (Object) null);\n    // Keep a separate variable to avoid Eclipse refactoring into a non varargs method\n    var emptyArgs = new Object[0];\n    logger.trace(\"msg\", emptyArgs);\n    logger.trace(\"msg {}\", \"a\");\n    logger.trace(\"msg {} {}\", \"a\", \"a\");\n    // Keep a separate variable to avoid Eclipse refactoring into a non varargs method\n    var args = new Object[] {\"b\"};\n    logger.trace(\"msg {}\", args);\n\n    var inOrder = Mockito.inOrder(output);\n    inOrder.verify(output).log(\"msg1\", Level.TRACE, null);\n    inOrder.verify(output, times(2)).log(\"msg\", Level.TRACE, null);\n    inOrder.verify(output).log(\"msg a\", Level.TRACE, null);\n    inOrder.verify(output).log(\"msg a a\", Level.TRACE, null);\n    inOrder.verify(output).log(\"msg b\", Level.TRACE, null);\n  }\n\n  // SLCORE-292\n  @Test\n  void extract_throwable_from_format_params() {\n    var throwable = new Throwable(\"thrown\");\n    logger.error(\"msg\", (Object) throwable);\n    logger.error(\"msg {}\", \"a\", throwable);\n    logger.error(\"msg {} {}\", \"a\", \"a\", throwable);\n\n    var inOrder = Mockito.inOrder(output);\n    inOrder.verify(output).log(eq(\"msg\"), eq(Level.ERROR), argThat(arg -> arg.contains(\"thrown\")));\n    inOrder.verify(output).log(eq(\"msg a\"), eq(Level.ERROR), argThat(arg -> arg.contains(\"thrown\")));\n    inOrder.verify(output).log(eq(\"msg a a\"), eq(Level.ERROR), argThat(arg -> arg.contains(\"thrown\")));\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/progress/ExecutorServiceShutdownWatchableTests.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.progress;\n\nimport java.util.ArrayList;\nimport java.util.concurrent.ExecutorService;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\n\nclass ExecutorServiceShutdownWatchableTests {\n\n  @Test\n  void should_cancel_all_monitors() {\n    ExecutorServiceShutdownWatchable<?> underTest = new ExecutorServiceShutdownWatchable<>(mock(ExecutorService.class));\n\n    var monitors = new ArrayList<SonarLintCancelMonitor>();\n    for (int i = 0; i < 1000; i++) {\n      var monitor = new SonarLintCancelMonitor();\n      underTest.cancelOnShutdown(monitor);\n      monitors.add(monitor);\n    }\n    underTest.shutdown();\n\n    assertThat(monitors).allMatch(SonarLintCancelMonitor::isCanceled);\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/storage/DatabaseExceptionReporterTests.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.storage;\n\nimport io.sentry.IScope;\nimport io.sentry.Sentry;\nimport io.sentry.ScopeCallback;\nimport io.sentry.logger.ILoggerApi;\nimport java.sql.SQLException;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.MockedStatic;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.mockStatic;\nimport static org.mockito.Mockito.times;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.Mockito.verify;\n\nclass DatabaseExceptionReporterTests {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private MockedStatic<Sentry> sentryMock;\n\n  @BeforeEach\n  void setUp() {\n    DatabaseExceptionReporter.clearRecentExceptions();\n    sentryMock = mockStatic(Sentry.class);\n    sentryMock.when(Sentry::logger).thenReturn(mock(ILoggerApi.class));\n  }\n\n  @AfterEach\n  void tearDown() {\n    sentryMock.close();\n    DatabaseExceptionReporter.clearRecentExceptions();\n    System.clearProperty(DatabaseExceptionReporter.DEDUP_WINDOW_PROPERTY);\n  }\n\n  @Test\n  void should_capture_generic_exception() {\n    var exception = new RuntimeException(\"Test database error\");\n\n    DatabaseExceptionReporter.capture(exception, \"runtime\", \"jooq.execute\", \"SELECT * FROM test\");\n\n    var exceptionCaptor = ArgumentCaptor.forClass(Throwable.class);\n    sentryMock.verify(() -> Sentry.captureException(exceptionCaptor.capture(), any(ScopeCallback.class)));\n    assertThat(exceptionCaptor.getValue()).isSameAs(exception);\n    assertThat(exceptionCaptor.getValue().getMessage()).isEqualTo(\"Test database error\");\n  }\n\n  @Test\n  void should_capture_sql_exception_with_details() {\n    var sqlException = new SQLException(\"SQL error\", \"42000\", 1234);\n\n    DatabaseExceptionReporter.capture(sqlException, \"startup\", \"flyway.migrate\", null);\n\n    var exceptionCaptor = ArgumentCaptor.forClass(Throwable.class);\n    sentryMock.verify(() -> Sentry.captureException(exceptionCaptor.capture(), any(ScopeCallback.class)));\n    assertThat(exceptionCaptor.getValue()).isInstanceOf(SQLException.class);\n    assertThat(((SQLException) exceptionCaptor.getValue()).getSQLState()).isEqualTo(\"42000\");\n  }\n\n  @Test\n  void should_capture_exception_without_sql() {\n    var exception = new RuntimeException(\"Pool creation failed\");\n\n    DatabaseExceptionReporter.capture(exception, \"startup\", \"h2.pool.create\");\n\n    var exceptionCaptor = ArgumentCaptor.forClass(Throwable.class);\n    sentryMock.verify(() -> Sentry.captureException(exceptionCaptor.capture(), any(ScopeCallback.class)));\n    assertThat(exceptionCaptor.getValue()).isSameAs(exception);\n  }\n\n  @Test\n  void should_deduplicate_same_message_within_window() {\n    var exception = new RuntimeException(\"Duplicate error\");\n\n    DatabaseExceptionReporter.capture(exception, \"runtime\", \"jooq.execute\", \"SELECT 1\");\n    DatabaseExceptionReporter.capture(exception, \"runtime\", \"jooq.execute\", \"SELECT 1\");\n\n    var exceptionCaptor = ArgumentCaptor.forClass(Throwable.class);\n    sentryMock.verify(() -> Sentry.captureException(exceptionCaptor.capture(), any(ScopeCallback.class)), times(1));\n    assertThat(exceptionCaptor.getValue()).isSameAs(exception);\n  }\n\n  @Test\n  void should_not_deduplicate_different_exceptions() {\n    var exception1 = new RuntimeException(\"Error 1\");\n    var exception2 = new RuntimeException(\"Error 2\");\n\n    DatabaseExceptionReporter.capture(exception1, \"runtime\", \"jooq.execute\", \"SELECT 1\");\n    DatabaseExceptionReporter.capture(exception2, \"runtime\", \"jooq.execute\", \"SELECT 2\");\n\n    var exceptionCaptor = ArgumentCaptor.forClass(Throwable.class);\n    sentryMock.verify(() -> Sentry.captureException(exceptionCaptor.capture(), any(ScopeCallback.class)), times(2));\n    assertThat(exceptionCaptor.getAllValues()).containsExactly(exception1, exception2);\n  }\n\n  @Test\n  void should_deduplicate_same_message_even_with_different_phase() {\n    var exception = new RuntimeException(\"Same error\");\n\n    DatabaseExceptionReporter.capture(exception, \"startup\", \"h2.pool.create\");\n    DatabaseExceptionReporter.capture(exception, \"shutdown\", \"h2.pool.dispose\");\n\n    var exceptionCaptor = ArgumentCaptor.forClass(Throwable.class);\n    sentryMock.verify(() -> Sentry.captureException(exceptionCaptor.capture(), any(ScopeCallback.class)), times(1));\n    assertThat(exceptionCaptor.getValue()).isSameAs(exception);\n  }\n\n  @Test\n  void should_always_report_null_message_exceptions_without_deduplication() {\n    var exception1 = new RuntimeException();\n    var exception2 = new RuntimeException();\n\n    DatabaseExceptionReporter.capture(exception1, \"runtime\", \"jooq.execute\");\n    DatabaseExceptionReporter.capture(exception2, \"runtime\", \"jooq.execute\");\n\n    var exceptionCaptor = ArgumentCaptor.forClass(Throwable.class);\n    sentryMock.verify(() -> Sentry.captureException(exceptionCaptor.capture(), any(ScopeCallback.class)), times(2));\n    assertThat(exceptionCaptor.getAllValues()).containsExactly(exception1, exception2);\n  }\n\n  @Test\n  void should_truncate_long_sql() {\n    var longSql = \"SELECT \" + \"a\".repeat(2000) + \" FROM test\";\n    var exception = new RuntimeException(\"SQL error\");\n\n    DatabaseExceptionReporter.capture(exception, \"runtime\", \"jooq.execute\", longSql);\n\n    var exceptionCaptor = ArgumentCaptor.forClass(Throwable.class);\n    sentryMock.verify(() -> Sentry.captureException(exceptionCaptor.capture(), any(ScopeCallback.class)));\n    assertThat(exceptionCaptor.getValue()).isSameAs(exception);\n  }\n\n  @Test\n  void should_cleanup_old_entries_after_dedup_window() {\n    System.setProperty(DatabaseExceptionReporter.DEDUP_WINDOW_PROPERTY, \"50\");\n\n    DatabaseExceptionReporter.capture(new RuntimeException(\"Error 1\"), \"runtime\", \"op1\");\n    DatabaseExceptionReporter.capture(new RuntimeException(\"Error 2\"), \"runtime\", \"op2\");\n    DatabaseExceptionReporter.capture(new RuntimeException(\"Error 3\"), \"runtime\", \"op3\");\n\n    assertThat(DatabaseExceptionReporter.getRecentExceptionsCount()).isEqualTo(3);\n\n    await().atLeast(java.time.Duration.ofMillis(100)).untilAsserted(() -> {\n      DatabaseExceptionReporter.capture(new RuntimeException(\"Error 4\"), \"runtime\", \"op4\");\n      assertThat(DatabaseExceptionReporter.getRecentExceptionsCount()).isEqualTo(1);\n    });\n  }\n\n  @Test\n  void should_set_scope_tags_for_generic_exception() {\n    var scope = mock(IScope.class);\n    sentryMock.when(() -> Sentry.captureException(any(Throwable.class), any(ScopeCallback.class)))\n      .thenAnswer(invocation -> {\n        var callback = invocation.getArgument(1, ScopeCallback.class);\n        callback.run(scope);\n        return null;\n      });\n\n    var exception = new RuntimeException(\"Test error\");\n    DatabaseExceptionReporter.capture(exception, \"startup\", \"h2.pool.create\");\n\n    verify(scope).setTag(\"component\", \"database\");\n    verify(scope).setTag(\"db.phase\", \"startup\");\n    verify(scope).setTag(\"db.operation\", \"h2.pool.create\");\n  }\n\n  @Test\n  void should_set_scope_tags_for_sql_exception_with_sql_state() {\n    var scope = mock(IScope.class);\n    sentryMock.when(() -> Sentry.captureException(any(Throwable.class), any(ScopeCallback.class)))\n      .thenAnswer(invocation -> {\n        var callback = invocation.getArgument(1, ScopeCallback.class);\n        callback.run(scope);\n        return null;\n      });\n\n    var sqlException = new SQLException(\"SQL error\", \"42000\", 1234);\n    DatabaseExceptionReporter.capture(sqlException, \"runtime\", \"jooq.execute\");\n\n    verify(scope).setTag(\"component\", \"database\");\n    verify(scope).setTag(\"db.phase\", \"runtime\");\n    verify(scope).setTag(\"db.operation\", \"jooq.execute\");\n    verify(scope).setTag(\"db.sqlState\", \"42000\");\n    verify(scope).setTag(\"db.errorCode\", \"1234\");\n  }\n\n  @Test\n  void should_not_set_sql_state_tag_when_null() {\n    var scope = mock(IScope.class);\n    sentryMock.when(() -> Sentry.captureException(any(Throwable.class), any(ScopeCallback.class)))\n      .thenAnswer(invocation -> {\n        var callback = invocation.getArgument(1, ScopeCallback.class);\n        callback.run(scope);\n        return null;\n      });\n\n    var sqlException = new SQLException(\"SQL error\", null, 5678);\n    DatabaseExceptionReporter.capture(sqlException, \"runtime\", \"jooq.execute\");\n\n    verify(scope).setTag(\"component\", \"database\");\n    verify(scope).setTag(\"db.errorCode\", \"5678\");\n    verify(scope, times(0)).setTag(\"db.sqlState\", null);\n  }\n\n  @Test\n  void should_set_sql_extra_when_provided() {\n    var scope = mock(IScope.class);\n    sentryMock.when(() -> Sentry.captureException(any(Throwable.class), any(ScopeCallback.class)))\n      .thenAnswer(invocation -> {\n        var callback = invocation.getArgument(1, ScopeCallback.class);\n        callback.run(scope);\n        return null;\n      });\n\n    var exception = new RuntimeException(\"Test error\");\n    DatabaseExceptionReporter.capture(exception, \"runtime\", \"jooq.execute\", \"SELECT * FROM test\");\n\n    verify(scope).setExtra(\"db.sql\", \"SELECT * FROM test\");\n  }\n\n  @Test\n  void should_not_set_sql_extra_when_empty() {\n    var scope = mock(IScope.class);\n    sentryMock.when(() -> Sentry.captureException(any(Throwable.class), any(ScopeCallback.class)))\n      .thenAnswer(invocation -> {\n        var callback = invocation.getArgument(1, ScopeCallback.class);\n        callback.run(scope);\n        return null;\n      });\n\n    var exception = new RuntimeException(\"Test error\");\n    DatabaseExceptionReporter.capture(exception, \"runtime\", \"jooq.execute\", \"\");\n\n    verify(scope, times(0)).setExtra(any(), any());\n  }\n\n  @Test\n  void should_truncate_sql_in_extra_when_exceeds_1000_chars() {\n    var scope = mock(IScope.class);\n    sentryMock.when(() -> Sentry.captureException(any(Throwable.class), any(ScopeCallback.class)))\n      .thenAnswer(invocation -> {\n        var callback = invocation.getArgument(1, ScopeCallback.class);\n        callback.run(scope);\n        return null;\n      });\n\n    var longSql = \"SELECT \" + \"a\".repeat(2000) + \" FROM test\";\n    var exception = new RuntimeException(\"SQL error\");\n    DatabaseExceptionReporter.capture(exception, \"runtime\", \"jooq.execute\", longSql);\n\n    var sqlCaptor = ArgumentCaptor.forClass(String.class);\n    verify(scope).setExtra(any(), sqlCaptor.capture());\n    assertThat(sqlCaptor.getValue()).hasSize(1000 + \"... [truncated]\".length());\n    assertThat(sqlCaptor.getValue()).endsWith(\"... [truncated]\");\n  }\n\n  @Test\n  void should_use_default_dedup_window_when_property_is_invalid() {\n    System.setProperty(DatabaseExceptionReporter.DEDUP_WINDOW_PROPERTY, \"not-a-number\");\n\n    var exception1 = new RuntimeException(\"Error\");\n    var exception2 = new RuntimeException(\"Error\");\n\n    DatabaseExceptionReporter.capture(exception1, \"runtime\", \"op1\");\n    DatabaseExceptionReporter.capture(exception2, \"runtime\", \"op2\");\n\n    // Should still deduplicate using default window (not crash)\n    sentryMock.verify(() -> Sentry.captureException(any(Throwable.class), any(ScopeCallback.class)), times(1));\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/storage/JooqDatabaseExceptionListenerTests.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.storage;\n\nimport io.sentry.Sentry;\nimport io.sentry.ScopeCallback;\nimport io.sentry.logger.ILoggerApi;\nimport java.sql.SQLException;\nimport org.jooq.ExecuteContext;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.MockedStatic;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.mockStatic;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.when;\n\nclass JooqDatabaseExceptionListenerTests {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private JooqDatabaseExceptionListener listener;\n  private MockedStatic<Sentry> sentryMock;\n\n  @BeforeEach\n  void setUp() {\n    listener = new JooqDatabaseExceptionListener();\n    DatabaseExceptionReporter.clearRecentExceptions();\n    sentryMock = mockStatic(Sentry.class);\n    sentryMock.when(Sentry::logger).thenReturn(mock(ILoggerApi.class));\n  }\n\n  @AfterEach\n  void tearDown() {\n    sentryMock.close();\n    DatabaseExceptionReporter.clearRecentExceptions();\n  }\n\n  @Test\n  void should_report_sql_exception_from_context() {\n    var ctx = mock(ExecuteContext.class);\n    var sqlException = new SQLException(\"SQL syntax error\", \"42000\", 1064);\n    var runtimeException = new RuntimeException(\"Wrapped exception\", sqlException);\n\n    when(ctx.exception()).thenReturn(runtimeException);\n    when(ctx.sqlException()).thenReturn(sqlException);\n    when(ctx.sql()).thenReturn(\"SELECT * FROM invalid_table\");\n\n    listener.exception(ctx);\n\n    var exceptionCaptor = ArgumentCaptor.forClass(Throwable.class);\n    sentryMock.verify(() -> Sentry.captureException(exceptionCaptor.capture(), any(ScopeCallback.class)));\n    assertThat(exceptionCaptor.getValue()).isInstanceOf(SQLException.class);\n    assertThat(((SQLException) exceptionCaptor.getValue()).getSQLState()).isEqualTo(\"42000\");\n  }\n\n  @Test\n  void should_report_runtime_exception_when_no_sql_exception() {\n    var ctx = mock(ExecuteContext.class);\n    var runtimeException = new RuntimeException(\"jOOQ execution failed\");\n\n    when(ctx.exception()).thenReturn(runtimeException);\n    when(ctx.sqlException()).thenReturn(null);\n    when(ctx.sql()).thenReturn(\"INSERT INTO test VALUES (1)\");\n\n    listener.exception(ctx);\n\n    var exceptionCaptor = ArgumentCaptor.forClass(Throwable.class);\n    sentryMock.verify(() -> Sentry.captureException(exceptionCaptor.capture(), any(ScopeCallback.class)));\n    assertThat(exceptionCaptor.getValue()).isSameAs(runtimeException);\n  }\n\n  @Test\n  void should_not_report_when_no_exception() {\n    var ctx = mock(ExecuteContext.class);\n\n    when(ctx.exception()).thenReturn(null);\n\n    listener.exception(ctx);\n\n    sentryMock.verify(() -> Sentry.captureException(any(Throwable.class), any(ScopeCallback.class)), never());\n  }\n\n  @Test\n  void should_handle_null_sql() {\n    var ctx = mock(ExecuteContext.class);\n    var exception = new RuntimeException(\"Error without SQL\");\n\n    when(ctx.exception()).thenReturn(exception);\n    when(ctx.sqlException()).thenReturn(null);\n    when(ctx.sql()).thenReturn(null);\n\n    listener.exception(ctx);\n\n    var exceptionCaptor = ArgumentCaptor.forClass(Throwable.class);\n    sentryMock.verify(() -> Sentry.captureException(exceptionCaptor.capture(), any(ScopeCallback.class)));\n    assertThat(exceptionCaptor.getValue()).isSameAs(exception);\n  }\n\n  @Test\n  void should_prefer_sql_exception_over_runtime_exception() {\n    var ctx = mock(ExecuteContext.class);\n    var sqlException = new SQLException(\"Constraint violation\", \"23000\", 1062);\n    var runtimeException = new RuntimeException(\"Wrapper\", sqlException);\n\n    when(ctx.exception()).thenReturn(runtimeException);\n    when(ctx.sqlException()).thenReturn(sqlException);\n    when(ctx.sql()).thenReturn(\"INSERT INTO test (id) VALUES (1)\");\n\n    listener.exception(ctx);\n\n    var exceptionCaptor = ArgumentCaptor.forClass(Throwable.class);\n    sentryMock.verify(() -> Sentry.captureException(exceptionCaptor.capture(), any(ScopeCallback.class)));\n    assertThat(exceptionCaptor.getValue()).isSameAs(sqlException);\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/storage/SonarLintDatabaseExceptionTests.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.storage;\n\nimport io.sentry.Sentry;\nimport io.sentry.ScopeCallback;\nimport io.sentry.logger.ILoggerApi;\nimport java.nio.file.Path;\nimport java.sql.SQLException;\nimport org.jooq.exception.DataAccessException;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.MockedStatic;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.mockStatic;\nimport static org.mockito.Mockito.never;\n\nclass SonarLintDatabaseExceptionTests {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private SonarLintDatabase db;\n  private MockedStatic<Sentry> sentryMock;\n\n  @BeforeEach\n  void setUp() {\n    DatabaseExceptionReporter.clearRecentExceptions();\n    sentryMock = mockStatic(Sentry.class);\n    sentryMock.when(Sentry::logger).thenReturn(mock(ILoggerApi.class));\n  }\n\n  @AfterEach\n  void tearDown() {\n    if (db != null) {\n      db.shutdown();\n    }\n    sentryMock.close();\n    DatabaseExceptionReporter.clearRecentExceptions();\n  }\n\n  @Test\n  void should_report_runtime_sql_exception_via_listener(@TempDir Path tempDir) {\n    var storageRoot = tempDir.resolve(\"storage\");\n    db = new SonarLintDatabase(storageRoot);\n\n    assertThatThrownBy(() -> db.dsl().execute(\"SELECT * FROM non_existent_table\"))\n      .isInstanceOf(DataAccessException.class);\n\n    var exceptionCaptor = ArgumentCaptor.forClass(Throwable.class);\n    sentryMock.verify(() -> Sentry.captureException(exceptionCaptor.capture(), any(ScopeCallback.class)));\n\n    var capturedException = exceptionCaptor.getValue();\n    assertThat(capturedException).isInstanceOf(SQLException.class);\n    var sqlException = (SQLException) capturedException;\n    assertThat(sqlException.getSQLState()).isEqualTo(\"42S02\");\n    assertThat(sqlException.getMessage()).contains(\"NON_EXISTENT_TABLE\");\n  }\n\n  @Test\n  void should_report_invalid_sql_syntax_exception(@TempDir Path tempDir) {\n    var storageRoot = tempDir.resolve(\"storage\");\n    db = new SonarLintDatabase(storageRoot);\n\n    assertThatThrownBy(() -> db.dsl().execute(\"INVALID SQL SYNTAX HERE\"))\n      .isInstanceOf(DataAccessException.class);\n\n    var exceptionCaptor = ArgumentCaptor.forClass(Throwable.class);\n    sentryMock.verify(() -> Sentry.captureException(exceptionCaptor.capture(), any(ScopeCallback.class)));\n\n    var capturedException = exceptionCaptor.getValue();\n    assertThat(capturedException).isInstanceOf(SQLException.class);\n    var sqlException = (SQLException) capturedException;\n    assertThat(sqlException.getSQLState()).isEqualTo(\"42001\");\n  }\n\n  @Test\n  void should_report_constraint_violation_exception(@TempDir Path tempDir) {\n    var storageRoot = tempDir.resolve(\"storage\");\n    db = new SonarLintDatabase(storageRoot);\n\n    db.dsl().execute(\"CREATE TABLE IF NOT EXISTS test_table (id INT PRIMARY KEY, name VARCHAR(100))\");\n    db.dsl().execute(\"INSERT INTO test_table (id, name) VALUES (1, 'test')\");\n\n    assertThatThrownBy(() -> db.dsl().execute(\"INSERT INTO test_table (id, name) VALUES (1, 'duplicate')\"))\n      .isInstanceOf(DataAccessException.class);\n\n    var exceptionCaptor = ArgumentCaptor.forClass(Throwable.class);\n    sentryMock.verify(() -> Sentry.captureException(exceptionCaptor.capture(), any(ScopeCallback.class)));\n\n    var capturedException = exceptionCaptor.getValue();\n    assertThat(capturedException).isInstanceOf(SQLException.class);\n    var sqlException = (SQLException) capturedException;\n    assertThat(sqlException.getSQLState()).isEqualTo(\"23505\");\n    assertThat(sqlException.getMessage()).contains(\"Unique index or primary key violation\");\n  }\n\n  @Test\n  void should_initialize_database_successfully(@TempDir Path tempDir) {\n    var storageRoot = tempDir.resolve(\"storage\");\n    db = new SonarLintDatabase(storageRoot);\n\n    assertThat(db.dsl()).isNotNull();\n    sentryMock.verify(() -> Sentry.captureException(any(Throwable.class), any(ScopeCallback.class)), never());\n  }\n\n  @Test\n  void should_shutdown_database_successfully(@TempDir Path tempDir) {\n    var storageRoot = tempDir.resolve(\"storage\");\n    db = new SonarLintDatabase(storageRoot);\n\n    db.shutdown();\n    db = null;\n\n    sentryMock.verify(() -> Sentry.captureException(any(Throwable.class), any(ScopeCallback.class)), never());\n  }\n\n  @Test\n  void should_execute_valid_queries_without_exception_reporting(@TempDir Path tempDir) {\n    var storageRoot = tempDir.resolve(\"storage\");\n    db = new SonarLintDatabase(storageRoot);\n\n    db.dsl().execute(\"CREATE TABLE IF NOT EXISTS valid_table (id INT, name VARCHAR(100))\");\n    db.dsl().execute(\"INSERT INTO valid_table (id, name) VALUES (1, 'test')\");\n    var result = db.dsl().fetch(\"SELECT * FROM valid_table WHERE id = 1\");\n\n    assertThat(result).hasSize(1);\n    sentryMock.verify(() -> Sentry.captureException(any(Throwable.class), any(ScopeCallback.class)), never());\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/storage/local/FileStorageManagerTest.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.storage.local;\n\nimport com.google.gson.GsonBuilder;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Path;\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.OffsetDateTime;\nimport java.util.ArrayList;\nimport java.util.Base64;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.function.Function;\nimport java.util.stream.IntStream;\nimport java.util.stream.Stream;\nimport org.apache.commons.io.FileUtils;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.DisabledOnOs;\nimport org.junit.jupiter.api.condition.OS;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.storage.adapter.LocalDateAdapter;\nimport org.sonarsource.sonarlint.core.commons.storage.adapter.LocalDateTimeAdapter;\nimport org.sonarsource.sonarlint.core.commons.storage.adapter.OffsetDateTimeAdapter;\n\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass FileStorageManagerTest {\n\n  private Path filePath;\n\n  @BeforeEach\n  void setUp(@TempDir Path temp) {\n    filePath = temp.resolve(\"test\");\n  }\n\n  @Test\n  void should_update() {\n    var storage = new FileStorageManager<>(filePath, Dummy::new, Dummy.class);\n\n    storage.getStorage();\n    assertThat(filePath).doesNotExist();\n\n    storage.tryUpdateAtomically(dummy -> dummy.data = \"update\");\n    assertThat(filePath).exists();\n\n    var dummy = storage.getStorage();\n\n    assertThat(dummy.data).isEqualTo(\"update\");\n  }\n\n  @Test\n  void supportConcurrentUpdates() {\n    var storage = new FileStorageManager<>(filePath, Dummy::new, Dummy.class);\n    int nThreads = 10;\n    var executorService = Executors.newFixedThreadPool(nThreads);\n    CountDownLatch latch = new CountDownLatch(1);\n    List<Future<?>> futures = new ArrayList<>();\n    // Each thread will attempt to increment the counter by one\n    IntStream.range(0, nThreads).forEach(i -> {\n      futures.add(executorService.submit(() -> {\n        try {\n          latch.await();\n        } catch (InterruptedException e) {\n          e.printStackTrace();\n        }\n        storage.tryUpdateAtomically(data -> data.counter++);\n      }));\n    });\n    latch.countDown();\n    futures.forEach(f -> {\n      try {\n        f.get();\n      } catch (ExecutionException e) {\n        fail(e.getCause());\n      } catch (InterruptedException e) {\n        e.printStackTrace();\n      }\n    });\n    assertThat(storage.getStorage().counter).isEqualTo(nThreads);\n  }\n\n  @Test\n  void tryUpdateAtomically_should_not_crash_if_too_many_read_write_requests() {\n    var storageManager = new FileStorageManager<>(filePath, Dummy::new, Dummy.class);\n\n    Runnable read = () -> storageManager.getStorage().getCounter();\n    Runnable write = () -> storageManager.tryUpdateAtomically(dummy -> dummy.counter++);\n    Stream.of(\n        IntStream.range(0, 100).mapToObj(operand -> CompletableFuture.runAsync(write)),\n        IntStream.range(0, 100).mapToObj(value -> CompletableFuture.runAsync(read)),\n        IntStream.range(0, 100).mapToObj(operand -> CompletableFuture.runAsync(write)),\n        IntStream.range(0, 100).mapToObj(value -> CompletableFuture.runAsync(read))\n      ).flatMap(Function.identity())\n      .forEach(CompletableFuture::join);\n\n    assertThat(storageManager.getStorage().counter).isEqualTo(200);\n  }\n\n  @Test\n  void tryRead_should_be_aware_of_file_deletion() {\n    var storageManager = new FileStorageManager<>(filePath, Dummy::new, Dummy.class);\n\n    assertThat(storageManager.getStorage().counter).isZero();\n\n    storageManager.tryUpdateAtomically(dummy -> dummy.counter++);\n    assertThat(storageManager.getStorage().counter).isEqualTo(1);\n\n    filePath.toFile().delete();\n\n    assertThat(storageManager.getStorage().counter).isZero();\n  }\n\n  /**\n   * Disabled on Windows because it doesn't always give the file modification time correctly\n   */\n  @Test\n  @DisabledOnOs(OS.WINDOWS)\n  void tryRead_should_be_aware_of_file_modification() throws IOException {\n    var storageManager = new FileStorageManager<>(filePath, Dummy::new, Dummy.class);\n\n    assertThat(storageManager.getStorage().counter).isZero();\n\n    storageManager.tryUpdateAtomically(dummy -> dummy.counter++);\n    assertThat(storageManager.getStorage().counter).isEqualTo(1);\n\n    var dummy = new Dummy();\n    dummy.counter = 2;\n    writeToLocalStorageFile(dummy);\n\n    await().atMost(5, SECONDS).untilAsserted(() -> assertThat(storageManager.getStorage().counter).isEqualTo(2));\n  }\n\n  private void writeToLocalStorageFile(Object newStorage) throws IOException {\n    var newJson = new GsonBuilder()\n      .registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter().nullSafe())\n      .registerTypeAdapter(LocalDate.class, new LocalDateAdapter().nullSafe())\n      .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter().nullSafe())\n      .create().toJson(newStorage);\n    var encoded = Base64.getEncoder().encode(newJson.getBytes(StandardCharsets.UTF_8));\n    writeToLocalStorageFile(encoded);\n  }\n\n  private void writeToLocalStorageFile(byte[] encoded) throws IOException {\n    FileUtils.writeByteArrayToFile(filePath.toFile(), encoded);\n  }\n\n  @Test\n  void tryRead_returns_default_local_storage_if_file_is_empty() throws IOException {\n    writeToLocalStorageFile(new byte[0]);\n    assertThat(filePath.toFile()).isEmpty();\n\n    var storageManager = new FileStorageManager<>(filePath, Dummy::new, Dummy.class);\n    assertThat(storageManager.getStorage().data).isEqualTo(\"default\");\n    assertThat(storageManager.getStorage().counter).isZero();\n  }\n\n  private static class Dummy implements LocalStorage {\n\n    private String data;\n    private int counter = 0;\n\n    Dummy() {\n      this(\"default\");\n    }\n\n    Dummy(String data) {\n      this.data = data;\n    }\n\n    public int getCounter() {\n      return counter;\n    }\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/testutils/GitUtils.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.testutils;\n\nimport java.io.File;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardOpenOption;\nimport java.time.Instant;\nimport java.time.ZoneId;\nimport org.apache.commons.io.FilenameUtils;\nimport org.eclipse.jgit.api.Git;\nimport org.eclipse.jgit.api.errors.GitAPIException;\nimport org.eclipse.jgit.lib.Constants;\nimport org.eclipse.jgit.lib.PersonIdent;\nimport org.eclipse.jgit.revwalk.RevCommit;\nimport org.eclipse.jgit.storage.file.FileRepositoryBuilder;\nimport org.eclipse.jgit.util.SystemReader;\n\npublic class GitUtils {\n\n  private GitUtils() {\n    // Utils class\n  }\n\n  public static Git createRepository(Path worktree) throws GitAPIException, IOException {\n    var repo = FileRepositoryBuilder.create(worktree.resolve(\".git\").toFile());\n    repo.create();\n    var git = new Git(repo);\n    createEmptyGitIgnoreFile(git);\n    return git;\n  }\n\n  private static void createEmptyGitIgnoreFile(Git git) throws GitAPIException, IOException {\n    var gitIgnoreFile = getGitIgnoreFile(git);\n    if (gitIgnoreFile.createNewFile()) {\n      git.add().addFilepattern(Constants.GITIGNORE_FILENAME);\n      git.commit().setMessage(\"Add empty .gitignore\").call();\n    }\n  }\n\n  public static void addFileToGitIgnoreAndCommit(Git git, String filePath) throws IOException, GitAPIException {\n    var gitIgnoreFile = getGitIgnoreFile(git);\n    // Append the file path to the .gitignore file\n    try (var writer = new FileWriter(gitIgnoreFile, true)) {\n      writer.write(\"\\n\" + filePath + \"\\n\");\n    }\n    commit(git, gitIgnoreFile.getPath());\n  }\n\n  private static File getGitIgnoreFile(Git git) {\n    return new File(git.getRepository().getDirectory().getParent(), Constants.GITIGNORE_FILENAME);\n  }\n\n  public static Instant commit(Git git, String... paths) throws GitAPIException {\n    return commit(git, Instant.now(), paths);\n  }\n\n  public static Instant commit(Git git, Instant commitDate, String... paths) throws GitAPIException {\n    return commitObject(git, commitDate, paths).getCommitterIdent().getWhenAsInstant();\n  }\n\n  private static RevCommit commitObject(Git git, Instant commitDate, String... paths) throws GitAPIException {\n    if (paths.length > 0) {\n      var add = git.add();\n      for (String p : paths) {\n        add.addFilepattern(FilenameUtils.separatorsToUnix(p));\n      }\n      add.call();\n    }\n    return git.commit().setCommitter(new PersonIdent(\"joe\", \"email@email.com\", commitDate, ZoneId.systemDefault())).setMessage(\"msg\").call();\n  }\n\n  public static Instant commitAtDate(Git git, Instant commitDate, String... paths) throws GitAPIException {\n    if (paths.length > 0) {\n      var add = git.add();\n      for (String p : paths) {\n        add.addFilepattern(FilenameUtils.separatorsToUnix(p));\n      }\n      add.call();\n    }\n\n    var commit = git.commit()\n      .setCommitter(new PersonIdent(\"joe\", \"email@email.com\", commitDate, SystemReader.getInstance().getTimeZoneAt(commitDate)))\n      .setMessage(\"msg\")\n      .call();\n    return commit.getCommitterIdent().getWhenAsInstant();\n  }\n\n  public static void createFile(Path worktree, String relativePath, String... lines) throws IOException {\n    var newFile = worktree.resolve(relativePath);\n    Files.createDirectories(newFile.getParent());\n    var content = String.join(System.lineSeparator(), lines) + System.lineSeparator();\n    Files.write(newFile, content.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);\n  }\n\n  public static void appendFile(Path file, String... lines) throws IOException {\n    var content = String.join(System.lineSeparator(), lines) + System.lineSeparator();\n    Files.write(file, content.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.APPEND);\n  }\n\n  public static void modifyFile(Path file, String... lines) throws IOException {\n    var content = String.join(System.lineSeparator(), lines) + System.lineSeparator();\n    Files.write(file, content.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/testutils/MockWebServerExtension.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.testutils;\n\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport mockwebserver3.Dispatcher;\nimport mockwebserver3.MockResponse;\nimport mockwebserver3.MockWebServer;\nimport mockwebserver3.RecordedRequest;\nimport okio.Buffer;\nimport org.junit.jupiter.api.extension.AfterEachCallback;\nimport org.junit.jupiter.api.extension.BeforeEachCallback;\nimport org.junit.jupiter.api.extension.ExtensionContext;\n\nimport static java.util.Objects.requireNonNull;\nimport static org.junit.jupiter.api.Assertions.fail;\n\npublic class MockWebServerExtension implements BeforeEachCallback, AfterEachCallback {\n\n  private MockWebServer server;\n  protected final Map<String, MockResponse> responsesByPath = new HashMap<>();\n\n  @Override\n  public void beforeEach(ExtensionContext context) {\n    start();\n    // Most test cases have a call to this endpoint when initializing the data, to decide whether to use Bearer or Basic scheme\n    addStringResponse(\"/api/system/status\", \"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"99.9\\\",\\\"status\\\": \\\"UP\\\"}\");\n  }\n\n  public void start() {\n    server = new MockWebServer();\n    responsesByPath.clear();\n    final Dispatcher dispatcher = new Dispatcher() {\n      @Override\n      public MockResponse dispatch(RecordedRequest request) {\n        if (responsesByPath.containsKey(request.getPath())) {\n          return responsesByPath.get(request.getPath());\n        }\n        return new MockResponse.Builder().code(404).build();\n      }\n    };\n    server.setDispatcher(dispatcher);\n    try {\n      server.start();\n    } catch (IOException e) {\n      throw new IllegalStateException(\"Cannot start the mock web server\", e);\n    }\n  }\n\n  @Override\n  public void afterEach(ExtensionContext context) {\n    shutdown();\n  }\n\n  public void shutdown() {\n    try {\n      server.shutdown();\n    } catch (IOException e) {\n      throw new IllegalStateException(\"Cannot stop the mock web server\", e);\n    }\n  }\n\n  public void addStringResponse(String path, String body) {\n    responsesByPath.put(path, new MockResponse.Builder().body(body).build());\n  }\n\n  public void removeResponse(String path) {\n    responsesByPath.remove(path);\n  }\n\n  public void addResponse(String path, MockResponse response) {\n    responsesByPath.put(path, response);\n  }\n\n  public int getRequestCount() {\n    return server.getRequestCount();\n  }\n\n  public RecordedRequest takeRequest() {\n    try {\n      return server.takeRequest();\n    } catch (InterruptedException e) {\n      fail(e);\n      return null; // appeasing the compiler: this line will never be executed.\n    }\n  }\n\n  public String url(String path) {\n    return server.url(path).toString();\n  }\n\n  public void addResponseFromResource(String path, String responseResourcePath) {\n    try (var b = new Buffer()) {\n      responsesByPath.put(path, new MockResponse.Builder().body(b.readFrom(requireNonNull(MockWebServerExtension.class.getResourceAsStream(responseResourcePath)))).build());\n    } catch (IOException e) {\n      fail(e);\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/util/git/BlameParserTests.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.util.git;\n\nimport java.time.Instant;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;\n\nclass BlameParserTests {\n\n  @Test\n  void shouldNotPopulateGitBlameResultForEmptyBlameOutput() {\n    var gitBlameReader = new GitBlameReader();\n\n    gitBlameReader.readLine(\"\");\n\n    assertThat(gitBlameReader.getResult().lineCommitDates())\n      .isEmpty();\n  }\n\n  @Test\n  void shouldSplitBlameOutputCorrectlyWhenLinesContainSplitPattern() {\n    var blameOutput = \"\"\"\n      5746f09bf53067450843eaddff52ea7b0f16cde3 1 1 2\n      author Some One\n      author-mail <some.one@sonarsource.com>\n      author-time 1553598120\n      author-tz +0100\n      committer Some One\n      committer-mail <some.one@sonarsource.com>\n      committer-time 1554191055\n      committer-tz +0200\n      summary Initial revision\n      previous 35c9ca0b1f41231508e706707d76ca0485b8a3ad file.txt\n      filename file.txt\n              First line with filename in it\n      5746f09bf53067450843eaddff52ea7b0f16cde3 2 2\n      author Some One\n      author-mail <some.one@sonarsource.com>\n      author-time 1553598120\n      author-tz +0100\n      committer Some One\n      committer-mail <some.one@sonarsource.com>\n      committer-time 1554191057\n      committer-tz +0200\n      summary Initial revision\n      previous 35c9ca0b1f41231508e706707d76ca0485b8a3ad file.txt\n      filename file.txt\n              Second line also with filename in it\n      \"\"\";\n    var gitBlameReader = new GitBlameReader();\n    var blameLines = blameOutput.split(\"\\\\n\");\n\n    for (String blameLine : blameLines) {\n      gitBlameReader.readLine(blameLine);\n    }\n\n    assertThat(gitBlameReader.getResult().lineCommitDates())\n      .containsExactly(Instant.ofEpochSecond(1554191055), Instant.ofEpochSecond(1554191057));\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/util/git/GitServiceTests.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.util.git;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.function.UnaryOperator;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\nimport java.util.stream.Stream;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.io.FilenameUtils;\nimport org.eclipse.jgit.api.Git;\nimport org.eclipse.jgit.api.errors.GitAPIException;\nimport org.eclipse.jgit.transport.URIish;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.LogTestStartAndEnd;\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static java.util.function.Predicate.not;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.eclipse.jgit.lib.Constants.GITIGNORE_FILENAME;\nimport static org.eclipse.jgit.util.FileUtils.RECURSIVE;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\nimport static org.sonarsource.sonarlint.core.commons.testutils.GitUtils.addFileToGitIgnoreAndCommit;\nimport static org.sonarsource.sonarlint.core.commons.testutils.GitUtils.commit;\nimport static org.sonarsource.sonarlint.core.commons.testutils.GitUtils.createFile;\nimport static org.sonarsource.sonarlint.core.commons.testutils.GitUtils.createRepository;\nimport static org.sonarsource.sonarlint.core.commons.testutils.GitUtils.modifyFile;\nimport static org.sonarsource.sonarlint.core.commons.util.git.GitService.blameWithGitFilesBlameLibrary;\nimport static org.sonarsource.sonarlint.core.commons.util.git.GitService.getVCSChangedFiles;\n\n@ExtendWith(LogTestStartAndEnd.class)\nclass GitServiceTests {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  private static final NativeGitLocator REAL_NATIVE_GIT_LOCATOR = new NativeGitLocator();\n  private static final GitService underTest = new GitService(REAL_NATIVE_GIT_LOCATOR);\n  private static Path bareRepoPath;\n  private static Path workingRepoPath;\n  @TempDir\n  private Path projectDirPath;\n  private Git git;\n\n  @BeforeAll\n  static void beforeAll() throws GitAPIException, IOException {\n    setUpBareRepo(Map.of(\n      \".gitignore\", \"*.log\\n*.tmp\\n\",\n      \"fileA\", \"lineA1\\nlineA2\\n\",\n      \"fileB\", \"lineB1\\nlineB2\\n\"\n    ));\n  }\n\n  @AfterAll\n  static void afterAll() {\n    try {\n      FileUtils.forceDelete(bareRepoPath.toFile());\n      FileUtils.forceDelete(workingRepoPath.toFile());\n    } catch (Exception ignored) {\n      //It throws an exception in windows\n    }\n  }\n\n  private static void setUpBareRepo(Map<String, String> filePathContentMap) throws IOException, GitAPIException {\n    bareRepoPath = Files.createTempDirectory(\"bare-repo\");\n    workingRepoPath = Files.createTempDirectory(\"working-repo\");\n    // Initialize a bare repository\n    try (var ignored = Git.init().setBare(true).setDirectory(bareRepoPath.toFile()).call()) {\n      // Initialize a working directory repository\n      try (var workingGit = Git.init().setDirectory(workingRepoPath.toFile()).call()) {\n        // Create a .gitignore file in the working directory\n        for (var filePath : filePathContentMap.keySet()) {\n          var gitignoreFile = new File(workingRepoPath.toFile(), filePath);\n          Files.writeString(gitignoreFile.toPath(), filePathContentMap.get(filePath));\n\n          // Stage and commit the .gitignore file\n          workingGit.add().addFilepattern(filePath).call();\n          workingGit.commit().setMessage(\"Add \" + filePath).call();\n        }\n\n        // Add the bare repository as a remote and push the commit\n        workingGit.remoteAdd()\n          .setName(\"origin\")\n          .setUri(new URIish(bareRepoPath.toUri().toString()))\n          .call();\n        workingGit.push().setRemote(\"origin\").call();\n      } catch (Exception e) {\n        throw new RuntimeException(e);\n      }\n    }\n  }\n\n  @BeforeEach\n  void prepare() throws Exception {\n    git = createRepository(projectDirPath);\n  }\n\n  @AfterEach\n  void cleanup() throws IOException {\n    org.eclipse.jgit.util.FileUtils.delete(projectDirPath.toFile(), RECURSIVE);\n  }\n\n  @Test\n  void it_should_blame_file() throws IOException, GitAPIException {\n    createFile(projectDirPath, \"fileA\", \"line1\", \"line2\", \"line3\");\n    var c1 = commit(git, \"fileA\");\n\n    var sonarLintBlameResult = blameWithGitFilesBlameLibrary(projectDirPath, Set.of(Path.of(\"fileA\")), null);\n    assertThat(IntStream.of(1, 2, 3)\n      .mapToObj(lineNumber -> sonarLintBlameResult.getLatestChangeDateForLinesInFile(Path.of(\"fileA\"), List.of(lineNumber))))\n      .map(Optional::get)\n      .allMatch(date -> date.equals(c1));\n  }\n\n  @Test\n  void it_should_not_blame_new_file() throws IOException {\n    createFile(projectDirPath, \"fileA\", \"line1\", \"line2\", \"line3\");\n\n    var fileAPath = projectDirPath.resolve(\"fileA\");\n    var filePaths = Set.of(fileAPath);\n    var fileUris = Set.of(fileAPath.toUri());\n    var now = Instant.now();\n    var blameResult = underTest.getBlameResult(projectDirPath, filePaths, fileUris, path -> \"\", now);\n    assertThat(blameResult.getLatestChangeDateForLinesInFile(fileAPath, List.of(1))).isEmpty();\n  }\n\n  @Test\n  void it_should_fallback_to_jgit_blame() throws IOException, GitAPIException {\n    createFile(projectDirPath, \"fileA\", \"line1\", \"line2\", \"line3\");\n    var c1 = commit(git, \"fileA\");\n\n    var locator = mock(NativeGitLocator.class);\n    when(locator.getNativeGitExecutable()).thenReturn(Optional.empty());\n    var service = new GitService(locator);\n    var sonarLintBlameResult = service.getBlameResult(projectDirPath, Set.of(Path.of(\"fileA\")), Set.of(Path.of(\"fileA\").toUri()), null, Instant.now());\n    assertThat(IntStream.of(1, 2, 3)\n      .mapToObj(lineNumber -> sonarLintBlameResult.getLatestChangeDateForLinesInFile(Path.of(\"fileA\"), List.of(lineNumber))))\n      .map(Optional::get)\n      .allMatch(date -> date.equals(c1));\n  }\n\n  @Test\n  void it_should_blame_with_given_contents_within_inner_dir() throws IOException, GitAPIException {\n    var deepFilePath = Path.of(\"innerDir\").resolve(\"fileA\").toString();\n    createFile(projectDirPath, deepFilePath, \"SonarQube\", \"SonarCloud\", \"SonarLint\");\n    var c1 = commit(git, deepFilePath);\n    var content = String.join(System.lineSeparator(), \"SonarQube\", \"Cloud\", \"SonarLint\", \"SonarSolution\") + System.lineSeparator();\n\n    UnaryOperator<String> fileContentProvider = path -> deepFilePath.equals(path) ? content : null;\n    var sonarLintBlameResult = blameWithGitFilesBlameLibrary(projectDirPath, Set.of(Path.of(deepFilePath)), fileContentProvider);\n    assertThat(IntStream.of(1, 2, 3, 4)\n      .mapToObj(lineNumber -> sonarLintBlameResult.getLatestChangeDateForLinesInFile(Path.of(deepFilePath), List.of(lineNumber))))\n      .map(dateOpt -> dateOpt.orElse(null))\n      .containsExactly(c1, null, c1, null);\n  }\n\n  @Test\n  void it_should_blame_file_within_inner_dir() throws IOException, GitAPIException {\n    var deepFilePath = Path.of(\"innerDir\").resolve(\"fileA\").toString();\n\n    createFile(projectDirPath, deepFilePath, \"line1\", \"line2\", \"line3\");\n    var c1 = commit(git, deepFilePath);\n\n    var sonarLintBlameResult = blameWithGitFilesBlameLibrary(projectDirPath, Set.of(Path.of(deepFilePath)), null);\n    var latestChangeDate = sonarLintBlameResult.getLatestChangeDateForLinesInFile(Path.of(deepFilePath), List.of(1, 2, 3));\n    assertThat(latestChangeDate).isPresent().contains(c1);\n  }\n\n  @Test\n  void it_should_blame_project_files_when_project_base_is_sub_folder_of_git_repo() throws IOException, GitAPIException {\n    projectDirPath = projectDirPath.resolve(\"subFolder\");\n\n    createFile(projectDirPath, \"fileA\", \"line1\", \"line2\", \"line3\");\n    var c1 = commit(git, git.getRepository().getWorkTree().toPath().relativize(projectDirPath).resolve(\"fileA\").toString());\n\n    var sonarLintBlameResult = blameWithGitFilesBlameLibrary(projectDirPath, Set.of(Path.of(\"fileA\")), null);\n    assertThat(IntStream.of(1, 2, 3)\n      .mapToObj(lineNumber -> sonarLintBlameResult.getLatestChangeDateForLinesInFile(Path.of(\"fileA\"), List.of(lineNumber))))\n      .map(Optional::get)\n      .allMatch(date -> date.equals(c1));\n  }\n\n  @Test\n  void it_should_get_uncommitted_files_including_untracked_ones() throws GitAPIException, IOException {\n    var committedFile = \"committedFile\";\n    var committedAndModifiedFile = \"committedAndModifiedFile\";\n    var uncommittedTrackedFile = \"uncommittedTrackedFile\";\n    var uncommittedUntrackedFile = \"uncommittedUntrackedFile\";\n    var committedFileUri = projectDirPath.resolve(committedFile).toUri();\n    var committedAndModifiedFileUri = projectDirPath.resolve(committedAndModifiedFile).toUri();\n    var uncommittedTrackedFileUri = projectDirPath.resolve(uncommittedTrackedFile).toUri();\n    var uncommittedUntrackedFileUri = projectDirPath.resolve(uncommittedUntrackedFile).toUri();\n\n    var folderFile = Path.of(\"folder\").resolve(\"folderFile\");\n    var string = FilenameUtils.separatorsToUnix(folderFile.toString());\n    createFile(projectDirPath, string, \"line1\", \"line2\", \"line3\");\n    git.add().setUpdate(true).addFilepattern(string).call();\n\n    createFile(projectDirPath, committedFile, \"line1\", \"line2\", \"line3\");\n    commit(git, committedFile);\n\n    createFile(projectDirPath, committedAndModifiedFile, \"line1\", \"line2\", \"line3\");\n    commit(git, committedAndModifiedFile);\n    modifyFile(projectDirPath.resolve(committedAndModifiedFile), \"line1\", \"line2\", \"line3\", \"line4\");\n\n    createFile(projectDirPath, uncommittedTrackedFile, \"line1\", \"line2\", \"line3\");\n    git.add().addFilepattern(uncommittedTrackedFile).call();\n\n    createFile(projectDirPath, uncommittedUntrackedFile, \"line1\", \"line2\", \"line3\");\n\n    var changedFiles = getVCSChangedFiles(projectDirPath);\n\n    assertThat(changedFiles).hasSize(4)\n      .doesNotContain(committedFileUri)\n      .contains(committedAndModifiedFileUri)\n      .contains(uncommittedTrackedFileUri)\n      .contains(uncommittedUntrackedFileUri)\n      .contains(projectDirPath.resolve(folderFile).toUri());\n  }\n\n  @Test\n  void it_should_get_uncommited_file_in_sub_base_dir() throws GitAPIException, IOException {\n    var folderFile = Path.of(\"folder\").resolve(\"folderFile\");\n    var string = FilenameUtils.separatorsToUnix(folderFile.toString());\n    createFile(projectDirPath, string, \"line1\", \"line2\", \"line3\");\n    git.add().setUpdate(true).addFilepattern(string).call();\n\n    var changedFiles = getVCSChangedFiles(projectDirPath.resolve(\"folder\"));\n\n    assertThat(changedFiles).hasSize(1)\n      .contains(projectDirPath.resolve(folderFile).toUri());\n  }\n\n  @Test\n  void it_should_return_empty_list_if_base_dir_not_resolved() {\n    assertThat(getVCSChangedFiles(null)).isEmpty();\n  }\n\n  @Test\n  void it_should_return_empty_list_on_git_exception(@TempDir Path nonGitDir) {\n    assertThat(getVCSChangedFiles(nonGitDir)).isEmpty();\n  }\n\n  @Test\n  void should_filter_ignored_files() throws IOException, GitAPIException {\n    createFile(projectDirPath, \"fileA\", \"line1\", \"line2\", \"line3\");\n    createFile(projectDirPath, \"fileB\", \"line1\", \"line2\", \"line3\");\n    createFile(projectDirPath, \"fileC\", \"line1\", \"line2\", \"line3\");\n\n    var fileAPath = Path.of(\"fileA\");\n    var fileBPath = Path.of(\"fileB\");\n    var fileCPath = Path.of(\"fileC\");\n\n    var sonarLintGitIgnore = GitService.createSonarLintGitIgnore(projectDirPath);\n    assertThat(Stream.of(fileAPath, fileBPath, fileCPath).filter(not(sonarLintGitIgnore::isFileIgnored)).toList())\n      .hasSize(3)\n      .containsExactly(fileAPath, fileBPath, fileCPath);\n\n    addFileToGitIgnoreAndCommit(git, \"fileB\");\n\n    sonarLintGitIgnore = GitService.createSonarLintGitIgnore(projectDirPath);\n    assertThat(Stream.of(fileAPath, fileBPath, fileCPath).filter(not(sonarLintGitIgnore::isFileIgnored)).toList())\n      .hasSize(2)\n      .containsExactly(fileAPath, fileCPath);\n  }\n\n  @Test\n  void should_filter_ignored_directories() throws IOException, GitAPIException {\n    var fileA = Path.of(\"fileA\");\n    var fileB = Path.of(\"myDir\").resolve(\"fileB\");\n    var fileC = Path.of(\"myDir\").resolve(\"fileC\");\n\n    createFile(projectDirPath, \"fileA\", \"line1\", \"line2\", \"line3\");\n    createFile(projectDirPath, fileB.toString(), \"line1\", \"line2\", \"line3\");\n    createFile(projectDirPath, fileC.toString(), \"line1\", \"line2\", \"line3\");\n\n    var sonarLintGitIgnore = GitService.createSonarLintGitIgnore(projectDirPath);\n    assertThat(Stream.of(fileA, fileB, fileC).filter(not(sonarLintGitIgnore::isFileIgnored)).toList())\n      .hasSize(3)\n      .containsExactly(fileA, fileB, fileC);\n\n    addFileToGitIgnoreAndCommit(git, \"myDir/\");\n\n    sonarLintGitIgnore = GitService.createSonarLintGitIgnore(projectDirPath);\n    assertThat(Stream.of(fileA, fileB, fileC).filter(not(sonarLintGitIgnore::isFileIgnored)).toList())\n      .hasSize(1)\n      .containsExactly(fileA);\n  }\n\n  @Test\n  void should_consider_all_files_not_ignored_on_gitignore() throws IOException {\n    createFile(projectDirPath, \"fileA\", \"line1\", \"line2\", \"line3\");\n    createFile(projectDirPath, \"fileB\", \"line1\", \"line2\", \"line3\");\n    createFile(projectDirPath, \"fileC\", \"line1\", \"line2\", \"line3\");\n\n    var fileAPath = projectDirPath.resolve(\"fileA\");\n    var fileBPath = projectDirPath.resolve(\"fileB\");\n    var fileCPath = projectDirPath.resolve(\"fileC\");\n\n    var gitIgnore = projectDirPath.resolve(GITIGNORE_FILENAME);\n    FileUtils.deleteQuietly(gitIgnore.toFile());\n\n    var sonarLintGitIgnore = GitService.createSonarLintGitIgnore(projectDirPath);\n\n    assertThat(logTester.logs(LogOutput.Level.INFO))\n      .anyMatch(s -> s.contains(\".gitignore file was not found for \"));\n\n    assertThat(Stream.of(fileAPath, fileBPath, fileCPath).filter(not(sonarLintGitIgnore::isFileIgnored)).toList())\n      .hasSize(3)\n      .containsExactly(fileAPath, fileBPath, fileCPath);\n  }\n\n  @Test\n  void should_continue_normally_with_null_basedir() {\n    var sonarLintGitIgnore = GitService.createSonarLintGitIgnore(null);\n\n    assertThat(sonarLintGitIgnore.isIgnored(Path.of(\"file/path\"))).isFalse();\n  }\n\n  @Test\n  void should_consider_files_ignored_when_git_root_above_project_root() throws IOException, GitAPIException {\n    var gitRoot = Files.createTempDirectory(\"test\");\n    var projectRoot = Files.createDirectory(gitRoot.resolve(\"toto\"));\n    try (var ignored = Git.init().setDirectory(gitRoot.toFile()).call()) {\n      var gitignoreFile = new File(gitRoot.toFile(), \".gitignore\");\n      Files.writeString(gitignoreFile.toPath(), \"*.js\");\n    }\n\n    var sonarLintGitIgnore = GitService.createSonarLintGitIgnore(projectRoot);\n\n    assertThat(sonarLintGitIgnore.isIgnored(Path.of(\"frontend/app/should_not_be_ignored.js\"))).isTrue();\n  }\n\n  @Test\n  void should_respect_gitignore_rules() throws IOException {\n    Files.write(projectDirPath.resolve(GITIGNORE_FILENAME), List.of(\"app/\", \"!frontend/app/\"), java.nio.file.StandardOpenOption.CREATE);\n    var sonarLintGitIgnore = GitService.createSonarLintGitIgnore(projectDirPath);\n\n    assertThat(sonarLintGitIgnore.isIgnored(Path.of(\"frontend/app/should_not_be_ignored.js\"))).isFalse();\n    assertThat(sonarLintGitIgnore.isIgnored(Path.of(\"should_be_ignored.js\"))).isFalse();\n    assertThat(sonarLintGitIgnore.isIgnored(Path.of(\"app/should_be_ignored.js\"))).isTrue();\n  }\n\n  @Test\n  void createSonarLintGitIgnore_works_for_bare_repos_too() {\n    var sonarLintGitIgnore = GitService.createSonarLintGitIgnore(bareRepoPath);\n\n    assertThat(sonarLintGitIgnore.isFileIgnored(Path.of(\"file.txt\"))).isFalse();\n    assertThat(sonarLintGitIgnore.isFileIgnored(Path.of(\"file.tmp\"))).isTrue();\n    assertThat(sonarLintGitIgnore.isFileIgnored(Path.of(\"file.log\"))).isTrue();\n  }\n\n  @Test\n  void nonAsciiCharacterFileName() {\n    var sonarLintGitIgnore = GitService.createSonarLintGitIgnore(bareRepoPath);\n\n    assertThat(sonarLintGitIgnore.isIgnored(Path.of(\"Sönar.txt\"))).isFalse();\n    assertThat(sonarLintGitIgnore.isIgnored(Path.of(\"Sönar.log\"))).isTrue();\n  }\n\n  @Test\n  void should_not_read_git_ignore_on_bare_repo_with_no_commit(@TempDir Path bareRepoNoCommitPath) throws GitAPIException {\n    try (var ignored = Git.init().setBare(true).setDirectory(bareRepoNoCommitPath.toFile()).call()) {\n      var sonarLintGitIgnore = GitService.createSonarLintGitIgnore(bareRepoNoCommitPath);\n\n      assertThat(sonarLintGitIgnore.isIgnored(Path.of(\"Sonar.txt\"))).isFalse();\n      assertThat(sonarLintGitIgnore.isIgnored(Path.of(\"Sonar.log\"))).isFalse();\n    }\n  }\n\n  @Test\n  void git_blame_works_for_bare_repos_too() {\n    var sonarLintBlameResult = blameWithGitFilesBlameLibrary(bareRepoPath, Stream.of(\"fileA\", \"fileB\").map(Path::of).collect(Collectors.toSet()), null);\n\n    assertThat(sonarLintBlameResult.getLatestChangeDateForLinesInFile(Path.of(\"fileA\"), List.of(1, 2))).isPresent();\n    assertThat(sonarLintBlameResult.getLatestChangeDateForLinesInFile(Path.of(\"fileA\"), List.of(3))).isEmpty();\n    assertThat(sonarLintBlameResult.getLatestChangeDateForLinesInFile(Path.of(\"fileB\"), List.of(1, 2))).isPresent();\n    assertThat(sonarLintBlameResult.getLatestChangeDateForLinesInFile(Path.of(\"fileB\"), List.of(3))).isEmpty();\n  }\n\n  @Test\n  void should_return_empty_blame_result_if_no_commits_in_repo() throws IOException, GitAPIException {\n    FileUtils.deleteDirectory(projectDirPath.resolve(\".git\").toFile());\n    try (var ignored = Git.init().setDirectory(projectDirPath.toFile()).call()) {\n      createFile(projectDirPath, \"fileA\", \"line1\", \"line2\", \"line3\");\n      var filePath = Path.of(\"fileA\");\n\n      var sonarLintBlameResult = blameWithGitFilesBlameLibrary(projectDirPath, Set.of(filePath), null);\n\n      assertThat(sonarLintBlameResult.getLatestChangeDateForLinesInFile(Path.of(\"fileA\"), List.of(1))).isEmpty();\n    }\n  }\n\n  @Test\n  void it_should_only_return_files_under_baseDir() throws IOException, GitAPIException {\n    // Create files in root and in a subfolder\n    var rootFile = \"rootFile.txt\";\n    var subDir = projectDirPath.resolve(\"subdir\");\n    Files.createDirectories(subDir);\n    var subFile = subDir.resolve(\"subFile.txt\");\n    createFile(projectDirPath, rootFile, \"root\");\n    createFile(subDir, \"subFile.txt\", \"sub\");\n\n    // Add and commit both files\n    git.add().addFilepattern(rootFile).call();\n    git.add().addFilepattern(\"subdir/subFile.txt\").call();\n    commit(git, rootFile);\n    commit(git, \"subdir/subFile.txt\");\n\n    // Modify both files (so they appear as changed)\n    modifyFile(projectDirPath.resolve(rootFile), \"root\", \"changed\");\n    modifyFile(subFile, \"sub\", \"changed\");\n\n    // getVCSChangedFiles for subdir should only return subFile\n    var changedFiles = getVCSChangedFiles(subDir);\n\n    assertThat(changedFiles)\n      .contains(subFile.toUri())\n      .doesNotContain(projectDirPath.resolve(rootFile).toUri());\n  }\n\n  @Test\n  void it_should_get_remote_url() throws GitAPIException, URISyntaxException {\n    // Set up a remote URL for the test repository\n    var remoteUrl = \"https://github.com/org/project.git\";\n    git.remoteAdd()\n      .setName(\"origin\")\n      .setUri(new URIish(remoteUrl))\n      .call();\n\n    var retrievedUrl = GitService.getRemoteUrl(projectDirPath);\n\n    assertThat(retrievedUrl).isEqualTo(remoteUrl);\n  }\n\n  @Test\n  void it_should_return_null_when_no_origin_remote() {\n    var retrievedUrl = GitService.getRemoteUrl(projectDirPath);\n\n    assertThat(retrievedUrl).isNull();\n  }\n\n  @Test\n  void it_should_return_null_for_null_base_dir() {\n    var retrievedUrl = GitService.getRemoteUrl(null);\n\n    assertThat(retrievedUrl).isNull();\n  }\n\n  @Test\n  void it_should_return_null_for_non_git_directory(@TempDir Path nonGitDir) {\n    var retrievedUrl = GitService.getRemoteUrl(nonGitDir);\n\n    assertThat(retrievedUrl).isNull();\n    assertThat(logTester.logs(LogOutput.Level.DEBUG))\n      .anyMatch(s -> s.contains(\"Git repository not found for\"));\n  }\n\n  @Test\n  void it_should_get_remote_url_from_subdirectory() throws GitAPIException, IOException, URISyntaxException {\n    var remoteUrl = \"git@github.com:org/project.git\";\n    git.remoteAdd()\n      .setName(\"origin\")\n      .setUri(new URIish(remoteUrl))\n      .call();\n\n    var subDir = projectDirPath.resolve(\"subdirectory\");\n    Files.createDirectories(subDir);\n\n    var retrievedUrl = GitService.getRemoteUrl(subDir);\n\n    assertThat(retrievedUrl).isEqualTo(remoteUrl);\n  }\n\n  @Test\n  void it_should_return_null_when_config_access_fails() throws GitAPIException, URISyntaxException, IOException {\n    var remoteUrl = \"https://github.com/org/project.git\";\n    git.remoteAdd()\n      .setName(\"origin\")\n      .setUri(new URIish(remoteUrl))\n      .call();\n\n    var gitConfigFile = projectDirPath.resolve(\".git\").resolve(\"config\");\n    Files.write(gitConfigFile, \"invalid config content\".getBytes());\n\n    var retrievedUrl = GitService.getRemoteUrl(projectDirPath);\n\n    assertThat(retrievedUrl).isNull();\n    assertThat(logTester.logs(LogOutput.Level.DEBUG))\n      .anyMatch(s -> s.contains(\"Error retrieving remote URL for\"));\n  }\n\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/util/git/NativeGitLocatorTests.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.util.git;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.Optional;\nimport java.util.stream.Stream;\nimport org.assertj.core.api.AssertionsForClassTypes;\nimport org.eclipse.jgit.util.FileUtils;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledOnOs;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.eclipse.jgit.util.FileUtils.RECURSIVE;\nimport static org.junit.jupiter.api.condition.OS.WINDOWS;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.spy;\n\nclass NativeGitLocatorTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  private static NativeGitLocator underTest;\n  @TempDir\n  private Path projectDirPath;\n\n  @BeforeEach\n  void prepare() {\n    underTest = spy(new NativeGitLocator());\n  }\n\n  @AfterEach\n  void cleanup() throws IOException {\n    FileUtils.delete(projectDirPath.toFile(), RECURSIVE);\n  }\n\n  @Test\n  void shouldConsiderNativeGitNotAvailableOnNull() {\n    doReturn(Optional.empty()).when(underTest).getGitExecutable();\n\n    assertThat(underTest.getNativeGitExecutable()).isEmpty();\n  }\n\n  @EnabledOnOs(WINDOWS)\n  @ParameterizedTest\n  @MethodSource(\"gitLocations\")\n  void should_return_first_git_location(TestData testData, Optional<String> expectedLocation) {\n    var location = NativeGitLocator.locateGitOnWindows(testData.whereToolResult, testData.lines());\n\n    AssertionsForClassTypes.assertThat(location).isEqualTo(expectedLocation);\n  }\n\n  private static Stream<Arguments> gitLocations() {\n    return Stream.of(\n      Arguments.of(result(0, \"\"), Optional.empty()),\n      Arguments.of(result(1, \"invalid location\"), Optional.empty()),\n      Arguments.of(result(0, \"C:\\\\Program Files\\\\Git\\\\bin\\\\git.exe\"), Optional.of(\"C:\\\\Program Files\\\\Git\\\\bin\\\\git.exe\")),\n      Arguments.of(result(0, \"C:\\\\Users\\\\user.name\\\\AppData\\\\Local\\\\Programs\\\\Git\\\\cmd\\\\git.exe\" + System.lineSeparator() +\n                             \"C:\\\\Users\\\\user.name\\\\AppData\\\\Local\\\\Programs\\\\Git\\\\mingw64\\\\bin\\\\git.exe\"), Optional.of(\"C:\\\\Users\\\\user.name\\\\AppData\\\\Local\\\\Programs\\\\Git\\\\cmd\\\\git.exe\")));\n  }\n\n  private static TestData result(int code, String output) {\n    return new TestData(new ProcessWrapperFactory.ProcessExecutionResult(code), output);\n  }\n\n  private record TestData(ProcessWrapperFactory.ProcessExecutionResult whereToolResult, String lines) {\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/util/git/NativeGitTest.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.util.git;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.time.Period;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Calendar;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.TimeZone;\nimport org.eclipse.jgit.api.Git;\nimport org.eclipse.jgit.api.errors.GitAPIException;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\nimport static org.sonarsource.sonarlint.core.commons.testutils.GitUtils.commitAtDate;\nimport static org.sonarsource.sonarlint.core.commons.testutils.GitUtils.createFile;\nimport static org.sonarsource.sonarlint.core.commons.testutils.GitUtils.createRepository;\nimport static org.sonarsource.sonarlint.core.commons.testutils.GitUtils.modifyFile;\n\nclass NativeGitTest {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @TempDir\n  private Path projectDirPath;\n  private Git git;\n\n  @BeforeEach\n  void prepare() throws Exception {\n    git = createRepository(projectDirPath);\n  }\n\n  @Test\n  void it_should_default_to_instant_now_git_blame_history_limit_if_older_than_one_year() throws IOException, GitAPIException {\n    var nativeGitExecutable = new NativeGitLocator().getNativeGitExecutable();\n    assumeTrue(nativeGitExecutable.isPresent());\n    var underTest = nativeGitExecutable.get();\n    var calendar = Calendar.getInstance(TimeZone.getTimeZone(\"UTC\"));\n    calendar.add(Calendar.YEAR, -2);\n    var fileAStr = \"fileA\";\n    createFile(projectDirPath, fileAStr, \"line1\");\n    var yearAgo = calendar.toInstant();\n    // initial commit 2 years ago\n    commitAtDate(git, yearAgo, fileAStr);\n    var lines = new String[3];\n\n    // second commit 4 months after initial commit\n    calendar.add(Calendar.MONTH, 4);\n    lines[0] = \"line1\";\n    lines[1] = \"line2\";\n    var eightMonthsAgo = calendar.toInstant();\n    modifyFile(projectDirPath.resolve(fileAStr), lines);\n    commitAtDate(git, eightMonthsAgo, fileAStr);\n\n    // third commit 4 months after second commit\n    calendar.add(Calendar.MONTH, 4);\n    lines[2] = \"line3\";\n    var oneYearAndFourMonthsAgo = calendar.toInstant();\n    modifyFile(projectDirPath.resolve(fileAStr), lines);\n    commitAtDate(git, oneYearAndFourMonthsAgo, fileAStr);\n    var fileA = Path.of(fileAStr);\n\n    var blameResult = underTest.blame(projectDirPath, Set.of(projectDirPath.resolve(fileA).toUri()), Instant.now());\n\n    var line1Date = blameResult.getLatestChangeDateForLinesInFile(fileA, List.of(1)).get();\n    var line2Date = blameResult.getLatestChangeDateForLinesInFile(fileA, List.of(2)).get();\n    var line3Date = blameResult.getLatestChangeDateForLinesInFile(fileA, List.of(3)).get();\n\n    assertThat(ChronoUnit.MINUTES.between(line1Date, oneYearAndFourMonthsAgo)).isZero();\n    assertThat(ChronoUnit.MINUTES.between(line2Date, oneYearAndFourMonthsAgo)).isZero();\n    assertThat(ChronoUnit.MINUTES.between(line3Date, oneYearAndFourMonthsAgo)).isZero();\n  }\n\n  @Test\n  void it_should_blame_file_since_effective_blame_period() throws IOException, GitAPIException {\n    var nativeGitExecutable = new NativeGitLocator().getNativeGitExecutable();\n    assumeTrue(nativeGitExecutable.isPresent());\n    var underTest = nativeGitExecutable.get();\n    var calendar = Calendar.getInstance(TimeZone.getTimeZone(\"UTC\"));\n    calendar.add(Calendar.MONTH, -18);\n    var fileAStr = \"fileA\";\n    createFile(projectDirPath, fileAStr, \"line1\");\n    var yearAgo = calendar.toInstant();\n    // initial commit 1 year ago\n    commitAtDate(git, yearAgo, fileAStr);\n    var lines = new String[3];\n\n    // second commit 4 months after initial commit\n    calendar.add(Calendar.MONTH, 4);\n    lines[0] = \"line1\";\n    lines[1] = \"line2\";\n    var eightMonthsAgo = calendar.toInstant();\n    modifyFile(projectDirPath.resolve(fileAStr), lines);\n    commitAtDate(git, eightMonthsAgo, fileAStr);\n\n    // third commit 4 months after second commit\n    calendar.add(Calendar.MONTH, 4);\n    lines[2] = \"line3\";\n    var fourMonthsAgo = calendar.toInstant();\n    modifyFile(projectDirPath.resolve(fileAStr), lines);\n    commitAtDate(git, fourMonthsAgo, fileAStr);\n    var fileA = Path.of(fileAStr);\n\n    var blameResult = underTest.blame(projectDirPath, Set.of(projectDirPath.resolve(fileA).toUri()), Instant.now().minus(Period.ofDays(180)));\n\n    var line1Date = blameResult.getLatestChangeDateForLinesInFile(fileA, List.of(1)).get();\n    var line2Date = blameResult.getLatestChangeDateForLinesInFile(fileA, List.of(2)).get();\n    var line3Date = blameResult.getLatestChangeDateForLinesInFile(fileA, List.of(3)).get();\n    // provided blame time limit is 180 days, but effective period will be 1 year\n    // line 1 was committed 1 year ago but should have commit date of the first commit made earlier than blame time window - 8 months ago\n    assertThat(ChronoUnit.MINUTES.between(line2Date, line1Date)).isZero();\n    // line 2 was committed 8 months ago, it's outside the blame time window, but it's a first commit outside the range, so it has real commit date\n    assertThat(ChronoUnit.MINUTES.between(line2Date, eightMonthsAgo)).isZero();\n    // line 3 was committed 4 months ago, it's inside the blame time window, so it has real commit date\n    assertThat(ChronoUnit.MINUTES.between(line3Date, fourMonthsAgo)).isZero();\n  }\n\n  @Test\n  void it_should_not_blame_file_on_git_command_error() {\n    var nativeGitExecutable = new NativeGitLocator().getNativeGitExecutable();\n    assumeTrue(nativeGitExecutable.isPresent());\n    var underTest = nativeGitExecutable.get();\n    var fileAStr = \"fileA\";\n    var fileA = projectDirPath.resolve(fileAStr);\n\n    underTest.blame(projectDirPath, Set.of(fileA.toUri()), Instant.now());\n\n    assertThat(logTester.logs()).contains(\"fatal: no such path 'fileA' in HEAD\", \"Command failed with code: 128\");\n  }\n\n  @Test\n  void it_should_successfully_parse_windows_like_output() {\n    var version = NativeGit.parseGitVersionOutput(List.of(\"git version 2.49.0.windows.1\"));\n\n    assertThat(version).contains(Version.create(\"2.49\"));\n  }\n}"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/util/git/ProcessWrapperFactoryTests.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.util.git;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.spy;\n\nclass ProcessWrapperFactoryTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @Test\n  void it_should_execute_git(@TempDir Path baseDir) {\n    assumeTrue(new NativeGitLocator().getNativeGitExecutable().isPresent());\n    var lines = new StringBuilder();\n    var result = new ProcessWrapperFactory().create(baseDir, lines::append, \"git\", \"--version\").execute();\n\n    assertThat(result.exitCode()).isZero();\n    assertThat(lines).contains(\"git version \");\n  }\n\n  @Test\n  void it_should_return_output_for_invalid_command(@TempDir Path baseDir) {\n    assumeTrue(new NativeGitLocator().getNativeGitExecutable().isPresent());\n    var processWrapper = new ProcessWrapperFactory().create(baseDir, l -> {\n    }, \"git\", \"-version\");\n    var result = processWrapper.execute();\n    assertThat(result.exitCode()).isEqualTo(129);\n    assertThat(logTester.logs()).contains(\"unknown option: -version\");\n  }\n\n  @Test\n  void it_should_gracefully_return_output_for_interrupted_exception(@TempDir Path baseDir) throws InterruptedException, IOException {\n    assumeTrue(new NativeGitLocator().getNativeGitExecutable().isPresent());\n    var lines = new StringBuilder();\n    var processWrapper = new ProcessWrapperFactory().create(baseDir, lines::append, \"git\", \"--version\");\n    var spy = spy(processWrapper);\n    doThrow(InterruptedException.class).when(spy).runProcessAndGetOutput(any());\n    var result = spy.execute();\n\n    assertThat(result.exitCode()).isEqualTo(-1);\n    assertThat(lines).contains(\"\");\n  }\n\n  @Test\n  void it_should_gracefully_return_output_for_exception(@TempDir Path baseDir) throws InterruptedException, IOException {\n    assumeTrue(new NativeGitLocator().getNativeGitExecutable().isPresent());\n    var lines = new StringBuilder();\n    var processWrapper = new ProcessWrapperFactory().create(baseDir, lines::append, \"git\", \"--version\");\n    var spy = spy(processWrapper);\n    doThrow(RuntimeException.class).when(spy).runProcessAndGetOutput(any());\n    var result = spy.execute();\n\n    assertThat(result.exitCode()).isEqualTo(-1);\n    assertThat(lines).contains(\"\");\n  }\n\n  @Test\n  void it_should_gracefully_return_output_when_not_able_to_create_process(@TempDir Path baseDir) throws IOException {\n    var lines = new StringBuilder();\n    var processWrapper = new ProcessWrapperFactory().create(baseDir, lines::append, \"git\", \"--version\");\n    var spy = spy(processWrapper);\n    doThrow(IOException.class).when(spy).createProcess();\n    var result = spy.execute();\n\n    assertThat(result.exitCode()).isEqualTo(-2);\n    assertThat(lines).contains(\"\");\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/validation/InvalidFieldsTest.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.validation;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass InvalidFieldsTest {\n\n  public static final String[] EXPECTED = {\"name1\", \"name2\", \"name3\"};\n\n  @Test\n  void should_have_no_invalid_fields_initially() {\n    InvalidFields tested = new InvalidFields();\n\n    assertThat(tested.hasInvalidFields()).isFalse();\n  }\n\n  @Test\n  void should_have_invalid_fields_after_adding_one() {\n    InvalidFields tested = new InvalidFields();\n\n    tested.add(\"name1\");\n\n    assertThat(tested.hasInvalidFields()).isTrue();\n  }\n\n  @Test\n  void should_include_all_added_fields() {\n    InvalidFields tested = new InvalidFields();\n\n    tested.add(\"name1\");\n    tested.add(\"name2\");\n    tested.add(\"name3\");\n    String[] names = tested.getNames();\n\n    assertThat(names).containsExactly(EXPECTED);\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/validation/RegexpValidatorTest.java",
    "content": "/*\n * SonarLint Core - Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons.validation;\n\nimport java.util.Map;\nimport java.util.regex.PatternSyntaxException;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass RegexpValidatorTest {\n\n  public static final String TEST_REGEXP = \"[0-9]+\";\n\n  @Test\n  void should_throw_exception_on_invalid_regexp() {\n    assertThatThrownBy(() -> new RegexpValidator(\"[4-[8)\"))\n      .isInstanceOf(PatternSyntaxException.class);\n  }\n\n  @Test\n  void should_return_empty_invalid_fields() {\n    RegexpValidator validator = new RegexpValidator(TEST_REGEXP);\n\n    InvalidFields invalidFields = validator.validateAll(Map.of(\n      \"field1\", \"12345\",\n      \"field2\", \"455668\",\n      \"field3\", \"0\"\n    ));\n\n    assertThat(invalidFields.hasInvalidFields()).isFalse();\n    assertThat(invalidFields.getNames()).isEmpty();\n  }\n\n  @Test\n  void should_return_one_invalid_field() {\n    RegexpValidator validator = new RegexpValidator(TEST_REGEXP);\n\n    InvalidFields invalidFields = validator.validateAll(Map.of(\n      \"field1\", \"12345\",\n      \"field2\", \"-455668\",\n      \"field3\", \"0\"\n    ));\n\n    assertThat(invalidFields.hasInvalidFields()).isTrue();\n    assertThat(invalidFields.getNames())\n      .containsExactlyInAnyOrder(\"field2\");\n  }\n\n  @Test\n  void should_return_all_invalid_fields() {\n    RegexpValidator validator = new RegexpValidator(TEST_REGEXP);\n\n    InvalidFields invalidFields = validator.validateAll(Map.of(\n      \"field1\", \"sqrt(12345)\",\n      \"field2\", \"-455668\",\n      \"field3\", \"^0^\"\n    ));\n\n    assertThat(invalidFields.hasInvalidFields()).isTrue();\n    assertThat(invalidFields.getNames())\n      .containsExactlyInAnyOrder(\"field1\", \"field2\", \"field3\");\n  }\n}\n"
  },
  {
    "path": "backend/commons/src/test/resources/logback.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE configuration>\n\n<configuration>\n  <logger name=\"ROOT\" level=\"OFF\" />\n</configuration>\n"
  },
  {
    "path": "backend/core/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-backend-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../pom.xml</relativePath>\n  </parent>\n  <artifactId>sonarlint-core</artifactId>\n  <name>SonarLint Core - Implementation</name>\n  <description>Common library used by some SonarLint flavors</description>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.code.findbugs</groupId>\n      <artifactId>jsr305</artifactId>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.slf4j</groupId>\n      <artifactId>slf4j-api</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>jakarta.inject</groupId>\n      <artifactId>jakarta.inject-api</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>javax.annotation</groupId>\n      <artifactId>javax.annotation-api</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>jakarta.annotation</groupId>\n      <artifactId>jakarta.annotation-api</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-commons</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-telemetry</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-plugin-commons</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-rule-extractor</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-analysis-engine</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-server-connection</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-rpc-protocol</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-http</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n\n    <dependency>\n      <groupId>commons-io</groupId>\n      <artifactId>commons-io</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-lang3</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-compress</artifactId>\n    </dependency>\n    <dependency>\n        <groupId>org.apache.commons</groupId>\n        <artifactId>commons-text</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>commons-codec</groupId>\n      <artifactId>commons-codec</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.google.guava</groupId>\n      <artifactId>guava</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.bouncycastle</groupId>\n      <artifactId>bcpg-jdk18on</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.bouncycastle</groupId>\n      <artifactId>bcprov-jdk18on</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.httpcomponents.client5</groupId>\n      <artifactId>httpclient5</artifactId>\n      <exclusions>\n        <exclusion>\n          <groupId>org.slf4j</groupId>\n          <artifactId>slf4j-api</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.httpcomponents.core5</groupId>\n      <artifactId>httpcore5</artifactId>\n      <version>5.3.2</version>\n    </dependency>\n    <dependency>\n      <groupId>com.squareup.okhttp3</groupId>\n      <artifactId>okhttp</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>com.squareup.okhttp3</groupId>\n      <artifactId>mockwebserver3</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.wiremock</groupId>\n      <artifactId>wiremock-jetty12</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-params</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-engine</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.assertj</groupId>\n      <artifactId>assertj-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.awaitility</groupId>\n      <artifactId>awaitility</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>uk.org.webcompere</groupId>\n      <artifactId>system-stubs-jupiter</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <!-- For Xodus -->\n    <dependency>\n      <groupId>ch.qos.logback</groupId>\n      <artifactId>logback-classic</artifactId>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <resources>\n      <resource>\n        <directory>src/main/resources</directory>\n        <filtering>true</filtering>\n        <includes>\n          <include>ondemand/plugins.properties</include>\n        </includes>\n      </resource>\n      <resource>\n        <directory>src/main/resources</directory>\n        <filtering>false</filtering>\n        <excludes>\n          <exclude>ondemand/plugins.properties</exclude>\n        </excludes>\n      </resource>\n    </resources>\n\n    <plugins>\n      <plugin>\n        <groupId>com.googlecode.maven-download-plugin</groupId>\n        <artifactId>download-maven-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>download-cfamily-signature</id>\n            <phase>generate-resources</phase>\n            <goals>\n              <goal>wget</goal>\n            </goals>\n            <configuration>\n              <url>https://binaries.sonarsource.com/CommercialDistribution/sonar-cfamily-plugin/sonar-cfamily-plugin-${cfamily.version}.jar.asc</url>\n              <outputDirectory>${project.build.outputDirectory}/ondemand</outputDirectory>\n              <outputFileName>sonar-cpp-plugin.jar.asc</outputFileName>\n              <skipCache>false</skipCache>\n            </configuration>\n          </execution>\n          <execution>\n            <id>download-csharp-signature</id>\n            <phase>generate-resources</phase>\n            <goals>\n              <goal>wget</goal>\n            </goals>\n            <configuration>\n              <url>https://binaries.sonarsource.com/Distribution/sonar-csharp-plugin/sonar-csharp-plugin-${csharp.version}.jar.asc</url>\n              <outputDirectory>${project.build.outputDirectory}/ondemand</outputDirectory>\n              <outputFileName>sonar-cs-plugin.jar.asc</outputFileName>\n              <skipCache>false</skipCache>\n            </configuration>\n          </execution>\n          <execution>\n            <id>download-omnisharp-mono-signature</id>\n            <phase>generate-resources</phase>\n            <goals>\n              <goal>wget</goal>\n            </goals>\n            <configuration>\n              <url>https://binaries.sonarsource.com/OmniSharp-Roslyn/${omnisharp.version}/omnisharp-mono.tar.gz.asc</url>\n              <outputDirectory>${project.build.outputDirectory}/ondemand</outputDirectory>\n              <outputFileName>omnisharp-mono.tar.gz.asc</outputFileName>\n              <skipCache>false</skipCache>\n            </configuration>\n          </execution>\n          <execution>\n            <id>download-omnisharp-net472-signature</id>\n            <phase>generate-resources</phase>\n            <goals>\n              <goal>wget</goal>\n            </goals>\n            <configuration>\n              <url>https://binaries.sonarsource.com/OmniSharp-Roslyn/${omnisharp.version}/omnisharp-net472.tar.gz.asc</url>\n              <outputDirectory>${project.build.outputDirectory}/ondemand</outputDirectory>\n              <outputFileName>omnisharp-net472.tar.gz.asc</outputFileName>\n              <skipCache>false</skipCache>\n            </configuration>\n          </execution>\n          <execution>\n            <id>download-omnisharp-net6-signature</id>\n            <phase>generate-resources</phase>\n            <goals>\n              <goal>wget</goal>\n            </goals>\n            <configuration>\n              <url>https://binaries.sonarsource.com/OmniSharp-Roslyn/${omnisharp.version}/omnisharp-net6.0.tar.gz.asc</url>\n              <outputDirectory>${project.build.outputDirectory}/ondemand</outputDirectory>\n              <outputFileName>omnisharp-net6.0.tar.gz.asc</outputFileName>\n              <skipCache>false</skipCache>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n    </plugins>\n  </build>\n\n  <profiles>\n    <!-- Workaround for https://issues.apache.org/jira/projects/MJAR/issues/MJAR-138 -->\n    <profile>\n      <id>conditionally-add-commons-tests-if-tests-not-skipped</id>\n      <activation>\n        <property>\n          <name>maven.test.skip</name>\n          <value>!true</value>\n        </property>\n      </activation>\n      <dependencies>\n        <dependency>\n          <groupId>${project.groupId}</groupId>\n          <artifactId>sonarlint-commons</artifactId>\n          <version>${project.version}</version>\n          <classifier>tests</classifier>\n          <type>test-jar</type>\n          <scope>test</scope>\n        </dependency>\n      </dependencies>\n    </profile>\n  </profiles>\n</project>\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/BindingCandidatesFinder.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport jakarta.inject.Inject;\nimport java.util.HashSet;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationScope;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin;\nimport org.sonarsource.sonarlint.core.serverapi.component.ServerProject;\n\nimport static org.apache.commons.lang3.StringUtils.isNotBlank;\n\npublic class BindingCandidatesFinder {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final ConfigurationRepository configRepository;\n  private final BindingClueProvider bindingClueProvider;\n  private final SonarProjectsCache sonarProjectsCache;\n\n  @Inject\n  public BindingCandidatesFinder(ConfigurationRepository configRepository, BindingClueProvider bindingClueProvider, SonarProjectsCache sonarProjectsCache) {\n    this.configRepository = configRepository;\n    this.bindingClueProvider = bindingClueProvider;\n    this.sonarProjectsCache = sonarProjectsCache;\n  }\n\n  public Set<ConfigurationScopeSharedContext> findConfigScopesToBind(String connectionId, String projectKey, SonarLintCancelMonitor cancelMonitor) {\n    var configScopeCandidates = configRepository.getAllBindableUnboundScopes();\n    if (configScopeCandidates.isEmpty()) {\n      return Set.of();\n    }\n\n    var goodConfigScopeCandidates = new HashSet<ConfigurationScopeSharedContext>();\n\n    for (var scope : configScopeCandidates) {\n      checkIfScopeIsGoodCandidateForBinding(scope, connectionId, projectKey, cancelMonitor)\n        .ifPresent(goodConfigScopeCandidates::add);\n    }\n\n    // if both a parent and a child configuration scope are candidates, preference should be given to the higher scope in the hierarchy\n    // we prefer to bind at the broadest possible scope\n    return filterOutLeafCandidates(goodConfigScopeCandidates);\n  }\n\n  private Optional<ConfigurationScopeSharedContext> checkIfScopeIsGoodCandidateForBinding(\n    ConfigurationScope scope, String connectionId, String projectKey, SonarLintCancelMonitor cancelMonitor) {\n    cancelMonitor.checkCanceled();\n\n    var cluesAndConnections = bindingClueProvider.collectBindingCluesWithConnections(scope.id(), Set.of(connectionId), cancelMonitor);\n\n    var cluesWithMatchingProjectKey = cluesAndConnections.stream()\n      .filter(c -> projectKey.equals(c.getBindingClue().getSonarProjectKey()))\n      .toList();\n\n\n    if (!cluesWithMatchingProjectKey.isEmpty()) {\n      var isFromSharedConfiguration = cluesWithMatchingProjectKey.stream().anyMatch(\n        c -> c.getBindingClue().getOrigin() == BindingSuggestionOrigin.SHARED_CONFIGURATION);\n      if (isFromSharedConfiguration) {\n        return Optional.of(new ConfigurationScopeSharedContext(scope, BindingSuggestionOrigin.SHARED_CONFIGURATION));\n      }\n      var isFromPropertiesFile = cluesWithMatchingProjectKey.stream().anyMatch(\n        c -> c.getBindingClue().getOrigin() == BindingSuggestionOrigin.PROPERTIES_FILE);\n      if (isFromPropertiesFile) {\n        return Optional.of(new ConfigurationScopeSharedContext(scope, BindingSuggestionOrigin.PROPERTIES_FILE));\n      }\n\n      var firstOrigin = cluesWithMatchingProjectKey.get(0).getBindingClue().getOrigin();\n      return Optional.of(new ConfigurationScopeSharedContext(scope, firstOrigin));\n    }\n    var configScopeName = scope.name();\n    if (isNotBlank(configScopeName) && isConfigScopeNameCloseEnoughToSonarProject(configScopeName, connectionId, projectKey, cancelMonitor)) {\n      return Optional.of(new ConfigurationScopeSharedContext(scope, BindingSuggestionOrigin.PROJECT_NAME));\n    }\n    return Optional.empty();\n  }\n\n  private static Set<ConfigurationScopeSharedContext> filterOutLeafCandidates(Set<ConfigurationScopeSharedContext> candidates) {\n    var candidateIds = candidates.stream().map(ConfigurationScopeSharedContext::getConfigurationScope).map(ConfigurationScope::id).collect(Collectors.toSet());\n    return candidates.stream().filter(bindableConfig -> {\n      var scope = bindableConfig.getConfigurationScope();\n      var parentId = scope.parentId();\n      return parentId == null || !candidateIds.contains(parentId);\n    }).collect(Collectors.toSet());\n  }\n\n  private boolean isConfigScopeNameCloseEnoughToSonarProject(String configScopeName, String connectionId, String projectKey, SonarLintCancelMonitor cancelMonitor) {\n    // FIXME: it looks a bit overkill to create a TextSearchIndex with just one element, apparently just to verify that the configScopeName is a good enough match for the SonarProject\n    var sonarProjectOpt = sonarProjectsCache.getSonarProject(connectionId, projectKey, cancelMonitor);\n    if (sonarProjectOpt.isEmpty()) {\n      LOG.debug(\"Unable to find SonarProject with key '{}' on connection '{}' in the cache\", projectKey, connectionId);\n      return false;\n    }\n    TextSearchIndex<ServerProject> index = new TextSearchIndex<>();\n    var p = sonarProjectOpt.get();\n    index.index(p, p.key() + \" \" + p.name());\n    var searchResult = index.search(configScopeName);\n    return !searchResult.isEmpty();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/BindingClueProvider.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport com.google.gson.Gson;\nimport com.google.gson.JsonObject;\nimport java.io.StringReader;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Objects;\nimport java.util.Properties;\nimport java.util.Set;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.fs.ClientFile;\nimport org.sonarsource.sonarlint.core.fs.ClientFileSystemService;\nimport org.sonarsource.sonarlint.core.repository.connection.AbstractConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarCloudConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarQubeConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion;\n\nimport static java.util.stream.Collectors.toSet;\nimport static org.apache.commons.lang3.StringUtils.isBlank;\nimport static org.apache.commons.lang3.StringUtils.trimToNull;\nimport static org.sonarsource.sonarlint.core.commons.log.SonarLintLogger.singlePlural;\n\npublic class BindingClueProvider {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final String SONAR_SCANNER_CONFIG_FILENAME = \"sonar-project.properties\";\n  private static final String AUTOSCAN_CONFIG_FILENAME = \".sonarcloud.properties\";\n\n  static final Set<String> ALL_BINDING_CLUE_FILENAMES = Set.of(SONAR_SCANNER_CONFIG_FILENAME, AUTOSCAN_CONFIG_FILENAME);\n\n  private final ConnectionConfigurationRepository connectionRepository;\n  private final ClientFileSystemService clientFs;\n  private final SonarCloudActiveEnvironment sonarCloudActiveEnvironment;\n\n  public BindingClueProvider(ConnectionConfigurationRepository connectionRepository, ClientFileSystemService clientFs, SonarCloudActiveEnvironment sonarCloudActiveEnvironment) {\n    this.connectionRepository = connectionRepository;\n    this.clientFs = clientFs;\n    this.sonarCloudActiveEnvironment = sonarCloudActiveEnvironment;\n  }\n\n  public List<BindingClueWithConnections> collectBindingCluesWithConnections(String configScopeId, Set<String> connectionIds, SonarLintCancelMonitor cancelMonitor) {\n    var bindingClues = collectBindingClues(configScopeId, cancelMonitor);\n    return matchConnections(bindingClues, connectionIds);\n  }\n\n  private List<BindingClueWithConnections> matchConnections(List<BindingClue> bindingClues, Set<String> eligibleConnectionIds) {\n    LOG.debug(\"Match connections...\");\n    List<BindingClueWithConnections> cluesAndConnections = new ArrayList<>();\n    for (var bindingClue : bindingClues) {\n      var connectionsIds = matchConnections(bindingClue, eligibleConnectionIds);\n      if (!connectionsIds.isEmpty()) {\n        cluesAndConnections.add(new BindingClueWithConnections(bindingClue, connectionsIds));\n      }\n    }\n    LOG.debug(\"{} {} having at least one matching connection\", cluesAndConnections.size(), singlePlural(cluesAndConnections.size(), \"clue\"));\n    return cluesAndConnections;\n  }\n\n  public static class BindingClueWithConnections {\n    private final BindingClue bindingClue;\n    private final Set<String> connectionIds;\n\n    BindingClueWithConnections(BindingClue bindingClue, Set<String> connectionIds) {\n      this.bindingClue = bindingClue;\n      this.connectionIds = connectionIds;\n    }\n\n    public BindingClue getBindingClue() {\n      return bindingClue;\n    }\n\n    public Set<String> getConnectionIds() {\n      return connectionIds;\n    }\n  }\n\n  public List<BindingClue> collectBindingClues(String checkedConfigScopeId, SonarLintCancelMonitor cancelMonitor) {\n    var sonarlintConfigurationFiles = clientFs.findSonarlintConfigurationFilesByScope(checkedConfigScopeId);\n    if (!sonarlintConfigurationFiles.isEmpty()) {\n      var collectedClues = collectFromFiles(sonarlintConfigurationFiles, cancelMonitor);\n      if (!collectedClues.isEmpty()) {\n        LOG.debug(\"Found {} binding {} from SonarLint configuration files\", collectedClues.size(), singlePlural(collectedClues.size(), \"clue\"));\n        return collectedClues;\n      }\n    }\n\n    var bindingCluesFiles = clientFs.findFilesByNamesInScope(checkedConfigScopeId, List.copyOf(ALL_BINDING_CLUE_FILENAMES));\n    if (!bindingCluesFiles.isEmpty()) {\n      var collectedClues = collectFromFiles(bindingCluesFiles, cancelMonitor);\n      if (!collectedClues.isEmpty()) {\n        LOG.debug(\"Found {} binding {}\", collectedClues.size(), singlePlural(collectedClues.size(), \"clue\"));\n        return collectedClues;\n      }\n    }\n\n    LOG.debug(\"No binding clues were found\");\n    return Collections.emptyList();\n  }\n\n  private List<BindingClue> collectFromFiles(List<ClientFile> files, SonarLintCancelMonitor cancelMonitor) {\n    var bindingClues = new ArrayList<BindingClue>();\n    for (var foundFile : files) {\n      cancelMonitor.checkCanceled();\n      var scannerProps = extractConnectionProperties(foundFile);\n      if (scannerProps == null || hasBlankValues(scannerProps)) {\n        continue;\n      }\n      var bindingClue = computeBindingClue(foundFile.getFileName(), scannerProps);\n      if (bindingClue != null) {\n        if (foundFile.isSonarlintConfigurationFile() && !(bindingClue instanceof UnknownBindingClue)) {\n          LOG.debug(\"Found a SonarLint configuration file with a clue\");\n        }\n        bindingClues.add(bindingClue);\n      }\n    }\n    return bindingClues;\n  }\n\n  private static boolean hasBlankValues(BindingProperties scannerProps) {\n    var serverUrl = scannerProps.serverUrl;\n    var projectKey = scannerProps.projectKey;\n    var organization = scannerProps.organization;\n    if (serverUrl == null) {\n      return isEmptyScConfig(projectKey, organization);\n    }\n    return isEmptySqConfig(projectKey, serverUrl);\n  }\n\n  private static boolean isEmptySqConfig(@Nullable String projectKey, @Nullable String serverUrl) {\n    return isBlank(projectKey) && isBlank(serverUrl);\n  }\n\n  private static boolean isEmptyScConfig(@Nullable String projectKey, @Nullable String organization) {\n    return isBlank(projectKey) && isBlank(organization);\n  }\n\n  private Set<String> matchConnections(BindingClue bindingClue, Set<String> eligibleConnectionIds) {\n    if (bindingClue instanceof SonarQubeBindingClue sonarQubeBindingClue) {\n      var serverUrl = sonarQubeBindingClue.serverUrl;\n      return eligibleConnectionIds.stream().map(connectionRepository::getConnectionById)\n        .filter(SonarQubeConnectionConfiguration.class::isInstance)\n        .map(SonarQubeConnectionConfiguration.class::cast)\n        .filter(c -> c.isSameServerUrl(serverUrl))\n        .map(AbstractConnectionConfiguration::getConnectionId)\n        .collect(toSet());\n    }\n    if (bindingClue instanceof SonarCloudBindingClue sonarCloudBindingClue) {\n      var organization = sonarCloudBindingClue.organization;\n      return eligibleConnectionIds.stream().map(connectionRepository::getConnectionById)\n        .filter(SonarCloudConnectionConfiguration.class::isInstance)\n        .map(SonarCloudConnectionConfiguration.class::cast)\n        .filter(c -> organization == null || Objects.equals(organization, c.getOrganization()))\n        .map(AbstractConnectionConfiguration::getConnectionId)\n        .collect(toSet());\n    }\n    return eligibleConnectionIds;\n  }\n\n  @CheckForNull\n  private static BindingProperties extractSonarLintConfiguration(ClientFile sonarLintConfigurationFile) {\n    try {\n      var configuration = new Gson().fromJson(sonarLintConfigurationFile.getContent(), JsonObject.class);\n      var projectKey = configuration.get(\"projectKey\");\n      var organization = configuration.get(\"sonarCloudOrganization\");\n      var serverUrl = configuration.get(\"sonarQubeUri\");\n      var region = configuration.get(\"region\");\n      // Checking for PascalCase due to VS backward compatibility\n      if (projectKey == null || ((organization == null) == (serverUrl == null))) {\n        projectKey = configuration.get(\"ProjectKey\");\n        organization = configuration.get(\"SonarCloudOrganization\");\n        serverUrl = configuration.get(\"SonarQubeUri\");\n      }\n      return new BindingProperties(projectKey != null ? projectKey.getAsString() : null,\n        organization != null ? organization.getAsString() : null,\n        serverUrl != null ? serverUrl.getAsString() : null,\n        region != null ? region.getAsString() : null,\n        BindingSuggestionOrigin.SHARED_CONFIGURATION);\n    } catch (Exception e) {\n      LOG.warn(\"Unable to parse candidate connected mode configuration file\", e);\n      return null;\n    }\n  }\n\n  @CheckForNull\n  private static BindingProperties extractConnectionProperties(ClientFile matchedFile) {\n    LOG.debug(\"Extracting scanner properties from {}\", matchedFile);\n    if (matchedFile.isSonarlintConfigurationFile()) {\n      return extractSonarLintConfiguration(matchedFile);\n    } else {\n      var properties = new Properties();\n      try {\n        properties.load(new StringReader(matchedFile.getContent()));\n      } catch (Exception e) {\n        LOG.error(\"Unable to parse content of file '{}'\", matchedFile, e);\n        return null;\n      }\n      return new BindingProperties(getAndTrim(properties, \"sonar.projectKey\"), getAndTrim(properties, \"sonar.organization\"),\n        getAndTrim(properties, \"sonar.host.url\"), getAndTrim(properties, \"sonar.region\"), BindingSuggestionOrigin.PROPERTIES_FILE);\n    }\n  }\n\n  @CheckForNull\n  private static String getAndTrim(Properties properties, String key) {\n    return trimToNull(properties.getProperty(key));\n  }\n\n  private static class BindingProperties {\n    private final String projectKey;\n    private final String organization;\n    private final String serverUrl;\n    private final BindingSuggestionOrigin origin;\n    private final SonarCloudRegion region;\n\n    private BindingProperties(@Nullable String projectKey, @Nullable String organization, @Nullable String serverUrl,\n      @Nullable String region, BindingSuggestionOrigin origin) {\n      this.projectKey = projectKey;\n      this.organization = organization;\n      this.serverUrl = serverUrl;\n      this.origin = origin;\n      SonarCloudRegion configuredRegion;\n      try {\n        configuredRegion = region != null ? SonarCloudRegion.valueOf(region.toUpperCase(Locale.ENGLISH)) : SonarCloudRegion.EU;\n      } catch (IllegalArgumentException e) {\n        LOG.warn(\"Cannot accept '{}' as a valid SonarQube Cloud region while reading shared Connected Mode settings. Falling back to EU region\", region);\n        configuredRegion = SonarCloudRegion.EU;\n      }\n      this.region = configuredRegion;\n    }\n  }\n\n  @CheckForNull\n  private BindingClue computeBindingClue(String filename, BindingProperties scannerProps) {\n    if (AUTOSCAN_CONFIG_FILENAME.equals(filename)) {\n      return new SonarCloudBindingClue(scannerProps.projectKey, scannerProps.organization, scannerProps.region, scannerProps.origin);\n    }\n    if (scannerProps.organization != null) {\n      return new SonarCloudBindingClue(scannerProps.projectKey, scannerProps.organization, scannerProps.region, scannerProps.origin);\n    }\n    if (scannerProps.serverUrl != null) {\n      if (sonarCloudActiveEnvironment.isSonarQubeCloud(scannerProps.serverUrl)) {\n        return new SonarCloudBindingClue(scannerProps.projectKey, null, scannerProps.region, scannerProps.origin);\n      } else {\n        return new SonarQubeBindingClue(scannerProps.projectKey, scannerProps.serverUrl, scannerProps.origin);\n      }\n    }\n    if (scannerProps.projectKey != null) {\n      return new UnknownBindingClue(scannerProps.projectKey, scannerProps.origin);\n    }\n    return null;\n  }\n\n  public interface BindingClue {\n\n    @CheckForNull\n    String getSonarProjectKey();\n\n    BindingSuggestionOrigin getOrigin();\n\n  }\n\n  public static class UnknownBindingClue implements BindingClue {\n    private final String sonarProjectKey;\n    BindingSuggestionOrigin origin;\n\n    UnknownBindingClue(String sonarProjectKey, BindingSuggestionOrigin origin) {\n      this.sonarProjectKey = sonarProjectKey;\n      this.origin = origin;\n    }\n\n    @Override\n    public String getSonarProjectKey() {\n      return sonarProjectKey;\n    }\n\n    @Override\n    public BindingSuggestionOrigin getOrigin() {\n      return origin;\n    }\n  }\n\n  public static class SonarQubeBindingClue implements BindingClue {\n    private final String sonarProjectKey;\n    private final String serverUrl;\n    BindingSuggestionOrigin origin;\n\n    SonarQubeBindingClue(@Nullable String sonarProjectKey, String serverUrl, BindingSuggestionOrigin origin) {\n      this.sonarProjectKey = sonarProjectKey;\n      this.serverUrl = serverUrl;\n      this.origin = origin;\n    }\n\n    @Override\n    public String getSonarProjectKey() {\n      return sonarProjectKey;\n    }\n\n    @Override\n    public BindingSuggestionOrigin getOrigin() {\n      return origin;\n    }\n\n    public String getServerUrl() {\n      return serverUrl;\n    }\n\n  }\n\n  public static class SonarCloudBindingClue implements BindingClue {\n\n    private final String sonarProjectKey;\n    private final String organization;\n    private final SonarCloudRegion region;\n    BindingSuggestionOrigin origin;\n\n    SonarCloudBindingClue(@Nullable String sonarProjectKey, @Nullable String organization,\n      @Nullable SonarCloudRegion region, BindingSuggestionOrigin origin) {\n      this.sonarProjectKey = sonarProjectKey;\n      this.organization = organization;\n      this.region = region != null ? region : SonarCloudRegion.EU;\n      this.origin = origin;\n    }\n\n    @Override\n    public String getSonarProjectKey() {\n      return sonarProjectKey;\n    }\n\n    @Override\n    public BindingSuggestionOrigin getOrigin() {\n      return origin;\n    }\n\n    public String getOrganization() {\n      return organization;\n    }\n\n    public SonarCloudRegion getRegion() {\n      return region;\n    }\n\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/BindingSuggestionProvider.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport com.google.common.util.concurrent.MoreExecutors;\nimport jakarta.annotation.PreDestroy;\nimport jakarta.inject.Inject;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport org.jetbrains.annotations.NotNull;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.ExecutorServiceShutdownWatchable;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.commons.util.FailSafeExecutors;\nimport org.sonarsource.sonarlint.core.commons.util.git.GitService;\nimport org.sonarsource.sonarlint.core.event.BindingConfigChangedEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationAddedEvent;\nimport org.sonarsource.sonarlint.core.fs.ClientFileSystemService;\nimport org.sonarsource.sonarlint.core.repository.config.BindingConfiguration;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationScope;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.SuggestBindingParams;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryService;\nimport org.springframework.context.event.EventListener;\n\nimport static java.lang.String.join;\nimport static java.util.Collections.emptyMap;\nimport static java.util.Objects.requireNonNull;\nimport static org.apache.commons.lang3.StringUtils.isNotBlank;\nimport static org.sonarsource.sonarlint.core.commons.log.SonarLintLogger.singlePlural;\n\npublic class BindingSuggestionProvider {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final ConfigurationRepository configRepository;\n  private final ConnectionConfigurationRepository connectionRepository;\n  private final SonarLintRpcClient client;\n  private final BindingClueProvider bindingClueProvider;\n  private final SonarProjectsCache sonarProjectsCache;\n  private final ExecutorServiceShutdownWatchable<?> executorService;\n  private final AtomicBoolean enabled = new AtomicBoolean(true);\n  private final SonarQubeClientManager sonarQubeClientManager;\n  private final ClientFileSystemService clientFs;\n  private final TelemetryService telemetryService;\n\n  @Inject\n  public BindingSuggestionProvider(ConfigurationRepository configRepository, ConnectionConfigurationRepository connectionRepository, SonarLintRpcClient client,\n    BindingClueProvider bindingClueProvider, SonarProjectsCache sonarProjectsCache, SonarQubeClientManager sonarQubeClientManager, ClientFileSystemService clientFs, TelemetryService telemetryService) {\n    this.configRepository = configRepository;\n    this.connectionRepository = connectionRepository;\n    this.client = client;\n    this.bindingClueProvider = bindingClueProvider;\n    this.sonarProjectsCache = sonarProjectsCache;\n    this.sonarQubeClientManager = sonarQubeClientManager;\n    this.clientFs = clientFs;\n    this.telemetryService = telemetryService;\n    this.executorService = new ExecutorServiceShutdownWatchable<>(FailSafeExecutors.newSingleThreadExecutor(\"Binding Suggestion Provider\"));\n  }\n\n  @EventListener\n  public void bindingConfigChanged(BindingConfigChangedEvent event) {\n    // Check if binding suggestion was switched on\n    if (!event.newConfig().bindingSuggestionDisabled() && event.previousConfig().bindingSuggestionDisabled()) {\n      suggestBindingForGivenScopesAndAllConnections(Set.of(event.configScopeId()));\n    }\n  }\n\n  public void suggestBindingForGivenScopesAndAllConnections(Set<String> configScopeIdsToSuggest) {\n    if (!configScopeIdsToSuggest.isEmpty()) {\n      var allConnectionIds = connectionRepository.getConnectionsById().keySet();\n      if (allConnectionIds.isEmpty()) {\n        LOG.debug(\"No connections configured, skipping binding suggestions.\");\n        return;\n      }\n      LOG.debug(\"Binding suggestion computation queued for config scopes '{}'...\", join(\",\", configScopeIdsToSuggest));\n      queueBindingSuggestionComputation(configScopeIdsToSuggest, allConnectionIds);\n    }\n  }\n\n  @EventListener\n  public void connectionAdded(ConnectionConfigurationAddedEvent event) {\n    // Double check if added connection has not been removed in the meantime\n    var addedConnectionId = event.addedConnectionId();\n    var allConfigScopeIds = configRepository.getConfigScopeIds();\n    if (connectionRepository.getConnectionById(addedConnectionId) != null && !allConfigScopeIds.isEmpty()) {\n      LOG.debug(\"Binding suggestions computation queued for connection '{}'...\", addedConnectionId);\n      var candidateConnectionIds = Set.of(addedConnectionId);\n      queueBindingSuggestionComputation(allConfigScopeIds, candidateConnectionIds);\n    }\n  }\n\n  public Map<String, List<BindingSuggestionDto>> getBindingSuggestions(String configScopeId, String connectionId, SonarLintCancelMonitor cancelMonitor) {\n    return computeBindingSuggestions(Set.of(configScopeId), Set.of(connectionId), cancelMonitor);\n  }\n\n  private void queueBindingSuggestionComputation(Set<String> configScopeIds, Set<String> candidateConnectionIds) {\n    var cancelMonitor = new SonarLintCancelMonitor();\n    cancelMonitor.watchForShutdown(executorService);\n    executorService.execute(() -> {\n      if (enabled.get()) {\n        computeAndNotifyBindingSuggestions(configScopeIds, candidateConnectionIds, cancelMonitor);\n      } else {\n        LOG.debug(\"Skipping binding suggestion computation as it is disabled\");\n      }\n    });\n  }\n\n  private void computeAndNotifyBindingSuggestions(Set<String> configScopeIds, Set<String> candidateConnectionIds, SonarLintCancelMonitor cancelMonitor) {\n    Map<String, List<BindingSuggestionDto>> suggestionsByConfigScope = computeBindingSuggestions(configScopeIds, candidateConnectionIds, cancelMonitor);\n    if (!suggestionsByConfigScope.isEmpty()) {\n      client.suggestBinding(new SuggestBindingParams(suggestionsByConfigScope));\n    }\n  }\n\n  private Map<String, List<BindingSuggestionDto>> computeBindingSuggestions(Set<String> configScopeIds, Set<String> candidateConnectionIds, SonarLintCancelMonitor cancelMonitor) {\n    var eligibleConfigScopesForBindingSuggestion = new HashSet<String>();\n    for (var configScopeId : configScopeIds) {\n      cancelMonitor.checkCanceled();\n      if (isScopeEligibleForBindingSuggestion(configScopeId)) {\n        eligibleConfigScopesForBindingSuggestion.add(configScopeId);\n      }\n    }\n\n    if (eligibleConfigScopesForBindingSuggestion.isEmpty()) {\n      return emptyMap();\n    }\n\n    var suggestionsByConfigScope = new HashMap<String, List<BindingSuggestionDto>>();\n\n    for (var configScopeId : eligibleConfigScopesForBindingSuggestion) {\n      cancelMonitor.checkCanceled();\n      var scopeSuggestions = suggestBindingForEligibleScope(configScopeId, candidateConnectionIds, cancelMonitor);\n      LOG.debug(\"Found {} {} for configuration scope '{}'\", scopeSuggestions.size(), singlePlural(scopeSuggestions.size(), \"suggestion\"), configScopeId);\n      if (!scopeSuggestions.isEmpty()) {\n        suggestionsByConfigScope.put(configScopeId, scopeSuggestions);\n      }\n    }\n\n    return suggestionsByConfigScope;\n  }\n\n  private List<BindingSuggestionDto> suggestBindingForEligibleScope(String checkedConfigScopeId, Set<String> candidateConnectionIds, SonarLintCancelMonitor cancelMonitor) {\n    var cluesAndConnections = bindingClueProvider.collectBindingCluesWithConnections(checkedConfigScopeId, candidateConnectionIds, cancelMonitor);\n\n    List<BindingSuggestionDto> suggestions = new ArrayList<>();\n    var cluesWithProjectKey = cluesAndConnections.stream().filter(c -> c.getBindingClue().getSonarProjectKey() != null).toList();\n    for (var bindingClueWithConnections : cluesWithProjectKey) {\n      var sonarProjectKey = requireNonNull(bindingClueWithConnections.getBindingClue().getSonarProjectKey());\n      for (var connectionId : bindingClueWithConnections.getConnectionIds()) {\n        sonarProjectsCache\n          .getSonarProject(connectionId, sonarProjectKey, cancelMonitor)\n          .ifPresent(serverProject -> suggestions.add(new BindingSuggestionDto(connectionId, sonarProjectKey, serverProject.name(),\n            bindingClueWithConnections.getBindingClue().getOrigin())));\n      }\n    }\n    if (suggestions.isEmpty()) {\n      var configScopeName = Optional.ofNullable(configRepository.getConfigurationScope(checkedConfigScopeId)).map(ConfigurationScope::name).orElse(null);\n      if (isNotBlank(configScopeName)) {\n        var cluesWithoutProjectKey = cluesAndConnections.stream().filter(c -> c.getBindingClue().getSonarProjectKey() == null).toList();\n        for (var bindingClueWithConnections : cluesWithoutProjectKey) {\n          searchGoodMatchInConnections(suggestions, configScopeName, bindingClueWithConnections.getConnectionIds(), cancelMonitor);\n        }\n        if (cluesWithoutProjectKey.isEmpty()) {\n          searchGoodMatchInConnections(suggestions, configScopeName, candidateConnectionIds, cancelMonitor);\n        }\n      }\n    }\n\n    if (suggestions.isEmpty()) {\n      searchByRemoteUrlInConnections(suggestions, checkedConfigScopeId, candidateConnectionIds, cancelMonitor);\n      if (!suggestions.isEmpty()) {\n        telemetryService.suggestedRemoteBinding();\n      }\n    }\n\n    return suggestions;\n  }\n\n  private void searchGoodMatchInConnections(List<BindingSuggestionDto> suggestions, String configScopeName, Set<String> connectionIdsToSearch,\n    SonarLintCancelMonitor cancelMonitor) {\n    for (var connectionId : connectionIdsToSearch) {\n      searchGoodMatchInConnection(suggestions, configScopeName, connectionId, cancelMonitor);\n    }\n  }\n\n  private void searchByRemoteUrlInConnections(List<BindingSuggestionDto> suggestions, String configScopeId, Set<String> connectionIds, SonarLintCancelMonitor cancelMonitor) {\n    var remoteUrl = GitService.getRemoteUrl(clientFs.getBaseDir(configScopeId));\n\n    if (remoteUrl == null) {\n      LOG.debug(\"No remote URL found for configuration scope '{}\", configScopeId);\n      return;\n    }\n\n    for (var connectionId : connectionIds) {\n      try {\n        var suggestion = sonarQubeClientManager.withActiveClientFlatMapOptionalAndReturn(connectionId, api ->\n          getBindingSuggestionByRemoteUrl(cancelMonitor, connectionId, api, remoteUrl));\n\n        suggestion.ifPresent(suggestions::add);\n      } catch (Exception e) {\n        LOG.debug(\"Failed to get binding suggestion by remote URL for connection '{}': {}\", connectionId, e.getMessage());\n      }\n    }\n  }\n\n  @NotNull\n  private static Optional<BindingSuggestionDto> getBindingSuggestionByRemoteUrl(SonarLintCancelMonitor cancelMonitor, String connectionId, ServerApi api, String remoteUrl) {\n    if (api.isSonarCloud()) {\n      var sqcResponse = api.projectBindings().getSQCProjectBindings(remoteUrl, cancelMonitor);\n      if (sqcResponse != null) {\n        var searchResponse = api.component().searchProjects(sqcResponse.projectId(), cancelMonitor);\n        if (searchResponse != null) {\n          return Optional.of(new BindingSuggestionDto(connectionId, searchResponse.projectKey(), searchResponse.projectName(), BindingSuggestionOrigin.REMOTE_URL));\n        }\n      }\n    } else {\n      var sqsResponse = api.projectBindings().getSQSProjectBindings(remoteUrl, cancelMonitor);\n      if (sqsResponse != null) {\n        var serverProject = api.component().getProject(sqsResponse.projectKey(), cancelMonitor);\n        if (serverProject.isPresent()) {\n          return Optional.of(new BindingSuggestionDto(connectionId, sqsResponse.projectKey(), serverProject.get().name(), BindingSuggestionOrigin.REMOTE_URL));\n        }\n      }\n    }\n    return Optional.empty();\n  }\n\n  private void searchGoodMatchInConnection(List<BindingSuggestionDto> suggestions, String configScopeName, String connectionId, SonarLintCancelMonitor cancelMonitor) {\n    LOG.debug(\"Attempt to find a good match for '{}' on connection '{}'...\", configScopeName, connectionId);\n    var index = sonarProjectsCache.getTextSearchIndex(connectionId, cancelMonitor);\n    var searchResult = index.search(configScopeName);\n    if (!searchResult.isEmpty()) {\n      Double bestScore = Double.MIN_VALUE;\n      for (var serverProjectScoreEntry : searchResult.entrySet()) {\n        if (serverProjectScoreEntry.getValue() < bestScore) {\n          break;\n        }\n        bestScore = serverProjectScoreEntry.getValue();\n        suggestions.add(new BindingSuggestionDto(connectionId, serverProjectScoreEntry.getKey().key(),\n          serverProjectScoreEntry.getKey().name(), BindingSuggestionOrigin.PROJECT_NAME));\n      }\n      LOG.debug(\"Best score = {}\", String.format(Locale.ENGLISH, \"%,.2f\", bestScore));\n    }\n  }\n\n  private boolean isScopeEligibleForBindingSuggestion(String configScopeId) {\n    var configScope = configRepository.getConfigurationScope(configScopeId);\n    var bindingConfiguration = configRepository.getBindingConfiguration(configScopeId);\n    if (configScope == null || bindingConfiguration == null) {\n      // Race condition\n      LOG.debug(\"Configuration scope '{}' is gone.\", configScopeId);\n      return false;\n    }\n    if (!configScope.bindable()) {\n      LOG.debug(\"Configuration scope '{}' is not bindable.\", configScopeId);\n      return false;\n    }\n    if (isValidBinding(bindingConfiguration)) {\n      LOG.debug(\"Configuration scope '{}' is already bound.\", configScopeId);\n      return false;\n    }\n    if (bindingConfiguration.bindingSuggestionDisabled()) {\n      LOG.debug(\"Configuration scope '{}' has binding suggestions disabled.\", configScopeId);\n      return false;\n    }\n    return true;\n  }\n\n  private boolean isValidBinding(BindingConfiguration bindingConfiguration) {\n    return bindingConfiguration.ifBound((connectionId, projectKey) -> connectionRepository.getConnectionById(connectionId) != null)\n      .orElse(false);\n  }\n\n  @PreDestroy\n  public void shutdown() {\n    if (!MoreExecutors.shutdownAndAwaitTermination(executorService, 1, TimeUnit.SECONDS)) {\n      LOG.warn(\"Unable to stop binding suggestions executor service in a timely manner\");\n    }\n  }\n\n  public void disable() {\n    this.enabled.set(false);\n  }\n\n  public void enable() {\n    this.enabled.set(true);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/ConfigurationScopeSharedContext.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationScope;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin;\n\npublic class ConfigurationScopeSharedContext {\n\n  private final ConfigurationScope configurationScope;\n  private final BindingSuggestionOrigin origin;\n\n  ConfigurationScopeSharedContext(ConfigurationScope configurationScope, BindingSuggestionOrigin origin) {\n    this.configurationScope = configurationScope;\n    this.origin = origin;\n  }\n\n  public ConfigurationScope getConfigurationScope() {\n    return configurationScope;\n  }\n\n  public BindingSuggestionOrigin getOrigin() {\n    return origin;\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/ConfigurationService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport java.util.HashSet;\nimport java.util.List;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.event.BindingConfigChangedEvent;\nimport org.sonarsource.sonarlint.core.event.ConfigurationScopeRemovedEvent;\nimport org.sonarsource.sonarlint.core.event.ConfigurationScopesAddedWithBindingEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationRemovedEvent;\nimport org.sonarsource.sonarlint.core.repository.config.BindingConfiguration;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationScope;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationScopeWithBinding;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.springframework.context.ApplicationEventPublisher;\nimport org.springframework.context.event.EventListener;\n\npublic class ConfigurationService {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final ApplicationEventPublisher applicationEventPublisher;\n  private final ConfigurationRepository repository;\n\n  public ConfigurationService(ApplicationEventPublisher applicationEventPublisher, ConfigurationRepository repository) {\n    this.applicationEventPublisher = applicationEventPublisher;\n    this.repository = repository;\n  }\n\n  public void didAddConfigurationScopes(List<ConfigurationScopeDto> addedScopes) {\n    var addedIds = new HashSet<ConfigurationScopeWithBinding>();\n    for (var addedDto : addedScopes) {\n      var configScopeInReferential = adapt(addedDto);\n      var bindingDto = addedDto.getBinding();\n      var bindingConfigInReferential = adapt(bindingDto);\n      var previous = repository.addOrReplace(configScopeInReferential, bindingConfigInReferential);\n      if (previous != null) {\n        LOG.error(\"Duplicate configuration scope registered: {}\", addedDto.getId());\n      } else {\n        LOG.debug(\"Added configuration scope '{}'\", configScopeInReferential.id());\n        addedIds.add(new ConfigurationScopeWithBinding(configScopeInReferential, bindingConfigInReferential));\n      }\n    }\n    if (!addedIds.isEmpty()) {\n      applicationEventPublisher.publishEvent(new ConfigurationScopesAddedWithBindingEvent(addedIds));\n    }\n  }\n\n  private static BindingConfiguration adapt(@Nullable BindingConfigurationDto dto) {\n    if (dto == null) {\n      return BindingConfiguration.noBinding();\n    }\n    return new BindingConfiguration(dto.getConnectionId(), dto.getSonarProjectKey(), dto.isBindingSuggestionDisabled());\n  }\n\n  private static ConfigurationScope adapt(ConfigurationScopeDto dto) {\n    return new ConfigurationScope(dto.getId(), dto.getParentId(), dto.isBindable(), dto.getName());\n  }\n\n  public void didRemoveConfigurationScope(String removedId) {\n    var removed = repository.remove(removedId);\n    if (removed == null) {\n      LOG.debug(\"Attempt to remove configuration scope '{}' that was not registered\", removedId);\n    } else {\n      LOG.debug(\"Removed configuration scope '{}'\", removedId);\n      applicationEventPublisher.publishEvent(new ConfigurationScopeRemovedEvent(removed.scope(), removed.bindingConfiguration()));\n    }\n  }\n\n  public void didUpdateBinding(String configScopeId, BindingConfigurationDto updatedBinding) {\n    LOG.debug(\"Did update binding for configuration scope '{}', new binding: '{}'\", configScopeId, updatedBinding);\n    var boundEvent = bind(configScopeId, updatedBinding);\n    if (boundEvent != null) {\n      applicationEventPublisher.publishEvent(boundEvent);\n    }\n  }\n\n  @EventListener\n  public void connectionRemoved(ConnectionConfigurationRemovedEvent event) {\n    var bindingConfigurationByConfigScope = repository.removeBindingForConnection(event.removedConnectionId());\n    bindingConfigurationByConfigScope.forEach((configScope, bindingConfiguration) ->\n      applicationEventPublisher.publishEvent(new BindingConfigChangedEvent(configScope, bindingConfiguration,\n        BindingConfiguration.noBinding(bindingConfiguration.bindingSuggestionDisabled()))));\n  }\n\n  @CheckForNull\n  private BindingConfigChangedEvent bind(String configurationScopeId, BindingConfigurationDto bindingConfiguration) {\n    var previousBindingConfig = repository.getBindingConfiguration(configurationScopeId);\n    if (previousBindingConfig == null) {\n      LOG.error(\"Attempt to update binding in configuration scope '{}' that was not registered\", configurationScopeId);\n      return null;\n    }\n    var newBindingConfig = adapt(bindingConfiguration);\n    repository.updateBinding(configurationScopeId, newBindingConfig);\n\n    return createChangedEventIfNeeded(configurationScopeId, previousBindingConfig, newBindingConfig);\n  }\n\n  @CheckForNull\n  private static BindingConfigChangedEvent createChangedEventIfNeeded(String configScopeId, BindingConfiguration previousBindingConfig,\n    BindingConfiguration newBindingConfig) {\n    if (!previousBindingConfig.equals(newBindingConfig)) {\n      return new BindingConfigChangedEvent(configScopeId, previousBindingConfig, newBindingConfig);\n    }\n    return null;\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/ConnectionService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport jakarta.inject.Inject;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport javax.annotation.Nullable;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseError;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.commons.validation.InvalidFields;\nimport org.sonarsource.sonarlint.core.commons.validation.RegexpValidator;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationAddedEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationRemovedEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationUpdatedEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionCredentialsChangedEvent;\nimport org.sonarsource.sonarlint.core.repository.connection.AbstractConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarCloudConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarQubeConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth.HelpGenerateUserTokenParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth.HelpGenerateUserTokenResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarCloudConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarQubeConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarCloudConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarQubeConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.SonarProjectDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.validate.ValidateConnectionResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.serverapi.component.ServerProject;\nimport org.sonarsource.sonarlint.core.serverapi.exception.UnauthorizedException;\nimport org.sonarsource.sonarlint.core.serverconnection.ServerVersionAndStatusChecker;\nimport org.springframework.context.ApplicationEventPublisher;\n\nimport static java.util.stream.Collectors.toMap;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode.UNAUTHORIZED;\n\npublic class ConnectionService {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  /**\n   * Validator for strings containing small letters, numbers and \"-\" symbols.\n   */\n  private static final RegexpValidator REGEXP_VALIDATOR = new RegexpValidator(\"[a-z0-9\\\\-]+\");\n\n  private final ApplicationEventPublisher applicationEventPublisher;\n  private final ConnectionConfigurationRepository repository;\n  private final SonarCloudActiveEnvironment sonarCloudActiveEnvironment;\n  private final SonarQubeClientManager sonarQubeClientManager;\n  private final TokenGeneratorHelper tokenGeneratorHelper;\n\n  @Inject\n  public ConnectionService(ApplicationEventPublisher applicationEventPublisher, ConnectionConfigurationRepository repository, InitializeParams params,\n    SonarCloudActiveEnvironment sonarCloudActiveEnvironment, TokenGeneratorHelper tokenGeneratorHelper, SonarQubeClientManager sonarQubeClientManager) {\n    this(applicationEventPublisher, repository, params.getSonarQubeConnections(), params.getSonarCloudConnections(), sonarCloudActiveEnvironment, sonarQubeClientManager,\n      tokenGeneratorHelper);\n  }\n\n  ConnectionService(ApplicationEventPublisher applicationEventPublisher, ConnectionConfigurationRepository repository,\n    @Nullable List<SonarQubeConnectionConfigurationDto> initSonarQubeConnections, @Nullable List<SonarCloudConnectionConfigurationDto> initSonarCloudConnections,\n    SonarCloudActiveEnvironment sonarCloudActiveEnvironment, SonarQubeClientManager sonarQubeClientManager, TokenGeneratorHelper tokenGeneratorHelper) {\n    this.applicationEventPublisher = applicationEventPublisher;\n    this.repository = repository;\n    this.sonarCloudActiveEnvironment = sonarCloudActiveEnvironment;\n    this.sonarQubeClientManager = sonarQubeClientManager;\n    this.tokenGeneratorHelper = tokenGeneratorHelper;\n    if (initSonarQubeConnections != null) {\n      initSonarQubeConnections.forEach(c -> repository.addOrReplace(adapt(c)));\n    }\n    if (initSonarCloudConnections != null) {\n      initSonarCloudConnections.forEach(c -> repository.addOrReplace(adapt(c)));\n    }\n  }\n\n  private static SonarQubeConnectionConfiguration adapt(SonarQubeConnectionConfigurationDto sqDto) {\n    return new SonarQubeConnectionConfiguration(sqDto.getConnectionId(), sqDto.getServerUrl(), sqDto.getDisableNotifications());\n  }\n\n  private SonarCloudConnectionConfiguration adapt(SonarCloudConnectionConfigurationDto scDto) {\n    var region = SonarCloudRegion.valueOf(scDto.getRegion().toString());\n    return new SonarCloudConnectionConfiguration(sonarCloudActiveEnvironment.getUri(region), sonarCloudActiveEnvironment.getApiUri(region), scDto.getConnectionId(),\n      scDto.getOrganization(), region, scDto.isDisableNotifications());\n  }\n\n  private static void putAndLogIfDuplicateId(Map<String, AbstractConnectionConfiguration> map, AbstractConnectionConfiguration config) {\n    if (map.put(config.getConnectionId(), config) != null) {\n      LOG.error(\"Duplicate connection registered: {}\", config.getConnectionId());\n    }\n  }\n\n  public void didUpdateConnections(List<SonarQubeConnectionConfigurationDto> sonarQubeConnections,\n    List<SonarCloudConnectionConfigurationDto> sonarCloudConnections) {\n    var newConnectionsById = new HashMap<String, AbstractConnectionConfiguration>();\n    sonarQubeConnections.forEach(config -> putAndLogIfDuplicateId(newConnectionsById, adapt(config)));\n    sonarCloudConnections.forEach(config -> putAndLogIfDuplicateId(newConnectionsById, adapt(config)));\n\n    var previousConnectionsById = repository.getConnectionsById();\n\n    var updatedConnections = newConnectionsById.entrySet().stream()\n      .filter(e -> previousConnectionsById.containsKey(e.getKey()))\n      .filter(e -> !previousConnectionsById.get(e.getKey()).equals(e.getValue()))\n      .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));\n    var addedConnections = newConnectionsById.entrySet().stream()\n      .filter(e -> !previousConnectionsById.containsKey(e.getKey()))\n      .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));\n    var removedConnectionIds = new HashSet<>(previousConnectionsById.keySet());\n    removedConnectionIds.removeAll(newConnectionsById.keySet());\n\n    updatedConnections.values().forEach(this::updateConnection);\n    addedConnections.values().forEach(this::addConnection);\n    removedConnectionIds.forEach(this::removeConnection);\n  }\n\n  public void didChangeCredentials(String connectionId) {\n    applicationEventPublisher.publishEvent(new ConnectionCredentialsChangedEvent(connectionId));\n  }\n\n  private void addConnection(AbstractConnectionConfiguration connectionConfiguration) {\n    repository.addOrReplace(connectionConfiguration);\n    LOG.debug(\"Connection '{}' added\", connectionConfiguration.getConnectionId());\n    applicationEventPublisher.publishEvent(new ConnectionConfigurationAddedEvent(connectionConfiguration.getConnectionId(), connectionConfiguration.getKind()));\n  }\n\n  private void removeConnection(String removedConnectionId) {\n    var removed = repository.remove(removedConnectionId);\n    if (removed == null) {\n      LOG.debug(\"Attempt to remove connection '{}' that was not registered. Possibly a race condition?\", removedConnectionId);\n    } else {\n      LOG.debug(\"Connection '{}' removed\", removedConnectionId);\n      applicationEventPublisher.publishEvent(new ConnectionConfigurationRemovedEvent(removedConnectionId));\n    }\n  }\n\n  private void updateConnection(AbstractConnectionConfiguration connectionConfiguration) {\n    var connectionId = connectionConfiguration.getConnectionId();\n    var previous = repository.addOrReplace(connectionConfiguration);\n    if (previous == null) {\n      LOG.debug(\"Attempt to update connection '{}' that was not registered. Possibly a race condition?\", connectionId);\n      applicationEventPublisher.publishEvent(new ConnectionConfigurationAddedEvent(connectionConfiguration.getConnectionId(), connectionConfiguration.getKind()));\n    } else {\n      LOG.debug(\"Connection '{}' updated\", previous.getConnectionId());\n      applicationEventPublisher.publishEvent(new ConnectionConfigurationUpdatedEvent(connectionConfiguration.getConnectionId()));\n    }\n  }\n\n  public ValidateConnectionResponse validateConnection(Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> transientConnection,\n    SonarLintCancelMonitor cancelMonitor) {\n    try {\n      var serverApi = sonarQubeClientManager.getForTransientConnection(transientConnection);\n      var serverChecker = new ServerVersionAndStatusChecker(serverApi);\n      serverChecker.checkVersionAndStatus(cancelMonitor);\n      var validateCredentials = serverApi.authentication().validate(cancelMonitor);\n      if (validateCredentials.success() && transientConnection.isRight()) {\n        var organizationKey = transientConnection.getRight().getOrganization();\n        if (organizationKey != null) {\n          var organization = serverApi.organization().searchOrganization(organizationKey, cancelMonitor);\n          if (organization.isEmpty()) {\n            return new ValidateConnectionResponse(false, \"No organizations found for key: \" + organizationKey);\n          }\n        }\n      }\n      return new ValidateConnectionResponse(validateCredentials.success(), validateCredentials.message());\n    } catch (Exception e) {\n      LOG.error(\"Error validating connection\", e);\n      return new ValidateConnectionResponse(false, e.getMessage());\n    }\n  }\n\n  public HelpGenerateUserTokenResponse helpGenerateUserToken(String serverUrl, @Nullable HelpGenerateUserTokenParams.Utm utm, SonarLintCancelMonitor cancelMonitor) {\n    if (utm != null) {\n      var invalidFields = validateUtm(utm);\n      if (invalidFields.hasInvalidFields()) {\n        throw new ResponseErrorException(new ResponseError(ResponseErrorCode.InvalidParams,\n          \"UTM parameters should match regular expression: [a-z0-9\\\\-]+\",\n          invalidFields.getNames()));\n      }\n    }\n\n    return tokenGeneratorHelper.helpGenerateUserToken(serverUrl, utm, cancelMonitor);\n  }\n\n  private static InvalidFields validateUtm(HelpGenerateUserTokenParams.Utm utm) {\n    return REGEXP_VALIDATOR.validateAll(Map.of(\n      \"utm_medium\", utm.getMedium(),\n      \"utm_source\", utm.getSource(),\n      \"utm_content\", utm.getContent(),\n      \"utm_term\", utm.getTerm()));\n  }\n\n  public List<SonarProjectDto> getAllProjects(Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> transientConnection, SonarLintCancelMonitor cancelMonitor) {\n    var serverApi = sonarQubeClientManager.getForTransientConnection(transientConnection);\n\n    try {\n      return serverApi.component().getAllProjects(cancelMonitor)\n        .stream().map(serverProject -> new SonarProjectDto(serverProject.key(), serverProject.name()))\n        .toList();\n    } catch (UnauthorizedException e) {\n      throw new ResponseErrorException(new ResponseError(UNAUTHORIZED, \"The authorization has failed. Please check your credentials.\", null));\n    }\n  }\n\n  public Map<String, String> getProjectNamesByKey(Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> transientConnection,\n    List<String> projectKeys, SonarLintCancelMonitor cancelMonitor) {\n    var serverApi = sonarQubeClientManager.getForTransientConnection(transientConnection);\n    var projectNamesByKey = new HashMap<String, String>();\n    projectKeys.forEach(key -> {\n      var projectName = serverApi.component().getProject(key, cancelMonitor).map(ServerProject::name).orElse(null);\n      projectNamesByKey.put(key, projectName);\n    });\n    return projectNamesByKey;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/ConnectionSuggestionProvider.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport jakarta.inject.Inject;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport org.apache.commons.lang3.Strings;\nimport org.jetbrains.annotations.NotNull;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.ExecutorServiceShutdownWatchable;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.commons.util.FailSafeExecutors;\nimport org.sonarsource.sonarlint.core.event.ConfigurationScopesAddedWithBindingEvent;\nimport org.sonarsource.sonarlint.core.fs.ClientFile;\nimport org.sonarsource.sonarlint.core.fs.ClientFileSystemService;\nimport org.sonarsource.sonarlint.core.fs.FileSystemUpdatedEvent;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.ConnectionSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.SonarCloudConnectionSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.SonarQubeConnectionSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.SuggestConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.springframework.context.event.EventListener;\n\nimport static org.sonarsource.sonarlint.core.BindingClueProvider.ALL_BINDING_CLUE_FILENAMES;\n\npublic class ConnectionSuggestionProvider {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final ConfigurationRepository configRepository;\n  private final ConnectionConfigurationRepository connectionRepository;\n  private final SonarLintRpcClient client;\n  private final BindingClueProvider bindingClueProvider;\n  private final ExecutorServiceShutdownWatchable<?> executorService;\n  private final BindingSuggestionProvider bindingSuggestionProvider;\n  private final ClientFileSystemService clientFs;\n\n  @Inject\n  public ConnectionSuggestionProvider(ConfigurationRepository configRepository, ConnectionConfigurationRepository connectionRepository, SonarLintRpcClient client,\n    BindingClueProvider bindingClueProvider, BindingSuggestionProvider bindingSuggestionProvider, ClientFileSystemService clientFs) {\n    this.configRepository = configRepository;\n    this.connectionRepository = connectionRepository;\n    this.client = client;\n    this.bindingClueProvider = bindingClueProvider;\n    this.executorService = new ExecutorServiceShutdownWatchable<>(FailSafeExecutors.newSingleThreadExecutor(\"Connection Suggestion Provider\"));\n    this.bindingSuggestionProvider = bindingSuggestionProvider;\n    this.clientFs = clientFs;\n  }\n\n  @EventListener\n  public void filesystemUpdated(FileSystemUpdatedEvent event) {\n    var listConfigScopeIds = event.getAddedOrUpdated().stream()\n      .filter(f -> ALL_BINDING_CLUE_FILENAMES.contains(f.getFileName()) || f.isSonarlintConfigurationFile())\n      .map(ClientFile::getConfigScopeId)\n      .collect(Collectors.toSet());\n\n    queueConnectionSuggestion(listConfigScopeIds);\n  }\n\n  @EventListener\n  public void configurationScopesAdded(ConfigurationScopesAddedWithBindingEvent event) {\n    var listConfigScopeIds = event.getConfigScopeIds().stream()\n      .map(clientFs::getFiles)\n      .flatMap(List::stream)\n      .filter(f -> ALL_BINDING_CLUE_FILENAMES.contains(f.getFileName()) || f.isSonarlintConfigurationFile())\n      .map(ClientFile::getConfigScopeId)\n      .collect(Collectors.toSet());\n\n    if (!listConfigScopeIds.isEmpty()) {\n      queueConnectionSuggestion(listConfigScopeIds);\n    } else {\n      bindingSuggestionProvider.suggestBindingForGivenScopesAndAllConnections(event.getConfigScopeIds());\n    }\n  }\n\n  private void queueConnectionSuggestion(Set<String> listConfigScopeIds) {\n    if (!listConfigScopeIds.isEmpty()) {\n      var cancelMonitor = new SonarLintCancelMonitor();\n      cancelMonitor.watchForShutdown(executorService);\n      executorService.execute(() -> suggestConnectionAndBindingForGivenScopes(listConfigScopeIds, cancelMonitor));\n    }\n  }\n\n  private void suggestConnectionAndBindingForGivenScopes(Set<String> configScopeIds, SonarLintCancelMonitor cancelMonitor) {\n    var connectionAndBindingSuggestions = computeConnectionAndBindingSuggestions(configScopeIds, cancelMonitor);\n\n    suggestConnectionToClientIfAny(connectionAndBindingSuggestions.connectionSuggestionsByConfigScopeIds());\n    computeBindingSuggestionIfAny(connectionAndBindingSuggestions.bindingSuggestionsForConfigScopeIds());\n  }\n\n  private @NotNull ConnectionAndBindingSuggestions computeConnectionAndBindingSuggestions(Set<String> configScopeIds, SonarLintCancelMonitor cancelMonitor) {\n    LOG.debug(\"Computing connection suggestions\");\n    var connectionSuggestionsByConfigScopeIds = new HashMap<String, List<ConnectionSuggestionDto>>();\n    var bindingSuggestionsForConfigScopeIds = new HashSet<String>();\n\n    for (var configScopeId : configScopeIds) {\n      var effectiveBinding = configRepository.getEffectiveBinding(configScopeId);\n      if (effectiveBinding.isPresent()) {\n        LOG.debug(\"A binding already exists, skipping the connection suggestion\");\n        continue;\n      }\n\n      var bindingClues = bindingClueProvider.collectBindingClues(configScopeId, cancelMonitor);\n      for (var bindingClue : bindingClues) {\n        var projectKey = bindingClue.getSonarProjectKey();\n        if (projectKey != null) {\n          handleBindingClue(bindingClue).ifPresentOrElse(clue -> clue.map(\n            serverUrl -> connectionSuggestionsByConfigScopeIds.computeIfAbsent(configScopeId, s -> new ArrayList<>())\n              .add(new ConnectionSuggestionDto(new SonarQubeConnectionSuggestionDto(serverUrl, projectKey), bindingClue.getOrigin())),\n            organization -> connectionSuggestionsByConfigScopeIds.computeIfAbsent(configScopeId, s -> new ArrayList<>())\n              .add(new ConnectionSuggestionDto(new SonarCloudConnectionSuggestionDto(organization, projectKey,\n                ((BindingClueProvider.SonarCloudBindingClue) bindingClue).getRegion()), bindingClue.getOrigin()))),\n            () -> bindingSuggestionsForConfigScopeIds.add(configScopeId));\n        }\n      }\n    }\n    return new ConnectionAndBindingSuggestions(connectionSuggestionsByConfigScopeIds, bindingSuggestionsForConfigScopeIds);\n  }\n\n  private Optional<Either<String, String>> handleBindingClue(BindingClueProvider.BindingClue bindingClue) {\n    switch (bindingClue) {\n      case BindingClueProvider.SonarCloudBindingClue sonarCloudBindingClue -> {\n        LOG.debug(\"Found a SonarCloud binding clue\");\n        var organization = sonarCloudBindingClue.getOrganization();\n        var connection = connectionRepository.findByOrganization(organization);\n        if (connection.isEmpty()) {\n          return Optional.of(Either.forRight(organization));\n        }\n      }\n      case BindingClueProvider.SonarQubeBindingClue sonarQubeBindingClue -> {\n        LOG.debug(\"Found a SonarQube binding clue\");\n        var serverUrl = sonarQubeBindingClue.getServerUrl();\n        var connection = connectionRepository.findByUrl(serverUrl);\n        if (connection.isEmpty()) {\n          return Optional.of(Either.forLeft(Strings.CS.removeEnd(serverUrl, \"/\")));\n        }\n      }\n      default -> LOG.debug(\"Found an invalid binding clue for connection suggestion\");\n    }\n    return Optional.empty();\n  }\n\n  private void suggestConnectionToClientIfAny(Map<String, List<ConnectionSuggestionDto>> connectionSuggestionsByConfigScopeIds) {\n    if (!connectionSuggestionsByConfigScopeIds.isEmpty()) {\n      var foundSuggestionsCount = connectionSuggestionsByConfigScopeIds.size();\n      LOG.debug(\"Found {} connection {}\", foundSuggestionsCount, SonarLintLogger.singlePlural(foundSuggestionsCount, \"suggestion\"));\n      client.suggestConnection(new SuggestConnectionParams(connectionSuggestionsByConfigScopeIds));\n    }\n  }\n\n  private void computeBindingSuggestionIfAny(Set<String> bindingSuggestionsForConfigScopeIds) {\n    if (!bindingSuggestionsForConfigScopeIds.isEmpty()) {\n      LOG.debug(\"Found binding suggestion(s) for %s configuration scope IDs\", bindingSuggestionsForConfigScopeIds.size());\n      bindingSuggestionProvider.suggestBindingForGivenScopesAndAllConnections(bindingSuggestionsForConfigScopeIds);\n    }\n  }\n\n  public List<ConnectionSuggestionDto> getConnectionSuggestions(String configScopeId, SonarLintCancelMonitor cancelMonitor) {\n    var connectionAndBindingSuggestions = computeConnectionAndBindingSuggestions(Set.of(configScopeId), cancelMonitor);\n    return connectionAndBindingSuggestions.connectionSuggestionsByConfigScopeIds.containsKey(configScopeId) ?\n      connectionAndBindingSuggestions.connectionSuggestionsByConfigScopeIds.get(configScopeId) :\n      List.of();\n  }\n\n  private record ConnectionAndBindingSuggestions(\n    Map<String, List<ConnectionSuggestionDto>> connectionSuggestionsByConfigScopeIds,\n    Set<String> bindingSuggestionsForConfigScopeIds\n  ) {}\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/DtoMapper.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport org.sonarsource.sonarlint.core.commons.NewCodeDefinition;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.HotspotStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.RaisedHotspotDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.MQRModeDetails;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.StandardModeDetails;\nimport org.sonarsource.sonarlint.core.rules.RuleDetailsAdapter;\nimport org.sonarsource.sonarlint.core.tracking.TrackedIssue;\n\nimport static java.util.Objects.requireNonNull;\nimport static org.sonarsource.sonarlint.core.tracking.TextRangeUtils.toTextRangeDto;\n\npublic class DtoMapper {\n\n  private DtoMapper() {\n    // util\n  }\n\n  public static RaisedIssueDto toRaisedIssueDto(TrackedIssue issue, NewCodeDefinition newCodeDefinition, boolean isMQRMode, boolean isAiCodeFixable) {\n    return new RaisedIssueDto(issue.getId(), issue.getServerKey(), issue.getRuleKey(), issue.getMessage(),\n      isMQRMode ? Either.forRight(new MQRModeDetails(RuleDetailsAdapter.adapt(issue.getCleanCodeAttribute()), RuleDetailsAdapter.toDto(issue.getImpacts())))\n        : Either.forLeft(new StandardModeDetails(RuleDetailsAdapter.adapt(issue.getSeverity()), RuleDetailsAdapter.adapt(issue.getType()))),\n      requireNonNull(issue.getIntroductionDate()), newCodeDefinition.isOnNewCode(issue.getIntroductionDate()), issue.isResolved(),\n      toTextRangeDto(issue.getTextRangeWithHash()),\n      issue.getFlows().stream().map(RuleDetailsAdapter::adapt).toList(),\n      issue.getQuickFixes().stream().map(RuleDetailsAdapter::adapt).toList(),\n      issue.getRuleDescriptionContextKey(), isAiCodeFixable,\n      issue.getResolutionStatus());\n  }\n\n  public static RaisedHotspotDto toRaisedHotspotDto(TrackedIssue issue, NewCodeDefinition newCodeDefinition, boolean isMQRMode) {\n    var status = issue.getHotspotStatus();\n    status = status != null ? status : HotspotStatus.TO_REVIEW;\n    var vp = RuleDetailsAdapter.adapt(issue.getVulnerabilityProbability());\n    if (vp == null) {\n      // this should not normally happen because all hotspots supposed to have the vulnerability probability set\n      throw new IllegalStateException(\"Vulnerability probability should be set for security hotspots\");\n    }\n    return new RaisedHotspotDto(issue.getId(), issue.getServerKey(), issue.getRuleKey(), issue.getMessage(),\n      isMQRMode && !issue.getImpacts().isEmpty() ?\n        Either.forRight(new MQRModeDetails(RuleDetailsAdapter.adapt(issue.getCleanCodeAttribute()), RuleDetailsAdapter.toDto(issue.getImpacts())))\n        : Either.forLeft(new StandardModeDetails(RuleDetailsAdapter.adapt(issue.getSeverity()), RuleDetailsAdapter.adapt(issue.getType()))),\n      requireNonNull(issue.getIntroductionDate()), newCodeDefinition.isOnNewCode(issue.getIntroductionDate()), issue.isResolved(),\n      toTextRangeDto(issue.getTextRangeWithHash()),\n      issue.getFlows().stream().map(RuleDetailsAdapter::adapt).toList(),\n      issue.getQuickFixes().stream().map(RuleDetailsAdapter::adapt).toList(),\n      issue.getRuleDescriptionContextKey(), vp, status);\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/MCPServerConfigurationProvider.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport org.sonarsource.sonarlint.core.commons.ConnectionKind;\nimport org.sonarsource.sonarlint.core.commons.SonarLintException;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.embedded.server.EmbeddedServer;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarCloudConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryService;\n\nimport static java.lang.String.format;\n\npublic class MCPServerConfigurationProvider {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final String SONARCLOUD_MCP_CONFIG = \"\"\"\n    {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\",\n        \"-i\",\n        \"--rm\",\n        \"-e\",\n        \"SONARQUBE_TOKEN\",\n        \"-e\",\n        \"SONARQUBE_ORG\",\n        \"-e\",\n        \"SONARQUBE_CLOUD_URL\",\n        \"-e\",\n        \"SONARQUBE_IDE_PORT\",\n        \"mcp/sonarqube\"\n      ],\n      \"env\": {\n        \"SONARQUBE_ORG\": \"%s\",\n        \"SONARQUBE_CLOUD_URL\": \"%s\",\n        \"SONARQUBE_TOKEN\": \"%s\",\n        \"SONARQUBE_IDE_PORT\": \"%s\"\n      }\n    }\n    \"\"\";\n  private static final String SONARQUBE_MCP_CONFIG = \"\"\"\n    {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\",\n        \"-i\",\n        \"--rm\",\n        \"-e\",\n        \"SONARQUBE_TOKEN\",\n        \"-e\",\n        \"SONARQUBE_URL\",\n        \"-e\",\n        \"SONARQUBE_IDE_PORT\",\n        \"mcp/sonarqube\"\n      ],\n      \"env\": {\n        \"SONARQUBE_URL\": \"%s\",\n        \"SONARQUBE_TOKEN\": \"%s\",\n        \"SONARQUBE_IDE_PORT\": \"%s\"\n      }\n    }\n    \"\"\";\n\n  private final ConnectionConfigurationRepository connectionRepository;\n  private final TelemetryService telemetryService;\n  private final EmbeddedServer embeddedServer;\n\n  public MCPServerConfigurationProvider(ConnectionConfigurationRepository connectionRepository, TelemetryService telemetryService, EmbeddedServer embeddedServer) {\n    this.connectionRepository = connectionRepository;\n    this.telemetryService = telemetryService;\n    this.embeddedServer = embeddedServer;\n  }\n\n  public String getMCPServerConfigurationJSON(String connectionId, String token) {\n    var connection = connectionRepository.getConnectionById(connectionId);\n    if (connection != null) {\n      telemetryService.mcpServerConfigurationRequested();\n      if (connection.getKind() == ConnectionKind.SONARCLOUD) {\n        var sonarCloudConnection = (SonarCloudConnectionConfiguration) connection;\n        var organization = sonarCloudConnection.getOrganization();\n        var url = connection.getUrl();\n\n        return format(SONARCLOUD_MCP_CONFIG, organization, url, token, embeddedServer.getPort());\n      } else {\n        var url = connection.getUrl();\n\n        return format(SONARQUBE_MCP_CONFIG, url, token, embeddedServer.getPort());\n      }\n    } else {\n      LOG.warn(\"Request for generating MCP server settings JSON failed; Connection not found for '{}'\", connectionId);\n      throw new SonarLintException(format(\"Connection not found for '%s'; Cannot generate MCP server settings JSON\", connectionId));\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/OrganizationsCache.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport com.google.common.cache.Cache;\nimport com.google.common.cache.CacheBuilder;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport javax.annotation.CheckForNull;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarCloudConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.OrganizationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\n\nimport static java.util.Objects.requireNonNull;\nimport static org.sonarsource.sonarlint.core.commons.log.SonarLintLogger.singlePlural;\n\n/**\n * Cache user organizations index for a certain amount of time.\n */\npublic class OrganizationsCache {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final SonarQubeClientManager sonarQubeClientManager;\n\n  private final Cache<Either<TokenDto, UsernamePasswordDto>, TextSearchIndex<OrganizationDto>> textSearchIndexCacheByCredentials = CacheBuilder.newBuilder()\n    .expireAfterWrite(5, TimeUnit.MINUTES)\n    .build();\n\n  public OrganizationsCache(SonarQubeClientManager sonarQubeClientManager) {\n    this.sonarQubeClientManager = sonarQubeClientManager;\n  }\n\n  public List<OrganizationDto> fuzzySearchOrganizations(TransientSonarCloudConnectionDto transientSonarCloudConnection, String searchText, SonarLintCancelMonitor cancelMonitor) {\n    return getTextSearchIndex(transientSonarCloudConnection, cancelMonitor).search(searchText)\n      .entrySet()\n      .stream()\n      .sorted(Comparator.comparing(Map.Entry<OrganizationDto, Double>::getValue).reversed()\n        .thenComparing(e -> e.getKey().getName(), String.CASE_INSENSITIVE_ORDER))\n      .limit(10)\n      .map(Map.Entry::getKey)\n      .toList();\n  }\n\n  public TextSearchIndex<OrganizationDto> getTextSearchIndex(TransientSonarCloudConnectionDto transientSonarCloudConnection, SonarLintCancelMonitor cancelMonitor) {\n    try {\n      return textSearchIndexCacheByCredentials.get(transientSonarCloudConnection.getCredentials(), () -> {\n        LOG.debug(\"Load user organizations...\");\n        List<OrganizationDto> orgs;\n        try {\n          orgs = sonarQubeClientManager.getForTransientConnection(Either.forRight(transientSonarCloudConnection))\n            .organization()\n            .listUserOrganizations(cancelMonitor).stream().map(o -> new OrganizationDto(o.getKey(), o.getName(), o.getDescription())).toList();\n        } catch (Exception e) {\n          LOG.error(\"Error while querying SonarCloud organizations\", e);\n          return new TextSearchIndex<>();\n        }\n        if (orgs.isEmpty()) {\n          LOG.debug(\"No organizations found\");\n          return new TextSearchIndex<>();\n        } else {\n          LOG.debug(\"Creating index for {} {}\", orgs.size(), singlePlural(orgs.size(), \"organization\"));\n          var index = new TextSearchIndex<OrganizationDto>();\n          orgs.forEach(org -> index.index(org, org.getKey() + \" \" + org.getName()));\n          return index;\n        }\n      });\n    } catch (ExecutionException e) {\n      throw new IllegalStateException(e.getCause());\n    }\n  }\n\n  public List<OrganizationDto> listUserOrganizations(TransientSonarCloudConnectionDto transientSonarCloudConnection, SonarLintCancelMonitor cancelMonitor) {\n    textSearchIndexCacheByCredentials.invalidate(transientSonarCloudConnection.getCredentials());\n    return getTextSearchIndex(transientSonarCloudConnection, cancelMonitor).getAll();\n  }\n\n  @CheckForNull\n  public OrganizationDto getOrganization(TransientSonarCloudConnectionDto transientSonarCloudConnection, SonarLintCancelMonitor cancelMonitor) {\n    return sonarQubeClientManager.getForTransientConnection(Either.forRight(transientSonarCloudConnection))\n      .organization().searchOrganization(requireNonNull(transientSonarCloudConnection.getOrganization()), cancelMonitor)\n      .map(o -> new OrganizationDto(o.getKey(), o.getName(), o.getDescription())).orElse(null);\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/ServerFileExclusions.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.config.Configuration;\nimport org.sonar.api.scan.filesystem.FileExclusions;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.SonarLintPathPattern;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic class ServerFileExclusions {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final FileExclusions exclusionSettings;\n\n  private SonarLintPathPattern[] mainInclusions;\n  private SonarLintPathPattern[] mainExclusions;\n  private SonarLintPathPattern[] testInclusions;\n  private SonarLintPathPattern[] testExclusions;\n\n  public ServerFileExclusions(Configuration configuration) {\n    this.exclusionSettings = new FileExclusions(configuration);\n  }\n\n  public void prepare() {\n    mainInclusions = prepareMainInclusions();\n    mainExclusions = prepareMainExclusions();\n    testInclusions = prepareTestInclusions();\n    testExclusions = prepareTestExclusions();\n    log(\"Server included sources: \", mainInclusions);\n    log(\"Server excluded sources: \", mainExclusions);\n    log(\"Server included tests: \", testInclusions);\n    log(\"Server excluded tests: \", testExclusions);\n  }\n\n  private static void log(String title, SonarLintPathPattern[] patterns) {\n    if (patterns.length > 0) {\n      LOG.debug(title);\n      for (SonarLintPathPattern pattern : patterns) {\n        LOG.debug(\"  {}\", pattern);\n      }\n    }\n  }\n\n  public boolean accept(String relativePath, InputFile.Type type) {\n    SonarLintPathPattern[] inclusionPatterns;\n    SonarLintPathPattern[] exclusionPatterns;\n    switch (type) {\n      case MAIN -> {\n        inclusionPatterns = mainInclusions;\n        exclusionPatterns = mainExclusions;\n      }\n      case TEST -> {\n        inclusionPatterns = testInclusions;\n        exclusionPatterns = testExclusions;\n      }\n      default -> throw new IllegalArgumentException(\"Unknown file type: \" + type);\n    }\n\n    if (inclusionPatterns.length > 0) {\n      var matchInclusion = false;\n      for (SonarLintPathPattern pattern : inclusionPatterns) {\n        matchInclusion |= pattern.match(relativePath);\n      }\n      if (!matchInclusion) {\n        return false;\n      }\n    }\n    for (SonarLintPathPattern pattern : exclusionPatterns) {\n      if (pattern.match(relativePath)) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  SonarLintPathPattern[] prepareMainInclusions() {\n    if (exclusionSettings.sourceInclusions().length > 0) {\n      // User defined params\n      return SonarLintPathPattern.create(exclusionSettings.sourceInclusions());\n    }\n    return new SonarLintPathPattern[0];\n  }\n\n  SonarLintPathPattern[] prepareTestInclusions() {\n    return SonarLintPathPattern.create(computeTestInclusions());\n  }\n\n  private String[] computeTestInclusions() {\n    if (exclusionSettings.testInclusions().length > 0) {\n      // User defined params\n      return exclusionSettings.testInclusions();\n    }\n    return ArrayUtils.EMPTY_STRING_ARRAY;\n  }\n\n  SonarLintPathPattern[] prepareMainExclusions() {\n    var patterns = ArrayUtils.addAll(\n      exclusionSettings.sourceExclusions(), computeTestInclusions());\n    return SonarLintPathPattern.create(patterns);\n  }\n\n  SonarLintPathPattern[] prepareTestExclusions() {\n    return SonarLintPathPattern.create(exclusionSettings.testExclusions());\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/SharedConnectedModeSettingsProvider.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport java.util.Objects;\nimport org.sonarsource.sonarlint.core.commons.ConnectionKind;\nimport org.sonarsource.sonarlint.core.commons.SonarLintException;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarCloudConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryService;\n\nimport static java.lang.String.format;\n\npublic class SharedConnectedModeSettingsProvider {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final String SONARCLOUD_CONNECTED_MODE_CONFIG = \"\"\"\n    {\n        \"sonarCloudOrganization\": \"%s\",\n        \"projectKey\": \"%s\",\n        \"region\": \"%s\"\n    }\"\"\";\n  private static final String SONARQUBE_CONNECTED_MODE_CONFIG = \"\"\"\n    {\n        \"sonarQubeUri\": \"%s\",\n        \"projectKey\": \"%s\"\n    }\"\"\";\n\n  private final ConfigurationRepository configurationRepository;\n  private final ConnectionConfigurationRepository connectionRepository;\n  private final TelemetryService telemetryService;\n\n  public SharedConnectedModeSettingsProvider(ConfigurationRepository configurationRepository,\n    ConnectionConfigurationRepository connectionRepository, TelemetryService telemetryService) {\n    this.configurationRepository = configurationRepository;\n    this.connectionRepository = connectionRepository;\n    this.telemetryService = telemetryService;\n  }\n\n  public String getSharedConnectedModeConfigFileContents(String configScopeId) {\n    var bindingConfiguration = configurationRepository.getBindingConfiguration(configScopeId);\n    if (bindingConfiguration != null && bindingConfiguration.isBound()) {\n      var projectKey = bindingConfiguration.sonarProjectKey();\n      var connectionId = bindingConfiguration.connectionId();\n\n      var connection =  Objects.requireNonNull(connectionRepository.getConnectionById(Objects.requireNonNull(connectionId)));\n      telemetryService.exportedConnectedMode();\n      if (connection.getKind() == ConnectionKind.SONARCLOUD) {\n        var organization = ((SonarCloudConnectionConfiguration) connection).getOrganization();\n        var region = ((SonarCloudConnectionConfiguration) connection).getRegion();\n\n        return format(SONARCLOUD_CONNECTED_MODE_CONFIG, organization, projectKey, region);\n      } else {\n        return format(SONARQUBE_CONNECTED_MODE_CONFIG, connection.getUrl(), projectKey);\n      }\n    } else {\n      LOG.warn(\"Request for generating shared Connected Mode configuration file content failed; Binding not yet available for '{}'\", configScopeId);\n      throw new SonarLintException(format(\"Binding not found for '%s'; Cannot generate shared Connected Mode file contents\", configScopeId));\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/SonarCloudActiveEnvironment.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport java.net.URI;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\nimport org.apache.commons.lang3.Strings;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.SonarQubeCloudRegionDto;\n\n\npublic class SonarCloudActiveEnvironment {\n  private final Map<SonarCloudRegion, SonarQubeCloudRegionDto> alternativeRegionUris;\n\n  public static SonarCloudActiveEnvironment prod() {\n    return new SonarCloudActiveEnvironment(Map.of());\n  }\n\n  public SonarCloudActiveEnvironment(Map<org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion, SonarQubeCloudRegionDto> alternativeRegionUris) {\n    this.alternativeRegionUris = alternativeRegionUris.entrySet().stream()\n      .collect(Collectors.toMap(entry -> SonarCloudRegion.valueOf(entry.getKey().name()), Map.Entry::getValue));\n  }\n\n  public URI getUri(SonarCloudRegion region) {\n    if (alternativeRegionUris.containsKey(region) && alternativeRegionUris.get(region).getUri() != null) {\n      return alternativeRegionUris.get(region).getUri();\n    }\n    return region.getProductionUri();\n  }\n\n  public URI getApiUri(SonarCloudRegion region) {\n    if (alternativeRegionUris.containsKey(region) && alternativeRegionUris.get(region).getApiUri() != null) {\n      return alternativeRegionUris.get(region).getApiUri();\n    }\n    return region.getApiProductionUri();\n  }\n\n  public URI getWebSocketsEndpointUri(SonarCloudRegion region) {\n    if (alternativeRegionUris.containsKey(region) && alternativeRegionUris.get(region).getWebSocketsEndpointUri() != null) {\n      return alternativeRegionUris.get(region).getWebSocketsEndpointUri();\n    }\n    return region.getWebSocketUri();\n  }\n\n  public boolean isSonarQubeCloud(String uri) {\n    return getRegionByUri(uri).isPresent();\n  }\n\n  /**\n   *  Before calling this method, caller should make sure URI is SonarCloud\n   */\n  public SonarCloudRegion getRegionOrThrow(String uri) {\n    var regionOpt = getRegionByUri(uri);\n    if (regionOpt.isPresent()) {\n      return regionOpt.get();\n    }\n    \n    throw new IllegalArgumentException(\"URI should be a known SonarCloud URI\");\n  }\n\n  private Optional<SonarCloudRegion> getRegionByUri(String uri) {\n    var cleanedUri = Strings.CS.removeEnd(uri, \"/\");\n    for (var entry : alternativeRegionUris.entrySet()) {\n      var regionUri = entry.getValue().getUri();\n      if (regionUri != null && Strings.CS.removeEnd(regionUri.toString(), \"/\").equals(cleanedUri)) {\n        return Optional.of(entry.getKey());\n      }\n    }\n\n    for (var region : SonarCloudRegion.values()) {\n      if (Strings.CS.removeEnd(region.getProductionUri().toString(), \"/\").equals(cleanedUri)) {\n        return Optional.of(region);\n      }\n    }\n    \n    return Optional.empty();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/SonarCloudRegion.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport java.net.URI;\nimport java.util.Arrays;\n\npublic enum SonarCloudRegion {\n  EU(\"https://sonarcloud.io\", \"https://api.sonarcloud.io\", \"wss://events-api.sonarcloud.io/\"),\n  US(\"https://sonarqube.us\", \"https://api.sonarqube.us\", \"wss://events-api.sonarqube.us/\");\n\n  public static final String[] CLOUD_URLS = Arrays.stream(values())\n    .map(SonarCloudRegion::getProductionUri)\n    .map(Object::toString)\n    .toArray(String[]::new);\n\n  private final URI productionUri;\n  private final URI apiProductionUri;\n  private final URI webSocketUri;\n\n  SonarCloudRegion(String productionUri, String apiProductionUri, String webSocketUri) {\n    this.productionUri = URI.create(productionUri);\n    this.apiProductionUri = URI.create(apiProductionUri);\n    this.webSocketUri = URI.create(webSocketUri);\n  }\n\n  public URI getProductionUri() {\n    return productionUri;\n  }\n\n  public URI getApiProductionUri() {\n    return apiProductionUri;\n  }\n\n  public URI getWebSocketUri() {\n    return webSocketUri;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/SonarCodeContextService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.Set;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.branch.SonarProjectBranchTrackingService;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.dogfood.DogfoodEnvironmentDetectionService;\nimport org.sonarsource.sonarlint.core.commons.util.git.ProcessWrapperFactory;\nimport org.sonarsource.sonarlint.core.event.ConfigurationScopesAddedWithBindingEvent;\nimport org.sonarsource.sonarlint.core.event.BindingConfigChangedEvent;\nimport org.sonarsource.sonarlint.core.fs.ClientFileSystemService;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.GetCredentialsParams;\nimport org.springframework.context.event.EventListener;\n\n/**\n * Dogfooding-only integration that runs SonarCodeContext CLI on repository open in connected mode.\n * Commands executed (in order): init, generate-md-guidelines, merge-md, install.\n * Outputs are expected under the '.sonar-code-context' directory.\n */\npublic class SonarCodeContextService {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final String SONAR_CODE_CONTEXT_DIR = \".sonar-code-context\";\n  private static final String CLI_EXECUTABLE = \"sonar-code-context\";\n  private static final String SONAR_MD_FILENAME = \"SONAR.md\";\n  private static final String CURSOR_MDC_FILENAME = \"sonar-code-context.mdc\";\n\n  private final ClientFileSystemService clientFileSystemService;\n  private final ConfigurationRepository configurationRepository;\n  private final ConnectionConfigurationRepository connectionConfigurationRepository;\n  private final SonarProjectBranchTrackingService branchTrackingService;\n  private final SonarLintRpcClient client;\n  private final ProcessWrapperFactory processWrapperFactory = new ProcessWrapperFactory();\n  private final boolean isEnabled;\n\n  private final Set<String> initializedScopes = new HashSet<>();\n  private final Set<String> mdcInstalledScopes = new HashSet<>();\n\n  public SonarCodeContextService(DogfoodEnvironmentDetectionService dogfoodEnvDetectionService,\n    ClientFileSystemService clientFileSystemService,\n    ConfigurationRepository configurationRepository,\n    ConnectionConfigurationRepository connectionConfigurationRepository,\n    SonarProjectBranchTrackingService branchTrackingService,\n    SonarLintRpcClient client, InitializeParams params) {\n    this.clientFileSystemService = clientFileSystemService;\n    this.configurationRepository = configurationRepository;\n    this.connectionConfigurationRepository = connectionConfigurationRepository;\n    this.branchTrackingService = branchTrackingService;\n    this.client = client;\n    this.isEnabled = dogfoodEnvDetectionService.isDogfoodEnvironment()\n      && params.getBackendCapabilities().contains(BackendCapability.CONTEXT_GENERATION);\n  }\n\n  @EventListener\n  public void onConfigurationScopesAdded(ConfigurationScopesAddedWithBindingEvent event) {\n    if (!isEnabled) {\n      return;\n    }\n\n    for (var configScopeId : event.getConfigScopeIds()) {\n      var baseDir = clientFileSystemService.getBaseDir(configScopeId);\n      // Only run for scopes that are directly bound (not inherited from parent)\n      var bindingOpt = configurationRepository.getConfiguredBinding(configScopeId);\n      if (baseDir != null && bindingOpt.isPresent()) {\n        handleGeneration(configScopeId, baseDir, bindingOpt.get());\n      } else {\n        LOG.debug(\"No baseDir for configuration scope '{}' - skipping SonarCodeContext CLI\", configScopeId);\n      }\n    }\n  }\n\n  @EventListener\n  public void onBindingChanged(BindingConfigChangedEvent event) {\n    if (!isEnabled) {\n      return;\n    }\n\n    var configScopeId = event.configScopeId();\n    var baseDir = clientFileSystemService.getBaseDir(configScopeId);\n    var bindingOpt = configurationRepository.getConfiguredBinding(configScopeId);\n    if (baseDir != null && bindingOpt.isPresent()) {\n      handleGeneration(configScopeId, baseDir, bindingOpt.get());\n    }\n  }\n\n  private void handleGeneration(String configScopeId, Path baseDir, Binding binding) {\n    try {\n      var paramsOpt = prepareCliParams(binding, configScopeId);\n      if (paramsOpt.isPresent()) {\n        var workingDir = computeWorkingBaseDir(baseDir);\n        if (initializedScopes.add(configScopeId)) {\n          runInit(workingDir);\n        }\n        runGenerateGuidelines(workingDir, paramsOpt.get());\n        runMergeMd(workingDir);\n        if (mdcInstalledScopes.add(configScopeId)) {\n          runInstall(workingDir);\n        }\n      } else {\n        LOG.debug(\"Missing parameters for SonarCodeContext CLI, skipping for configuration scope '{}'\", configScopeId);\n      }\n    } catch (Exception e) {\n      LOG.debug(\"[DOGFOOD] Failed to run code context CLI\", e.getMessage());\n    }\n  }\n\n  private Optional<CliParams> prepareCliParams(Binding binding, String configScopeId) {\n    var connection = connectionConfigurationRepository.getConnectionById(binding.connectionId());\n    if (connection == null) {\n      return Optional.empty();\n    }\n    var url = connection.getUrl();\n    var token = getTokenForConnection(binding.connectionId());\n    if (token.isEmpty()) {\n      return Optional.empty();\n    }\n    var branch = branchTrackingService.awaitEffectiveSonarProjectBranch(configScopeId).orElse(null);\n    return Optional.of(new CliParams(url, token.get(), binding.sonarProjectKey(), branch));\n  }\n\n  private Optional<String> getTokenForConnection(String connectionId) {\n    try {\n      var creds = client.getCredentials(new GetCredentialsParams(connectionId)).join().getCredentials();\n      if (creds != null && creds.isLeft()) {\n        var tokenDto = creds.getLeft();\n        return Optional.ofNullable(tokenDto.getToken());\n      }\n      return Optional.empty();\n    } catch (Exception e) {\n      LOG.debug(\"Unable to retrieve token for connection '{}'\", connectionId, e);\n      return Optional.empty();\n    }\n  }\n\n  private void runInit(Path baseDir) {\n    var command = new ArrayList<>(List.of(resolveCliExecutable(), \"init\"));\n    execute(baseDir, command);\n    var settings = baseDir.resolve(SONAR_CODE_CONTEXT_DIR).resolve(\"settings.json\");\n    if (Files.exists(settings)) {\n      LOG.debug(\"Initialized SonarCodeContext settings at {}\", settings);\n    }\n  }\n\n  private void runGenerateGuidelines(Path baseDir, CliParams params) {\n    var command = new ArrayList<>(List.of(\n      resolveCliExecutable(),\n      \"generate-md-guidelines\",\n      \"--sq-url=\" + params.sqUrl,\n      \"--sq-token=\" + params.sqToken,\n      \"--sq-project-key=\" + params.projectKey\n    ));\n    if (params.sqBranch() != null) {\n      command.add(\"--sq-branch=\" + params.sqBranch());\n    }\n    execute(baseDir, command);\n  }\n\n  private void runMergeMd(Path baseDir) {\n    var command = new ArrayList<>(List.of(resolveCliExecutable(), \"merge-md\"));\n    execute(baseDir, command);\n    var merged = baseDir.resolve(SONAR_CODE_CONTEXT_DIR).resolve(SONAR_MD_FILENAME);\n    if (Files.exists(merged)) {\n      LOG.debug(\"Merged {} at {}\", SONAR_MD_FILENAME, merged);\n    } else {\n      LOG.debug(\"{} was not generated under {}\", SONAR_MD_FILENAME, baseDir.resolve(SONAR_CODE_CONTEXT_DIR));\n    }\n  }\n\n  private void runInstall(Path baseDir) {\n    var command = new ArrayList<>(List.of(resolveCliExecutable(), \"install\", \"--force\", \"--cursor-mdc\"));\n    execute(baseDir, command);\n    var cursorRule = baseDir.resolve(\".cursor\").resolve(\"rules\").resolve(CURSOR_MDC_FILENAME);\n    if (Files.exists(cursorRule)) {\n      LOG.debug(\"Generated {} at {}\", CURSOR_MDC_FILENAME, cursorRule);\n    }\n  }\n\n  private void execute(Path baseDir, List<String> command) {\n    var result = processWrapperFactory.create(baseDir, LOG::debug, command.toArray(new String[0])).execute();\n    if (result.exitCode() != 0) {\n      LOG.debug(\"Command '{}' exited with code {} in {}\", String.join(\" \", command), result.exitCode(), baseDir);\n    }\n  }\n\n  private record CliParams(String sqUrl, String sqToken, String projectKey, @Nullable String sqBranch) {}\n\n  private static Path computeWorkingBaseDir(Path baseDir) {\n    try {\n      var current = baseDir;\n      while (current != null) {\n        if (Files.isDirectory(current.resolve(\".git\"))) {\n          return current;\n        }\n        current = current.getParent();\n      }\n    } catch (Exception e) {\n      // ignore and fallback\n    }\n    return baseDir;\n  }\n\n  private static String resolveCliExecutable() {\n    // Used for testing\n    var prop = System.getProperty(\"sonar.code.context.executable\");\n    if (prop != null && !prop.isBlank()) {\n      return prop;\n    }\n    return CLI_EXECUTABLE;\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/SonarLintMDC.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport javax.annotation.Nullable;\nimport org.slf4j.MDC;\n\npublic class SonarLintMDC {\n\n  public static final String CONFIG_SCOPE_ID_MDC_KEY = \"configScopeId\";\n\n  private SonarLintMDC() {\n    // only static methods\n  }\n\n  public static void putConfigScopeId(@Nullable String configScopeId) {\n    if (configScopeId == null) {\n      MDC.remove(CONFIG_SCOPE_ID_MDC_KEY);\n    } else {\n      MDC.put(CONFIG_SCOPE_ID_MDC_KEY, configScopeId);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/SonarProjectsCache.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport com.google.common.cache.Cache;\nimport com.google.common.cache.CacheBuilder;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationRemovedEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationUpdatedEvent;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.SonarProjectDto;\nimport org.sonarsource.sonarlint.core.serverapi.component.ServerProject;\nimport org.springframework.context.event.EventListener;\n\nimport static org.sonarsource.sonarlint.core.commons.log.SonarLintLogger.singlePlural;\n\npublic class SonarProjectsCache {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final SonarQubeClientManager sonarQubeClientManager;\n\n  private final Cache<String, TextSearchIndex<ServerProject>> textSearchIndexCacheByConnectionId = CacheBuilder.newBuilder()\n    .expireAfterWrite(1, TimeUnit.HOURS)\n    .build();\n\n  private final Cache<SonarProjectKey, Optional<ServerProject>> singleProjectsCache = CacheBuilder.newBuilder()\n    .expireAfterWrite(1, TimeUnit.HOURS)\n    .build();\n\n  public SonarProjectsCache(SonarQubeClientManager sonarQubeClientManager) {\n    this.sonarQubeClientManager = sonarQubeClientManager;\n  }\n\n  public List<SonarProjectDto> fuzzySearchProjects(String connectionId, String searchText, SonarLintCancelMonitor cancelMonitor) {\n    return getTextSearchIndex(connectionId, cancelMonitor).search(searchText)\n      .entrySet()\n      .stream()\n      .sorted(Comparator.comparing(Map.Entry<ServerProject, Double>::getValue).reversed()\n        .thenComparing(Comparator.comparing(e -> e.getKey().name(), String.CASE_INSENSITIVE_ORDER)))\n      .limit(10)\n      .map(e -> new SonarProjectDto(e.getKey().key(), e.getKey().name()))\n      .toList();\n  }\n\n  private static class SonarProjectKey {\n    private final String connectionId;\n    private final String projectKey;\n\n    private SonarProjectKey(String connectionId, String projectKey) {\n      this.connectionId = connectionId;\n      this.projectKey = projectKey;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n      if (this == o) {\n        return true;\n      }\n      if (o == null || getClass() != o.getClass()) {\n        return false;\n      }\n      var that = (SonarProjectKey) o;\n      return connectionId.equals(that.connectionId) && projectKey.equals(that.projectKey);\n    }\n\n    @Override\n    public int hashCode() {\n      return Objects.hash(connectionId, projectKey);\n    }\n  }\n\n  @EventListener\n  public void connectionRemoved(ConnectionConfigurationRemovedEvent e) {\n    evictAll(e.removedConnectionId());\n  }\n\n  @EventListener\n  public void connectionUpdated(ConnectionConfigurationUpdatedEvent e) {\n    // If connection config was modified (url, credentials, ...) then the projects the user might be able to \"see\" could be different\n    evictAll(e.updatedConnectionId());\n  }\n\n  private void evictAll(String connectionId) {\n    textSearchIndexCacheByConnectionId.invalidate(connectionId);\n    // Not possible to evict only entries of the given connection, so simply evict all\n    singleProjectsCache.invalidateAll();\n  }\n\n  public Optional<ServerProject> getSonarProject(String connectionId, String sonarProjectKey, SonarLintCancelMonitor cancelMonitor) {\n    try {\n      return singleProjectsCache.get(new SonarProjectKey(connectionId, sonarProjectKey), () -> {\n        LOG.debug(\"Query project '{}' on connection '{}'...\", sonarProjectKey, connectionId);\n        try {\n          return sonarQubeClientManager.withActiveClientAndReturn(connectionId,\n            s -> s.component().getProject(sonarProjectKey, cancelMonitor)).orElse(Optional.empty());\n        } catch (Exception e) {\n          LOG.error(\"Error while querying project '{}' from connection '{}'\", sonarProjectKey, connectionId, e);\n          return Optional.empty();\n        }\n      });\n    } catch (ExecutionException e) {\n      throw new IllegalStateException(e.getCause());\n    }\n  }\n\n  public TextSearchIndex<ServerProject> getTextSearchIndex(String connectionId, SonarLintCancelMonitor cancelMonitor) {\n    try {\n      return textSearchIndexCacheByConnectionId.get(connectionId, () -> {\n        LOG.debug(\"Load projects from connection '{}'...\", connectionId);\n        List<ServerProject> projects;\n        try {\n          projects = sonarQubeClientManager.withActiveClientAndReturn(connectionId,\n              s -> s.component().getAllProjects(cancelMonitor))\n            .orElse(List.of());\n        } catch (Exception e) {\n          LOG.error(\"Error while querying projects from connection '{}'\", connectionId, e);\n          return new TextSearchIndex<>();\n        }\n        if (projects.isEmpty()) {\n          LOG.debug(\"No projects found for connection '{}'\", connectionId);\n          return new TextSearchIndex<>();\n        } else {\n          LOG.debug(\"Creating index for {} {}\", projects.size(), singlePlural(projects.size(), \"project\"));\n          var index = new TextSearchIndex<ServerProject>();\n          projects.forEach(p -> index.index(p, p.key() + \" \" + p.name()));\n          return index;\n        }\n      });\n    } catch (ExecutionException e) {\n      throw new IllegalStateException(e.getCause());\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/SonarQubeClientManager.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport javax.annotation.Nullable;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseError;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.connection.SonarQubeClient;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationRemovedEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationUpdatedEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionCredentialsChangedEvent;\nimport org.sonarsource.sonarlint.core.http.HttpClientProvider;\nimport org.sonarsource.sonarlint.core.http.WebSocketClient;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarCloudConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarQubeConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.GetCredentialsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.sync.InvalidTokenParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\nimport org.sonarsource.sonarlint.core.serverapi.EndpointParams;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\nimport org.sonarsource.sonarlint.core.serverconnection.ServerVersionAndStatusChecker;\nimport org.springframework.context.event.EventListener;\n\npublic class SonarQubeClientManager {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final ConnectionConfigurationRepository connectionRepository;\n  private final HttpClientProvider httpClientProvider;\n  private final SonarLintRpcClient client;\n  private final SonarCloudActiveEnvironment sonarCloudActiveEnvironment;\n  private final Map<String, Optional<SonarQubeClient>> clientsByConnectionId = new ConcurrentHashMap<>();\n\n  public SonarQubeClientManager(ConnectionConfigurationRepository connectionRepository, HttpClientProvider httpClientProvider,\n    SonarCloudActiveEnvironment sonarCloudActiveEnvironment, SonarLintRpcClient client) {\n    this.connectionRepository = connectionRepository;\n    this.httpClientProvider = httpClientProvider;\n    this.client = client;\n    this.sonarCloudActiveEnvironment = sonarCloudActiveEnvironment;\n  }\n\n  /**\n   * Throws ResponseErrorException if connection with provided ID is not found in ConnectionConfigurationRepository\n   */\n  public SonarQubeClient getValidClientOrThrow(String connectionId) {\n    return clientsByConnectionId.computeIfAbsent(connectionId, this::createSonarQubeClient)\n      .orElseThrow(() -> new ResponseErrorException(new ResponseError(SonarLintRpcErrorCode.CONNECTION_NOT_FOUND, \"Connection '\" + connectionId + \"' is not valid\", connectionId)));\n  }\n\n  public void withActiveClient(String connectionId, Consumer<ServerApi> serverApiConsumer) {\n    getValidClient(connectionId).ifPresent(connection -> connection.withClientApi(serverApiConsumer));\n  }\n\n  public <T> Optional<T> withActiveClientAndReturn(String connectionId, Function<ServerApi, T> serverApiConsumer) {\n    return getValidClient(connectionId).map(connection -> connection.withClientApiAndReturn(serverApiConsumer));\n  }\n\n  public <T> Optional<T> withActiveClientFlatMapOptionalAndReturn(String connectionId, Function<ServerApi, Optional<T>> serverApiConsumer) {\n    return getValidClient(connectionId).map(connection -> connection.withClientApiAndReturn(serverApiConsumer)).flatMap(Function.identity());\n  }\n\n  private Optional<SonarQubeClient> getValidClient(String connectionId) {\n    return clientsByConnectionId.computeIfAbsent(connectionId, this::createSonarQubeClient)\n      .filter(connection -> isConnectionActive(connectionId, connection));\n  }\n\n  private Optional<SonarQubeClient> createSonarQubeClient(String connectionId) {\n    var connection = connectionRepository.getConnectionById(connectionId);\n    if (connection == null) {\n      LOG.debug(\"Connection '{}' is gone\", connectionId);\n      return Optional.empty();\n    }\n    var credentials = getValidCredentialsFromClient(connectionId);\n    if (credentials.isEmpty()) {\n      client.invalidToken(new InvalidTokenParams(connectionId));\n      return Optional.empty();\n    }\n    var endpointParams = connection.getEndpointParams();\n    var isBearerSupported = checkIfBearerIsSupported(endpointParams);\n    var httpClient = credentials.get().map(\n      tokenDto -> httpClientProvider.getHttpClientWithPreemptiveAuth(tokenDto.getToken(), isBearerSupported),\n      userPass -> httpClientProvider.getHttpClientWithPreemptiveAuth(userPass.getUsername(), userPass.getPassword()));\n    return Optional.of(new SonarQubeClient(connectionId, new ServerApi(endpointParams, httpClient), credentials.get(), client));\n  }\n\n  private static boolean isConnectionActive(String connectionId, SonarQubeClient connection) {\n    var isValid = connection.isActive();\n    if (!isValid) {\n      LOG.debug(\"Connection '{}' is invalid\", connectionId);\n    }\n    return isValid;\n  }\n\n  public ServerApi getForTransientConnection(Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> transientConnection) {\n    var endpointParams = transientConnection.map(\n      sq -> new EndpointParams(sq.getServerUrl(), null, false, null),\n      sc -> {\n        var region = SonarCloudRegion.valueOf(sc.getRegion().toString());\n        return new EndpointParams(sonarCloudActiveEnvironment.getUri(region).toString(), sonarCloudActiveEnvironment.getApiUri(region).toString(), true, sc.getOrganization());\n      });\n    var httpClient = transientConnection\n      .map(TransientSonarQubeConnectionDto::getCredentials, TransientSonarCloudConnectionDto::getCredentials)\n      .map(\n        tokenDto -> {\n          var isBearerSupported = checkIfBearerIsSupported(endpointParams);\n          return httpClientProvider.getHttpClientWithPreemptiveAuth(tokenDto.getToken(), isBearerSupported);\n        },\n        userPass -> httpClientProvider.getHttpClientWithPreemptiveAuth(userPass.getUsername(), userPass.getPassword()));\n    return new ServerApi(new ServerApiHelper(endpointParams, httpClient));\n  }\n\n  public Optional<WebSocketClient> getValidWebSocketClient(String connectionId) {\n    return getValidClient(connectionId)\n      .map(validClient -> {\n        var credentials = validClient.getCredentials();\n        if (credentials.isRight()) {\n          // We are normally only supporting tokens for SonarCloud connections\n          throw new IllegalStateException(\"Expected token for connection \" + connectionId);\n        }\n        return httpClientProvider.getWebSocketClient(credentials.getLeft().getToken());\n      });\n  }\n\n  private boolean checkIfBearerIsSupported(EndpointParams params) {\n    if (params.isSonarCloud()) {\n      return true;\n    }\n    var cancelMonitor = new SonarLintCancelMonitor();\n    var serverApi = new ServerApi(params, httpClientProvider.getHttpClientWithoutAuth());\n    var status = serverApi.system().getStatus(cancelMonitor);\n    var serverChecker = new ServerVersionAndStatusChecker(serverApi);\n    return serverChecker.isSupportingBearer(status);\n  }\n\n  private Optional<Either<TokenDto, UsernamePasswordDto>> getValidCredentialsFromClient(String connectionId) {\n    var response = client.getCredentials(new GetCredentialsParams(connectionId)).join();\n    var credentials = response.getCredentials();\n    return validateCredentials(connectionId, credentials);\n  }\n\n  private static Optional<Either<TokenDto, UsernamePasswordDto>> validateCredentials(String connectionId, @Nullable Either<TokenDto, UsernamePasswordDto> credentials) {\n    if (credentials == null) {\n      LOG.error(\"No credentials for connection \" + connectionId);\n      return Optional.empty();\n    }\n    if (credentials.isLeft()) {\n      if (isNullOrEmpty(credentials.getLeft().getToken())) {\n        LOG.error(\"No token for connection \" + connectionId);\n        return Optional.empty();\n      }\n      return Optional.of(credentials);\n    }\n    var right = credentials.getRight();\n    if (right == null) {\n      LOG.error(\"No username/password for connection \" + connectionId);\n      return Optional.empty();\n    }\n    if (isNullOrEmpty(right.getUsername())) {\n      LOG.error(\"No username for connection \" + connectionId);\n      return Optional.empty();\n    }\n    if (isNullOrEmpty(right.getPassword())) {\n      LOG.error(\"No password for connection \" + connectionId);\n      return Optional.empty();\n    }\n    return Optional.of(credentials);\n  }\n\n  private static boolean isNullOrEmpty(@Nullable String s) {\n    return s == null || s.trim().isEmpty();\n  }\n\n  @EventListener\n  public void onConnectionRemoved(ConnectionConfigurationRemovedEvent event) {\n    clientsByConnectionId.remove(event.removedConnectionId());\n  }\n\n  @EventListener\n  public void onConnectionUpdated(ConnectionConfigurationUpdatedEvent event) {\n    clientsByConnectionId.remove(event.updatedConnectionId());\n  }\n\n  @EventListener\n  public void onCredentialsChanged(ConnectionCredentialsChangedEvent event) {\n    clientsByConnectionId.remove(event.getConnectionId());\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/TextSearchIndex.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.TreeMap;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\n\n/**\n * Indexes text associated to objects, and performs full text search to find matching objects.\n * It is a positional index, so it supports queries consisted of multiple terms, in which case it will find partial term matches in sequence (distance = 1).\n * The result is sorted by score. The score of each term matches is the ratio of the term matches (1 for exact match),\n * and the global score is the sum of the term's scores in the object divided by the total term frequency in the object.\n * <br/><br/>\n * The generic type should properly implement equals and hashCode.\n * <b>An object cannot be indexed twice</b>.\n * <br/><br/>\n * Performance of indexing: O(N)\n * Performance of search: O(log N) on the number of indexed terms + O(N) on the number of results\n */\npublic class TextSearchIndex<T> {\n\n  /**\n   * Any non-letter, non-digit symbols in a row. Unlike [\\W]+ dos include underscore as many relevant strings use it as a separator.\n   */\n  private static final String DEFAULT_SPLIT_PATTERN = \"[^a-zA-Z0-9]+\";\n  private final Pattern splitPattern = Pattern.compile(DEFAULT_SPLIT_PATTERN);\n  private final TreeMap<String, List<DictEntry>> termToObj;\n  private final Map<T, Integer> objToWordFrequency;\n\n  public TextSearchIndex() {\n    termToObj = new TreeMap<>();\n    objToWordFrequency = new HashMap<>();\n  }\n\n  public int size() {\n    return objToWordFrequency.size();\n  }\n\n  public boolean isEmpty() {\n    return objToWordFrequency.isEmpty();\n  }\n\n  public void index(T obj, String text) {\n    if (objToWordFrequency.containsKey(obj)) {\n      throw new IllegalArgumentException(\"Already indexed\");\n    }\n    var terms = tokenize(text);\n    objToWordFrequency.put(obj, terms.size());\n\n    var i = 0;\n    for (String s : terms) {\n      addToDictionary(s, i, obj);\n      i++;\n    }\n  }\n\n  /**\n   * Search for indexed objects based on a query. Results will be sorted by score (highest first).\n   * Score is in the interval [0,1].\n   *\n   * @return A map of results reverse-sorted by value (score). Can be empty, but never null\n   */\n  public Map<T, Double> search(String query) {\n    var terms = tokenize(query);\n\n    if (terms.isEmpty()) {\n      return Collections.emptyMap();\n    }\n\n    List<SearchResult> matched;\n\n    // positional search\n    var it = terms.iterator();\n    matched = searchTerm(it.next());\n\n    while (it.hasNext()) {\n      var termMatches = searchTerm(it.next());\n      matched = matchPositional(matched, termMatches, 1);\n\n      if (matched.isEmpty()) {\n        break;\n      }\n    }\n\n    // convert results and calc score\n    return prepareResult(matched);\n  }\n\n  private List<SearchResult> matchPositional(List<SearchResult> previousMatches, List<SearchResult> termMatches, int maxDistance) {\n    List<SearchResult> matches = new LinkedList<>();\n\n    for (SearchResult e1 : previousMatches) {\n      for (SearchResult e2 : termMatches) {\n        if (!e1.obj.equals(e2.obj)) {\n          continue;\n        }\n\n        var dist = e2.lastIdx - e1.lastIdx;\n        if (dist > 0 && dist <= maxDistance) {\n          e2.score += e1.score;\n          matches.add(e2);\n        }\n      }\n    }\n    return matches;\n  }\n\n  private Map<T, Double> prepareResult(List<SearchResult> entries) {\n    Map<T, Double> objToScore = new HashMap<>();\n\n    for (SearchResult e : entries) {\n      var score = e.score / objToWordFrequency.get(e.obj);\n      var previousScore = objToScore.get(e.obj);\n\n      if (previousScore == null || previousScore < score) {\n        objToScore.put(e.obj, score);\n      }\n    }\n\n    return objToScore.entrySet().stream()\n      .sorted(Map.Entry.<T, Double>comparingByValue().reversed())\n      .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));\n  }\n\n  /**\n   * Returns any term prefixed by the given text\n   */\n  private List<SearchResult> searchTerm(String termPrefix) {\n    List<SearchResult> entries = new LinkedList<>();\n\n    var tailMap = termToObj.tailMap(termPrefix);\n    for (Entry<String, List<DictEntry>> e : tailMap.entrySet()) {\n      if (!e.getKey().startsWith(termPrefix)) {\n        break;\n      }\n      var score = ((double) termPrefix.length()) / e.getKey().length();\n      e.getValue().stream()\n        .map(v -> new SearchResult(score, v.obj, v.tokenIndex))\n        .forEach(entries::add);\n    }\n\n    return entries;\n  }\n\n  private void addToDictionary(String token, int tokenIndex, T obj) {\n    var entries = termToObj.computeIfAbsent(token, t -> new LinkedList<>());\n    entries.add(new DictEntry(obj, tokenIndex));\n  }\n\n  private List<String> tokenize(String text) {\n    var split = splitPattern.split(text);\n    List<String> terms = new ArrayList<>(split.length);\n\n    for (String s : split) {\n      if (!s.isEmpty()) {\n        terms.add(s.toLowerCase(Locale.ENGLISH));\n      }\n    }\n\n    return terms;\n  }\n\n  public List<T> getAll() {\n    return List.copyOf(objToWordFrequency.keySet());\n  }\n\n  private class SearchResult {\n    private double score;\n    private final T obj;\n    private final int lastIdx;\n\n    public SearchResult(double score, T obj, int lastIdx) {\n      this.score = score;\n      this.obj = obj;\n      this.lastIdx = lastIdx;\n    }\n  }\n\n  private class DictEntry {\n    T obj;\n    int tokenIndex;\n\n    public DictEntry(T obj, int tokenIndex) {\n      this.obj = obj;\n      this.tokenIndex = tokenIndex;\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/TokenGeneratorHelper.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport java.util.concurrent.CompletableFuture;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.embedded.server.AwaitingUserTokenFutureRepository;\nimport org.sonarsource.sonarlint.core.embedded.server.EmbeddedServer;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth.HelpGenerateUserTokenParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth.HelpGenerateUserTokenResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.OpenUrlInBrowserParams;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\n\nimport static org.sonarsource.sonarlint.core.serverapi.UrlUtils.urlEncode;\n\npublic class TokenGeneratorHelper {\n\n  private final SonarLintRpcClient client;\n  private final EmbeddedServer embeddedServer;\n  private final AwaitingUserTokenFutureRepository awaitingUserTokenFutureRepository;\n\n  private final String clientName;\n\n  public TokenGeneratorHelper(SonarLintRpcClient client, EmbeddedServer embeddedServer, AwaitingUserTokenFutureRepository awaitingUserTokenFutureRepository,\n    InitializeParams params) {\n    this.client = client;\n    this.embeddedServer = embeddedServer;\n    this.awaitingUserTokenFutureRepository = awaitingUserTokenFutureRepository;\n    this.clientName = params.getClientConstantInfo().getName();\n  }\n\n  public HelpGenerateUserTokenResponse helpGenerateUserToken(String serverBaseUrl, @Nullable HelpGenerateUserTokenParams.Utm utm, SonarLintCancelMonitor cancelMonitor) {\n    client.openUrlInBrowser(new OpenUrlInBrowserParams(ServerApiHelper.concat(serverBaseUrl, getUserTokenGenerationRelativeUrlToOpen(utm))));\n    var shouldWaitIncomingToken = embeddedServer.isStarted();\n    if (shouldWaitIncomingToken) {\n      var future = new CompletableFuture<HelpGenerateUserTokenResponse>();\n      awaitingUserTokenFutureRepository.addExpectedResponse(serverBaseUrl, future);\n      cancelMonitor.onCancel(() -> future.cancel(false));\n      return future.join();\n    } else {\n      return new HelpGenerateUserTokenResponse(null);\n    }\n  }\n\n  private String getUserTokenGenerationRelativeUrlToOpen(@Nullable HelpGenerateUserTokenParams.Utm utm) {\n    var params = new StringBuilder(\"ideName=\" + urlEncode(clientName) + (embeddedServer.isStarted() ? (\"&port=\" + embeddedServer.getPort()) : \"\"));\n    if (utm != null) {\n      params.append(String.format(\"&utm_medium=%s&utm_source=%s&utm_content=%s&utm_term=%s\",\n          urlEncode(utm.getMedium()), urlEncode(utm.getSource()), urlEncode(utm.getContent()), urlEncode(utm.getTerm())\n        ));\n    }\n    return \"/sonarlint/auth?\" + params;\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/UserPaths.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.SonarLintUserHome;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\n\npublic class UserPaths {\n\n  public static UserPaths from(InitializeParams initializeParams) {\n    var userHome = computeUserHome(initializeParams.getSonarlintUserHome());\n    createFolderIfNeeded(userHome);\n    var workDir = Optional.ofNullable(initializeParams.getWorkDir()).orElse(userHome.resolve(\"work\"));\n    createFolderIfNeeded(workDir);\n    var storageRoot = Optional.ofNullable(initializeParams.getStorageRoot()).orElse(userHome.resolve(\"storage\"));\n    createFolderIfNeeded(storageRoot);\n    return new UserPaths(userHome, workDir, storageRoot, initializeParams.getTelemetryConstantAttributes().getProductKey());\n  }\n\n  static Path computeUserHome(@Nullable String clientUserHome) {\n    if (clientUserHome != null) {\n      return Paths.get(clientUserHome);\n    }\n    return SonarLintUserHome.get();\n  }\n\n  private static void createFolderIfNeeded(Path path) {\n    try {\n      Files.createDirectories(path);\n    } catch (IOException e) {\n      throw new IllegalStateException(\"Cannot create directory '\" + path + \"'\", e);\n    }\n  }\n\n  private final Path userHome;\n  private final Path workDir;\n  private final Path storageRoot;\n  private final String productKey;\n\n  private UserPaths(Path userHome, Path workDir, Path storageRoot, String productKey) {\n    this.userHome = userHome;\n    this.workDir = workDir;\n    this.storageRoot = storageRoot;\n    this.productKey = productKey;\n  }\n\n  public Path getUserHome() {\n    return userHome;\n  }\n\n  public Path getWorkDir() {\n    return workDir;\n  }\n\n  public Path getStorageRoot() {\n    return storageRoot;\n  }\n\n  public Path getHomeIdeSpecificDir(String intermediateDir) {\n    return userHome.resolve(intermediateDir).resolve(productKey);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/VersionSoonUnsupportedHelper.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport com.google.common.util.concurrent.MoreExecutors;\nimport jakarta.annotation.PreDestroy;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.TimeUnit;\nimport org.sonarsource.sonarlint.core.commons.ConnectionKind;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.ExecutorServiceShutdownWatchable;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.commons.util.FailSafeExecutors;\nimport org.sonarsource.sonarlint.core.event.BindingConfigChangedEvent;\nimport org.sonarsource.sonarlint.core.event.ConfigurationScopesAddedWithBindingEvent;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowSoonUnsupportedMessageParams;\nimport org.sonarsource.sonarlint.core.serverconnection.VersionUtils;\nimport org.sonarsource.sonarlint.core.sync.SynchronizationService;\nimport org.springframework.context.event.EventListener;\n\npublic class VersionSoonUnsupportedHelper {\n\n  private static final String UNSUPPORTED_NOTIFICATION_ID = \"sonarlint.unsupported.%s.%s.id\";\n  private static final String NOTIFICATION_MESSAGE = \"The version '%s' used by the current connection '%s' will be soon unsupported. \" +\n    \"Please consider upgrading to the latest %s LTS version to ensure continued support and access to the latest features.\";\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final SonarLintRpcClient client;\n  private final ConfigurationRepository configRepository;\n  private final ConnectionConfigurationRepository connectionRepository;\n  private final SonarQubeClientManager sonarQubeClientManager;\n  private final SynchronizationService synchronizationService;\n  private final Map<String, Version> cacheConnectionIdPerVersion = new ConcurrentHashMap<>();\n  private final ExecutorServiceShutdownWatchable<?> executorService;\n\n  public VersionSoonUnsupportedHelper(SonarLintRpcClient client, ConfigurationRepository configRepository, SonarQubeClientManager sonarQubeClientManager,\n    ConnectionConfigurationRepository connectionRepository, SynchronizationService synchronizationService) {\n    this.client = client;\n    this.configRepository = configRepository;\n    this.connectionRepository = connectionRepository;\n    this.sonarQubeClientManager = sonarQubeClientManager;\n    this.synchronizationService = synchronizationService;\n    this.executorService = new ExecutorServiceShutdownWatchable<>(FailSafeExecutors.newSingleThreadExecutor(\"Version Soon Unsupported Helper\"));\n  }\n\n  @EventListener\n  public void configurationScopesAdded(ConfigurationScopesAddedWithBindingEvent event) {\n    var configScopeIds = event.getConfigScopeIds();\n    checkIfSoonUnsupportedOncePerConnection(configScopeIds);\n  }\n\n  @EventListener\n  public void bindingConfigChanged(BindingConfigChangedEvent event) {\n    var configScopeId = event.configScopeId();\n    var connectionId = event.newConfig().connectionId();\n    if (connectionId != null) {\n      queueCheckIfSoonUnsupported(connectionId, configScopeId);\n    }\n  }\n\n  private void checkIfSoonUnsupportedOncePerConnection(Set<String> configScopeIds) {\n    // We will check once per connection, and send the notification for the first config scope associated to this connection\n    var oneConfigScopeIdPerConnection = new HashMap<String, String>();\n    configScopeIds.forEach(configScopeId -> {\n      var effectiveBinding = configRepository.getEffectiveBinding(configScopeId);\n      if (effectiveBinding.isPresent()) {\n        var connectionId = effectiveBinding.get().connectionId();\n        oneConfigScopeIdPerConnection.putIfAbsent(connectionId, configScopeId);\n      }\n    });\n    oneConfigScopeIdPerConnection.forEach(this::queueCheckIfSoonUnsupported);\n  }\n\n  private void queueCheckIfSoonUnsupported(String connectionId, String configScopeId) {\n    var cancelMonitor = new SonarLintCancelMonitor();\n    cancelMonitor.watchForShutdown(executorService);\n    executorService.execute(() -> {\n      try {\n        var connection = connectionRepository.getConnectionById(connectionId);\n        if (connection != null && connection.getKind() == ConnectionKind.SONARQUBE) {\n          sonarQubeClientManager.withActiveClient(connectionId, serverApi -> {\n            var version = synchronizationService.readOrSynchronizeServerVersion(connectionId, serverApi, cancelMonitor);\n            var isCached = cacheConnectionIdPerVersion.containsKey(connectionId) && cacheConnectionIdPerVersion.get(connectionId).compareTo(version) == 0;\n            if (!isCached && VersionUtils.isVersionSupportedDuringGracePeriod(version)) {\n              client.showSoonUnsupportedMessage(\n                new ShowSoonUnsupportedMessageParams(\n                  String.format(UNSUPPORTED_NOTIFICATION_ID, connectionId, version.getName()),\n                  configScopeId,\n                  String.format(NOTIFICATION_MESSAGE, version.getName(), connectionId, VersionUtils.getCurrentLts())));\n              LOG.debug(String.format(\"Connection '%s' with version '%s' is detected to be soon unsupported\",\n                connection.getConnectionId(), version.getName()));\n            }\n            cacheConnectionIdPerVersion.put(connectionId, version);\n          });\n        }\n      } catch (Exception e) {\n        LOG.error(\"Error while checking if soon unsupported\", e);\n      }\n    });\n  }\n\n  @PreDestroy\n  public void shutdown() {\n    if (!MoreExecutors.shutdownAndAwaitTermination(executorService, 1, TimeUnit.SECONDS)) {\n      LOG.warn(\"Unable to stop version soon unsupported executor service in a timely manner\");\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/active/rules/ActiveRuleDetails.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.active.rules;\n\nimport java.util.Map;\nimport javax.annotation.Nullable;\nimport org.apache.commons.lang3.StringUtils;\nimport org.sonar.api.batch.rule.ActiveRule;\nimport org.sonar.api.rule.RuleKey;\nimport org.sonarsource.sonarlint.core.commons.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\n\npublic record ActiveRuleDetails(\n  String ruleKeyString,\n  String languageKey,\n  @Nullable Map<String, String> params,\n  @Nullable String fullTemplateRuleKey,\n  IssueSeverity issueSeverity,\n  RuleType type,\n  CleanCodeAttribute cleanCodeAttribute,\n  Map<SoftwareQuality, ImpactSeverity> impacts,\n  @Nullable VulnerabilityProbability vulnerabilityProbability) implements ActiveRule {\n\n  public ActiveRuleDetails {\n    if (params == null) {\n      params = Map.of();\n    }\n  }\n\n  @Override\n  public RuleKey ruleKey() {\n    return RuleKey.parse(ruleKeyString);\n  }\n\n  @Override\n  public String severity() {\n    throw new UnsupportedOperationException(\"severity not supported in SonarLint\");\n  }\n\n  @Override\n  public String language() {\n    return languageKey();\n  }\n\n  @Override\n  public String param(String key) {\n    return params().get(key);\n  }\n\n  @Override\n  public String internalKey() {\n    // This is a hack for old versions of CFamily (https://github.com/SonarSource/sonar-cpp/pull/1598)\n    return ruleKey().rule();\n  }\n\n  @Override\n  public String templateRuleKey() {\n    if (!StringUtils.isEmpty(fullTemplateRuleKey)) {\n      // The SQ plugin API expect template rule key to be only the \"rule\" part of the key (without the repository key)\n      var ruleKey = RuleKey.parse(fullTemplateRuleKey);\n      return ruleKey.rule();\n    }\n    return null;\n  }\n\n  @Override\n  public String qpKey() {\n    throw new UnsupportedOperationException(\"qpKey not supported in SonarLint\");\n  }\n\n  @Override\n  public String toString() {\n    var sb = new StringBuilder();\n    sb.append(ruleKeyString());\n    if (!params.isEmpty()) {\n      sb.append(params);\n    }\n    return sb.toString();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/active/rules/ActiveRulesService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.active.rules;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.Predicate;\nimport javax.annotation.Nullable;\nimport org.apache.commons.lang3.StringUtils;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseError;\nimport org.jetbrains.annotations.NotNull;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.RuleKey;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.event.BindingConfigChangedEvent;\nimport org.sonarsource.sonarlint.core.event.SonarServerEventReceivedEvent;\nimport org.sonarsource.sonarlint.core.languages.LanguageSupportRepository;\nimport org.sonarsource.sonarlint.core.mode.SeverityModeService;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.rules.RulesRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.EffectiveRuleDetailsDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.GetStandaloneRuleDescriptionResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.StandaloneRuleConfigDto;\nimport org.sonarsource.sonarlint.core.rule.extractor.SonarLintRuleDefinition;\nimport org.sonarsource.sonarlint.core.rules.RuleDetails;\nimport org.sonarsource.sonarlint.core.rules.RuleDetailsAdapter;\nimport org.sonarsource.sonarlint.core.rules.RuleNotFoundException;\nimport org.sonarsource.sonarlint.core.rules.RulesService;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.push.RuleSetChangedEvent;\nimport org.sonarsource.sonarlint.core.serverapi.rules.ServerActiveRule;\nimport org.sonarsource.sonarlint.core.serverapi.rules.ServerRule;\nimport org.sonarsource.sonarlint.core.serverconnection.AnalyzerConfiguration;\nimport org.sonarsource.sonarlint.core.serverconnection.RuleSet;\nimport org.sonarsource.sonarlint.core.serverconnection.SonarServerSettingsChangedEvent;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.StorageException;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.sonarsource.sonarlint.core.sync.AnalyzerConfigurationSynchronized;\nimport org.springframework.context.ApplicationEventPublisher;\nimport org.springframework.context.event.EventListener;\n\nimport static java.util.Optional.ofNullable;\nimport static java.util.function.Predicate.not;\nimport static java.util.stream.Collectors.toSet;\nimport static org.apache.commons.lang3.StringUtils.trimToNull;\nimport static org.sonarsource.sonarlint.core.commons.CleanCodeAttribute.CONVENTIONAL;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SECURITY_HOTSPOTS;\nimport static org.sonarsource.sonarlint.core.rules.RulesService.COULD_NOT_FIND_RULE;\nimport static org.sonarsource.sonarlint.core.rules.RulesService.IN_EMBEDDED_RULES;\n\npublic class ActiveRulesService {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final ConfigurationRepository configurationRepository;\n  private final LanguageSupportRepository languageSupportRepository;\n  private final SonarQubeClientManager sonarQubeClientManager;\n  private final SeverityModeService severityModeService;\n  private final StorageService storageService;\n  private final RulesRepository rulesRepository;\n  private final ConnectionConfigurationRepository connectionConfigurationRepository;\n  private final boolean hotspotEnabled;\n  private final ApplicationEventPublisher eventPublisher;\n\n  private final Map<String, StandaloneRuleConfigDto> standaloneRuleConfig = new ConcurrentHashMap<>();\n  private List<ActiveRuleDetails> standaloneActiveRules;\n  private final Map<Binding, List<ActiveRuleDetails>> activeRulesPerBinding = new ConcurrentHashMap<>();\n\n  public ActiveRulesService(ConfigurationRepository configurationRepository, LanguageSupportRepository languageSupportRepository, SonarQubeClientManager sonarQubeClientManager,\n    SeverityModeService severityModeService, StorageService storageService, RulesRepository rulesRepository, ConnectionConfigurationRepository connectionConfigurationRepository,\n    InitializeParams initializeParams, ApplicationEventPublisher eventPublisher) {\n    this.configurationRepository = configurationRepository;\n    this.languageSupportRepository = languageSupportRepository;\n    this.sonarQubeClientManager = sonarQubeClientManager;\n    this.severityModeService = severityModeService;\n    this.storageService = storageService;\n    this.rulesRepository = rulesRepository;\n    this.connectionConfigurationRepository = connectionConfigurationRepository;\n    this.hotspotEnabled = initializeParams.getBackendCapabilities().contains(SECURITY_HOTSPOTS);\n    this.eventPublisher = eventPublisher;\n    this.standaloneRuleConfig.putAll(initializeParams.getStandaloneRuleConfigByKey());\n  }\n\n  public void updateStandaloneRulesConfiguration(Map<String, StandaloneRuleConfigDto> standaloneRuleConfig) {\n    this.standaloneRuleConfig.clear();\n    this.standaloneRuleConfig.putAll(standaloneRuleConfig);\n    this.standaloneActiveRules = null;\n    eventPublisher.publishEvent(new StandaloneRulesConfigurationChanged(standaloneRuleConfig));\n  }\n\n  public Map<String, StandaloneRuleConfigDto> getStandaloneRuleConfig() {\n    return Collections.unmodifiableMap(standaloneRuleConfig);\n  }\n\n  public GetStandaloneRuleDescriptionResponse getStandaloneRuleDescription(String ruleKey) {\n    var embeddedRule = rulesRepository.getEmbeddedRule(ruleKey);\n    if (embeddedRule.isEmpty()) {\n      var error = new ResponseError(SonarLintRpcErrorCode.RULE_NOT_FOUND, COULD_NOT_FIND_RULE + ruleKey + IN_EMBEDDED_RULES, new Object[] {ruleKey});\n      throw new ResponseErrorException(error);\n    }\n    var ruleDefinition = embeddedRule.get();\n    var ruleDetails = RuleDetails.from(ruleDefinition, standaloneRuleConfig.get(ruleKey));\n\n    return new GetStandaloneRuleDescriptionResponse(RulesService.convert(ruleDefinition), RuleDetailsAdapter.transformDescriptions(ruleDetails, null));\n  }\n\n  public synchronized List<ActiveRuleDetails> getStandaloneActiveRules() {\n    if (standaloneActiveRules == null) {\n      standaloneActiveRules = buildStandaloneActiveRules();\n    }\n    return standaloneActiveRules;\n  }\n\n  private List<ActiveRuleDetails> buildStandaloneActiveRules() {\n    Set<String> excludedRules = standaloneRuleConfig.entrySet().stream()\n      .filter(not(e -> e.getValue().isActive()))\n      .map(Map.Entry::getKey).collect(toSet());\n    Set<String> includedRules = standaloneRuleConfig.entrySet().stream()\n      .filter(e -> e.getValue().isActive())\n      .map(Map.Entry::getKey)\n      .filter(r -> !excludedRules.contains(r))\n      .collect(toSet());\n\n    var filteredActiveRules = new ArrayList<SonarLintRuleDefinition>();\n\n    var allRulesDefinitions = rulesRepository.getEmbeddedRules().stream()\n      .filter(rule -> !rule.getType().equals(RuleType.SECURITY_HOTSPOT))\n      .toList();\n\n    filteredActiveRules.addAll(allRulesDefinitions.stream()\n      .filter(SonarLintRuleDefinition::isActiveByDefault)\n      .filter(isExcludedByConfiguration(excludedRules))\n      .toList());\n    filteredActiveRules.addAll(allRulesDefinitions.stream()\n      .filter(r -> !r.isActiveByDefault())\n      .filter(isIncludedByConfiguration(includedRules))\n      .toList());\n\n    return filteredActiveRules.stream().map(ruleDefinition -> {\n      Map<String, String> effectiveParams = new HashMap<>(ruleDefinition.getDefaultParams());\n      ofNullable(standaloneRuleConfig.get(ruleDefinition.getKey())).ifPresent(config -> effectiveParams.putAll(config.getParamValueByKey()));\n      // No template rules in standalone mode\n      return new ActiveRuleDetails(ruleDefinition.getKey(), ruleDefinition.getLanguage().getSonarLanguageKey(), effectiveParams, null, ruleDefinition.getDefaultSeverity(),\n        ruleDefinition.getType(), ruleDefinition.getCleanCodeAttribute().orElse(CONVENTIONAL), ruleDefinition.getDefaultImpacts(),\n        ruleDefinition.getVulnerabilityProbability().orElse(null));\n    })\n      .toList();\n  }\n\n  public List<ActiveRuleDetails> getConnectedActiveRules(Binding binding) {\n    return activeRulesPerBinding.computeIfAbsent(binding, k -> buildConnectedActiveRules(binding));\n  }\n\n  private List<ActiveRuleDetails> buildConnectedActiveRules(Binding binding) {\n    var analyzerConfig = storageService.binding(binding).analyzerConfiguration().read();\n    var ruleSetByLanguageKey = analyzerConfig.getRuleSetByLanguageKey();\n    var result = new ArrayList<ActiveRuleDetails>();\n    ruleSetByLanguageKey.entrySet()\n      .stream().filter(e -> SonarLanguage.forKey(e.getKey()).filter(l -> languageSupportRepository.getEnabledLanguagesInConnectedMode().contains(l)).isPresent())\n      .forEach(e -> {\n        var languageKey = e.getKey();\n        var ruleSet = e.getValue();\n\n        LOG.debug(\"  * {}: {} active rules\", languageKey, ruleSet.getRules().size());\n        var missingRuleOrTemplateDefinitions = new LinkedHashSet<>();\n        for (ServerActiveRule possiblyDeprecatedActiveRuleFromStorage : ruleSet.getRules()) {\n          var activeRuleFromStorage = tryConvertDeprecatedKeys(binding.connectionId(), possiblyDeprecatedActiveRuleFromStorage);\n          SonarLintRuleDefinition ruleOrTemplateDefinition;\n          if (StringUtils.isNotBlank(activeRuleFromStorage.getTemplateKey())) {\n            ruleOrTemplateDefinition = rulesRepository.getRule(binding.connectionId(), activeRuleFromStorage.getTemplateKey()).orElse(null);\n            if (ruleOrTemplateDefinition == null) {\n              LOG.debug(\"Rule {} is enabled on the server, but its template {} is not available in SonarLint\", activeRuleFromStorage.getRuleKey(),\n                activeRuleFromStorage.getTemplateKey());\n              continue;\n            }\n          } else {\n            ruleOrTemplateDefinition = rulesRepository.getRule(binding.connectionId(), activeRuleFromStorage.getRuleKey()).orElse(null);\n            if (ruleOrTemplateDefinition == null) {\n              missingRuleOrTemplateDefinitions.add(activeRuleFromStorage.getRuleKey());\n              continue;\n            }\n          }\n          if (shouldIncludeRuleForAnalysis(binding.connectionId(), ruleOrTemplateDefinition)) {\n            result.add(buildActiveRule(ruleOrTemplateDefinition, activeRuleFromStorage));\n          }\n        }\n        if (!missingRuleOrTemplateDefinitions.isEmpty()) {\n          LOG.debug(\"The following rules are enabled on the server, but not available in SonarLint: {}\", missingRuleOrTemplateDefinitions);\n        }\n      });\n    if (languageSupportRepository.getEnabledLanguagesInConnectedMode().contains(SonarLanguage.IPYTHON)) {\n      // Jupyter Notebooks are not yet fully supported in connected mode, use standalone rule configuration in the meantime\n      var iPythonRules = getStandaloneActiveRules()\n        .stream().filter(rule -> rule.languageKey().equals(SonarLanguage.IPYTHON.getSonarLanguageKey()))\n        .toList();\n      result.addAll(iPythonRules);\n    }\n    return result;\n  }\n\n  public ActiveRuleDetails buildActiveRule(SonarLintRuleDefinition ruleOrTemplateDefinition, ServerActiveRule activeRule) {\n    return new ActiveRuleDetails(activeRule.getRuleKey(),\n      ruleOrTemplateDefinition.getLanguage().getSonarLanguageKey(),\n      getEffectiveParams(ruleOrTemplateDefinition, activeRule),\n      trimToNull(activeRule.getTemplateKey()), activeRule.getSeverity(), ruleOrTemplateDefinition.getType(),\n      ruleOrTemplateDefinition.getCleanCodeAttribute().orElse(CONVENTIONAL),\n      RuleDetails.mergeImpacts(ruleOrTemplateDefinition.getDefaultImpacts(), activeRule.getOverriddenImpacts()),\n      ruleOrTemplateDefinition.getVulnerabilityProbability().orElse(null));\n  }\n\n  private static Map<String, String> getEffectiveParams(SonarLintRuleDefinition ruleOrTemplateDefinition, ServerActiveRule activeRule) {\n    Map<String, String> effectiveParams = new HashMap<>(ruleOrTemplateDefinition.getDefaultParams());\n    activeRule.getParams().forEach((paramName, paramValue) -> {\n      if (!ruleOrTemplateDefinition.getParams().containsKey(paramName)) {\n        LOG.debug(\"Rule parameter '{}' for rule '{}' does not exist in embedded analyzer, ignoring.\", paramName, ruleOrTemplateDefinition.getKey());\n        return;\n      }\n      effectiveParams.put(paramName, paramValue);\n    });\n    return effectiveParams;\n  }\n\n  private boolean shouldIncludeRuleForAnalysis(String connectionId, SonarLintRuleDefinition ruleDefinition) {\n    var isHotspot = ruleDefinition.getType().equals(RuleType.SECURITY_HOTSPOT);\n    return !isHotspot || (hotspotEnabled && isHotspotTrackingPossible(connectionId));\n  }\n\n  private boolean isHotspotTrackingPossible(String connectionId) {\n    var connection = connectionConfigurationRepository.getConnectionById(connectionId);\n    if (connection == null) {\n      // Connection is gone\n      return false;\n    }\n    // when storage is not present, consider hotspots should not be detected\n    return storageService.connection(connectionId).serverInfo().read().isPresent();\n  }\n\n  private static Predicate<? super SonarLintRuleDefinition> isExcludedByConfiguration(Set<String> excludedRules) {\n    return r -> {\n      if (excludedRules.contains(r.getKey())) {\n        return false;\n      }\n      for (String deprecatedKey : r.getDeprecatedKeys()) {\n        if (excludedRules.contains(deprecatedKey)) {\n          LOG.warn(\"Rule '{}' was excluded using its deprecated key '{}'. Please fix your configuration.\", r.getKey(), deprecatedKey);\n          return false;\n        }\n      }\n      return true;\n    };\n  }\n\n  private static Predicate<? super SonarLintRuleDefinition> isIncludedByConfiguration(Set<String> includedRules) {\n    return r -> {\n      if (includedRules.contains(r.getKey())) {\n        return true;\n      }\n      for (String deprecatedKey : r.getDeprecatedKeys()) {\n        if (includedRules.contains(deprecatedKey)) {\n          LOG.warn(\"Rule '{}' was included using its deprecated key '{}'. Please fix your configuration.\", r.getKey(), deprecatedKey);\n          return true;\n        }\n      }\n      return false;\n    };\n  }\n\n  public void evictFor(String connectionId) {\n    LOG.debug(\"Evict cached active rules for connection '{}'\", connectionId);\n    activeRulesPerBinding.entrySet().removeIf(\n      entry -> entry.getKey().connectionId().equals(connectionId)\n    );\n  }\n\n  @EventListener\n  public void settingsChanged(SonarServerSettingsChangedEvent event) {\n    // settings have an impact on rule definitions\n    rulesRepository.evictFor(event.connectionId());\n    evictFor(event.connectionId());\n  }\n\n  @EventListener\n  public void onBindingUpdated(BindingConfigChangedEvent event) {\n    var previousBinding = event.previousConfig();\n    var previousConnectionId = previousBinding.connectionId();\n    var previousProjectKey = previousBinding.sonarProjectKey();\n    if (previousConnectionId != null && previousProjectKey != null\n      && configurationRepository.getBoundScopesToConnectionAndSonarProject(previousConnectionId, previousProjectKey).isEmpty()) {\n      // evict the cache, active rules will be lazily loaded next time they are needed\n      activeRulesPerBinding.remove(new Binding(previousConnectionId, previousProjectKey));\n    }\n  }\n\n  @EventListener\n  public void onAnalyzerConfigurationSynchronized(AnalyzerConfigurationSynchronized event) {\n    // evict the cache, active rules will be lazily loaded next time they are needed\n    activeRulesPerBinding.remove(event.binding());\n  }\n\n  @EventListener\n  public void onServerEventReceived(SonarServerEventReceivedEvent eventReceived) {\n    var connectionId = eventReceived.getConnectionId();\n    var serverEvent = eventReceived.getEvent();\n    if (serverEvent instanceof RuleSetChangedEvent ruleSetChangedEvent) {\n      // evict the cache, active rules will be lazily loaded next time they are needed\n      ruleSetChangedEvent.getProjectKeys().forEach(projectKey -> activeRulesPerBinding.remove(new Binding(connectionId, projectKey)));\n      updateStorage(connectionId, ruleSetChangedEvent);\n      eventPublisher.publishEvent(\n        new ServerActiveRulesChanged(connectionId, ruleSetChangedEvent.getProjectKeys(), ruleSetChangedEvent.getActivatedRules(), ruleSetChangedEvent.getDeactivatedRules()));\n    }\n  }\n\n  private void updateStorage(String connectionId, RuleSetChangedEvent event) {\n    event.getProjectKeys().forEach(projectKey -> storageService.connection(connectionId).project(projectKey).analyzerConfiguration().update(currentConfiguration -> {\n      var newRuleSetByLanguageKey = incorporate(event, currentConfiguration.getRuleSetByLanguageKey());\n      return new AnalyzerConfiguration(currentConfiguration.getSettings(), newRuleSetByLanguageKey, currentConfiguration.getSchemaVersion());\n    }));\n  }\n\n  private static Map<String, RuleSet> incorporate(RuleSetChangedEvent event, Map<String, RuleSet> ruleSetByLanguageKey) {\n    Map<String, RuleSet> resultingRuleSetsByLanguageKey = new HashMap<>(ruleSetByLanguageKey);\n    event.getDeactivatedRules().forEach(deactivatedRule -> deactivate(deactivatedRule, resultingRuleSetsByLanguageKey));\n    event.getActivatedRules().forEach(activatedRule -> activate(activatedRule, resultingRuleSetsByLanguageKey));\n    return resultingRuleSetsByLanguageKey;\n  }\n\n  private static void activate(RuleSetChangedEvent.ActiveRule activatedRule, Map<String, RuleSet> ruleSetsByLanguageKey) {\n    var ruleLanguageKey = activatedRule.getLanguageKey();\n    var currentRuleSet = ruleSetsByLanguageKey.computeIfAbsent(ruleLanguageKey, k -> new RuleSet(Collections.emptyList(), \"\"));\n    var languageRulesByKey = new HashMap<>(currentRuleSet.getRulesByKey());\n    var ruleTemplateKey = activatedRule.getTemplateKey();\n    languageRulesByKey.put(activatedRule.getKey(), new ServerActiveRule(\n      activatedRule.getKey(),\n      activatedRule.getSeverity(),\n      activatedRule.getParameters(),\n      ruleTemplateKey == null ? \"\" : ruleTemplateKey,\n      activatedRule.getOverriddenImpacts()));\n    ruleSetsByLanguageKey.put(ruleLanguageKey, new RuleSet(new ArrayList<>(languageRulesByKey.values()), currentRuleSet.getLastModified()));\n  }\n\n  private static void deactivate(String deactivatedRuleKey, Map<String, RuleSet> ruleSetsByLanguageKey) {\n    var ruleSetsIterator = ruleSetsByLanguageKey.entrySet().iterator();\n    while (ruleSetsIterator.hasNext()) {\n      var ruleSetEntry = ruleSetsIterator.next();\n      var ruleSet = ruleSetEntry.getValue();\n      var newRules = new HashMap<>(ruleSet.getRulesByKey());\n      newRules.remove(deactivatedRuleKey);\n      if (newRules.isEmpty()) {\n        ruleSetsIterator.remove();\n      } else {\n        ruleSetsByLanguageKey.put(ruleSetEntry.getKey(), new RuleSet(List.copyOf(newRules.values()), ruleSet.getLastModified()));\n      }\n    }\n  }\n\n  public EffectiveRuleDetailsDto getEffectiveRuleDetails(String configurationScopeId, String ruleKey, @Nullable String contextKey,\n    SonarLintCancelMonitor cancelMonitor) throws RuleNotFoundException {\n    var ruleDetails = getActiveRuleDetails(configurationScopeId, ruleKey, cancelMonitor);\n    return RuleDetailsAdapter.transform(ruleDetails, contextKey);\n  }\n\n  public RuleDetails getActiveRuleDetails(String configurationScopeId, String ruleKey, SonarLintCancelMonitor cancelMonitor) throws RuleNotFoundException {\n    var effectiveBinding = configurationRepository.getEffectiveBinding(configurationScopeId);\n    RuleDetails ruleDetails;\n    if (effectiveBinding.isEmpty()) {\n      var embeddedRule = rulesRepository.getEmbeddedRule(ruleKey);\n      if (embeddedRule.isEmpty()) {\n        throw new RuleNotFoundException(COULD_NOT_FIND_RULE + ruleKey + IN_EMBEDDED_RULES, ruleKey);\n      }\n      ruleDetails = RuleDetails.from(embeddedRule.get(), standaloneRuleConfig.get(ruleKey));\n    } else {\n      ruleDetails = getActiveRuleForBinding(ruleKey, effectiveBinding.get(), cancelMonitor);\n    }\n    return ruleDetails;\n  }\n\n  public synchronized void evictStandalone() {\n    LOG.debug(\"Evict cached standalone active rules\");\n    standaloneActiveRules = null;\n  }\n\n  private RuleDetails getActiveRuleForBinding(String ruleKey, Binding binding, SonarLintCancelMonitor cancelMonitor) {\n    var connectionId = binding.connectionId();\n    sonarQubeClientManager.getValidClientOrThrow(connectionId);\n\n    var serverUsesStandardSeverityMode = !severityModeService.isMQRModeForConnection(connectionId);\n\n    return findServerActiveRuleInStorage(binding, ruleKey)\n      .map(storageRule -> hydrateDetailsWithServer(connectionId, storageRule, serverUsesStandardSeverityMode, cancelMonitor))\n      // try from loaded rules, for e.g. extra analyzers\n      .orElseGet(() -> rulesRepository.getRule(connectionId, ruleKey)\n        .map(r -> RuleDetails.from(r, standaloneRuleConfig.get(ruleKey)))\n        .orElseThrow(() -> ruleNotFoundInPlugins(ruleKey, connectionId)));\n  }\n\n  private Optional<ServerActiveRule> findServerActiveRuleInStorage(Binding binding, String ruleKey) {\n    AnalyzerConfiguration analyzerConfiguration;\n    try {\n      analyzerConfiguration = storageService.binding(binding).analyzerConfiguration().read();\n    } catch (StorageException e) {\n      // XXX we should make sure this situation can not happen (sync should be enforced at least once)\n      return Optional.empty();\n    }\n    return analyzerConfiguration.getRuleSetByLanguageKey().values().stream()\n      .flatMap(s -> s.getRules().stream())\n      // XXX is it important to migrate the rule repos in tryConvertDeprecatedKeys?\n      .filter(r -> tryConvertDeprecatedKeys(binding.connectionId(), r).getRuleKey().equals(ruleKey)).findFirst();\n  }\n\n  private ServerActiveRule tryConvertDeprecatedKeys(String connectionId, ServerActiveRule possiblyDeprecatedActiveRuleFromStorage) {\n    SonarLintRuleDefinition ruleOrTemplateDefinition;\n    if (StringUtils.isNotBlank(possiblyDeprecatedActiveRuleFromStorage.getTemplateKey())) {\n      ruleOrTemplateDefinition = rulesRepository.getRule(connectionId, possiblyDeprecatedActiveRuleFromStorage.getTemplateKey()).orElse(null);\n      if (ruleOrTemplateDefinition == null) {\n        // The rule template is not known among our loaded analyzers, so return it untouched, to let calling code take appropriate decision\n        return possiblyDeprecatedActiveRuleFromStorage;\n      }\n      var ruleKeyPossiblyWithDeprecatedRepo = RuleKey.parse(possiblyDeprecatedActiveRuleFromStorage.getRuleKey());\n      var templateRuleKeyWithCorrectRepo = RuleKey.parse(ruleOrTemplateDefinition.getKey());\n      var ruleKey = new RuleKey(templateRuleKeyWithCorrectRepo.repository(), ruleKeyPossiblyWithDeprecatedRepo.rule()).toString();\n      return new ServerActiveRule(ruleKey, possiblyDeprecatedActiveRuleFromStorage.getSeverity(), possiblyDeprecatedActiveRuleFromStorage.getParams(),\n        ruleOrTemplateDefinition.getKey(), possiblyDeprecatedActiveRuleFromStorage.getOverriddenImpacts());\n    } else {\n      ruleOrTemplateDefinition = rulesRepository.getRule(connectionId, possiblyDeprecatedActiveRuleFromStorage.getRuleKey()).orElse(null);\n      if (ruleOrTemplateDefinition == null) {\n        // The rule is not known among our loaded analyzers, so return it untouched, to let calling code take appropriate decision\n        return possiblyDeprecatedActiveRuleFromStorage;\n      }\n      return new ServerActiveRule(ruleOrTemplateDefinition.getKey(), possiblyDeprecatedActiveRuleFromStorage.getSeverity(), possiblyDeprecatedActiveRuleFromStorage.getParams(),\n        null, possiblyDeprecatedActiveRuleFromStorage.getOverriddenImpacts());\n    }\n  }\n\n  private RuleDetails hydrateDetailsWithServer(String connectionId, ServerActiveRule activeRuleFromStorage, boolean skipCleanCodeTaxonomy, SonarLintCancelMonitor cancelMonitor) {\n    var ruleKey = activeRuleFromStorage.getRuleKey();\n    var templateKey = activeRuleFromStorage.getTemplateKey();\n    var serverConnection = sonarQubeClientManager.getValidClientOrThrow(connectionId);\n    if (StringUtils.isNotBlank(templateKey)) {\n      var templateRule = rulesRepository.getRule(connectionId, templateKey);\n      if (templateRule.isEmpty()) {\n        throw ruleDefinitionNotFound(templateKey);\n      }\n      var serverRule = serverConnection.withClientApiAndReturn(serverApi -> fetchRuleFromServer(connectionId, ruleKey, serverApi, cancelMonitor));\n      return RuleDetails.merging(activeRuleFromStorage, serverRule, templateRule.get(), skipCleanCodeTaxonomy);\n    } else {\n      var serverRule = serverConnection.withClientApiAndReturn(serverApi -> fetchRuleFromServer(connectionId, ruleKey, serverApi, cancelMonitor));\n      var ruleDefFromPluginOpt = rulesRepository.getRule(connectionId, ruleKey);\n      return ruleDefFromPluginOpt\n        .map(ruleDefFromPlugin -> RuleDetails.merging(serverRule, ruleDefFromPlugin, skipCleanCodeTaxonomy))\n        .orElseGet(() -> RuleDetails.merging(activeRuleFromStorage, serverRule));\n    }\n  }\n\n  private static ServerRule fetchRuleFromServer(String connectionId, String ruleKey, ServerApi serverApi, SonarLintCancelMonitor cancelMonitor) {\n    return serverApi.rules().getRule(ruleKey, cancelMonitor).orElseThrow(() -> ruleNotFoundOnServer(ruleKey, connectionId));\n  }\n\n  private static ResponseErrorException ruleDefinitionNotFound(String templateKey) {\n    var error = new ResponseError(SonarLintRpcErrorCode.RULE_NOT_FOUND, \"Unable to find rule definition for rule template \" + templateKey, templateKey);\n    return new ResponseErrorException(error);\n  }\n\n  @NotNull\n  private static ResponseErrorException ruleNotFoundInPlugins(String ruleKey, String connectionId) {\n    var error = new ResponseError(SonarLintRpcErrorCode.RULE_NOT_FOUND, COULD_NOT_FIND_RULE + ruleKey + \"' in plugins loaded from '\" + connectionId + \"'\",\n      new Object[] {connectionId, ruleKey});\n    return new ResponseErrorException(error);\n  }\n\n  private static ResponseErrorException ruleNotFoundOnServer(String ruleKey, String connectionId) {\n    var error = new ResponseError(SonarLintRpcErrorCode.RULE_NOT_FOUND, COULD_NOT_FIND_RULE + ruleKey + \"' on server '\" + connectionId + \"'\",\n      new Object[] {connectionId, ruleKey});\n    return new ResponseErrorException(error);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/active/rules/ServerActiveRulesChanged.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.active.rules;\n\nimport java.util.List;\nimport org.sonarsource.sonarlint.core.serverapi.push.RuleSetChangedEvent;\n\npublic record ServerActiveRulesChanged(String connectionId, List<String> projectKeys, List<RuleSetChangedEvent.ActiveRule> activatedRules, List<String> deactivatedRules) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/active/rules/StandaloneRulesConfigurationChanged.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.active.rules;\n\nimport java.util.List;\nimport java.util.Map;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.StandaloneRuleConfigDto;\n\npublic class StandaloneRulesConfigurationChanged {\n  private final Map<String, StandaloneRuleConfigDto> standaloneRuleConfig;\n\n  StandaloneRulesConfigurationChanged(Map<String, StandaloneRuleConfigDto> standaloneRuleConfig) {\n    this.standaloneRuleConfig = standaloneRuleConfig;\n  }\n\n  public boolean isOnlyDeactivated() {\n    return standaloneRuleConfig.values().stream()\n      .noneMatch(StandaloneRuleConfigDto::isActive);\n  }\n\n  public List<String> getDeactivatedRules() {\n    return standaloneRuleConfig.entrySet().stream()\n      .filter(entry -> !entry.getValue().isActive())\n      .map(Map.Entry::getKey)\n      .toList();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/active/rules/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.active.rules;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/ai/ide/AiAgentService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.ai.ide;\n\nimport jakarta.inject.Inject;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.AiAgent;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.GetRuleFileContentResponse;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryService;\n\npublic class AiAgentService {\n\n  private final TelemetryService telemetryService;\n\n  @Inject\n  public AiAgentService(TelemetryService telemetryService) {\n    this.telemetryService = telemetryService;\n  }\n\n  public GetRuleFileContentResponse getRuleFileContent(AiAgent agent) {\n    var header = switch (agent) {\n      case CURSOR, WINDSURF -> \"\"\"\n        ---\n        description: SonarQube MCP Server usage guidelines\n        globs:\n        alwaysApply: true\n        ---\n\n        \"\"\";\n      case KIRO -> \"\"\"\n        ---\n        inclusion: always\n        ---\n\n        \"\"\";\n      case GITHUB_COPILOT -> \"\"\"\n        ---\n        applyTo: \"**/*\"\n        ---\n\n        \"\"\";\n    };\n    var response = new GetRuleFileContentResponse(header\n      + \"\"\"\n        These are some guidelines when using the SonarQube MCP server.\n\n        # Important Tool Guidelines\n\n        ## Basic usage\n        - **IMPORTANT**: After you finish generating or modifying any code files at the very end of the task, you MUST call the `analyze_file_list` \\\n        tool (if it exists) to analyze the files you created or modified.\n        - **IMPORTANT**: When starting a new task, you MUST disable automatic analysis with the `toggle_automatic_analysis` tool if it exists.\n        - **IMPORTANT**: When you are done generating code at the very end of the task, \\\n        you MUST re-enable automatic analysis with the `toggle_automatic_analysis` tool if it exists.\n\n        ## Project Keys\n        - When a user mentions a project key, use `search_my_sonarqube_projects` first to find the exact project key\n        - Don't guess project keys - always look them up\n\n        ## Code Language Detection\n        - When analyzing code snippets, try to detect the programming language from the code syntax\n        - If unclear, ask the user or make an educated guess based on syntax\n\n        ## Branch and Pull Request Context\n        - Many operations support branch-specific analysis\n        - If user mentions working on a feature branch, include the branch parameter\n\n        ## Code Issues and Violations\n        - After fixing issues, do not attempt to verify them using `search_sonar_issues_in_projects`, as the server will not yet reflect the updates\n\n        # Common Troubleshooting\n\n        ## Authentication Issues\n        - SonarQube requires USER tokens (not project tokens)\n        - When the error `SonarQube answered with Not authorized` occurs, verify the token type\n\n        ## Project Not Found\n        - Use `search_my_sonarqube_projects` to find available projects\n        - Verify project key spelling and format\n\n        ## Code Analysis Issues\n        - Ensure programming language is correctly specified\n        - Remind users that snippet analysis doesn't replace full project scans\n        - Provide full file content for better analysis results\n        \"\"\");\n\n    telemetryService.mcpRuleFileRequested();\n\n    return response;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/ai/ide/AiHookService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.ai.ide;\n\nimport jakarta.inject.Inject;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.embedded.server.EmbeddedServer;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.AiAgent;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.GetHookScriptContentResponse;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryService;\n\npublic class AiHookService {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private static final String WINDSURF_HOOK_CONFIG = \"\"\"\n      {\n        \"hooks\": {\n          \"post_write_code\": [\n            {\n              \"command\": \"{{SCRIPT_PATH}}\",\n              \"show_output\": true\n            }\n          ]\n        }\n      }\n      \"\"\";\n\n  private final EmbeddedServer embeddedServer;\n  private final ExecutableLocator executableLocator;\n  private final TelemetryService telemetryService;\n\n  @Inject\n  public AiHookService(EmbeddedServer embeddedServer, TelemetryService telemetryService) {\n    this(embeddedServer, telemetryService, new ExecutableLocator());\n  }\n\n  // For testing\n  AiHookService(EmbeddedServer embeddedServer, TelemetryService telemetryService, ExecutableLocator executableLocator) {\n    this.embeddedServer = embeddedServer;\n    this.telemetryService = telemetryService;\n    this.executableLocator = executableLocator;\n  }\n\n  public GetHookScriptContentResponse getHookScriptContent(AiAgent agent) {\n    var port = embeddedServer.getPort();\n    if (port <= 0) {\n      throw new IllegalStateException(\"Embedded server is not started. Cannot generate hook script.\");\n    }\n\n    var hookScriptType = executableLocator.detectBestExecutable()\n      .orElseThrow(() -> new IllegalStateException(\"No suitable executable found for hook script generation. \" +\n        \"Please ensure Node.js, Python, or Bash is available on your system.\"));\n\n    var scriptContent = loadTemplateAndReplacePlaceholders(hookScriptType.getFileName(), port, agent);\n    var configContent = generateHookConfiguration(agent);\n    var configFileName = getConfigFileName(agent);\n\n    telemetryService.aiHookInstalled(agent);\n\n    return new GetHookScriptContentResponse(scriptContent, hookScriptType.getFileName(), configContent, configFileName);\n  }\n\n  private static String generateHookConfiguration(AiAgent agent) {\n    return switch (agent) {\n      case WINDSURF -> WINDSURF_HOOK_CONFIG;\n      case CURSOR, KIRO -> throw new UnsupportedOperationException(agent + \" hook configuration not yet implemented\");\n      case GITHUB_COPILOT -> throw new UnsupportedOperationException(\"GitHub Copilot does not support hooks\");\n    };\n  }\n\n  private static String getConfigFileName(AiAgent agent) {\n    return switch (agent) {\n      case WINDSURF -> \"hooks.json\";\n      case CURSOR, KIRO -> throw new UnsupportedOperationException(agent + \" hook configuration not yet implemented\");\n      case GITHUB_COPILOT -> throw new UnsupportedOperationException(\"GitHub Copilot does not support hooks\");\n    };\n  }\n\n  private static String loadTemplateAndReplacePlaceholders(String templateFileName, int port, AiAgent agent) {\n    var resourcePath = \"/ai/hooks/\" + templateFileName;\n    try (var inputStream = AiHookService.class.getResourceAsStream(resourcePath)) {\n      if (inputStream == null) {\n        throw new IllegalStateException(\"Hook script template not found: \" + resourcePath);\n      }\n      var template = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);\n      return template\n        .replace(\"{{PORT}}\", String.valueOf(port))\n        .replace(\"{{AGENT}}\", getIdeName(agent));\n    } catch (IOException e) {\n      LOG.error(\"Failed to load hook script template: {}\", templateFileName, e);\n      throw new IllegalStateException(\"Failed to load hook script template: \" + templateFileName, e);\n    }\n  }\n\n  private static String getIdeName(AiAgent agent) {\n    return switch (agent) {\n      case WINDSURF -> \"Windsurf\";\n      case CURSOR -> \"Cursor\";\n      case KIRO -> \"Kiro\";\n      case GITHUB_COPILOT -> throw new UnsupportedOperationException(\"GitHub Copilot does not support hooks\");\n    };\n  }\n\n}\n\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/ai/ide/ExecutableLocator.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.ai.ide;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Optional;\nimport java.util.regex.Pattern;\nimport javax.annotation.CheckForNull;\nimport org.sonar.api.utils.System2;\nimport org.sonar.api.utils.command.Command;\nimport org.sonar.api.utils.command.CommandException;\nimport org.sonar.api.utils.command.CommandExecutor;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.nodejs.NodeJsHelper;\n\npublic class ExecutableLocator {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final System2 system2;\n  private final Path pathHelperLocationOnMac;\n  private final CommandExecutor commandExecutor;\n  private final NodeJsHelper nodeJsHelper;\n\n  private boolean checkedForExecutable = false;\n  private HookScriptType detectedExecutable = null;\n\n  public ExecutableLocator() {\n    this(System2.INSTANCE, Paths.get(\"/usr/libexec/path_helper\"), CommandExecutor.create(), new NodeJsHelper());\n  }\n\n  // For testing\n  ExecutableLocator(System2 system2, Path pathHelperLocationOnMac, CommandExecutor commandExecutor, NodeJsHelper nodeJsHelper) {\n    this.system2 = system2;\n    this.pathHelperLocationOnMac = pathHelperLocationOnMac;\n    this.commandExecutor = commandExecutor;\n    this.nodeJsHelper = nodeJsHelper;\n  }\n\n  public Optional<HookScriptType> detectBestExecutable() {\n    if (checkedForExecutable) {\n      return Optional.ofNullable(detectedExecutable);\n    }\n\n    // Priority: Node.js > Python > Bash\n    if (isNodeJsAvailable()) {\n      LOG.debug(\"Detected Node.js for hook scripts\");\n      detectedExecutable = HookScriptType.NODEJS;\n    } else if (isPythonAvailable()) {\n      LOG.debug(\"Detected Python for hook scripts\");\n      detectedExecutable = HookScriptType.PYTHON;\n    } else if (isBashAvailable()) {\n      LOG.debug(\"Detected Bash for hook scripts\");\n      detectedExecutable = HookScriptType.BASH;\n    } else {\n      LOG.debug(\"No suitable executable found for hook scripts\");\n      detectedExecutable = null;\n    }\n\n    checkedForExecutable = true;\n    return Optional.ofNullable(detectedExecutable);\n  }\n\n  private boolean isNodeJsAvailable() {\n    try {\n      var installedNodeJs = nodeJsHelper.autoDetect();\n      return installedNodeJs != null;\n    } catch (Exception e) {\n      LOG.debug(\"Error detecting Node.js\", e);\n      return false;\n    }\n  }\n\n  private boolean isPythonAvailable() {\n    // Try python3 first, then python\n    var python3Path = locatePythonExecutable(\"python3\");\n    if (python3Path != null) {\n      return true;\n    }\n    var pythonPath = locatePythonExecutable(\"python\");\n    return pythonPath != null;\n  }\n\n  @CheckForNull\n  private String locatePythonExecutable(String executable) {\n    LOG.debug(\"Looking for {} in the PATH\", executable);\n    \n    String result;\n    if (system2.isOsWindows()) {\n      result = runSimpleCommand(Command.create(\"C:\\\\Windows\\\\System32\\\\where.exe\").addArgument(\"$PATH:\" + executable + \".exe\"));\n    } else {\n      var which = Command.create(\"/usr/bin/which\").addArgument(executable);\n      computePathEnvForMacOs(which);\n      result = runSimpleCommand(which);\n    }\n    \n    if (result != null) {\n      LOG.debug(\"Found {} at {}\", executable, result);\n      return result;\n    } else {\n      LOG.debug(\"Unable to locate {}\", executable);\n      return null;\n    }\n  }\n\n  private boolean isBashAvailable() {\n    if (system2.isOsWindows()) {\n      // On Windows, try to locate bash.exe (Git Bash, WSL, etc.)\n      var bashPath = runSimpleCommand(Command.create(\"C:\\\\Windows\\\\System32\\\\where.exe\").addArgument(\"$PATH:bash.exe\"));\n      return bashPath != null;\n    } else {\n      // On Unix/Mac, bash is always available\n      return Files.exists(Paths.get(\"/bin/bash\"));\n    }\n  }\n\n  void computePathEnvForMacOs(Command command) {\n    if (system2.isOsMac() && Files.exists(pathHelperLocationOnMac)) {\n      var pathHelperCommand = Command.create(pathHelperLocationOnMac.toString()).addArgument(\"-s\");\n      var pathHelperOutput = runSimpleCommand(pathHelperCommand);\n      if (pathHelperOutput != null) {\n        var regex = Pattern.compile(\"^\\\\s*PATH=\\\"([^\\\"]+)\\\"; export PATH;?\\\\s*$\");\n        var matchResult = regex.matcher(pathHelperOutput);\n        if (matchResult.matches()) {\n          command.setEnvironmentVariable(\"PATH\", matchResult.group(1));\n        }\n      }\n    }\n  }\n\n  @CheckForNull\n  String runSimpleCommand(Command command) {\n    var stdOut = new ArrayList<String>();\n    var stdErr = new ArrayList<String>();\n    LOG.debug(\"Execute command '{}'...\", command);\n    int exitCode;\n    try {\n      exitCode = commandExecutor.execute(command, stdOut::add, stdErr::add, 10_000);\n    } catch (CommandException e) {\n      LOG.debug(\"Unable to execute the command\", e);\n      return null;\n    }\n    var msg = new StringBuilder(String.format(\"Command '%s' exited with %s\", command, exitCode));\n    if (!stdOut.isEmpty()) {\n      msg.append(\"\\nstdout: \").append(String.join(\"\\n\", stdOut));\n    }\n    if (!stdErr.isEmpty()) {\n      msg.append(\"\\nstderr: \").append(String.join(\"\\n\", stdErr));\n    }\n    LOG.debug(\"{}\", msg);\n    if (exitCode != 0 || stdOut.isEmpty()) {\n      return null;\n    }\n    return stdOut.get(0);\n  }\n\n}\n\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/ai/ide/HookScriptType.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.ai.ide;\n\npublic enum HookScriptType {\n  NODEJS(\"sonarqube_analysis_hook.js\"),\n  PYTHON(\"sonarqube_analysis_hook.py\"),\n  BASH(\"sonarqube_analysis_hook.sh\");\n\n  private final String fileName;\n\n  HookScriptType(String fileName) {\n    this.fileName = fileName;\n  }\n\n  public String getFileName() {\n    return fileName;\n  }\n}\n\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/ai/ide/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.ai.ide;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/analysis/AnalysisFailedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport java.util.UUID;\n\npublic record AnalysisFailedEvent(UUID analysisId) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/analysis/AnalysisFinishedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport java.net.URI;\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\n\nimport static java.util.function.Predicate.not;\n\npublic class AnalysisFinishedEvent {\n  private final UUID analysisId;\n  private final String configurationScopeId;\n  private final Duration analysisDuration;\n  private final Map<URI, SonarLanguage> languagePerFile;\n  private final boolean succeededForAllFiles;\n  private final List<RawIssue> issues;\n  private final Set<String> reportedRuleKeys;\n  private final Set<SonarLanguage> detectedLanguages;\n  private final boolean shouldFetchServerIssues;\n\n  public AnalysisFinishedEvent(UUID analysisId, String configurationScopeId, Duration analysisDuration, Map<URI, SonarLanguage> languagePerFile, boolean succeededForAllFiles,\n    List<RawIssue> issues, boolean shouldFetchServerIssues) {\n    this.analysisId = analysisId;\n    this.configurationScopeId = configurationScopeId;\n    this.analysisDuration = analysisDuration;\n    this.languagePerFile = languagePerFile;\n    this.succeededForAllFiles = succeededForAllFiles;\n    this.issues = issues;\n    this.reportedRuleKeys = issues.stream().map(RawIssue::getRuleKey).collect(Collectors.toSet());\n    this.detectedLanguages = languagePerFile.values().stream().filter(Objects::nonNull).collect(Collectors.toSet());\n    this.shouldFetchServerIssues = shouldFetchServerIssues;\n  }\n\n  public UUID getAnalysisId() {\n    return analysisId;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public Duration getAnalysisDuration() {\n    return analysisDuration;\n  }\n\n  public Map<URI, SonarLanguage> getLanguagePerFile() {\n    return languagePerFile;\n  }\n\n  public boolean succeededForAllFiles() {\n    return succeededForAllFiles;\n  }\n\n  public Set<String> getReportedRuleKeys() {\n    return reportedRuleKeys;\n  }\n\n  public Set<SonarLanguage> getDetectedLanguages() {\n    return detectedLanguages;\n  }\n\n  public List<RawIssue> getIssues() {\n    return issues.stream().filter(not(RawIssue::isSecurityHotspot)).toList();\n  }\n\n  public List<RawIssue> getHotspots() {\n    return issues.stream().filter(RawIssue::isSecurityHotspot).toList();\n  }\n\n  public boolean shouldFetchServerIssues() {\n    return shouldFetchServerIssues;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/analysis/AnalysisResult.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport java.net.URI;\nimport java.util.List;\nimport java.util.Set;\n\npublic record AnalysisResult(Set<URI> failedAnalysisFiles, List<RawIssue> rawIssues) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/analysis/AnalysisSchedulerCache.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport java.nio.file.Path;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicReference;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport javax.annotation.PreDestroy;\nimport org.sonarsource.sonarlint.core.UserPaths;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisSchedulerConfiguration;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientModuleFileSystem;\nimport org.sonarsource.sonarlint.core.analysis.command.UnregisterModuleCommand;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.tracing.Trace;\nimport org.sonarsource.sonarlint.core.event.BindingConfigChangedEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationRemovedEvent;\nimport org.sonarsource.sonarlint.core.fs.ClientFileSystemService;\nimport org.sonarsource.sonarlint.core.plugin.PluginLifecycleService;\nimport org.sonarsource.sonarlint.core.plugin.PluginsConfiguration;\nimport org.sonarsource.sonarlint.core.plugin.PluginsService;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.springframework.context.event.EventListener;\n\nimport static org.sonarsource.sonarlint.core.commons.tracing.Trace.startChild;\n\npublic class AnalysisSchedulerCache {\n  private final Path workDir;\n  private final ClientFileSystemService clientFileSystemService;\n  private final ConfigurationRepository configurationRepository;\n  private final PluginsService pluginsService;\n  private final PluginLifecycleService pluginLifecycleService;\n  private final NodeJsService nodeJsService;\n  private final AtomicReference<AnalysisScheduler> standaloneScheduler = new AtomicReference<>();\n  private final ConcurrentHashMap<String, AnalysisScheduler> connectedSchedulerByConnectionId = new ConcurrentHashMap<>();\n\n  public AnalysisSchedulerCache(UserPaths userPaths, ConfigurationRepository configurationRepository,\n    NodeJsService nodeJsService, PluginsService pluginsService, PluginLifecycleService pluginLifecycleService, ClientFileSystemService clientFileSystemService) {\n    this.configurationRepository = configurationRepository;\n    this.pluginsService = pluginsService;\n    this.pluginLifecycleService = pluginLifecycleService;\n    this.nodeJsService = nodeJsService;\n    this.workDir = userPaths.getWorkDir();\n    this.clientFileSystemService = clientFileSystemService;\n  }\n\n  @CheckForNull\n  public AnalysisScheduler getAnalysisSchedulerIfStarted(String configurationScopeId) {\n    return configurationRepository.getEffectiveBinding(configurationScopeId)\n      .map(binding -> getConnectedSchedulerIfStarted(binding.connectionId()))\n      .orElseGet(this::getStandaloneSchedulerIfStarted);\n  }\n\n  public AnalysisScheduler getOrCreateAnalysisScheduler(String configurationScopeId) {\n    return getOrCreateAnalysisScheduler(configurationScopeId, null);\n  }\n\n  public AnalysisScheduler getOrCreateAnalysisScheduler(String configurationScopeId, @Nullable Trace trace) {\n    return configurationRepository.getEffectiveBinding(configurationScopeId)\n      .map(binding -> getOrCreateConnectedScheduler(binding.connectionId(), trace))\n      .orElseGet(() -> getOrCreateStandaloneScheduler(trace));\n  }\n\n  private synchronized AnalysisScheduler getOrCreateConnectedScheduler(String connectionId, @Nullable Trace trace) {\n    return connectedSchedulerByConnectionId.computeIfAbsent(connectionId,\n      k -> createScheduler(pluginsService.getPlugins(connectionId), trace));\n  }\n\n  @CheckForNull\n  private synchronized AnalysisScheduler getConnectedSchedulerIfStarted(String connectionId) {\n    return connectedSchedulerByConnectionId.get(connectionId);\n  }\n\n  private synchronized AnalysisScheduler getOrCreateStandaloneScheduler(@Nullable Trace trace) {\n    var scheduler = standaloneScheduler.get();\n    if (scheduler == null) {\n      scheduler = createScheduler(pluginsService.getEmbeddedPlugins(), trace);\n      standaloneScheduler.set(scheduler);\n    }\n    return scheduler;\n  }\n\n  @CheckForNull\n  private synchronized AnalysisScheduler getStandaloneSchedulerIfStarted() {\n    return standaloneScheduler.get();\n  }\n\n  private AnalysisScheduler createScheduler(PluginsConfiguration pluginsConfiguration, @Nullable Trace trace) {\n    var config = buildSchedulerConfiguration(pluginsConfiguration.extraProperties(), trace);\n    return new AnalysisScheduler(config, pluginsConfiguration.plugins(), SonarLintLogger.get().getTargetForCopy());\n  }\n\n  private AnalysisSchedulerConfiguration buildSchedulerConfiguration(Map<String, String> extraProperties, @Nullable Trace trace) {\n    var activeNodeJs = startChild(trace, \"getActiveNodeJs\", \"createSchedulerConfiguration\", nodeJsService::getActiveNodeJs);\n    var nodeJsPath = activeNodeJs == null ? null : activeNodeJs.getPath();\n    return AnalysisSchedulerConfiguration.builder()\n      .setWorkDir(workDir)\n      .setClientPid(ProcessHandle.current().pid())\n      .setExtraProperties(extraProperties)\n      .setNodeJs(nodeJsPath)\n      .setFileSystemProvider(this::getFileSystem)\n      .build();\n  }\n\n  private SchedulerResetConfiguration toSchedulerResetConfiguration(PluginsConfiguration pluginsConfiguration) {\n    return new SchedulerResetConfiguration(buildSchedulerConfiguration(pluginsConfiguration.extraProperties(), null), pluginsConfiguration.plugins());\n  }\n\n  private ClientModuleFileSystem getFileSystem(String configurationScopeId) {\n    return new BackendModuleFileSystem(clientFileSystemService, configurationScopeId);\n  }\n\n  @EventListener\n  public void onConnectionRemoved(ConnectionConfigurationRemovedEvent event) {\n    stop(event.removedConnectionId());\n  }\n\n  public synchronized void reloadPlugins(String connectionId) {\n    var scheduler = connectedSchedulerByConnectionId.get(connectionId);\n    if (scheduler != null) {\n      scheduler.reset(() -> toSchedulerResetConfiguration(pluginLifecycleService.reloadPluginsAndEvictCaches(connectionId)));\n    } else {\n      // Scheduler doesn't exist yet (lazy initialization), but still need to unload old plugins and evict caches\n      // This ensures that when the scheduler is eventually created, it won't use stale cached data\n      pluginLifecycleService.unloadPluginsAndEvictCaches(connectionId);\n    }\n  }\n\n  public synchronized void reloadStandalonePlugins() {\n    var scheduler = standaloneScheduler.get();\n    if (scheduler != null) {\n      scheduler.reset(() -> toSchedulerResetConfiguration(pluginLifecycleService.reloadEmbeddedPluginsAndEvictCaches()));\n    } else {\n      pluginLifecycleService.unloadEmbeddedPluginsAndEvictCaches();\n    }\n  }\n\n  @EventListener\n  public void onClientNodeJsPathChanged(ClientNodeJsPathChanged event) {\n    resetStartedSchedulers();\n  }\n\n  @EventListener\n  public void onBindingConfigurationChanged(BindingConfigChangedEvent event) {\n    var schedulerBeforeBindingChange = event.previousConfig().isBound() ? getConnectedSchedulerIfStarted(Objects.requireNonNull(event.previousConfig().connectionId()))\n      : getStandaloneSchedulerIfStarted();\n    var schedulerAfterBindingChange = getAnalysisSchedulerIfStarted(event.configScopeId());\n    if (schedulerBeforeBindingChange != null && schedulerAfterBindingChange != schedulerBeforeBindingChange) {\n      schedulerBeforeBindingChange.post(new UnregisterModuleCommand(event.configScopeId()));\n      configurationRepository.getChildrenWithInheritedBinding(event.configScopeId())\n        .forEach(childId -> schedulerBeforeBindingChange.post(new UnregisterModuleCommand(childId)));\n    }\n  }\n\n  @PreDestroy\n  public void shutdown() {\n    try {\n      stopAll();\n    } catch (Exception e) {\n      SonarLintLogger.get().error(\"Error shutting down analysis scheduler cache\", e);\n    }\n  }\n\n  private synchronized void resetStartedSchedulers() {\n    var standaloneAnalysisScheduler = this.standaloneScheduler.get();\n    if (standaloneAnalysisScheduler != null) {\n      standaloneAnalysisScheduler.reset(() -> toSchedulerResetConfiguration(pluginsService.getEmbeddedPlugins()));\n    }\n    connectedSchedulerByConnectionId.forEach(\n      (connectionId, scheduler) -> scheduler.reset(() -> toSchedulerResetConfiguration(pluginsService.getPlugins(connectionId))));\n  }\n\n  private synchronized void stopAll() {\n    var standaloneAnalysisScheduler = this.standaloneScheduler.getAndSet(null);\n    if (standaloneAnalysisScheduler != null) {\n      standaloneAnalysisScheduler.stop();\n    }\n    connectedSchedulerByConnectionId.forEach((connectionId, scheduler) -> scheduler.stop());\n    connectedSchedulerByConnectionId.clear();\n  }\n\n  private synchronized void stop(String connectionId) {\n    var scheduler = connectedSchedulerByConnectionId.remove(connectionId);\n    if (scheduler != null) {\n      scheduler.stop();\n    }\n    pluginLifecycleService.unloadPluginsAndEvictCaches(connectionId);\n  }\n\n  public void unregisterModule(String scopeId, @Nullable String connectionId) {\n    var analysisScheduler = connectionId == null ? getStandaloneSchedulerIfStarted() : getConnectedSchedulerIfStarted(connectionId);\n    if (analysisScheduler != null) {\n      if (connectionId != null && !configurationRepository.hasScopesBoundToConnection(connectionId)) {\n        stop(connectionId);\n      } else {\n        analysisScheduler.post(new UnregisterModuleCommand(scopeId));\n      }\n    } else if (connectionId != null && !configurationRepository.hasScopesBoundToConnection(connectionId)) {\n      pluginLifecycleService.unloadPluginsAndEvictCaches(connectionId);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/analysis/AnalysisService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport java.net.URI;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.CompletionException;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\nimport java.util.stream.Collectors;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.apache.commons.lang3.BooleanUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.sonarsource.sonarlint.core.active.rules.ActiveRuleDetails;\nimport org.sonarsource.sonarlint.core.active.rules.ActiveRulesService;\nimport org.sonarsource.sonarlint.core.active.rules.ServerActiveRulesChanged;\nimport org.sonarsource.sonarlint.core.active.rules.StandaloneRulesConfigurationChanged;\nimport org.sonarsource.sonarlint.core.analysis.api.AnalysisConfiguration;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientModuleFileEvent;\nimport org.sonarsource.sonarlint.core.analysis.api.Issue;\nimport org.sonarsource.sonarlint.core.analysis.api.TriggerType;\nimport org.sonarsource.sonarlint.core.analysis.command.AnalyzeCommand;\nimport org.sonarsource.sonarlint.core.analysis.command.NotifyModuleEventCommand;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.BoundScope;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.commons.progress.TaskManager;\nimport org.sonarsource.sonarlint.core.commons.tracing.Trace;\nimport org.sonarsource.sonarlint.core.event.BindingConfigChangedEvent;\nimport org.sonarsource.sonarlint.core.event.ConfigurationScopeRemovedEvent;\nimport org.sonarsource.sonarlint.core.event.ConfigurationScopesAddedWithBindingEvent;\nimport org.sonarsource.sonarlint.core.event.PluginStatusUpdateEvent;\nimport org.sonarsource.sonarlint.core.fs.ClientFile;\nimport org.sonarsource.sonarlint.core.fs.ClientFileSystemService;\nimport org.sonarsource.sonarlint.core.fs.FileExclusionService;\nimport org.sonarsource.sonarlint.core.fs.FileOpenedEvent;\nimport org.sonarsource.sonarlint.core.fs.FileSystemUpdatedEvent;\nimport org.sonarsource.sonarlint.core.fs.OpenFilesRepository;\nimport org.sonarsource.sonarlint.core.languages.LanguageSupportRepository;\nimport org.sonarsource.sonarlint.core.monitoring.MonitoringService;\nimport org.sonarsource.sonarlint.core.nodejs.InstalledNodeJs;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactState;\nimport org.sonarsource.sonarlint.core.plugin.PluginsService;\nimport org.sonarsource.sonarlint.core.plugin.commons.MultivalueProperty;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.DidChangeAnalysisReadinessParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.DidDetectSecretParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.GetInferredAnalysisPropertiesParams;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.sonarsource.sonarlint.core.sync.AnalyzerConfigurationSynchronized;\nimport org.sonarsource.sonarlint.core.sync.ConfigurationScopesSynchronizedEvent;\nimport org.sonarsource.sonarlint.core.sync.PluginsSynchronizedEvent;\nimport org.sonarsource.sonarlint.plugin.api.module.file.ModuleFileEvent;\nimport org.springframework.context.ApplicationEventPublisher;\nimport org.springframework.context.event.EventListener;\n\nimport static java.util.function.Predicate.not;\nimport static java.util.stream.Collectors.groupingBy;\nimport static java.util.stream.Collectors.mapping;\nimport static java.util.stream.Collectors.toMap;\nimport static java.util.stream.Collectors.toSet;\nimport static org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.LanguageDetection.sanitizeExtension;\nimport static org.sonarsource.sonarlint.core.commons.tracing.Trace.startChild;\nimport static org.sonarsource.sonarlint.core.commons.util.StringUtils.pluralize;\nimport static org.sonarsource.sonarlint.core.commons.util.git.GitService.getVCSChangedFiles;\n\npublic class AnalysisService {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final String SONAR_INTERNAL_BUNDLE_PATH_ANALYSIS_PROP = \"sonar.js.internal.bundlePath\";\n  private static final String ANALYSIS_CFG_FOR_ENGINE = \"getAnalysisConfigForEngine\";\n  private static final String GET_ANALYSIS_CFG = \"getAnalysisConfig\";\n\n  private final SonarLintRpcClient client;\n  private final ConfigurationRepository configurationRepository;\n  private final LanguageSupportRepository languageSupportRepository;\n  private final StorageService storageService;\n  private final PluginsService pluginsService;\n  private final ActiveRulesService activeRulesService;\n  private final ClientFileSystemService fileSystemService;\n  private final FileExclusionService fileExclusionService;\n  private final MonitoringService monitoringService;\n  private final TaskManager taskManager;\n  private final NodeJsService nodeJsService;\n  private final AnalysisSchedulerCache schedulerCache;\n  private final ApplicationEventPublisher eventPublisher;\n  private final UserAnalysisPropertiesRepository userAnalysisPropertiesRepository;\n  private final Map<String, Boolean> analysisReadinessByConfigScopeId = new ConcurrentHashMap<>();\n  private final OpenFilesRepository openFilesRepository;\n  private final ClientFileSystemService clientFileSystemService;\n  private final Path esLintBridgeServerPath;\n  private boolean automaticAnalysisEnabled;\n\n  public AnalysisService(SonarLintRpcClient client, ConfigurationRepository configurationRepository, LanguageSupportRepository languageSupportRepository,\n    StorageService storageService, PluginsService pluginsService, ActiveRulesService activeRulesService, ClientFileSystemService fileSystemService,\n    FileExclusionService fileExclusionService, MonitoringService monitoringService, TaskManager taskManager, InitializeParams initializeParams, NodeJsService nodeJsService,\n    AnalysisSchedulerCache schedulerCache, ApplicationEventPublisher eventPublisher, UserAnalysisPropertiesRepository clientAnalysisPropertiesRepository,\n    OpenFilesRepository openFilesRepository, ClientFileSystemService clientFileSystemService) {\n    this.client = client;\n    this.configurationRepository = configurationRepository;\n    this.languageSupportRepository = languageSupportRepository;\n    this.storageService = storageService;\n    this.pluginsService = pluginsService;\n    this.activeRulesService = activeRulesService;\n    this.fileSystemService = fileSystemService;\n    this.fileExclusionService = fileExclusionService;\n    this.monitoringService = monitoringService;\n    this.taskManager = taskManager;\n    this.nodeJsService = nodeJsService;\n    this.schedulerCache = schedulerCache;\n    this.eventPublisher = eventPublisher;\n    this.userAnalysisPropertiesRepository = clientAnalysisPropertiesRepository;\n    this.openFilesRepository = openFilesRepository;\n    this.automaticAnalysisEnabled = initializeParams.isAutomaticAnalysisEnabled();\n    this.clientFileSystemService = clientFileSystemService;\n    this.esLintBridgeServerPath = initializeParams.getLanguageSpecificRequirements() != null && initializeParams.getLanguageSpecificRequirements().getJsTsRequirements() != null\n      ? initializeParams.getLanguageSpecificRequirements().getJsTsRequirements().getBundlePath()\n      : null;\n  }\n\n  @NotNull\n  private static List<String> getPatterns(Set<SonarLanguage> enabledLanguages, Map<String, String> analysisSettings) {\n    List<String> patterns = new ArrayList<>();\n\n    for (SonarLanguage language : enabledLanguages) {\n      String propertyValue = analysisSettings.get(language.getFileSuffixesPropKey());\n      String[] extensions;\n      if (propertyValue == null) {\n        extensions = language.getDefaultFileSuffixes();\n      } else {\n        extensions = MultivalueProperty.parseAsCsv(language.getFileSuffixesPropKey(), propertyValue);\n      }\n      for (String suffix : extensions) {\n        var sanitizedExtension = sanitizeExtension(suffix);\n        patterns.add(\"**/*.\" + sanitizedExtension);\n      }\n    }\n    return patterns;\n  }\n\n  public List<String> getSupportedFilePatterns(String configScopeId) {\n    var effectiveBinding = configurationRepository.getEffectiveBinding(configScopeId);\n    Set<SonarLanguage> enabledLanguages;\n    Map<String, String> analysisSettings;\n    if (effectiveBinding.isEmpty()) {\n      enabledLanguages = languageSupportRepository.getEnabledLanguagesInStandaloneMode();\n      analysisSettings = Collections.emptyMap();\n    } else {\n      enabledLanguages = languageSupportRepository.getEnabledLanguagesInConnectedMode();\n      analysisSettings = storageService.binding(effectiveBinding.get())\n        .analyzerConfiguration().read().getSettings().getAll();\n    }\n    // TODO merge client side analysis settings\n    return getPatterns(enabledLanguages, analysisSettings);\n  }\n\n  private AnalysisConfiguration getAnalysisConfigForEngine(String configScopeId, Set<URI> filesUrisToAnalyze, Map<String, String> extraProperties, boolean hotspotsOnly,\n    TriggerType triggerType, Trace trace) {\n    trace.setData(\"trigger\", triggerType);\n    var baseDir = startChild(trace, \"getBaseDir\", ANALYSIS_CFG_FOR_ENGINE, () -> fileSystemService.getBaseDir(configScopeId));\n    var filesToAnalyze = startChild(trace, \"refineAnalysisScope\", ANALYSIS_CFG_FOR_ENGINE,\n      () -> fileExclusionService.filterOutExcludedFiles(configScopeId, baseDir, filesUrisToAnalyze));\n    var actualBaseDir = baseDir == null ? findCommonPrefix(filesUrisToAnalyze) : baseDir;\n    var analysisConfig = getAnalysisConfig(configScopeId, hotspotsOnly, trace);\n    var analysisProperties = analysisConfig.analysisProperties();\n    var inferredAnalysisProperties = startChild(trace, \"getInferredAnalysisProperties\", ANALYSIS_CFG_FOR_ENGINE,\n      () -> client.getInferredAnalysisProperties(new GetInferredAnalysisPropertiesParams(\n        configScopeId, filesToAnalyze.stream().map(ClientFile::getUri).toList())).join().getProperties());\n    analysisProperties.putAll(inferredAnalysisProperties);\n    trace.setData(\"activeRulesCount\", analysisConfig.activeRules().size());\n    return startChild(trace, \"buildAnalysisConfiguration\", ANALYSIS_CFG_FOR_ENGINE, () -> AnalysisConfiguration.builder()\n      .addInputFiles(filesToAnalyze.stream().map(BackendInputFile::new).toList())\n      .putAllExtraProperties(analysisProperties)\n      // properties sent by client using new API were merged above\n      // but this line is important for backward compatibility for clients directly triggering analysis\n      .putAllExtraProperties(extraProperties)\n      .addActiveRules(analysisConfig.activeRules())\n      .setBaseDir(actualBaseDir)\n      .build());\n  }\n\n  private AnalysisConfig getAnalysisConfig(String configScopeId, boolean hotspotsOnly, @Nullable Trace trace) {\n    var bindingOpt = configurationRepository.getEffectiveBinding(configScopeId);\n    var userAnalysisProperties = userAnalysisPropertiesRepository.getUserProperties(configScopeId);\n    // If the client (IDE) has specified a bundle path, use it\n    if (this.esLintBridgeServerPath != null) {\n      userAnalysisProperties.put(SONAR_INTERNAL_BUNDLE_PATH_ANALYSIS_PROP, this.esLintBridgeServerPath.toString());\n    }\n    if (trace != null) {\n      trace.setData(\"connected\", bindingOpt.isPresent());\n    }\n    if (bindingOpt.isPresent()) {\n      var binding = bindingOpt.get();\n      var analyzerConfig = storageService.binding(binding).analyzerConfiguration();\n      if (analyzerConfig.isValid()) {\n        return getConnectedAnalysisConfig(binding, hotspotsOnly, userAnalysisProperties, trace);\n      } else {\n        // This can happen when a standalone analysis was scheduled and a synchronization happened in between.\n        // The config scope is bound, but the config file is not yet created.\n        // In this case, we still trigger the analysis as if it was in standalone instead of failing.\n        // This log should not appear when a synchronization is not happening.\n        LOG.warn(\"Could not retrieve connected analysis configuration, falling back to standalone configuration\");\n      }\n    }\n    return getStandaloneAnalysisConfig(userAnalysisProperties, trace);\n  }\n\n  private AnalysisConfig getConnectedAnalysisConfig(Binding binding, boolean hotspotsOnly, Map<String, String> userAnalysisProperties, @Nullable Trace trace) {\n    var serverProperties = startChild(trace, \"serverProperties\", GET_ANALYSIS_CFG,\n      () -> storageService.binding(binding).analyzerConfiguration().read().getSettings().getAll());\n    var analysisProperties = new HashMap<>(serverProperties);\n    analysisProperties.putAll(userAnalysisProperties);\n    var connectedActiveRules = startChild(trace, \"buildConnectedActiveRules\", GET_ANALYSIS_CFG,\n      () -> {\n        var activeRules = activeRulesService.getConnectedActiveRules(binding);\n        return hotspotsOnly ? activeRules.stream().filter(activeRule -> activeRule.type() == RuleType.SECURITY_HOTSPOT).toList()\n          : activeRules;\n      });\n    return new AnalysisConfig(connectedActiveRules, analysisProperties);\n  }\n\n  private AnalysisConfig getStandaloneAnalysisConfig(Map<String, String> userAnalysisProperties, @Nullable Trace trace) {\n    var standaloneActiveRules = startChild(trace, \"buildStandaloneActiveRules\", GET_ANALYSIS_CFG, activeRulesService::getStandaloneActiveRules);\n    return new AnalysisConfig(standaloneActiveRules, userAnalysisProperties);\n  }\n\n  private static Path findCommonPrefix(Set<URI> uris) {\n    var paths = uris.stream().map(Paths::get).toList();\n    Path currentPrefixCandidate = paths.get(0).getParent();\n    while (currentPrefixCandidate.getNameCount() > 0 && !isPrefixForAll(currentPrefixCandidate, paths)) {\n      currentPrefixCandidate = currentPrefixCandidate.getParent();\n    }\n    return currentPrefixCandidate;\n  }\n\n  private static boolean isPrefixForAll(Path prefixCandidate, Collection<Path> paths) {\n    return paths.stream().allMatch(p -> p.startsWith(prefixCandidate));\n  }\n\n  public void setUserAnalysisProperties(String configScopeId, Map<String, String> properties) {\n    if (userAnalysisPropertiesRepository.setUserProperties(configScopeId, properties)) {\n      autoAnalyzeOpenFiles(configScopeId);\n    }\n  }\n\n  public void didChangePathToCompileCommands(String configScopeId, @Nullable String pathToCompileCommands) {\n    if (userAnalysisPropertiesRepository.setOrUpdatePathToCompileCommands(configScopeId, pathToCompileCommands)) {\n      autoAnalyzeOpenFiles(configScopeId);\n    }\n  }\n\n  @EventListener\n  public void onPluginsSynchronized(PluginsSynchronizedEvent event) {\n    var connectionId = event.connectionId();\n    if (connectionId != null) {\n      schedulerCache.reloadPlugins(connectionId);\n      checkIfReadyForAnalysis(configurationRepository.getBoundScopesToConnection(connectionId)\n        .stream().map(BoundScope::getConfigScopeId).collect(Collectors.toSet()));\n    } else {\n      // On-demand plugins are application-wide and used as fallback in connected mode\n      schedulerCache.reloadStandalonePlugins();\n      checkIfReadyForAnalysis(new HashSet<>(analysisReadinessByConfigScopeId.keySet()));\n    }\n  }\n\n  @EventListener\n  public void onConfigurationScopeAdded(ConfigurationScopesAddedWithBindingEvent event) {\n    var configScopeIds = event.getConfigScopeIds();\n    checkIfReadyForAnalysis(configScopeIds);\n  }\n\n  @EventListener\n  public void onConfigurationScopeRemoved(ConfigurationScopeRemovedEvent event) {\n    var removedConfigurationScopeId = event.getRemovedConfigurationScopeId();\n    analysisReadinessByConfigScopeId.remove(removedConfigurationScopeId);\n    client.didChangeAnalysisReadiness(new DidChangeAnalysisReadinessParams(Set.of(removedConfigurationScopeId), false));\n    schedulerCache.unregisterModule(removedConfigurationScopeId, event.removedBindingConfiguration().connectionId());\n  }\n\n  @EventListener\n  public void onBindingConfigurationChanged(BindingConfigChangedEvent event) {\n    var configScopeId = event.configScopeId();\n    checkIfReadyForAnalysis(Set.of(configScopeId));\n  }\n\n  @EventListener\n  public void onAnalyzerConfigurationSynchronized(AnalyzerConfigurationSynchronized event) {\n    checkIfReadyForAnalysis(event.configScopeIds());\n  }\n\n  @EventListener\n  public void onConfigurationScopesSynchronized(ConfigurationScopesSynchronizedEvent event) {\n    checkIfReadyForAnalysis(event.getConfigScopeIds());\n  }\n\n  @EventListener\n  public void onPluginStatusUpdateEvent(PluginStatusUpdateEvent event) {\n    if (event.newStatuses().stream().anyMatch(s -> s.state() == ArtifactState.ACTIVE || s.state() == ArtifactState.SYNCED || s.state() == ArtifactState.FAILED)) {\n      var connectionId = event.connectionId();\n      Set<String> configScopeIds;\n      if (connectionId == null) {\n        // On-demand plugins are application-wide and used as fallback in connected mode, so re-check all scopes\n        configScopeIds = new HashSet<>(analysisReadinessByConfigScopeId.keySet());\n      } else {\n        configScopeIds = configurationRepository.getBoundScopesToConnection(connectionId)\n          .stream().map(BoundScope::getConfigScopeId).collect(Collectors.toSet());\n      }\n      checkIfReadyForAnalysis(configScopeIds);\n    }\n  }\n\n  @EventListener\n  public void onFileSystemUpdated(FileSystemUpdatedEvent event) {\n    sendModuleEvents(event.getAdded(), ModuleFileEvent.Type.CREATED);\n    sendModuleEvents(event.getUpdated(), ModuleFileEvent.Type.MODIFIED);\n    sendModuleEvents(event.getRemoved(), ModuleFileEvent.Type.DELETED);\n    var updatedFileUrisByConfigScope = event.getUpdated().stream().collect(groupingBy(ClientFile::getConfigScopeId, mapping(ClientFile::getUri, toSet())));\n    updatedFileUrisByConfigScope.forEach((configScopeId, fileUris) -> {\n      var openFileUris = openFilesRepository.getOpenFilesAmong(configScopeId, fileUris);\n      scheduleAutomaticAnalysis(configScopeId, openFileUris);\n    });\n\n  }\n\n  @EventListener\n  public void onFileOpened(FileOpenedEvent event) {\n    scheduleAutomaticAnalysis(event.configurationScopeId(), Set.of(event.fileUri()));\n  }\n\n  @EventListener\n  public void onStandaloneRulesConfigurationChanged(StandaloneRulesConfigurationChanged event) {\n    if (!event.isOnlyDeactivated()) {\n      // trigger an analysis if any rule was enabled\n      reanalyseOpenFiles(this::isStandalone);\n    }\n  }\n\n  @CheckForNull\n  public UUID forceAnalyzeOpenFiles(String configScopeId) {\n    var openFiles = openFilesRepository.getOpenFilesForConfigScope(configScopeId);\n    if (openFiles.isEmpty()) {\n      // we return UUID because one of the callers is RPC client, it should not call it for empty list of files\n      return null;\n    }\n    return scheduleForcedAnalysis(configScopeId, openFiles, false);\n  }\n\n  public void autoAnalyzeOpenFiles(String configScopeId) {\n    var openFiles = openFilesRepository.getOpenFilesForConfigScope(configScopeId);\n    scheduleAutomaticAnalysis(configScopeId, openFiles);\n  }\n\n  @EventListener\n  public void onServerActiveRulesChanged(ServerActiveRulesChanged event) {\n    var activatedRules = event.activatedRules();\n    if (!activatedRules.isEmpty()) {\n      reanalyseOpenFiles(not(this::isStandalone));\n    }\n  }\n\n  private boolean isStandalone(String configScopeId) {\n    return configurationRepository.getEffectiveBinding(configScopeId).isEmpty();\n  }\n\n  private void sendModuleEvents(List<ClientFile> filesToProcess, ModuleFileEvent.Type type) {\n    var filesByScopeId = filesToProcess.stream().collect(groupingBy(ClientFile::getConfigScopeId));\n    filesByScopeId.forEach((scopeId, files) -> {\n      var scheduler = schedulerCache.getAnalysisSchedulerIfStarted(scopeId);\n      if (scheduler != null) {\n        files.forEach(file -> scheduler.post(new NotifyModuleEventCommand(scopeId, ClientModuleFileEvent.of(new BackendInputFile(file), type))));\n      }\n    });\n  }\n\n  public boolean shouldUseEnterpriseCSharpAnalyzer(String configurationScopeId) {\n    var binding = configurationRepository.getEffectiveBinding(configurationScopeId);\n    if (binding.isEmpty()) {\n      return false;\n    } else {\n      var connectionId = binding.get().connectionId();\n      return pluginsService.shouldUseEnterpriseCSharpAnalyzer(connectionId);\n    }\n  }\n\n  private void streamIssue(String configScopeId, UUID analysisId, List<RawIssue> rawIssues, Issue issue) {\n    var rawIssue = new RawIssue(issue);\n    rawIssues.add(rawIssue);\n    if (rawIssue.getRuleKey().contains(\"secrets\")) {\n      client.didDetectSecret(new DidDetectSecretParams(configScopeId));\n    }\n    eventPublisher.publishEvent(new RawIssueDetectedEvent(configScopeId, analysisId, rawIssue));\n  }\n\n  private void checkIfReadyForAnalysis(Set<String> configurationScopeIds) {\n    var readyConfigScopeIds = new HashSet<String>();\n    var scopeThatBecameReady = new HashSet<String>();\n    var scopeThatBecameNotReady = new HashSet<String>();\n    configurationScopeIds.forEach(configScopeId -> {\n      var readyForAnalysis = isReadyForAnalysis(configScopeId);\n      var childrenScopesWithSameReadiness = configurationRepository.getChildrenWithInheritedBinding(configScopeId);\n      var wasReady = analysisReadinessByConfigScopeId.put(configScopeId, readyForAnalysis);\n      analysisReadinessByConfigScopeId.putAll(childrenScopesWithSameReadiness.stream().collect(toMap(Function.identity(), k -> readyForAnalysis)));\n      if (readyForAnalysis && !Boolean.TRUE.equals(wasReady)) {\n        scopeThatBecameReady.add(configScopeId);\n        scopeThatBecameReady.addAll(childrenScopesWithSameReadiness);\n      } else if (!readyForAnalysis && Boolean.TRUE.equals(wasReady)) {\n        scopeThatBecameNotReady.add(configScopeId);\n        scopeThatBecameNotReady.addAll(childrenScopesWithSameReadiness);\n      }\n      if (readyForAnalysis) {\n        readyConfigScopeIds.add(configScopeId);\n        readyConfigScopeIds.addAll(childrenScopesWithSameReadiness);\n      }\n    });\n    if (!scopeThatBecameReady.isEmpty()) {\n      scopeThatBecameReady.forEach(scopeId -> {\n        var scheduler = schedulerCache.getOrCreateAnalysisScheduler(scopeId);\n        if (scheduler != null) {\n          scheduler.wakeUp();\n        }\n      });\n      client.didChangeAnalysisReadiness(new DidChangeAnalysisReadinessParams(scopeThatBecameReady, true));\n    }\n    if (!scopeThatBecameNotReady.isEmpty()) {\n      client.didChangeAnalysisReadiness(new DidChangeAnalysisReadinessParams(scopeThatBecameNotReady, false));\n    }\n    reanalyseOpenFiles(readyConfigScopeIds::contains);\n  }\n\n  private boolean isReadyForAnalysis(String configScopeId) {\n    return configurationRepository.getEffectiveBinding(configScopeId)\n      .map(this::isReadyForAnalysis)\n      // standalone mode\n      .orElse(true);\n  }\n\n  private boolean isReadyForAnalysis(Binding binding) {\n    var bindingStorage = storageService.binding(binding);\n    var analyzerConfigValid = bindingStorage.analyzerConfiguration().isValid();\n    var findingsStorageValid = bindingStorage.findings().wasEverUpdated();\n    var isReady = analyzerConfigValid\n      // this is not strictly for analysis but for tracking\n      && findingsStorageValid;\n    LOG.debug(\"isReadyForAnalysis(connectionId: {}, sonarProjectKey: {}, plugins: {}, analyzer config: {}, findings: {}) => {}\",\n      binding.connectionId(), binding.sonarProjectKey(), true, analyzerConfigValid, findingsStorageValid, isReady);\n    return isReady;\n  }\n\n  public InstalledNodeJs getAutoDetectedNodeJs() {\n    return nodeJsService.getAutoDetectedNodeJs();\n  }\n\n  public void didChangeAutomaticAnalysisSetting(boolean enabled) {\n    var previouslyEnabled = this.automaticAnalysisEnabled;\n    this.automaticAnalysisEnabled = enabled;\n    if (previouslyEnabled != enabled) {\n      LOG.debug(\"Automatic analysis setting changed to: {}\", enabled);\n      eventPublisher.publishEvent(new AutomaticAnalysisSettingChangedEvent(enabled));\n      if (enabled) {\n        triggerAnalysisForOpenFiles();\n      }\n    }\n  }\n\n  public UUID analyzeFullProject(String configScopeId, boolean hotspotsOnly) {\n    var files = clientFileSystemService.getFiles(configScopeId);\n    return scheduleForcedAnalysis(configScopeId, files.stream().map(ClientFile::getUri).collect(toSet()), hotspotsOnly);\n  }\n\n  public UUID analyzeFileList(String configScopeId, List<URI> filesToAnalyze) {\n    return scheduleForcedAnalysis(configScopeId, Set.copyOf(filesToAnalyze), false);\n  }\n\n  public UUID analyzeVCSChangedFiles(String configScopeId) {\n    var changedFiles = getVCSChangedFiles(clientFileSystemService.getBaseDir(configScopeId));\n    return scheduleForcedAnalysis(configScopeId, changedFiles, false);\n  }\n\n  private void triggerAnalysisForOpenFiles() {\n    openFilesRepository.getOpenFilesByConfigScopeId()\n      .forEach((configurationScopeId, files) -> scheduleForcedAnalysis(configurationScopeId, files, false));\n  }\n\n  private UUID scheduleForcedAnalysis(String configurationScopeId, Set<URI> files, boolean hotspotsOnly) {\n    var analysisId = UUID.randomUUID();\n    var rawIssues = new ArrayList<RawIssue>();\n    schedule(configurationScopeId, getAnalyzeCommand(configurationScopeId, files, rawIssues, hotspotsOnly, TriggerType.FORCED, analysisId),\n      analysisId, rawIssues, true, null)\n      .exceptionally(e -> {\n        if (!(e instanceof CancellationException)) {\n          LOG.error(\"Error during analysis\", e);\n        }\n        return null;\n      });\n    return analysisId;\n  }\n\n  public CompletableFuture<AnalysisResult> scheduleAnalysis(String configurationScopeId, UUID analysisId, Set<URI> files, Map<String, String> extraProperties,\n    boolean shouldFetchServerIssues, TriggerType triggerType, SonarLintCancelMonitor cancelChecker) {\n    var rawIssues = new ArrayList<RawIssue>();\n    var trace = newAnalysisTrace();\n    var analysisTask = new AnalyzeCommand(configurationScopeId, analysisId, triggerType,\n      () -> getAnalysisConfigForEngine(configurationScopeId, files, extraProperties, false, triggerType, trace),\n      issue -> streamIssue(configurationScopeId, analysisId, rawIssues, issue), trace, cancelChecker,\n      taskManager, inputFiles -> analysisStarted(configurationScopeId, analysisId, inputFiles), () -> analysisReadinessByConfigScopeId.getOrDefault(configurationScopeId, false),\n      files, extraProperties);\n    return schedule(configurationScopeId, analysisTask, analysisId, rawIssues, shouldFetchServerIssues, trace);\n  }\n\n  private Trace newAnalysisTrace() {\n    var newTrace = monitoringService.newTrace(\"AnalysisService\", \"analyze\");\n    var currentRuntime = Runtime.getRuntime();\n    newTrace.setData(\"availableProcessors\", currentRuntime.availableProcessors());\n    newTrace.setData(\"totalMemory\", currentRuntime.totalMemory());\n    newTrace.setData(\"maxMemory\", currentRuntime.maxMemory());\n    return newTrace;\n  }\n\n  private void scheduleAutomaticAnalysis(String configScopeId, Set<URI> filesToAnalyze) {\n    if (automaticAnalysisEnabled && !filesToAnalyze.isEmpty()) {\n      var rawIssues = new ArrayList<RawIssue>();\n      var analysisId = UUID.randomUUID();\n      var command = getAnalyzeCommand(configScopeId, filesToAnalyze, rawIssues, false, TriggerType.AUTO, analysisId);\n      schedule(configScopeId, command, analysisId, rawIssues, true, null)\n        .exceptionally(exception -> {\n          if (!(exception instanceof CancellationException) && !(exception instanceof CompletionException && exception.getCause() instanceof CancellationException)) {\n            LOG.error(\"Error during automatic analysis\", exception);\n          }\n          return null;\n        });\n    }\n  }\n\n  private void analysisStarted(String configurationScopeId, UUID analysisId, List<ClientInputFile> inputFiles) {\n    eventPublisher.publishEvent(new AnalysisStartedEvent(configurationScopeId, analysisId, inputFiles));\n  }\n\n  private CompletableFuture<AnalysisResult> schedule(String configScopeId, AnalyzeCommand command, UUID analysisId, ArrayList<RawIssue> rawIssues,\n    boolean shouldFetchServerIssues, @Nullable Trace trace) {\n    var scheduler = startChild(trace, \"getOrCreateAnalysisScheduler\", \"schedule\", () -> schedulerCache.getOrCreateAnalysisScheduler(configScopeId, command.getTrace()));\n    // Plugins may have become ready during scheduler creation (e.g. on-demand cache hit); re-check readiness so the scheduler is woken if needed\n    if (BooleanUtils.isNotTrue(analysisReadinessByConfigScopeId.get(configScopeId))) {\n      checkIfReadyForAnalysis(Set.of(configScopeId));\n    }\n    startChild(trace, \"post\", \"schedule\", () -> scheduler.post(command));\n    var result = command.getFutureResult();\n    result.exceptionally(exception -> {\n      eventPublisher.publishEvent(new AnalysisFailedEvent(analysisId));\n      if (exception instanceof CancellationException) {\n        LOG.debug(\"Analysis canceled\");\n      } else {\n        LOG.error(\"Error during analysis\", exception);\n      }\n      return null;\n    });\n    return result\n      .thenApply(analysisResults -> {\n        var languagePerFile = analysisResults.languagePerFile().entrySet().stream().collect(HashMap<URI, SonarLanguage>::new,\n          (map, entry) -> map.put(entry.getKey().uri(), entry.getValue()), HashMap::putAll);\n        logSummary(rawIssues, analysisResults.getDuration());\n        eventPublisher.publishEvent(new AnalysisFinishedEvent(analysisId, configScopeId, analysisResults.getDuration(),\n          languagePerFile, analysisResults.failedAnalysisFiles().isEmpty(), rawIssues, shouldFetchServerIssues));\n        return new AnalysisResult(\n          analysisResults.failedAnalysisFiles().stream().map(ClientInputFile::getClientObject).map(clientObj -> ((ClientFile) clientObj).getUri()).collect(Collectors.toSet()),\n          rawIssues);\n      });\n  }\n\n  private AnalyzeCommand getAnalyzeCommand(String configurationScopeId, Set<URI> files, ArrayList<RawIssue> rawIssues, boolean hotspotsOnly, TriggerType triggerType,\n    UUID analysisId) {\n    var trace = newAnalysisTrace();\n    return new AnalyzeCommand(configurationScopeId, analysisId, triggerType,\n      () -> getAnalysisConfigForEngine(configurationScopeId, files, Map.of(), hotspotsOnly, triggerType, trace),\n      issue -> streamIssue(configurationScopeId, analysisId, rawIssues, issue), trace,\n      new SonarLintCancelMonitor(), taskManager, inputFiles -> analysisStarted(configurationScopeId, analysisId, inputFiles),\n      () -> analysisReadinessByConfigScopeId.getOrDefault(configurationScopeId, false), files, Map.of());\n  }\n\n  private void reanalyseOpenFiles(Predicate<String> configScopeFilter) {\n    openFilesRepository.getOpenFilesByConfigScopeId()\n      .entrySet()\n      .stream().filter(entry -> configScopeFilter.test(entry.getKey()))\n      .forEach(entry -> scheduleAutomaticAnalysis(entry.getKey(), entry.getValue()));\n  }\n\n  private static void logSummary(List<RawIssue> rawIssues, Duration analysisDuration) {\n    // ignore project-level issues for now\n    var fileRawIssues = rawIssues.stream().filter(issue -> issue.getTextRange() != null).toList();\n    var issuesCount = fileRawIssues.stream().filter(not(RawIssue::isSecurityHotspot)).count();\n    var hotspotsCount = fileRawIssues.stream().filter(RawIssue::isSecurityHotspot).count();\n    LOG.info(\"Analysis detected {} and {} in {}ms\", pluralize(issuesCount, \"issue\"), pluralize(hotspotsCount, \"Security Hotspot\"), analysisDuration.toMillis());\n  }\n\n  private record AnalysisConfig(List<ActiveRuleDetails> activeRules, Map<String, String> analysisProperties) {\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/analysis/AnalysisStartedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.function.UnaryOperator;\nimport java.util.stream.StreamSupport;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\n\nimport static java.util.stream.Collectors.toSet;\n\npublic class AnalysisStartedEvent {\n  private final String configurationScopeId;\n  private final UUID analysisId;\n  private final List<ClientInputFile> files;\n\n  public AnalysisStartedEvent(String configurationScopeId, UUID analysisId, Iterable<ClientInputFile> files) {\n    this.configurationScopeId = configurationScopeId;\n    this.analysisId = analysisId;\n    this.files = StreamSupport.stream(files.spliterator(), false).toList();\n  }\n\n  public UUID getAnalysisId() {\n    return analysisId;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public Set<Path> getFileRelativePaths() {\n    return files.stream().map(ClientInputFile::relativePath).map(Path::of).collect(toSet());\n  }\n\n  public Set<URI> getFileUris() {\n    return files.stream().map(ClientInputFile::uri).collect(toSet());\n  }\n\n  public UnaryOperator<String> getFileContentProvider() {\n    return path -> files.stream()\n      .filter(ClientInputFile::isDirty)\n      .filter(clientInputFile -> clientInputFile.relativePath().equals(path))\n      .findFirst()\n      .map(AnalysisStartedEvent::getClientInputFileContent)\n      .orElse(null);\n  }\n\n  private static String getClientInputFileContent(ClientInputFile clientInputFile) {\n    try {\n      return clientInputFile.contents();\n    } catch (IOException e) {\n      return \"\";\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/analysis/AutomaticAnalysisSettingChangedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\npublic record AutomaticAnalysisSettingChangedEvent(boolean isAutomaticAnalysisEnabled) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/analysis/BackendInputFile.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.nio.charset.Charset;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.util.FileUtils;\nimport org.sonarsource.sonarlint.core.fs.ClientFile;\n\npublic class BackendInputFile implements ClientInputFile {\n  private final ClientFile clientFile;\n\n  public BackendInputFile(ClientFile clientFile) {\n    this.clientFile = clientFile;\n  }\n\n  @Override\n  public String getPath() {\n    return FileUtils.getFilePathFromUri(clientFile.getUri()).toAbsolutePath().toString();\n  }\n\n  @Override\n  public boolean isTest() {\n    return clientFile.isTest();\n  }\n\n  @Override\n  public Charset getCharset() {\n    return clientFile.getCharset();\n  }\n\n  @Override\n  public ClientFile getClientObject() {\n    return clientFile;\n  }\n\n  @Override\n  public InputStream inputStream() throws IOException {\n    return clientFile.inputStream();\n  }\n\n  @Override\n  public String contents() {\n    return clientFile.getContent();\n  }\n\n  @Override\n  public String relativePath() {\n    return clientFile.getClientRelativePath().toString();\n  }\n\n  @Override\n  public URI uri() {\n    return clientFile.getUri();\n  }\n\n  @Override\n  public SonarLanguage language() {\n    return clientFile.getDetectedLanguage();\n  }\n\n  @Override\n  public boolean isDirty() {\n    return clientFile.isDirty();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/analysis/BackendModuleFileSystem.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport java.util.stream.Stream;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientModuleFileSystem;\nimport org.sonarsource.sonarlint.core.fs.ClientFileSystemService;\n\n\npublic class BackendModuleFileSystem implements ClientModuleFileSystem {\n\n  private final ClientFileSystemService clientFileSystemService;\n  private final String configScopeId;\n\n  public BackendModuleFileSystem(ClientFileSystemService clientFileSystemService, String configScopeId) {\n    this.clientFileSystemService = clientFileSystemService;\n    this.configScopeId = configScopeId;\n  }\n\n  @Override\n  public Stream<ClientInputFile> files(String suffix, InputFile.Type type) {\n    return files()\n      .filter(file -> file.relativePath().endsWith(suffix))\n      .filter(file -> file.isTest() == (type == InputFile.Type.TEST));\n  }\n\n  @Override\n  public Stream<ClientInputFile> files() {\n    return this.clientFileSystemService.getFiles(configScopeId).stream().map(BackendInputFile::new);\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/analysis/ClientNodeJsPathChanged.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\npublic class ClientNodeJsPathChanged {\n  ClientNodeJsPathChanged() {\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/analysis/IssuesRaisedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport java.util.List;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\n\npublic record IssuesRaisedEvent(List<RaisedIssueDto> issues) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/analysis/NodeJsService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\nimport org.sonarsource.sonarlint.core.nodejs.InstalledNodeJs;\nimport org.sonarsource.sonarlint.core.nodejs.NodeJsHelper;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.springframework.context.ApplicationEventPublisher;\n\n/**\n * Keep track of the Node.js executable to be used by analysis\n */\npublic class NodeJsService {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final ApplicationEventPublisher eventPublisher;\n  private final boolean isNodeJsNeeded;\n  private volatile boolean nodeAutoDetected;\n  @Nullable\n  private InstalledNodeJs autoDetectedNodeJs;\n  @Nullable\n  private Path clientNodeJsPath;\n  private boolean clientForcedNodeJsDetected;\n  @Nullable\n  private InstalledNodeJs clientForcedNodeJs;\n\n  public NodeJsService(InitializeParams initializeParams, ApplicationEventPublisher eventPublisher) {\n    var languageSpecificRequirements = initializeParams.getLanguageSpecificRequirements();\n    this.clientNodeJsPath = languageSpecificRequirements == null || languageSpecificRequirements.getJsTsRequirements() == null ? null\n      : languageSpecificRequirements.getJsTsRequirements().getClientNodeJsPath();\n    this.isNodeJsNeeded = isNodeJsNeeded(initializeParams);\n    this.eventPublisher = eventPublisher;\n  }\n\n  private static boolean isNodeJsNeeded(InitializeParams initializeParams) {\n    // in theory all clients bundle SonarJS, so this should always return true\n    // in practice and to speed up tests, we will avoid looking up Node.js if SonarJS is not present\n    var languagesNeedingNodeJsInSonarJs = SonarPlugin.JS.getLanguages().stream().map(l -> Language.valueOf(l.name())).collect(Collectors.toSet());\n    return !Collections.disjoint(initializeParams.getEnabledLanguagesInStandaloneMode(), languagesNeedingNodeJsInSonarJs)\n      || !Collections.disjoint(initializeParams.getExtraEnabledLanguagesInConnectedMode(), languagesNeedingNodeJsInSonarJs);\n  }\n\n  @CheckForNull\n  public synchronized InstalledNodeJs didChangeClientNodeJsPath(@Nullable Path clientNodeJsPath) {\n    if (!Objects.equals(this.clientNodeJsPath, clientNodeJsPath)) {\n      this.clientNodeJsPath = clientNodeJsPath;\n      this.clientForcedNodeJsDetected = false;\n      this.eventPublisher.publishEvent(new ClientNodeJsPathChanged());\n    }\n    var forcedNodeJs = getClientForcedNodeJs();\n    return forcedNodeJs == null ? null : new InstalledNodeJs(forcedNodeJs.getPath(), forcedNodeJs.getVersion());\n  }\n\n  @CheckForNull\n  public synchronized InstalledNodeJs getActiveNodeJs() {\n    return clientNodeJsPath == null ? getAutoDetectedNodeJs() : getClientForcedNodeJs();\n  }\n\n  public synchronized Optional<Version> getActiveNodeJsVersion() {\n    return Optional.ofNullable(getActiveNodeJs()).map(InstalledNodeJs::getVersion);\n  }\n\n  @CheckForNull\n  public InstalledNodeJs getAutoDetectedNodeJs() {\n    if (!nodeAutoDetected) {\n      if (!isNodeJsNeeded) {\n        LOG.debug(\"Skip Node.js auto-detection as no plugins require it\");\n        nodeAutoDetected = true;\n        return null;\n      }\n      var helper = new NodeJsHelper();\n      autoDetectedNodeJs = helper.autoDetect();\n      nodeAutoDetected = true;\n      logAutoDetectionResults(autoDetectedNodeJs);\n    }\n    return autoDetectedNodeJs;\n  }\n\n  @CheckForNull\n  private InstalledNodeJs getClientForcedNodeJs() {\n    if (!clientForcedNodeJsDetected) {\n      var helper = new NodeJsHelper();\n      clientForcedNodeJs = helper.detect(clientNodeJsPath);\n      clientForcedNodeJsDetected = true;\n      logClientForcedDetectionResults(clientForcedNodeJs);\n    }\n    return clientForcedNodeJs;\n  }\n\n  private static void logAutoDetectionResults(@Nullable InstalledNodeJs autoDetectedNode) {\n    if (autoDetectedNode != null) {\n      LOG.debug(\"Auto-detected Node.js path set to: {} (version {})\", autoDetectedNode.getPath(), autoDetectedNode.getVersion());\n    } else {\n      LOG.warn(\n        \"Node.js could not be automatically detected, has to be configured manually in the SonarLint preferences!\");\n\n      if (SystemUtils.IS_OS_MAC_OSX) {\n        // In case of macOS or could not be found, just add the warning for the user and us if we have to provide\n        // support on that matter at some point.\n        LOG.warn(\n          \"Automatic detection does not work on macOS when added to PATH from user shell configuration (e.g. Bash)\");\n      }\n    }\n  }\n\n  private static void logClientForcedDetectionResults(@Nullable InstalledNodeJs detectedNode) {\n    if (detectedNode != null) {\n      LOG.debug(\"Node.js path set to: {} (version {})\", detectedNode.getPath(), detectedNode.getVersion());\n    } else {\n      LOG.warn(\n        \"Configured Node.js could not be detected, please check your configuration in the SonarLint settings\");\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/analysis/RawIssue.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport java.net.URI;\nimport java.nio.file.Path;\nimport java.util.Collection;\nimport java.util.EnumMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.IntStream;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.active.rules.ActiveRuleDetails;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.analysis.api.Flow;\nimport org.sonarsource.sonarlint.core.analysis.api.Issue;\nimport org.sonarsource.sonarlint.core.analysis.api.QuickFix;\nimport org.sonarsource.sonarlint.core.commons.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.tracking.TextRangeUtils;\n\npublic class RawIssue {\n\n  private final Issue issue;\n  private final ActiveRuleDetails activeRule;\n  private final Map<SoftwareQuality, ImpactSeverity> impacts = new EnumMap<>(SoftwareQuality.class);\n  @Nullable\n  private final String textRangeHash;\n  @Nullable\n  private final String lineHash;\n\n  public RawIssue(Issue issue) {\n    this.issue = issue;\n    this.activeRule = (ActiveRuleDetails) issue.getActiveRule();\n    this.impacts.putAll(activeRule.impacts());\n    this.impacts.putAll(issue.getOverriddenImpacts());\n    var textRangeWithHash = TextRangeUtils.getTextRangeWithHash(getTextRange(), getClientInputFile());\n    this.textRangeHash = textRangeWithHash == null ? null : textRangeWithHash.getHash();\n    var lineWithHash = TextRangeUtils.getLineWithHash(issue.getTextRange(), getClientInputFile());\n    this.lineHash = lineWithHash == null ? null : lineWithHash.getHash();\n  }\n\n  public IssueSeverity getSeverity() {\n    return activeRule.issueSeverity();\n  }\n\n  public RuleType getRuleType() {\n    return activeRule.type();\n  }\n\n  public boolean isSecurityHotspot() {\n    return getRuleType() == RuleType.SECURITY_HOTSPOT;\n  }\n\n  public CleanCodeAttribute getCleanCodeAttribute() {\n    return activeRule.cleanCodeAttribute();\n  }\n\n  public Map<SoftwareQuality, ImpactSeverity> getImpacts() {\n    return impacts;\n  }\n\n  public String getRuleKey() {\n    return activeRule.ruleKeyString();\n  }\n\n  public String getMessage() {\n    return issue.getMessage();\n  }\n\n  public List<Flow> getFlows() {\n    return issue.flows();\n  }\n\n  public List<QuickFix> getQuickFixes() {\n    return issue.quickFixes();\n  }\n\n  @CheckForNull\n  public TextRange getTextRange() {\n    return issue.getTextRange();\n  }\n\n  @CheckForNull\n  public Path getIdeRelativePath() {\n    var inputFile = issue.getInputFile();\n    return inputFile != null ? Path.of(inputFile.relativePath()) : null;\n  }\n\n  @CheckForNull\n  public URI getFileUri() {\n    var inputFile = issue.getInputFile();\n    return inputFile != null ? inputFile.uri() : null;\n  }\n\n  public boolean isInFile() {\n    return issue.getInputFile() != null;\n  }\n\n  @CheckForNull\n  public ClientInputFile getClientInputFile() {\n    return issue.getInputFile();\n  }\n\n  @CheckForNull\n  public VulnerabilityProbability getVulnerabilityProbability() {\n    return activeRule.vulnerabilityProbability();\n  }\n\n  @CheckForNull\n  public String getRuleDescriptionContextKey() {\n    return issue.getRuleDescriptionContextKey().orElse(null);\n  }\n\n  public Collection<Integer> getLineNumbers() {\n    Set<Integer> lineNumbers = new HashSet<>();\n    Optional.ofNullable(getTextRange())\n      .map(textRange -> IntStream.rangeClosed(textRange.getStartLine(), textRange.getEndLine()))\n      .ifPresent(intStream -> intStream.forEach(lineNumbers::add));\n\n    getFlows()\n      .forEach(flow -> flow.locations().stream()\n        .filter(issueLocation -> Objects.nonNull(issueLocation.getStartLine()))\n        .filter(issueLocation -> Objects.nonNull(issueLocation.getEndLine()))\n        .map(issueLocation -> IntStream.rangeClosed(issueLocation.getStartLine(), issueLocation.getEndLine()))\n        .forEach(intStream -> intStream.forEach(lineNumbers::add)));\n\n    return lineNumbers;\n  }\n\n  public Optional<Integer> getLine() {\n    return Optional.ofNullable(issue.getStartLine());\n  }\n\n  public Optional<String> getTextRangeHash() {\n    return Optional.ofNullable(textRangeHash);\n  }\n\n  public Optional<String> getLineHash() {\n    return Optional.ofNullable(lineHash);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/analysis/RawIssueDetectedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport java.util.UUID;\n\npublic record RawIssueDetectedEvent(String configurationScopeId, UUID analysisId, RawIssue detectedIssue) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/analysis/UserAnalysisPropertiesRepository.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport javax.annotation.Nullable;\n\npublic class UserAnalysisPropertiesRepository {\n  private static final String PATH_TO_COMPILE_COMMANDS_ANALYZER_PROPERTY = \"sonar.cfamily.compile-commands\";\n  private final Map<String, String> pathToCompileCommandsByConfigScope = new ConcurrentHashMap<>();\n  private final Map<String, Map<String, String>> propertiesByConfigScope = new ConcurrentHashMap<>();\n\n  public Map<String, String> getUserProperties(String configurationScopeId) {\n    var properties = propertiesByConfigScope.getOrDefault(configurationScopeId, new HashMap<>());\n    var pathToCompileCommands = pathToCompileCommandsByConfigScope.get(configurationScopeId);\n    if (pathToCompileCommands == null) {\n      return properties;\n    }\n    properties.put(PATH_TO_COMPILE_COMMANDS_ANALYZER_PROPERTY, pathToCompileCommands);\n    return properties;\n  }\n\n  public boolean setUserProperties(String configurationScopeId, Map<String, String> userProperties) {\n    var oldProperties = propertiesByConfigScope.get(configurationScopeId);\n    var newProperties = new HashMap<>(userProperties);\n    var changed = !newProperties.equals(oldProperties);\n    if (changed) {\n      propertiesByConfigScope.put(configurationScopeId, newProperties);\n    }\n    return changed;\n  }\n\n  public boolean setOrUpdatePathToCompileCommands(String configurationScopeId, @Nullable String value) {\n    var newValue = value == null ? \"\" : value;\n    var oldValue = pathToCompileCommandsByConfigScope.get(configurationScopeId);\n    var changed = !newValue.equals(oldValue);\n    if (changed) {\n      pathToCompileCommandsByConfigScope.put(configurationScopeId, newValue);\n    }\n    return changed;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/analysis/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/branch/MatchedSonarProjectBranchChangedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.branch;\n\npublic class MatchedSonarProjectBranchChangedEvent {\n\n  private final String configurationScopeId;\n  private final String newBranchName;\n\n  public MatchedSonarProjectBranchChangedEvent(String configurationScopeId, String newBranchName) {\n    this.configurationScopeId = configurationScopeId;\n    this.newBranchName = newBranchName;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public String getNewBranchName() {\n    return newBranchName;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/branch/SonarProjectBranchMatchingEndedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.branch;\n\npublic class SonarProjectBranchMatchingEndedEvent {\n\n  private final String configurationScopeId;\n\n  public SonarProjectBranchMatchingEndedEvent(String configurationScopeId) {\n    this.configurationScopeId = configurationScopeId;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/branch/SonarProjectBranchTrackingService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.branch;\n\nimport jakarta.annotation.PreDestroy;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.CancellationException;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.slf4j.MDC;\nimport org.sonarsource.sonarlint.core.commons.SmartCancelableLoadingCache;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.event.BindingConfigChangedEvent;\nimport org.sonarsource.sonarlint.core.event.ConfigurationScopeRemovedEvent;\nimport org.sonarsource.sonarlint.core.event.ConfigurationScopesAddedWithBindingEvent;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.branch.DidChangeMatchedSonarProjectBranchParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.branch.MatchSonarProjectBranchParams;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.sonarsource.sonarlint.core.sync.SonarProjectBranchesChangedEvent;\nimport org.springframework.context.ApplicationEventPublisher;\nimport org.springframework.context.event.EventListener;\n\n/**\n * This service keep track of the currently matched Sonar project branch for each configuration scope.\n */\npublic class SonarProjectBranchTrackingService {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final SonarLintRpcClient client;\n  private final StorageService storageService;\n  private final ConfigurationRepository configurationRepository;\n  private final ApplicationEventPublisher applicationEventPublisher;\n  private final SmartCancelableLoadingCache<String, String> cachedMatchingBranchByConfigScope = new SmartCancelableLoadingCache<>(\"sonarlint-branch-matcher\",\n    this::matchSonarProjectBranch, this::afterCachedValueRefreshed);\n\n  public SonarProjectBranchTrackingService(SonarLintRpcClient client, StorageService storageService,\n    ConfigurationRepository configurationRepository, ApplicationEventPublisher applicationEventPublisher) {\n    this.client = client;\n    this.storageService = storageService;\n    this.configurationRepository = configurationRepository;\n    this.applicationEventPublisher = applicationEventPublisher;\n  }\n\n  public Optional<String> awaitEffectiveSonarProjectBranch(String configurationScopeId) {\n    return Optional.ofNullable(cachedMatchingBranchByConfigScope.get(configurationScopeId));\n  }\n\n  private void afterCachedValueRefreshed(String configScopeId, @Nullable String oldValue, @Nullable String newValue) {\n    if (!Objects.equals(newValue, oldValue)) {\n      LOG.debug(\"Matched Sonar project branch for configuration scope '{}' changed from '{}' to '{}'\", configScopeId, oldValue, newValue);\n      if (newValue != null) {\n        client.didChangeMatchedSonarProjectBranch(new DidChangeMatchedSonarProjectBranchParams(configScopeId, newValue));\n        applicationEventPublisher.publishEvent(new MatchedSonarProjectBranchChangedEvent(configScopeId, newValue));\n      }\n    } else {\n      LOG.debug(\"Matched Sonar project branch for configuration scope '{}' is still '{}'\", configScopeId, newValue);\n    }\n  }\n\n  @EventListener\n  public void onConfigurationScopeRemoved(ConfigurationScopeRemovedEvent event) {\n    var removedConfigScopeId = event.getRemovedConfigurationScopeId();\n    LOG.debug(\"Configuration scope '{}' removed, clearing matched branch\", removedConfigScopeId);\n    cachedMatchingBranchByConfigScope.clear(removedConfigScopeId);\n  }\n\n  @EventListener\n  public void onConfigurationScopesAdded(ConfigurationScopesAddedWithBindingEvent event) {\n    var configScopeIds = event.getConfigScopeIds();\n    configScopeIds.forEach(configScopeId -> {\n      var effectiveBinding = configurationRepository.getEffectiveBinding(configScopeId);\n      if (effectiveBinding.isPresent()) {\n        var branchesStorage = storageService.binding(effectiveBinding.get()).branches();\n        if (branchesStorage.exists()) {\n          LOG.debug(\"Bound configuration scope '{}' added with an existing storage, queuing matching of the Sonar project branch...\", configScopeId);\n          cachedMatchingBranchByConfigScope.refreshAsync(configScopeId);\n        }\n      }\n    });\n  }\n\n  @EventListener\n  public void onBindingChanged(BindingConfigChangedEvent bindingChanged) {\n    var configScopeId = bindingChanged.configScopeId();\n    if (!bindingChanged.newConfig().isBound()) {\n      LOG.debug(\"Configuration scope '{}' unbound, clearing matched branch\", configScopeId);\n      cachedMatchingBranchByConfigScope.clear(configScopeId);\n    } else {\n      LOG.debug(\"Configuration scope '{}' binding changed, queuing matching of the Sonar project branch...\", configScopeId);\n      cachedMatchingBranchByConfigScope.refreshAsync(configScopeId);\n    }\n  }\n\n  @EventListener\n  public void onSonarProjectBranchChanged(SonarProjectBranchesChangedEvent event) {\n    var configScopeIds = configurationRepository.getBoundScopesToConnectionAndSonarProject(event.getConnectionId(), event.getSonarProjectKey());\n    configScopeIds.forEach(boundScope -> {\n      LOG.debug(\"Sonar project branch changed for configuration scope '{}', queuing matching of the Sonar project branch...\", boundScope.getConfigScopeId());\n      cachedMatchingBranchByConfigScope.refreshAsync(boundScope.getConfigScopeId());\n    });\n  }\n\n  public void didVcsRepositoryChange(String configScopeId) {\n    LOG.debug(\"VCS repository changed for configuration scope '{}', queuing matching of the Sonar project branch...\", configScopeId);\n    cachedMatchingBranchByConfigScope.refreshAsync(configScopeId);\n  }\n\n  private String matchSonarProjectBranch(String configurationScopeId, SonarLintCancelMonitor cancelMonitor) {\n    MDC.put(\"configScopeId\", configurationScopeId);\n    LOG.debug(\"Matching Sonar project branch\");\n    var effectiveBindingOpt = configurationRepository.getEffectiveBinding(configurationScopeId);\n    if (effectiveBindingOpt.isEmpty()) {\n      LOG.debug(\"No binding for configuration scope\");\n      return null;\n    }\n    var effectiveBinding = effectiveBindingOpt.get();\n\n    var branchesStorage = storageService.binding(effectiveBinding).branches();\n    if (!branchesStorage.exists()) {\n      LOG.info(\"Cannot match Sonar branch, storage is empty\");\n      return null;\n    }\n    var storedBranches = branchesStorage.read();\n    var mainBranchName = storedBranches.getMainBranchName();\n    var matchedSonarBranch = requestClientToMatchSonarProjectBranch(configurationScopeId, mainBranchName, storedBranches.getBranchNames(), cancelMonitor);\n    if (matchedSonarBranch == null) {\n      matchedSonarBranch = mainBranchName;\n    }\n    cancelMonitor.checkCanceled();\n    return matchedSonarBranch;\n  }\n\n  @CheckForNull\n  private String requestClientToMatchSonarProjectBranch(String configurationScopeId, String mainSonarBranchName, Set<String> allSonarBranchesNames,\n    SonarLintCancelMonitor cancelMonitor) {\n    var matchSonarProjectBranchResponseCompletableFuture = client\n      .matchSonarProjectBranch(new MatchSonarProjectBranchParams(configurationScopeId, mainSonarBranchName, allSonarBranchesNames));\n    cancelMonitor.onCancel(() -> matchSonarProjectBranchResponseCompletableFuture.cancel(true));\n    try {\n      return matchSonarProjectBranchResponseCompletableFuture.join().getMatchedSonarProjectBranch();\n    } catch (CancellationException e) {\n      throw e;\n    } catch (Exception e) {\n      LOG.debug(\"Error while matching Sonar project branch for configuration scope '{}'\", configurationScopeId, e);\n      return null;\n    }\n  }\n\n  @PreDestroy\n  public void shutdown() {\n    cachedMatchingBranchByConfigScope.close();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/branch/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.branch;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/commons/Binding.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\npublic record Binding(String connectionId, String sonarProjectKey) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/commons/BoundScope.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\n/**\n * A configuration scope that is bound to a connection and a Sonar project.\n */\npublic class BoundScope {\n\n  private final String configScopeId;\n  private final Binding binding;\n\n  public BoundScope(String configScopeId, String connectionId, String sonarProjectKey) {\n    this(configScopeId, new Binding(connectionId, sonarProjectKey));\n  }\n\n  public BoundScope(String configScopeId, Binding binding) {\n    this.configScopeId = configScopeId;\n    this.binding = binding;\n  }\n\n  public String getConfigScopeId() {\n    return configScopeId;\n  }\n\n  public Binding getBinding() {\n    return binding;\n  }\n\n  public String getConnectionId() {\n    return binding.connectionId();\n  }\n\n  public String getSonarProjectKey() {\n    return binding.sonarProjectKey();\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/commons/DebounceComputer.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.locks.ReentrantReadWriteLock;\nimport java.util.function.Function;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.progress.ExecutorServiceShutdownWatchable;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\n\n/**\n * The goal of this class is to debounce calls to a function that computes a value. Multiple threads can be blocked on the {@link #get()} method, waiting for the end of the computation.\n * If a {@link #scheduleComputationAsync()} is called while a computation is in progress, an attempt will be made to cancel the current computation, and a new computation will be scheduled.\n * Last feature: it is possible to register a listener that will be notified only after a successful computation (not after a cancellation).\n */\nclass DebounceComputer<V> {\n  private final Function<SonarLintCancelMonitor, V> valueComputer;\n  private final ExecutorServiceShutdownWatchable<?> executorService;\n  @Nullable\n  private final Listener<V> listener;\n  private CompletableFuture<V> valueFuture = new CompletableFuture<>();\n  @Nullable\n  private CompletableFuture<V> computeFuture;\n  // The last computed value (a compute task went to completion without cancellation). Can be null if the compute task failed.\n  @Nullable\n  private V value;\n  private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();\n\n  public interface Listener<V> {\n\n    void afterComputedValueRefreshed(@Nullable V oldValue, @Nullable V newValue);\n\n  }\n\n  public DebounceComputer(Function<SonarLintCancelMonitor, V> valueComputer, ExecutorServiceShutdownWatchable<?> executorService, @Nullable Listener<V> listener) {\n    this.valueComputer = valueComputer;\n    this.executorService = executorService;\n    this.listener = listener;\n  }\n\n  public V get() {\n    return getValueFuture().join();\n  }\n\n  public void scheduleComputationAsync() {\n    lock.writeLock().lock();\n    try {\n      if (computeFuture != null) {\n        computeFuture.cancel(false);\n        try {\n          computeFuture.join();\n        } catch (Exception ignore) {\n          // expected Cancellation exception, but we can ignore any other error since we are going to compute a new value anyway\n        }\n        computeFuture = null;\n      }\n      if (valueFuture.isDone()) {\n        valueFuture = new CompletableFuture<>();\n      }\n      var cancelMonitor = new SonarLintCancelMonitor();\n      cancelMonitor.watchForShutdown(executorService);\n      CompletableFuture<V> newComputeFuture = CompletableFuture.supplyAsync(() -> {\n        cancelMonitor.checkCanceled();\n        return valueComputer.apply(cancelMonitor);\n      }, executorService);\n      newComputeFuture.whenComplete((newValue, error) -> {\n        if (error instanceof CancellationException) {\n          cancelMonitor.cancel();\n        }\n      });\n      newComputeFuture.whenComplete(this::whenComputeCompleted);\n      computeFuture = newComputeFuture;\n    } finally {\n      lock.writeLock().unlock();\n    }\n  }\n\n  private void whenComputeCompleted(@Nullable V newValue, @Nullable Throwable error) {\n    lock.writeLock().lock();\n    try {\n      computeFuture = null;\n      if (error instanceof CancellationException) {\n        return;\n      }\n      var previousValue = value;\n      value = newValue;\n      try {\n        if (listener != null) {\n          listener.afterComputedValueRefreshed(previousValue, newValue);\n        }\n      } finally {\n        if (error != null) {\n          valueFuture.completeExceptionally(error);\n        } else {\n          valueFuture.complete(newValue);\n        }\n      }\n    } finally {\n      lock.writeLock().unlock();\n    }\n  }\n\n  private CompletableFuture<V> getValueFuture() {\n    lock.readLock().lock();\n    try {\n      return valueFuture;\n    } finally {\n      lock.readLock().unlock();\n    }\n  }\n\n  public void cancel() {\n    lock.writeLock().lock();\n    try {\n      if (computeFuture != null) {\n        computeFuture.cancel(false);\n        computeFuture = null;\n      }\n      valueFuture.cancel(false);\n    } finally {\n      lock.writeLock().unlock();\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/commons/SmartCancelableLoadingCache.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport com.google.common.util.concurrent.MoreExecutors;\nimport java.util.Objects;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.BiFunction;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.ExecutorServiceShutdownWatchable;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.commons.util.FailSafeExecutors;\n\n/**\n * A cache with async computation of values, and supporting cancellation.\n * \"Smart\" because when a computation is cancelled, it will return to the previous callers the result of the new computation.\n */\npublic class SmartCancelableLoadingCache<K, V> implements AutoCloseable {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final ExecutorServiceShutdownWatchable<?> executorService;\n  private final String threadName;\n  private final BiFunction<K, SonarLintCancelMonitor, V> valueComputer;\n  private final ConcurrentHashMap<K, DebounceComputer<V>> cache = new ConcurrentHashMap<>();\n\n  @Nullable\n  private final Listener<K, V> listener;\n\n  public interface Listener<K, V> {\n\n    void afterCachedValueRefreshed(K key, @Nullable V oldValue, @Nullable V newValue);\n\n  }\n\n  public SmartCancelableLoadingCache(String threadName, BiFunction<K, SonarLintCancelMonitor, V> valueComputer) {\n    this(threadName, valueComputer, null);\n  }\n\n  public SmartCancelableLoadingCache(String threadName, BiFunction<K, SonarLintCancelMonitor, V> valueComputer, @Nullable Listener<K, V> listener) {\n    this.executorService = new ExecutorServiceShutdownWatchable<>(FailSafeExecutors.newSingleThreadExecutor(threadName));\n    this.threadName = threadName;\n    this.valueComputer = valueComputer;\n    this.listener = listener;\n  }\n\n\n  /**\n   * Clear the cached value for this key. Attempt to cancel the computation if it is still running.\n   * Awaiting #get() will throw a {@link CancellationException}.\n   */\n  public void clear(K key) {\n    var valueAndComputeFutures = cache.remove(key);\n    if (valueAndComputeFutures != null) {\n      valueAndComputeFutures.cancel();\n    }\n  }\n\n  /**\n   * Force a new computation for this key. Ensure {@link Listener#afterCachedValueRefreshed(Object, Object, Object)} is called.\n   * Awaiting #get() will receive the newly computed value\n   */\n  public void refreshAsync(K key) {\n    cache.compute(key, (k, v) -> {\n      if (v == null) {\n        return newValueAndScheduleComputation(k);\n      } else {\n        v.scheduleComputationAsync();\n        return v;\n      }\n    });\n  }\n\n  public V get(K key) {\n    return cache.computeIfAbsent(key, this::newValueAndScheduleComputation).get();\n  }\n\n  private DebounceComputer<V> newValueAndScheduleComputation(K k) {\n    var value = new DebounceComputer<>(c -> valueComputer.apply(k, c), executorService, (oldValue, newValue) -> {\n      if (listener != null && !Objects.equals(oldValue, newValue)) {\n        listener.afterCachedValueRefreshed(k, oldValue, newValue);\n      }\n    });\n    value.scheduleComputationAsync();\n    return value;\n  }\n\n\n  @Override\n  public void close() {\n    if (!MoreExecutors.shutdownAndAwaitTermination(executorService, 1, TimeUnit.SECONDS)) {\n      LOG.warn(\"Unable to stop \" + threadName + \" executor service in a timely manner\");\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/commons/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.commons;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/SonarQubeClient.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.connection;\n\nimport java.time.Instant;\nimport java.time.Period;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.sync.InvalidTokenParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.exception.UnauthorizedException;\n\npublic class SonarQubeClient {\n\n  private static final Period WRONG_TOKEN_NOTIFICATION_INTERVAL = Period.ofDays(1);\n  private final String connectionId;\n  private final ServerApi serverApi;\n  private final Either<TokenDto, UsernamePasswordDto> credentials;\n  private final SonarLintRpcClient client;\n  private SonarQubeClientState state = SonarQubeClientState.ACTIVE;\n  @Nullable\n  private Instant lastNotificationTime;\n\n  public SonarQubeClient(String connectionId, ServerApi serverApi, Either<TokenDto, UsernamePasswordDto> credentials, SonarLintRpcClient client) {\n    this.connectionId = connectionId;\n    this.serverApi = serverApi;\n    this.credentials = credentials;\n    this.client = client;\n  }\n\n  public Either<TokenDto, UsernamePasswordDto> getCredentials() {\n    return credentials;\n  }\n\n  public boolean isActive() {\n    return state == SonarQubeClientState.ACTIVE;\n  }\n\n  public <T> T withClientApiAndReturn(Function<ServerApi, T> serverApiConsumer) {\n    try {\n      var result = serverApiConsumer.apply(serverApi);\n      state = SonarQubeClientState.ACTIVE;\n      lastNotificationTime = null;\n      return result;\n    } catch (UnauthorizedException e) {\n      state = SonarQubeClientState.INVALID_CREDENTIALS;\n      notifyClientAboutWrongTokenIfNeeded();\n    }\n    return null;\n  }\n\n  public void withClientApi(Consumer<ServerApi> serverApiConsumer) {\n    try {\n      serverApiConsumer.accept(serverApi);\n      state = SonarQubeClientState.ACTIVE;\n      lastNotificationTime = null;\n    } catch (UnauthorizedException e) {\n      state = SonarQubeClientState.INVALID_CREDENTIALS;\n      notifyClientAboutWrongTokenIfNeeded();\n    }\n  }\n\n  private boolean shouldNotifyAboutWrongToken() {\n    if (state != SonarQubeClientState.INVALID_CREDENTIALS && state != SonarQubeClientState.MISSING_PERMISSION) {\n      return false;\n    }\n    if (lastNotificationTime == null) {\n      return true;\n    }\n    return lastNotificationTime.plus(WRONG_TOKEN_NOTIFICATION_INTERVAL).isBefore(Instant.now());\n  }\n\n  private void notifyClientAboutWrongTokenIfNeeded() {\n    if (shouldNotifyAboutWrongToken()) {\n      client.invalidToken(new InvalidTokenParams(connectionId));\n      lastNotificationTime = Instant.now();\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/SonarQubeClientState.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.connection;\n\npublic enum SonarQubeClientState {\n  ACTIVE, INVALID_CREDENTIALS, MISSING_PERMISSION\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.connection;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/AnalyzeFileListRequestHandler.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server;\n\nimport com.google.gson.Gson;\nimport java.io.IOException;\nimport java.net.URI;\nimport java.nio.file.Paths;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.ExecutionException;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport javax.annotation.Nullable;\nimport org.apache.hc.core5.http.ClassicHttpRequest;\nimport org.apache.hc.core5.http.ClassicHttpResponse;\nimport org.apache.hc.core5.http.ContentType;\nimport org.apache.hc.core5.http.HttpException;\nimport org.apache.hc.core5.http.HttpStatus;\nimport org.apache.hc.core5.http.Method;\nimport org.apache.hc.core5.http.io.HttpRequestHandler;\nimport org.apache.hc.core5.http.io.entity.EntityUtils;\nimport org.apache.hc.core5.http.io.entity.StringEntity;\nimport org.apache.hc.core5.http.protocol.HttpContext;\nimport org.sonarsource.sonarlint.core.analysis.api.TriggerType;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.analysis.AnalysisService;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.fs.ClientFileSystemService;\nimport org.sonarsource.sonarlint.core.tracking.TaintVulnerabilityTrackingService;\n\npublic class AnalyzeFileListRequestHandler implements HttpRequestHandler {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final AnalysisService analysisService;\n  private final ClientFileSystemService clientFileSystemService;\n  private final TaintVulnerabilityTrackingService taintService;\n  private final Gson gson = new Gson();\n\n  public AnalyzeFileListRequestHandler(AnalysisService analysisService, ClientFileSystemService clientFileSystemService,\n    TaintVulnerabilityTrackingService taintService) {\n    this.analysisService = analysisService;\n    this.clientFileSystemService = clientFileSystemService;\n    this.taintService = taintService;\n  }\n\n  @Override\n  public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext httpContext) throws HttpException, IOException {\n    LOG.debug(\"Received request for analyzing a list of files\");\n\n    if (!Method.POST.isSame(request.getMethod())) {\n      response.setCode(HttpStatus.SC_BAD_REQUEST);\n      return;\n    }\n\n    AnalyzeFileListRequest analysisRequest;\n    try {\n      var requestBody = EntityUtils.toString(request.getEntity(), \"UTF-8\");\n      analysisRequest = gson.fromJson(requestBody, AnalyzeFileListRequest.class);\n    } catch (Exception e) {\n      LOG.warn(\"Failed to parse analyze file list request\", e);\n      response.setCode(HttpStatus.SC_BAD_REQUEST);\n      response.setEntity(new StringEntity(\"Failed to parse analyze file list request\", ContentType.APPLICATION_JSON));\n      return;\n    }\n\n    if (analysisRequest == null || analysisRequest.fileAbsolutePaths == null || analysisRequest.fileAbsolutePaths.isEmpty()) {\n      LOG.warn(\"Empty or invalid file list in analyze request\");\n      response.setCode(HttpStatus.SC_BAD_REQUEST);\n      response.setEntity(new StringEntity(\"Empty or invalid file list in analyze request\", ContentType.APPLICATION_JSON));\n      return;\n    }\n\n    try {\n      response.setEntity(new StringEntity(new Gson().toJson(analyze(analysisRequest)), ContentType.APPLICATION_JSON));\n    } catch (Exception e) {\n      LOG.error(\"Failed to analyze files\", e);\n      response.setCode(HttpStatus.SC_INTERNAL_SERVER_ERROR);\n      response.setEntity(new StringEntity(\"Failed to analyze files, reason: \" + e.getMessage(), ContentType.APPLICATION_JSON));\n    }\n  }\n\n  private AnalyzeFileListResult analyze(AnalyzeFileListRequest request) {\n    var cancelMonitor = new SonarLintCancelMonitor();\n    var filePaths = request.fileAbsolutePaths.stream()\n      .map(path -> Paths.get(path).toUri().normalize())\n      .collect(Collectors.toSet());\n\n    LOG.debug(\"Analyzing list of {} files: {}\", filePaths.size(), filePaths);\n\n    var filesByScope = clientFileSystemService.groupFilesByConfigScope(filePaths);\n\n    if (filesByScope.isEmpty()) {\n      LOG.warn(\"No files belong to any configured scope, skipping analysis\");\n      throw new IllegalStateException(\"No files were found to be indexed by SonarQube for IDE\");\n    }\n\n    var allIssues = filesByScope.entrySet().stream().flatMap(entry -> {\n      var configScopeId = entry.getKey();\n      var files = entry.getValue();\n      LOG.info(\"Analyzing list of {} files: {}\", files.size(), files);\n      try {\n        var taints = getTaintsAsRawFindings(configScopeId, files, cancelMonitor);\n        var issues = getIssuesAndHotspotsAsRawFindings(configScopeId, files, cancelMonitor);\n        return Stream.concat(taints, issues);\n      } catch (ExecutionException | InterruptedException e) {\n        LOG.error(\"Failed to analyze files for config scope {}\", configScopeId, e);\n        throw new RuntimeException(e);\n      }\n    }).toList();\n\n    return new AnalyzeFileListResult(allIssues);\n  }\n\n  private Stream<RawFindingResponse> getTaintsAsRawFindings(String configScopeId, Set<URI> files, SonarLintCancelMonitor cancelMonitor) {\n    return taintService.listAll(configScopeId, true, cancelMonitor)\n      .stream()\n      .filter(taint -> files.contains(taint.getIdeFilePath().toUri()))\n      .map(taint -> {\n        var isMqrMode = taint.getSeverityMode().isRight();\n        var textRange = taint.getTextRange();\n        return new RawFindingResponse(\n          taint.getRuleKey(),\n          taint.getMessage(),\n          isMqrMode ? taint.getSeverityMode().getRight().getImpacts().stream()\n            .map(impact -> impact.getImpactSeverity().name())\n            .collect(Collectors.joining(\",\")) : taint.getSeverityMode().getLeft().getSeverity().name(),\n          taint.getIdeFilePath().toString(),\n          textRange == null ? null : new TextRange(textRange.getStartLine(), textRange.getStartLineOffset(), textRange.getEndLine(), textRange.getEndLineOffset())\n        );\n      });\n  }\n\n  private Stream<RawFindingResponse> getIssuesAndHotspotsAsRawFindings(String configScopeId, Set<URI> files, SonarLintCancelMonitor cancelMonitor)\n    throws ExecutionException, InterruptedException {\n    return analysisService.scheduleAnalysis(configScopeId, UUID.randomUUID(), files, Collections.emptyMap(),\n        false, TriggerType.FORCED, cancelMonitor)\n      .thenApplyAsync(results ->\n        results.rawIssues().stream().map(i -> new RawFindingResponse(\n          i.getRuleKey(),\n          i.getMessage(),\n          i.getSeverity() == null ? null : i.getSeverity().name(),\n          i.getFileUri() == null ? null : i.getFileUri().getPath(),\n          i.getTextRange()\n        )))\n      .get();\n  }\n\n  public record AnalyzeFileListRequest(List<String> fileAbsolutePaths) {\n  }\n\n  public record AnalyzeFileListResult(List<RawFindingResponse> findings) {\n  }\n\n  public record RawFindingResponse(String ruleKey, String message, @Nullable String severity, @Nullable String filePath, @Nullable TextRange textRange) {\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/AttributeUtils.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server;\n\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Optional;\nimport org.apache.hc.core5.http.protocol.HttpContext;\n\npublic class AttributeUtils {\n\n  public static final String PARAMS_ATTRIBUTE = \"params\";\n  public static final String ORIGIN_ATTRIBUTE = \"origin\";\n\n  private AttributeUtils() { }\n\n  /**\n   * Parsed query parameters of an HTTP request.\n   * Is set in {@link org.sonarsource.sonarlint.core.embedded.server.filter.ParseParamsFilter}\n   */\n  public static Map<String, String> getParams(HttpContext context) {\n    return Optional.of(context)\n      .map(c -> (Map<String, String>) c.getAttribute(PARAMS_ATTRIBUTE))\n      .orElse(Collections.emptyMap());\n  }\n\n  /**\n   * Value of 'Origin' header.\n   * Is set in {@link org.sonarsource.sonarlint.core.embedded.server.filter.RateLimitFilter} and can not be null afterward.\n   */\n  public static String getOrigin(HttpContext context) {\n    return (String) context.getAttribute(ORIGIN_ATTRIBUTE);\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/AwaitingUserTokenFutureRepository.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server;\n\nimport java.util.Optional;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ConcurrentHashMap;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth.HelpGenerateUserTokenResponse;\n\nimport static org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository.haveSameOrigin;\n\npublic class AwaitingUserTokenFutureRepository {\n  private final ConcurrentHashMap<String, CompletableFuture<HelpGenerateUserTokenResponse>> awaitingFuturesByServerUrl = new ConcurrentHashMap<>();\n\n  public void addExpectedResponse(String serverBaseUrl, CompletableFuture<HelpGenerateUserTokenResponse> futureResponse) {\n    var previousFuture = awaitingFuturesByServerUrl.put(serverBaseUrl, futureResponse);\n    if (previousFuture != null) {\n      previousFuture.cancel(false);\n    }\n    futureResponse.whenComplete((r, e) -> awaitingFuturesByServerUrl.remove(serverBaseUrl, futureResponse));\n  }\n\n  public Optional<CompletableFuture<HelpGenerateUserTokenResponse>> consumeFutureResponse(String serverOrigin) {\n    for (var iterator = awaitingFuturesByServerUrl.entrySet().iterator(); iterator.hasNext();) {\n      var entry = iterator.next();\n      if (haveSameOrigin(entry.getKey(), serverOrigin)) {\n        iterator.remove();\n        return Optional.of(entry.getValue());\n      }\n    }\n    return Optional.empty();\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/EmbeddedServer.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server;\n\nimport jakarta.annotation.PostConstruct;\nimport jakarta.annotation.PreDestroy;\nimport java.net.InetAddress;\nimport java.util.concurrent.TimeUnit;\nimport org.apache.hc.core5.http.ConnectionReuseStrategy;\nimport org.apache.hc.core5.http.HttpRequest;\nimport org.apache.hc.core5.http.HttpResponse;\nimport org.apache.hc.core5.http.impl.bootstrap.HttpServer;\nimport org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap;\nimport org.apache.hc.core5.http.io.SocketConfig;\nimport org.apache.hc.core5.http.protocol.HttpContext;\nimport org.apache.hc.core5.io.CloseMode;\nimport org.sonarsource.sonarlint.core.SonarCloudActiveEnvironment;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.embedded.server.filter.CorsFilter;\nimport org.sonarsource.sonarlint.core.embedded.server.filter.CspFilter;\nimport org.sonarsource.sonarlint.core.embedded.server.filter.ParseParamsFilter;\nimport org.sonarsource.sonarlint.core.embedded.server.filter.RateLimitFilter;\nimport org.sonarsource.sonarlint.core.embedded.server.filter.ValidationFilter;\nimport org.sonarsource.sonarlint.core.embedded.server.handler.GeneratedUserTokenHandler;\nimport org.sonarsource.sonarlint.core.embedded.server.handler.ShowFixSuggestionRequestHandler;\nimport org.sonarsource.sonarlint.core.embedded.server.handler.ShowHotspotRequestHandler;\nimport org.sonarsource.sonarlint.core.embedded.server.handler.ShowIssueRequestHandler;\nimport org.sonarsource.sonarlint.core.embedded.server.handler.StatusRequestHandler;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.embeddedserver.EmbeddedServerStartedParams;\n\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.EMBEDDED_SERVER;\n\npublic class EmbeddedServer {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private static final int STARTING_PORT = 64120;\n  private static final int ENDING_PORT = 64130;\n\n  private static final int INVALID_PORT = -1;\n\n  private HttpServer server;\n  private int port;\n  private final boolean enabled;\n  private final StatusRequestHandler statusRequestHandler;\n  private final GeneratedUserTokenHandler generatedUserTokenHandler;\n  private final ShowHotspotRequestHandler showHotspotRequestHandler;\n  private final ShowIssueRequestHandler showIssueRequestHandler;\n  private final ShowFixSuggestionRequestHandler showFixSuggestionRequestHandler;\n  private final ToggleAutomaticAnalysisRequestHandler toggleAutomaticAnalysisRequestHandler;\n  private final AnalyzeFileListRequestHandler analyzeFileListRequestHandler;\n  private final SonarLintRpcClient client;\n\n  public EmbeddedServer(InitializeParams params, StatusRequestHandler statusRequestHandler, GeneratedUserTokenHandler generatedUserTokenHandler,\n    ShowHotspotRequestHandler showHotspotRequestHandler, ShowIssueRequestHandler showIssueRequestHandler, ShowFixSuggestionRequestHandler showFixSuggestionRequestHandler,\n    ToggleAutomaticAnalysisRequestHandler toggleAutomaticAnalysisRequestHandler, AnalyzeFileListRequestHandler analyzeFileListRequestHandler, SonarLintRpcClient client) {\n    this.enabled = params.getBackendCapabilities().contains(EMBEDDED_SERVER);\n    this.statusRequestHandler = statusRequestHandler;\n    this.generatedUserTokenHandler = generatedUserTokenHandler;\n    this.showHotspotRequestHandler = showHotspotRequestHandler;\n    this.showIssueRequestHandler = showIssueRequestHandler;\n    this.showFixSuggestionRequestHandler = showFixSuggestionRequestHandler;\n    this.toggleAutomaticAnalysisRequestHandler = toggleAutomaticAnalysisRequestHandler;\n    this.analyzeFileListRequestHandler = analyzeFileListRequestHandler;\n    this.client = client;\n  }\n\n  @PostConstruct\n  public void start() {\n    if (!enabled) {\n      return;\n    }\n    final var socketConfig = SocketConfig.custom()\n      .setSoTimeout(15, TimeUnit.SECONDS)\n      // let the port be bindable again immediately\n      .setSoReuseAddress(true)\n      .setTcpNoDelay(true)\n      .build();\n    port = INVALID_PORT;\n    var triedPort = STARTING_PORT;\n    HttpServer startedServer = null;\n    var loopbackAddress = InetAddress.getLoopbackAddress();\n    while (port < 0 && triedPort <= ENDING_PORT) {\n      try {\n        startedServer = ServerBootstrap.bootstrap()\n          .setLocalAddress(loopbackAddress)\n          .setCanonicalHostName(loopbackAddress.getHostName())\n          // we will never have long connections\n          .setConnectionReuseStrategy(new DontKeepAliveReuseStrategy())\n          .setListenerPort(triedPort)\n          .setSocketConfig(socketConfig)\n          .addFilterFirst(\"RateLimiter\", new RateLimitFilter())\n          .addFilterAfter(\"RateLimiter\", \"CORS\", new CorsFilter())\n          .addFilterAfter(\"CORS\", \"Params\", new ParseParamsFilter())\n          .addFilterAfter(\"Params\", \"Validation\", new ValidationFilter(client, SonarCloudActiveEnvironment.prod()))\n          .register(\"/sonarlint/api/status\", statusRequestHandler)\n          .register(\"/sonarlint/api/token\", generatedUserTokenHandler)\n          .register(\"/sonarlint/api/hotspots/show\", showHotspotRequestHandler)\n          .register(\"/sonarlint/api/issues/show\", showIssueRequestHandler)\n          .register(\"/sonarlint/api/fix/show\", showFixSuggestionRequestHandler)\n          .register(\"/sonarlint/api/analysis/automatic/config\", toggleAutomaticAnalysisRequestHandler)\n          .register(\"/sonarlint/api/analysis/files\", analyzeFileListRequestHandler)\n          .addFilterLast(\"CSP\", new CspFilter())\n          .create();\n        startedServer.start();\n        port = triedPort;\n      } catch (Exception t) {\n        LOG.debug(\"Error while starting port: \" + triedPort + \", \" + t.getMessage());\n        triedPort++;\n        if (startedServer != null) {\n          startedServer.close();\n        }\n      }\n    }\n    if (port > 0) {\n      LOG.info(\"Started embedded server on port \" + port);\n      client.embeddedServerStarted(new EmbeddedServerStartedParams(port));\n      server = startedServer;\n    } else {\n      LOG.error(\"Unable to start request handler\");\n      server = null;\n    }\n  }\n\n  public int getPort() {\n    return port;\n  }\n\n  public boolean isStarted() {\n    return server != null;\n  }\n\n  @PreDestroy\n  public void shutdown() {\n    if (isStarted()) {\n      server.close(CloseMode.GRACEFUL);\n      server = null;\n      port = INVALID_PORT;\n    }\n  }\n\n  private static class DontKeepAliveReuseStrategy implements ConnectionReuseStrategy {\n    @Override\n    public boolean keepAlive(HttpRequest request, HttpResponse response, HttpContext context) {\n      return false;\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/RequestHandlerBindingAssistant.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server;\n\nimport com.google.common.util.concurrent.MoreExecutors;\nimport jakarta.annotation.PreDestroy;\nimport java.util.Collection;\nimport java.util.HashSet;\nimport java.util.Objects;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.BindingCandidatesFinder;\nimport org.sonarsource.sonarlint.core.BindingSuggestionProvider;\nimport org.sonarsource.sonarlint.core.SonarCloudActiveEnvironment;\nimport org.sonarsource.sonarlint.core.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.commons.BoundScope;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.ExecutorServiceShutdownWatchable;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.commons.util.FailSafeExecutors;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.AssistBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.NoBindingSuggestionFoundParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.MessageType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowMessageParams;\n\nimport static org.apache.commons.text.StringEscapeUtils.escapeHtml4;\n\npublic class RequestHandlerBindingAssistant {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final BindingSuggestionProvider bindingSuggestionProvider;\n  private final BindingCandidatesFinder bindingCandidatesFinder;\n  private final SonarLintRpcClient client;\n  private final ConnectionConfigurationRepository connectionConfigurationRepository;\n  private final ConfigurationRepository configurationRepository;\n  private final ExecutorServiceShutdownWatchable<?> executorService;\n  private final SonarCloudActiveEnvironment sonarCloudActiveEnvironment;\n  private final ConnectionConfigurationRepository repository;\n\n  public RequestHandlerBindingAssistant(BindingSuggestionProvider bindingSuggestionProvider, BindingCandidatesFinder bindingCandidatesFinder,\n    SonarLintRpcClient client, ConnectionConfigurationRepository connectionConfigurationRepository, ConfigurationRepository configurationRepository,\n    SonarCloudActiveEnvironment sonarCloudActiveEnvironment, ConnectionConfigurationRepository repository) {\n    this.bindingSuggestionProvider = bindingSuggestionProvider;\n    this.bindingCandidatesFinder = bindingCandidatesFinder;\n    this.client = client;\n    this.connectionConfigurationRepository = connectionConfigurationRepository;\n    this.configurationRepository = configurationRepository;\n    this.executorService = new ExecutorServiceShutdownWatchable<>(FailSafeExecutors.newSingleThreadExecutor(\"Show Issue or Hotspot Request Handler\"));\n    this.sonarCloudActiveEnvironment = sonarCloudActiveEnvironment;\n    this.repository = repository;\n  }\n\n  public interface Callback {\n    void andThen(String connectionId, Collection<String> boundScopes, @Nullable String configurationScopeId, SonarLintCancelMonitor cancelMonitor);\n  }\n\n  public void assistConnectionAndBindingIfNeededAsync(AssistCreatingConnectionParams connectionParams, String projectKey, String origin, Callback callback) {\n    var cancelMonitor = new SonarLintCancelMonitor();\n    cancelMonitor.watchForShutdown(executorService);\n    executorService.execute(() -> assistConnectionAndBindingIfNeeded(connectionParams, projectKey, origin, callback, cancelMonitor));\n  }\n\n  private void assistConnectionAndBindingIfNeeded(AssistCreatingConnectionParams connectionParams, String projectKey, String origin,\n    Callback callback, SonarLintCancelMonitor cancelMonitor) {\n    var serverUrl = getServerUrl(connectionParams);\n    LOG.debug(\"Assist connection and binding if needed for project {} and server {}\", projectKey, serverUrl);\n    try {\n      var isSonarCloud = connectionParams.getConnectionParams().isRight();\n      var connectionsMatchingOrigin = isSonarCloud ? connectionConfigurationRepository.findByOrganization(connectionParams.getConnectionParams().getRight().getOrganizationKey())\n        : connectionConfigurationRepository.findByUrl(serverUrl);\n      if (connectionsMatchingOrigin.isEmpty()) {\n        startFullBindingProcess();\n        try {\n          var assistNewConnectionResult = assistCreatingConnectionAndWaitForRepositoryUpdate(connectionParams, cancelMonitor);\n          var assistNewBindingResult = assistBindingAndWaitForRepositoryUpdate(assistNewConnectionResult.getNewConnectionId(), isSonarCloud,\n            projectKey, cancelMonitor);\n          var boundScopes = new HashSet<String>();\n          if (assistNewBindingResult.getConfigurationScopeId() != null) {\n            boundScopes.add(assistNewBindingResult.getConfigurationScopeId());\n          }\n          callback.andThen(assistNewConnectionResult.getNewConnectionId(), boundScopes, assistNewBindingResult.getConfigurationScopeId(), cancelMonitor);\n        } finally {\n          endFullBindingProcess();\n        }\n      } else {\n        var isOriginTrusted = repository.hasConnectionWithOrigin(origin);\n        if (isOriginTrusted) {\n          // we pick the first connection but this could lead to issues later if there were several matches (make the user select the right\n          // one?)\n          assistBindingIfNeeded(connectionsMatchingOrigin.get(0).getConnectionId(), isSonarCloud, projectKey, callback, cancelMonitor);\n        } else {\n          LOG.warn(\"The origin '\" + origin + \"' is not trusted, this could be a malicious request\");\n          client.showMessage(new ShowMessageParams(MessageType.ERROR, \"SonarQube for IDE received a non-trusted request and could not proceed with it. \" +\n            \"See logs for more details.\"));\n        }\n      }\n    } catch (Exception e) {\n      LOG.error(\"Unable to show issue\", e);\n    }\n  }\n\n  private String getServerUrl(AssistCreatingConnectionParams connectionParams) {\n    return connectionParams.getConnectionParams().isLeft() ? connectionParams.getConnectionParams().getLeft().getServerUrl()\n      : sonarCloudActiveEnvironment.getUri(SonarCloudRegion.valueOf(connectionParams.getConnectionParams().getRight().getRegion().name())).toString();\n  }\n\n  private AssistCreatingConnectionResponse assistCreatingConnectionAndWaitForRepositoryUpdate(\n    AssistCreatingConnectionParams connectionParams, SonarLintCancelMonitor cancelMonitor) {\n    var assistNewConnectionResult = assistCreatingConnection(connectionParams, cancelMonitor);\n\n    // Wait 5s for the connection to be created in the repository. This is happening asynchronously by the\n    // ConnectionService::didUpdateConnections event\n    LOG.debug(\"Waiting for connection creation notification...\");\n    for (var i = 50; i >= 0; i--) {\n      if (connectionConfigurationRepository.getConnectionsById().containsKey(assistNewConnectionResult.getNewConnectionId())) {\n        break;\n      }\n      sleep();\n    }\n    if (!connectionConfigurationRepository.getConnectionsById().containsKey(assistNewConnectionResult.getNewConnectionId())) {\n      LOG.warn(\"Did not receive connection creation notification on a timely manner\");\n      throw new CancellationException();\n    }\n\n    return assistNewConnectionResult;\n  }\n\n  private void assistBindingIfNeeded(String connectionId, boolean isSonarCloud, String projectKey, Callback callback, SonarLintCancelMonitor cancelMonitor) {\n    var scopes = configurationRepository.getBoundScopesToConnectionAndSonarProject(connectionId, projectKey);\n    if (scopes.isEmpty()) {\n      var assistNewBindingResult = assistBindingAndWaitForRepositoryUpdate(connectionId, isSonarCloud, projectKey, cancelMonitor);\n      var boundScopes = new HashSet<String>();\n      if (assistNewBindingResult.getConfigurationScopeId() != null) {\n        boundScopes.add(assistNewBindingResult.getConfigurationScopeId());\n      }\n      callback.andThen(connectionId, boundScopes, assistNewBindingResult.getConfigurationScopeId(), cancelMonitor);\n    } else {\n      var boundScopes = scopes.stream().map(BoundScope::getConfigScopeId).filter(Objects::nonNull).collect(Collectors.toSet());\n      // we pick the first bound scope but this could lead to issues later if there were several matches (make the user select the right one?)\n      callback.andThen(connectionId, boundScopes, scopes.iterator().next().getConfigScopeId(), cancelMonitor);\n    }\n  }\n\n  private NewBinding assistBindingAndWaitForRepositoryUpdate(String connectionId, boolean isSonarCloud, String projectKey, SonarLintCancelMonitor cancelMonitor) {\n    var assistNewBindingResult = assistBinding(connectionId, isSonarCloud, projectKey, cancelMonitor);\n    // Wait 5s for the binding to be created in the repository. This is happening asynchronously by the\n    // ConfigurationService::didUpdateBinding event\n    var configurationScopeId = assistNewBindingResult.getConfigurationScopeId();\n    if (configurationScopeId != null) {\n      LOG.debug(\"Waiting for binding creation notification...\");\n      for (var i = 50; i >= 0; i--) {\n        if (configurationRepository.getEffectiveBinding(configurationScopeId).isPresent()) {\n          break;\n        }\n        sleep();\n      }\n      if (configurationRepository.getEffectiveBinding(configurationScopeId).isEmpty()) {\n        LOG.warn(\"Did not receive binding creation notification on a timely manner\");\n        throw new CancellationException();\n      }\n    }\n\n    return assistNewBindingResult;\n  }\n\n  private static void sleep() {\n    try {\n      Thread.sleep(100);\n    } catch (InterruptedException e) {\n      Thread.currentThread().interrupt();\n      throw new CancellationException(\"Interrupted!\");\n    }\n  }\n\n  void startFullBindingProcess() {\n    // we don't want binding suggestions to appear in the middle of a full binding creation process (connection + binding)\n    // the other possibility would be to still notify the client anyway and let it handle UI interactions one at a time (assists, messages,\n    // suggestions, ...)\n    bindingSuggestionProvider.disable();\n  }\n\n  void endFullBindingProcess() {\n    bindingSuggestionProvider.enable();\n  }\n\n  AssistCreatingConnectionResponse assistCreatingConnection(AssistCreatingConnectionParams connectionParams, SonarLintCancelMonitor cancelMonitor) {\n    var future = client.assistCreatingConnection(connectionParams);\n    cancelMonitor.onCancel(() -> future.cancel(true));\n    return future.join();\n  }\n\n  NewBinding assistBinding(String connectionId, boolean isSonarCloud, String projectKey, SonarLintCancelMonitor cancelMonitor) {\n    var configScopeCandidates = bindingCandidatesFinder.findConfigScopesToBind(connectionId, projectKey, cancelMonitor);\n    // For now, we decided to only support automatic binding if there is only one clear candidate\n    if (configScopeCandidates.size() != 1) {\n      client.noBindingSuggestionFound(new NoBindingSuggestionFoundParams(escapeHtml4(projectKey), isSonarCloud));\n      return new NewBinding(connectionId, null);\n    }\n    var bindableConfig = configScopeCandidates.iterator().next();\n    var future = client.assistBinding(new AssistBindingParams(connectionId, projectKey, bindableConfig.getConfigurationScope().id(),\n      bindableConfig.getOrigin()));\n    cancelMonitor.onCancel(() -> future.cancel(true));\n    var response = future.join();\n    return new NewBinding(connectionId, response.getConfigurationScopeId());\n  }\n\n  static class NewBinding {\n    private final String connectionId;\n    private final String configurationScopeId;\n\n    private NewBinding(String connectionId, @Nullable String configurationScopeId) {\n      this.connectionId = connectionId;\n      this.configurationScopeId = configurationScopeId;\n    }\n\n    public String getConnectionId() {\n      return connectionId;\n    }\n\n    @CheckForNull\n    public String getConfigurationScopeId() {\n      return configurationScopeId;\n    }\n  }\n\n  @PreDestroy\n  public void shutdown() {\n    if (!MoreExecutors.shutdownAndAwaitTermination(executorService, 1, TimeUnit.SECONDS)) {\n      LOG.warn(\"Unable to stop show issue request handler executor service in a timely manner\");\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/ToggleAutomaticAnalysisRequestHandler.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server;\n\nimport com.google.gson.Gson;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.HashMap;\nimport org.apache.hc.core5.http.ClassicHttpRequest;\nimport org.apache.hc.core5.http.ClassicHttpResponse;\nimport org.apache.hc.core5.http.ContentType;\nimport org.apache.hc.core5.http.HttpException;\nimport org.apache.hc.core5.http.HttpStatus;\nimport org.apache.hc.core5.http.Method;\nimport org.apache.hc.core5.http.io.HttpRequestHandler;\nimport org.apache.hc.core5.http.io.entity.StringEntity;\nimport org.apache.hc.core5.http.protocol.HttpContext;\nimport org.apache.hc.core5.net.URIBuilder;\nimport org.sonarsource.sonarlint.core.analysis.AnalysisService;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic class ToggleAutomaticAnalysisRequestHandler implements HttpRequestHandler {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final AnalysisService analysisService;\n  private final Gson gson = new Gson();\n\n  public ToggleAutomaticAnalysisRequestHandler(AnalysisService analysisService) {\n    this.analysisService = analysisService;\n  }\n\n  @Override\n  public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException {\n    LOG.debug(\"Received request for toggling automatic analysis\");\n\n    if (!Method.POST.isSame(request.getMethod())) {\n      response.setCode(HttpStatus.SC_BAD_REQUEST);\n      return;\n    }\n\n    var params = new HashMap<String, String>();\n    try {\n      new URIBuilder(request.getUri(), StandardCharsets.UTF_8)\n        .getQueryParams()\n        .forEach(p -> params.put(p.getName(), p.getValue()));\n    } catch (URISyntaxException e) {\n      handleError(response,  \"Invalid URI\");\n      return;\n    }\n\n    var enabledParam = params.get(\"enabled\");\n    if (enabledParam == null) {\n      handleError(response, \"Missing 'enabled' query parameter\");\n      return;\n    }\n\n    boolean enabled;\n    try {\n      enabled = Boolean.parseBoolean(enabledParam);\n    } catch (Exception e) {\n      handleError(response, \"Invalid 'enabled' parameter value\");\n      return;\n    }\n\n    try {\n      analysisService.didChangeAutomaticAnalysisSetting(enabled);\n      response.setCode(HttpStatus.SC_OK);\n    } catch (Exception e) {\n      LOG.error(\"Failed to toggle automatic analysis\", e);\n      response.setCode(HttpStatus.SC_INTERNAL_SERVER_ERROR);\n      var errorResponse = new ErrorMessage(\"Failed to toggle automatic analysis: \" + e.getMessage());\n      response.setEntity(new StringEntity(gson.toJson(errorResponse), ContentType.APPLICATION_JSON));\n    }\n  }\n\n  private void handleError(ClassicHttpResponse response, String clientMessage) {\n    response.setCode(HttpStatus.SC_BAD_REQUEST);\n    var errorResponse = new ErrorMessage(clientMessage);\n    response.setEntity(new StringEntity(gson.toJson(errorResponse), ContentType.APPLICATION_JSON));\n  }\n\n  public record ErrorMessage(String message) {\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/filter/CorsFilter.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server.filter;\n\nimport java.io.IOException;\nimport org.apache.hc.core5.http.ClassicHttpRequest;\nimport org.apache.hc.core5.http.ClassicHttpResponse;\nimport org.apache.hc.core5.http.HttpException;\nimport org.apache.hc.core5.http.HttpStatus;\nimport org.apache.hc.core5.http.Method;\nimport org.apache.hc.core5.http.io.HttpFilterChain;\nimport org.apache.hc.core5.http.io.HttpFilterHandler;\nimport org.apache.hc.core5.http.message.BasicClassicHttpResponse;\nimport org.apache.hc.core5.http.protocol.HttpContext;\nimport org.sonarsource.sonarlint.core.embedded.server.AttributeUtils;\n\npublic class CorsFilter implements HttpFilterHandler {\n\n  @Override\n  public void handle(ClassicHttpRequest request, HttpFilterChain.ResponseTrigger responseTrigger, HttpContext context, HttpFilterChain chain)\n    throws HttpException, IOException {\n    var origin = AttributeUtils.getOrigin(context);\n\n    if (Method.OPTIONS.name().equalsIgnoreCase(request.getMethod())) {\n      var response = new BasicClassicHttpResponse(HttpStatus.SC_OK);\n      response.addHeader(\"Access-Control-Allow-Origin\", origin);\n\n      response.addHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n      response.addHeader(\"Access-Control-Allow-Private-Network\", true);\n      responseTrigger.submitResponse(response);\n    } else {\n      chain.proceed(request, new HttpFilterChain.ResponseTrigger() {\n        @Override\n        public void sendInformation(ClassicHttpResponse classicHttpResponse) throws HttpException, IOException {\n          responseTrigger.sendInformation(classicHttpResponse);\n        }\n\n        @Override\n        public void submitResponse(ClassicHttpResponse classicHttpResponse) throws HttpException, IOException {\n          classicHttpResponse.addHeader(\"Access-Control-Allow-Origin\", origin);\n          responseTrigger.submitResponse(classicHttpResponse);\n        }\n      }, context);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/filter/CspFilter.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server.filter;\n\nimport java.io.IOException;\nimport org.apache.hc.core5.http.ClassicHttpRequest;\nimport org.apache.hc.core5.http.ClassicHttpResponse;\nimport org.apache.hc.core5.http.HttpException;\nimport org.apache.hc.core5.http.HttpStatus;\nimport org.apache.hc.core5.http.io.HttpFilterChain;\nimport org.apache.hc.core5.http.io.HttpFilterHandler;\nimport org.apache.hc.core5.http.protocol.HttpContext;\n\npublic class CspFilter implements HttpFilterHandler {\n\n  @Override\n  public void handle(ClassicHttpRequest request, HttpFilterChain.ResponseTrigger responseTrigger, HttpContext context, HttpFilterChain chain)\n    throws HttpException, IOException {\n    chain.proceed(request, new HttpFilterChain.ResponseTrigger() {\n      @Override\n      public void sendInformation(ClassicHttpResponse classicHttpResponse) throws HttpException, IOException {\n        responseTrigger.sendInformation(classicHttpResponse);\n      }\n\n      @Override\n      public void submitResponse(ClassicHttpResponse response) throws HttpException, IOException {\n        if (response.getCode() >= HttpStatus.SC_BAD_REQUEST) {\n          responseTrigger.submitResponse(response);\n          return;\n        }\n        var port = request.getAuthority().getPort();\n        response.setHeader(\"Content-Security-Policy-Report-Only\", \"connect-src 'self' http://localhost:\" + port + \";\");\n        responseTrigger.submitResponse(response);\n      }\n    }, context);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/filter/ParseParamsFilter.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server.filter;\n\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.stream.Collectors;\nimport org.apache.hc.core5.http.ClassicHttpRequest;\nimport org.apache.hc.core5.http.HttpException;\nimport org.apache.hc.core5.http.NameValuePair;\nimport org.apache.hc.core5.http.io.HttpFilterChain;\nimport org.apache.hc.core5.http.io.HttpFilterHandler;\nimport org.apache.hc.core5.http.protocol.HttpContext;\nimport org.apache.hc.core5.net.URIBuilder;\nimport org.sonarsource.sonarlint.core.embedded.server.AttributeUtils;\n\npublic class ParseParamsFilter implements HttpFilterHandler {\n\n  @Override\n  public void handle(ClassicHttpRequest request, HttpFilterChain.ResponseTrigger responseTrigger, HttpContext context, HttpFilterChain chain) throws HttpException, IOException {\n    context.setAttribute(AttributeUtils.PARAMS_ATTRIBUTE, parseParams(request));\n    chain.proceed(request, responseTrigger, context);\n  }\n\n  private static Map<String, String> parseParams(ClassicHttpRequest request) {\n    try {\n      return new URIBuilder(request.getUri(), StandardCharsets.UTF_8)\n          .getQueryParams()\n          .stream()\n          .collect(Collectors.toMap(NameValuePair::getName, NameValuePair::getValue));\n    } catch (URISyntaxException e) {\n      // Ignored\n    }\n    return new HashMap<>();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/filter/RateLimitFilter.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server.filter;\n\nimport org.apache.hc.core5.http.ClassicHttpRequest;\nimport org.apache.hc.core5.http.HttpException;\nimport org.apache.hc.core5.http.HttpStatus;\nimport org.apache.hc.core5.http.io.HttpFilterChain;\nimport org.apache.hc.core5.http.io.HttpFilterHandler;\nimport org.apache.hc.core5.http.message.BasicClassicHttpResponse;\nimport org.apache.hc.core5.http.protocol.HttpContext;\n\nimport java.io.IOException;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.TimeUnit;\nimport org.sonarsource.sonarlint.core.embedded.server.AttributeUtils;\n\npublic class RateLimitFilter implements HttpFilterHandler {\n\n  private static final int MAX_REQUESTS_PER_ORIGIN = 10;\n  private static final long TIME_FRAME_MS = TimeUnit.SECONDS.toMillis(10);\n  private final ConcurrentHashMap<String, RequestCounter> requestCounters = new ConcurrentHashMap<>();\n\n  @Override\n  public void handle(ClassicHttpRequest request, HttpFilterChain.ResponseTrigger responseTrigger, HttpContext context, HttpFilterChain chain)\n    throws HttpException, IOException {\n    var originHeader = request.getHeader(\"Origin\");\n    var origin = originHeader != null ? originHeader.getValue() : null;\n    if (origin == null) {\n      var response = new BasicClassicHttpResponse(HttpStatus.SC_BAD_REQUEST);\n      responseTrigger.submitResponse(response);\n    } else {\n      if (!isRequestAllowed(origin)) {\n        var response = new BasicClassicHttpResponse(HttpStatus.SC_TOO_MANY_REQUESTS);\n        responseTrigger.submitResponse(response);\n      } else {\n        context.setAttribute(AttributeUtils.ORIGIN_ATTRIBUTE, origin);\n        chain.proceed(request, responseTrigger, context);\n      }\n    }\n  }\n\n  private boolean isRequestAllowed(String origin) {\n    long currentTime = System.currentTimeMillis();\n    var counter = requestCounters.computeIfAbsent(origin, k -> new RequestCounter(currentTime));\n    requestCounters.compute(origin, (k, v) -> {\n      if (currentTime - counter.timestamp > TIME_FRAME_MS) {\n        counter.timestamp = currentTime;\n        counter.count = 1;\n      } else {\n        counter.count++;\n      }\n      return counter;\n    });\n    return counter.count <= MAX_REQUESTS_PER_ORIGIN;\n  }\n\n  private static class RequestCounter {\n    long timestamp;\n    int count;\n\n    RequestCounter(long timestamp) {\n      this.timestamp = timestamp;\n      this.count = 0;\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/filter/ValidationFilter.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server.filter;\n\nimport java.io.IOException;\nimport org.apache.commons.lang3.Strings;\nimport org.apache.hc.core5.http.ClassicHttpRequest;\nimport org.apache.hc.core5.http.HttpException;\nimport org.apache.hc.core5.http.HttpStatus;\nimport org.apache.hc.core5.http.io.HttpFilterChain;\nimport org.apache.hc.core5.http.io.HttpFilterHandler;\nimport org.apache.hc.core5.http.message.BasicClassicHttpResponse;\nimport org.apache.hc.core5.http.protocol.HttpContext;\nimport org.sonarsource.sonarlint.core.SonarCloudActiveEnvironment;\nimport org.sonarsource.sonarlint.core.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.embedded.server.AttributeUtils;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.MessageType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowMessageParams;\n\nimport static org.apache.hc.core5.http.io.HttpFilterChain.ResponseTrigger;\n\npublic class ValidationFilter implements HttpFilterHandler {\n\n  private final SonarLintRpcClient client;\n  private final SonarCloudActiveEnvironment sonarCloudActiveEnvironment;\n\n  public ValidationFilter(SonarLintRpcClient client, SonarCloudActiveEnvironment sonarCloudActiveEnvironment) {\n    this.client = client;\n    this.sonarCloudActiveEnvironment = sonarCloudActiveEnvironment;\n  }\n\n  @Override\n  public void handle(ClassicHttpRequest request, ResponseTrigger responseTrigger, HttpContext context, HttpFilterChain chain) throws HttpException, IOException {\n    var origin = AttributeUtils.getOrigin(context);\n    boolean isSonarCloud = sonarCloudActiveEnvironment.isSonarQubeCloud(origin);\n    var params = AttributeUtils.getParams(context);\n    if (!isSonarCloud && params.containsKey(\"server\")) {\n      var serverUrl = params.get(\"server\");\n      if (Strings.CI.startsWithAny(serverUrl, SonarCloudRegion.CLOUD_URLS)) {\n        var response = new BasicClassicHttpResponse(HttpStatus.SC_BAD_REQUEST);\n        client.showMessage(new ShowMessageParams(MessageType.ERROR,\n          \"Invalid request to SonarQube backend. \" +\n            \"The 'server' parameter should not be SonarQube Cloud URL, use it only to specify URL of a SonarQube Server.\"));\n        responseTrigger.submitResponse(response);\n      }\n    }\n    chain.proceed(request, responseTrigger, context);\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/filter/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.embedded.server.filter;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/handler/GeneratedUserTokenHandler.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server.handler;\n\nimport com.google.gson.Gson;\nimport java.io.IOException;\nimport java.util.concurrent.CompletableFuture;\nimport org.apache.hc.core5.http.ClassicHttpRequest;\nimport org.apache.hc.core5.http.ClassicHttpResponse;\nimport org.apache.hc.core5.http.HttpException;\nimport org.apache.hc.core5.http.HttpStatus;\nimport org.apache.hc.core5.http.Method;\nimport org.apache.hc.core5.http.ParseException;\nimport org.apache.hc.core5.http.io.HttpRequestHandler;\nimport org.apache.hc.core5.http.io.entity.EntityUtils;\nimport org.apache.hc.core5.http.io.entity.StringEntity;\nimport org.apache.hc.core5.http.protocol.HttpContext;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.embedded.server.AwaitingUserTokenFutureRepository;\nimport org.sonarsource.sonarlint.core.embedded.server.AttributeUtils;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth.HelpGenerateUserTokenResponse;\n\nimport static java.util.function.Predicate.not;\n\npublic class GeneratedUserTokenHandler implements HttpRequestHandler {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final AwaitingUserTokenFutureRepository awaitingUserTokenFutureRepository;\n\n  public GeneratedUserTokenHandler(AwaitingUserTokenFutureRepository awaitingUserTokenFutureRepository) {\n    this.awaitingUserTokenFutureRepository = awaitingUserTokenFutureRepository;\n  }\n\n  @Override\n  public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException {\n    if (!Method.POST.isSame(request.getMethod())) {\n      response.setCode(HttpStatus.SC_BAD_REQUEST);\n      return;\n    }\n\n    String token = extractAndValidateToken(request);\n    if (token == null) {\n      response.setCode(HttpStatus.SC_BAD_REQUEST);\n      return;\n    }\n\n    var origin = AttributeUtils.getOrigin(context);\n\n    awaitingUserTokenFutureRepository.consumeFutureResponse(origin)\n      .filter(not(CompletableFuture::isCancelled))\n      .ifPresentOrElse(pendingFuture -> {\n        pendingFuture.complete(new HelpGenerateUserTokenResponse(token));\n        response.setCode(HttpStatus.SC_OK);\n        response.setEntity(new StringEntity(\"OK\"));\n      }, () -> response.setCode(HttpStatus.SC_FORBIDDEN));\n  }\n\n  private static String extractAndValidateToken(ClassicHttpRequest request) throws IOException, ParseException {\n    var requestEntityString = EntityUtils.toString(request.getEntity(), \"UTF-8\");\n    String token = null;\n    try {\n      token = new Gson().fromJson(requestEntityString, TokenPayload.class).token;\n    } catch (Exception e) {\n      // will be converted to HTTP response later\n      LOG.error(\"Could not deserialize user token\", e);\n    }\n    return token;\n  }\n\n  private static class TokenPayload {\n    private String token;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/handler/ShowFixSuggestionRequestHandler.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server.handler;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.gson.Gson;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\nimport javax.annotation.Nullable;\nimport org.apache.commons.lang3.Strings;\nimport org.apache.hc.core5.http.ClassicHttpRequest;\nimport org.apache.hc.core5.http.ClassicHttpResponse;\nimport org.apache.hc.core5.http.HttpException;\nimport org.apache.hc.core5.http.HttpStatus;\nimport org.apache.hc.core5.http.Method;\nimport org.apache.hc.core5.http.ParseException;\nimport org.apache.hc.core5.http.io.HttpRequestHandler;\nimport org.apache.hc.core5.http.io.entity.EntityUtils;\nimport org.apache.hc.core5.http.io.entity.StringEntity;\nimport org.apache.hc.core5.http.protocol.HttpContext;\nimport org.sonarsource.sonarlint.core.SonarCloudActiveEnvironment;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.embedded.server.AttributeUtils;\nimport org.sonarsource.sonarlint.core.embedded.server.RequestHandlerBindingAssistant;\nimport org.sonarsource.sonarlint.core.event.FixSuggestionReceivedEvent;\nimport org.sonarsource.sonarlint.core.file.PathTranslationService;\nimport org.sonarsource.sonarlint.core.fs.ClientFileSystemService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.SonarCloudConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.SonarQubeConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fix.ChangesDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fix.FileEditDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fix.FixSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fix.LineRangeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fix.ShowFixSuggestionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.MessageType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowMessageParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AiSuggestionSource;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion;\nimport org.springframework.context.ApplicationEventPublisher;\n\nimport static org.apache.commons.lang3.StringUtils.isNotBlank;\nimport static org.apache.commons.lang3.StringUtils.isNotEmpty;\nimport static org.apache.commons.text.StringEscapeUtils.escapeHtml4;\nimport static org.sonarsource.sonarlint.core.commons.util.StringUtils.sanitizeAgainstRTLO;\n\npublic class ShowFixSuggestionRequestHandler implements HttpRequestHandler {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final SonarLintRpcClient client;\n  private final ApplicationEventPublisher eventPublisher;\n  private final RequestHandlerBindingAssistant requestHandlerBindingAssistant;\n  private final PathTranslationService pathTranslationService;\n  private final SonarCloudActiveEnvironment sonarCloudActiveEnvironment;\n  private final ClientFileSystemService clientFs;\n\n  public ShowFixSuggestionRequestHandler(SonarLintRpcClient client, ApplicationEventPublisher eventPublisher,\n    RequestHandlerBindingAssistant requestHandlerBindingAssistant,\n    PathTranslationService pathTranslationService, SonarCloudActiveEnvironment sonarCloudActiveEnvironment, ClientFileSystemService clientFs) {\n    this.client = client;\n    this.eventPublisher = eventPublisher;\n    this.requestHandlerBindingAssistant = requestHandlerBindingAssistant;\n    this.pathTranslationService = pathTranslationService;\n    this.sonarCloudActiveEnvironment = sonarCloudActiveEnvironment;\n    this.clientFs = clientFs;\n  }\n\n  @Override\n  public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException {\n    var origin = AttributeUtils.getOrigin(context);\n    var showFixSuggestionQuery = extractQuery(request, origin, AttributeUtils.getParams(context));\n\n    if (!Method.POST.isSame(request.getMethod()) || !showFixSuggestionQuery.isValid()) {\n      response.setCode(HttpStatus.SC_BAD_REQUEST);\n      return;\n    }\n\n    eventPublisher.publishEvent(new FixSuggestionReceivedEvent(showFixSuggestionQuery.getFixSuggestion().suggestionId,\n      showFixSuggestionQuery.isSonarCloud ? AiSuggestionSource.SONARCLOUD : AiSuggestionSource.SONARQUBE,\n      showFixSuggestionQuery.fixSuggestion.fileEdit.changes.size(), false));\n\n    AssistCreatingConnectionParams serverConnectionParams = createAssistServerConnectionParams(showFixSuggestionQuery, sonarCloudActiveEnvironment);\n\n    requestHandlerBindingAssistant.assistConnectionAndBindingIfNeededAsync(\n      serverConnectionParams,\n      showFixSuggestionQuery.projectKey, origin,\n      (connectionId, boundScopes, configScopeId, cancelMonitor) -> {\n        if (configScopeId != null) {\n          if (doesClientFileExists(configScopeId, showFixSuggestionQuery.fixSuggestion.fileEdit.path, boundScopes)) {\n            showFixSuggestionForScope(configScopeId, showFixSuggestionQuery.issueKey, showFixSuggestionQuery.fixSuggestion);\n          } else {\n            client.showMessage(new ShowMessageParams(MessageType.ERROR, \"Attempted to show a fix suggestion for a file that is \" +\n              \"not known by SonarQube for IDE\"));\n          }\n        }\n      });\n\n    response.setCode(HttpStatus.SC_OK);\n    response.setEntity(new StringEntity(\"OK\"));\n  }\n\n  private boolean doesClientFileExists(String configScopeId, String filePath, Collection<String> boundScopes) {\n    var optTranslation = pathTranslationService.getOrComputePathTranslation(configScopeId);\n    if (optTranslation.isPresent()) {\n      var translation = optTranslation.get();\n      var idePath = translation.serverToIdePath(Paths.get(filePath));\n      for (var scope : boundScopes) {\n        for (var file : clientFs.getFiles(scope)) {\n          if (Path.of(file.getUri()).endsWith(idePath)) {\n            return true;\n          }\n        }\n      }\n    }\n    return false;\n  }\n\n  private static AssistCreatingConnectionParams createAssistServerConnectionParams(ShowFixSuggestionQuery query, SonarCloudActiveEnvironment sonarCloudActiveEnvironment) {\n    String tokenName = query.getTokenName();\n    String tokenValue = query.getTokenValue();\n    if (query.isSonarCloud) {\n      // If 'isSonarCloud' check passed, we are sure we will have a region\n      var region = sonarCloudActiveEnvironment.getRegionOrThrow(query.getServerUrl());\n      return new AssistCreatingConnectionParams(new SonarCloudConnectionParams(query.getOrganizationKey(), tokenName, tokenValue, SonarCloudRegion.valueOf(region.name())));\n    } else {\n      return new AssistCreatingConnectionParams(new SonarQubeConnectionParams(query.getServerUrl(), tokenName, tokenValue));\n    }\n  }\n\n  private void showFixSuggestionForScope(String configScopeId, String issueKey, FixSuggestionPayload fixSuggestion) {\n    pathTranslationService.getOrComputePathTranslation(configScopeId).ifPresent(translation -> {\n      var fixSuggestionDto = new FixSuggestionDto(\n        fixSuggestion.suggestionId,\n        fixSuggestion.explanation(),\n        new FileEditDto(\n          translation.serverToIdePath(Paths.get(fixSuggestion.fileEdit.path)),\n          fixSuggestion.fileEdit.changes.stream().map(c ->\n            new ChangesDto(\n              new LineRangeDto(c.beforeLineRange.startLine, c.beforeLineRange.endLine),\n              c.before,\n              c.after)\n          ).toList()\n        )\n      );\n      client.showFixSuggestion(new ShowFixSuggestionParams(configScopeId, issueKey, fixSuggestionDto));\n    });\n  }\n\n  @VisibleForTesting\n  ShowFixSuggestionQuery extractQuery(ClassicHttpRequest request, String origin, Map<String, String> params) throws HttpException, IOException {\n    var payload = extractAndValidatePayload(request);\n    boolean isSonarCloud = sonarCloudActiveEnvironment.isSonarQubeCloud(origin);\n    String serverUrl;\n    if (isSonarCloud) {\n      serverUrl = Strings.CS.removeEnd(origin, \"/\");\n    } else {\n      serverUrl = params.get(\"server\");\n    }\n    return new ShowFixSuggestionQuery(serverUrl, params.get(\"project\"), params.get(\"issue\"), params.get(\"branch\"),\n      params.get(\"tokenName\"), params.get(\"tokenValue\"), params.get(\"organizationKey\"), isSonarCloud, payload);\n  }\n\n  private static FixSuggestionPayload extractAndValidatePayload(ClassicHttpRequest request) throws IOException, ParseException {\n    var requestEntityString = EntityUtils.toString(request.getEntity(), \"UTF-8\");\n    FixSuggestionPayload payload = null;\n    try {\n      payload = new Gson().fromJson(requestEntityString, FixSuggestionPayload.class);\n    } catch (Exception e) {\n      // will be converted to HTTP response later\n      LOG.error(\"Could not deserialize fix suggestion payload\", e);\n    }\n    return payload;\n  }\n\n  @VisibleForTesting\n  public static class ShowFixSuggestionQuery {\n    private final String serverUrl;\n    private final String projectKey;\n    private final String issueKey;\n    @Nullable\n    private final String branch;\n    @Nullable\n    private final String tokenName;\n    @Nullable\n    private final String tokenValue;\n    @Nullable\n    private final String organizationKey;\n    private final boolean isSonarCloud;\n    private final FixSuggestionPayload fixSuggestion;\n\n    public ShowFixSuggestionQuery(@Nullable String serverUrl, String projectKey, String issueKey, @Nullable String branch,\n      @Nullable String tokenName, @Nullable String tokenValue, @Nullable String organizationKey, boolean isSonarCloud,\n      FixSuggestionPayload fixSuggestion) {\n      this.serverUrl = serverUrl;\n      this.projectKey = projectKey;\n      this.issueKey = issueKey;\n      this.branch = branch;\n      this.tokenName = tokenName;\n      this.tokenValue = tokenValue;\n      this.organizationKey = organizationKey;\n      this.isSonarCloud = isSonarCloud;\n      this.fixSuggestion = fixSuggestion;\n    }\n\n    public boolean isValid() {\n      return isNotBlank(projectKey) && isNotBlank(issueKey)\n        && (isSonarCloud || isNotBlank(serverUrl))\n        && (!isSonarCloud || isNotBlank(organizationKey))\n        && fixSuggestion.isValid() && isTokenValid();\n    }\n\n    /**\n     * Either we get a token combination or we don't get a token combination: There is nothing in between\n     */\n    public boolean isTokenValid() {\n      if (tokenName != null && tokenValue != null) {\n        return isNotEmpty(tokenName) && isNotEmpty(tokenValue);\n      }\n\n      return tokenName == null && tokenValue == null;\n    }\n\n    public String getServerUrl() {\n      return serverUrl;\n    }\n\n    public String getProjectKey() {\n      return projectKey;\n    }\n\n    @Nullable\n    public String getOrganizationKey() {\n      return organizationKey;\n    }\n\n    public String getIssueKey() {\n      return issueKey;\n    }\n\n    @Nullable\n    public String getBranch() {\n      return branch;\n    }\n\n    @Nullable\n    public String getTokenName() {\n      return tokenName;\n    }\n\n    @Nullable\n    public String getTokenValue() {\n      return tokenValue;\n    }\n\n    public FixSuggestionPayload getFixSuggestion() {\n      return fixSuggestion;\n    }\n  }\n\n  @VisibleForTesting\n  public record FixSuggestionPayload(FileEditPayload fileEdit, String suggestionId, String explanation) {\n\n    public FixSuggestionPayload(FileEditPayload fileEdit, String suggestionId, String explanation) {\n      this.fileEdit = fileEdit;\n      this.suggestionId = suggestionId;\n      this.explanation = escapeHtml4(explanation);\n    }\n\n    public boolean isValid() {\n      return fileEdit.isValid() && !suggestionId.isBlank();\n    }\n\n  }\n\n  @VisibleForTesting\n  public record FileEditPayload(List<ChangesPayload> changes, String path) {\n\n    public boolean isValid() {\n      return !path.isBlank() && changes.stream().allMatch(ChangesPayload::isValid);\n    }\n\n  }\n\n  @VisibleForTesting\n  public record ChangesPayload(TextRangePayload beforeLineRange, String before, String after) {\n\n    public ChangesPayload(TextRangePayload beforeLineRange, String before, String after) {\n      this.beforeLineRange = beforeLineRange;\n      this.before = sanitizeAgainstRTLO(before);\n      this.after = sanitizeAgainstRTLO(after);\n    }\n\n    public boolean isValid() {\n      return beforeLineRange.isValid();\n    }\n\n  }\n\n  @VisibleForTesting\n  public record TextRangePayload(int startLine, int endLine) {\n\n    public boolean isValid() {\n      return startLine >= 0 && endLine >= 0 && startLine <= endLine;\n    }\n\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/handler/ShowHotspotRequestHandler.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server.handler;\n\nimport java.io.IOException;\nimport java.util.Map;\nimport java.util.Optional;\nimport org.apache.hc.core5.http.ClassicHttpRequest;\nimport org.apache.hc.core5.http.ClassicHttpResponse;\nimport org.apache.hc.core5.http.HttpException;\nimport org.apache.hc.core5.http.HttpStatus;\nimport org.apache.hc.core5.http.Method;\nimport org.apache.hc.core5.http.io.HttpRequestHandler;\nimport org.apache.hc.core5.http.io.entity.StringEntity;\nimport org.apache.hc.core5.http.protocol.HttpContext;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.embedded.server.AttributeUtils;\nimport org.sonarsource.sonarlint.core.embedded.server.RequestHandlerBindingAssistant;\nimport org.sonarsource.sonarlint.core.file.FilePathTranslation;\nimport org.sonarsource.sonarlint.core.file.PathTranslationService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.SonarQubeConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.HotspotDetailsDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.ShowHotspotParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.MessageType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowMessageParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;\nimport org.sonarsource.sonarlint.core.serverapi.hotspot.ServerHotspotDetails;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryService;\n\nimport static org.apache.commons.lang3.StringUtils.isNotBlank;\n\npublic class ShowHotspotRequestHandler implements HttpRequestHandler {\n  private final SonarLintRpcClient client;\n  private final SonarQubeClientManager sonarQubeClientManager;\n  private final TelemetryService telemetryService;\n  private final RequestHandlerBindingAssistant requestHandlerBindingAssistant;\n  private final PathTranslationService pathTranslationService;\n\n  public ShowHotspotRequestHandler(SonarLintRpcClient client, SonarQubeClientManager sonarQubeClientManager, TelemetryService telemetryService,\n    RequestHandlerBindingAssistant requestHandlerBindingAssistant, PathTranslationService pathTranslationService) {\n    this.client = client;\n    this.sonarQubeClientManager = sonarQubeClientManager;\n    this.telemetryService = telemetryService;\n    this.requestHandlerBindingAssistant = requestHandlerBindingAssistant;\n    this.pathTranslationService = pathTranslationService;\n  }\n\n  @Override\n  public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException {\n    var origin = AttributeUtils.getOrigin(context);\n    var showHotspotQuery = extractQuery(AttributeUtils.getParams(context));\n    if (!Method.GET.isSame(request.getMethod()) || !showHotspotQuery.isValid()) {\n      response.setCode(HttpStatus.SC_BAD_REQUEST);\n      return;\n    }\n    telemetryService.showHotspotRequestReceived();\n    var sonarQubeConnectionParams = new SonarQubeConnectionParams(showHotspotQuery.serverUrl, null, null);\n    var connectionParams = new AssistCreatingConnectionParams(sonarQubeConnectionParams);\n    requestHandlerBindingAssistant.assistConnectionAndBindingIfNeededAsync(connectionParams, showHotspotQuery.projectKey, origin,\n      (connectionId, boundScopes, configScopeId, cancelMonitor) -> {\n        if (configScopeId != null) {\n          showHotspotForScope(connectionId, configScopeId, showHotspotQuery.hotspotKey, cancelMonitor);\n        }\n      });\n\n    response.setCode(HttpStatus.SC_OK);\n    response.setEntity(new StringEntity(\"OK\"));\n  }\n\n  private void showHotspotForScope(String connectionId, String configurationScopeId, String hotspotKey,\n    SonarLintCancelMonitor cancelMonitor) {\n    var hotspotOpt = tryFetchHotspot(connectionId, hotspotKey, cancelMonitor);\n    if (hotspotOpt.isPresent()) {\n      pathTranslationService.getOrComputePathTranslation(configurationScopeId)\n        .ifPresent(translation -> client.showHotspot(new ShowHotspotParams(configurationScopeId, adapt(hotspotKey, hotspotOpt.get(),\n          translation))));\n    } else {\n      client.showMessage(new ShowMessageParams(MessageType.ERROR, \"Could not show the hotspot. See logs for more details\"));\n    }\n  }\n\n  private Optional<ServerHotspotDetails> tryFetchHotspot(String connectionId, String hotspotKey, SonarLintCancelMonitor cancelMonitor) {\n    return sonarQubeClientManager.withActiveClientFlatMapOptionalAndReturn(connectionId, api -> api.hotspot().fetch(hotspotKey,\n      cancelMonitor));\n  }\n\n  private static HotspotDetailsDto adapt(String hotspotKey, ServerHotspotDetails hotspot, FilePathTranslation translation) {\n    return new HotspotDetailsDto(\n      hotspotKey,\n      hotspot.message,\n      translation.serverToIdePath(hotspot.filePath),\n      adapt(hotspot.textRange),\n      hotspot.author,\n      hotspot.status.toString(),\n      hotspot.resolution != null ? hotspot.resolution.toString() : null,\n      adapt(hotspot.rule),\n      hotspot.codeSnippet);\n  }\n\n  private static HotspotDetailsDto.HotspotRule adapt(ServerHotspotDetails.Rule rule) {\n    return new HotspotDetailsDto.HotspotRule(\n      rule.key,\n      rule.name,\n      rule.securityCategory,\n      rule.vulnerabilityProbability.toString(),\n      rule.riskDescription,\n      rule.vulnerabilityDescription,\n      rule.fixRecommendations);\n  }\n\n  private static TextRangeDto adapt(TextRange textRange) {\n    return new TextRangeDto(textRange.getStartLine(), textRange.getStartLineOffset(), textRange.getEndLine(), textRange.getEndLineOffset());\n  }\n\n  private static ShowHotspotQuery extractQuery(Map<String, String> params) {\n    return new ShowHotspotQuery(params.get(\"server\"), params.get(\"project\"), params.get(\"hotspot\"));\n  }\n\n  private record ShowHotspotQuery(String serverUrl, String projectKey, String hotspotKey) {\n\n    public boolean isValid() {\n      return isNotBlank(serverUrl) && isNotBlank(projectKey) && isNotBlank(hotspotKey);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/handler/ShowIssueRequestHandler.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server.handler;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport java.io.IOException;\nimport java.nio.file.Paths;\nimport java.util.Map;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\nimport org.apache.commons.lang3.Strings;\nimport org.apache.hc.core5.http.ClassicHttpRequest;\nimport org.apache.hc.core5.http.ClassicHttpResponse;\nimport org.apache.hc.core5.http.HttpException;\nimport org.apache.hc.core5.http.HttpStatus;\nimport org.apache.hc.core5.http.Method;\nimport org.apache.hc.core5.http.io.HttpRequestHandler;\nimport org.apache.hc.core5.http.io.entity.StringEntity;\nimport org.apache.hc.core5.http.protocol.HttpContext;\nimport org.sonarsource.sonarlint.core.SonarCloudActiveEnvironment;\nimport org.sonarsource.sonarlint.core.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.embedded.server.AttributeUtils;\nimport org.sonarsource.sonarlint.core.embedded.server.RequestHandlerBindingAssistant;\nimport org.sonarsource.sonarlint.core.file.FilePathTranslation;\nimport org.sonarsource.sonarlint.core.file.PathTranslationService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.SonarCloudConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.SonarQubeConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.IssueDetailsDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.ShowIssueParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.MessageType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowMessageParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.FlowDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.LocationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;\nimport org.sonarsource.sonarlint.core.serverapi.issue.IssueApi;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues;\nimport org.sonarsource.sonarlint.core.serverapi.rules.RulesApi;\nimport org.sonarsource.sonarlint.core.sync.SonarProjectBranchesSynchronizationService;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryService;\n\nimport static org.apache.commons.lang3.StringUtils.isNotBlank;\nimport static org.apache.commons.lang3.StringUtils.isNotEmpty;\n\npublic class ShowIssueRequestHandler implements HttpRequestHandler {\n\n  private final SonarLintRpcClient client;\n  private final SonarQubeClientManager sonarQubeClientManager;\n  private final TelemetryService telemetryService;\n  private final RequestHandlerBindingAssistant requestHandlerBindingAssistant;\n  private final PathTranslationService pathTranslationService;\n  private final SonarCloudActiveEnvironment sonarCloudActiveEnvironment;\n  private final SonarProjectBranchesSynchronizationService sonarProjectBranchesSynchronizationService;\n\n  public ShowIssueRequestHandler(SonarLintRpcClient client, SonarQubeClientManager sonarQubeClientManager, TelemetryService telemetryService,\n    RequestHandlerBindingAssistant requestHandlerBindingAssistant, PathTranslationService pathTranslationService, SonarCloudActiveEnvironment sonarCloudActiveEnvironment,\n    SonarProjectBranchesSynchronizationService sonarProjectBranchesSynchronizationService) {\n    this.client = client;\n    this.sonarQubeClientManager = sonarQubeClientManager;\n    this.telemetryService = telemetryService;\n    this.requestHandlerBindingAssistant = requestHandlerBindingAssistant;\n    this.pathTranslationService = pathTranslationService;\n    this.sonarCloudActiveEnvironment = sonarCloudActiveEnvironment;\n    this.sonarProjectBranchesSynchronizationService = sonarProjectBranchesSynchronizationService;\n  }\n\n  @Override\n  public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException {\n    var origin = AttributeUtils.getOrigin(context);\n    var showIssueQuery = extractQuery(origin, AttributeUtils.getParams(context));\n    if (!Method.GET.isSame(request.getMethod()) || !showIssueQuery.isValid()) {\n      response.setCode(HttpStatus.SC_BAD_REQUEST);\n      return;\n    }\n    telemetryService.showIssueRequestReceived();\n\n    AssistCreatingConnectionParams serverConnectionParams = createAssistServerConnectionParams(showIssueQuery);\n\n    requestHandlerBindingAssistant.assistConnectionAndBindingIfNeededAsync(\n      serverConnectionParams,\n      showIssueQuery.projectKey,\n      origin,\n      (connectionId, boundScopes, configScopeId, cancelMonitor) -> {\n        if (configScopeId != null) {\n          var branch = showIssueQuery.branch;\n          if (branch == null) {\n            branch = sonarProjectBranchesSynchronizationService.findMainBranch(connectionId, showIssueQuery.projectKey, cancelMonitor);\n          }\n\n          showIssueForScope(connectionId, configScopeId, showIssueQuery.issueKey, showIssueQuery.projectKey, branch,\n            showIssueQuery.pullRequest, cancelMonitor);\n        }\n      });\n\n    response.setCode(HttpStatus.SC_OK);\n    response.setEntity(new StringEntity(\"OK\"));\n  }\n\n  private static AssistCreatingConnectionParams createAssistServerConnectionParams(ShowIssueQuery query) {\n    var tokenName = query.getTokenName();\n    var tokenValue = query.getTokenValue();\n    return query.isSonarCloud ?\n      new AssistCreatingConnectionParams(new SonarCloudConnectionParams(query.getOrganizationKey(), tokenName, tokenValue,\n        org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.valueOf(query.getRegion().name())))\n      : new AssistCreatingConnectionParams(new SonarQubeConnectionParams(query.getServerUrl(), tokenName, tokenValue));\n  }\n\n  private void showIssueForScope(String connectionId, String configScopeId, String issueKey, String projectKey,\n    String branch, @Nullable String pullRequest, SonarLintCancelMonitor cancelMonitor) {\n    var issueDetailsOpt = tryFetchIssue(connectionId, issueKey, projectKey, branch, pullRequest, cancelMonitor);\n    if (issueDetailsOpt.isPresent()) {\n      pathTranslationService.getOrComputePathTranslation(configScopeId)\n        .ifPresent(translation -> client.showIssue(getShowIssueParams(issueDetailsOpt.get(), connectionId, configScopeId, branch, pullRequest, translation, cancelMonitor)));\n    } else {\n      client.showMessage(new ShowMessageParams(MessageType.ERROR, \"Could not show the issue. See logs for more details\"));\n    }\n  }\n\n  @VisibleForTesting\n  ShowIssueParams getShowIssueParams(IssueApi.ServerIssueDetails issueDetails, String connectionId,\n    String configScopeId, String branch, @Nullable String pullRequest, FilePathTranslation translation, SonarLintCancelMonitor cancelMonitor) {\n    var flowLocations = issueDetails.flowList.stream().map(flow -> {\n      var locations = flow.getLocationsList().stream().map(location -> {\n        var locationComponent = issueDetails.componentsList.stream().filter(component -> component.getKey().equals(location.getComponent())).findFirst();\n        var filePath = locationComponent.map(Issues.Component::getPath).orElse(\"\");\n        var locationTextRange = location.getTextRange();\n        var codeSnippet = tryFetchCodeSnippet(connectionId, locationComponent.map(Issues.Component::getKey).orElse(\"\"), locationTextRange, branch, pullRequest, cancelMonitor);\n        var locationTextRangeDto = new TextRangeDto(locationTextRange.getStartLine(), locationTextRange.getStartOffset(),\n          locationTextRange.getEndLine(), locationTextRange.getEndOffset());\n        return new LocationDto(locationTextRangeDto, location.getMsg(), translation.serverToIdePath(Paths.get(filePath)), codeSnippet.orElse(\"\"));\n      }).toList();\n      return new FlowDto(locations);\n    }).toList();\n\n    var textRange = issueDetails.textRange;\n    var textRangeDto = new TextRangeDto(textRange.getStartLine(), textRange.getStartOffset(), textRange.getEndLine(),\n      textRange.getEndOffset());\n\n    var isTaint = isIssueTaint(issueDetails.ruleKey);\n\n    return new ShowIssueParams(configScopeId, new IssueDetailsDto(textRangeDto, issueDetails.ruleKey, issueDetails.key, translation.serverToIdePath(issueDetails.path),\n      issueDetails.message, issueDetails.creationDate, issueDetails.codeSnippet, isTaint, flowLocations));\n  }\n\n  static boolean isIssueTaint(String ruleKey) {\n    return RulesApi.TAINT_REPOS.stream().anyMatch(ruleKey::startsWith);\n  }\n\n  private Optional<IssueApi.ServerIssueDetails> tryFetchIssue(String connectionId, String issueKey, String projectKey, String branch, @Nullable String pullRequest,\n    SonarLintCancelMonitor cancelMonitor) {\n    return sonarQubeClientManager.withActiveClientFlatMapOptionalAndReturn(connectionId,\n      serverApi -> serverApi.issue().fetchServerIssue(issueKey, projectKey, branch, pullRequest, cancelMonitor));\n  }\n\n  private Optional<String> tryFetchCodeSnippet(String connectionId, String fileKey, Common.TextRange textRange, String branch, @Nullable String pullRequest,\n    SonarLintCancelMonitor cancelMonitor) {\n    return sonarQubeClientManager.withActiveClientFlatMapOptionalAndReturn(connectionId,\n      api -> api.issue().getCodeSnippet(fileKey, textRange, branch, pullRequest, cancelMonitor));\n  }\n\n  @VisibleForTesting\n  ShowIssueQuery extractQuery(String origin, Map<String, String> params) {\n    boolean isSonarCloud = sonarCloudActiveEnvironment.isSonarQubeCloud(origin);\n    String serverUrl;\n    SonarCloudRegion region = null;\n    if (isSonarCloud) {\n      serverUrl = Strings.CS.removeEnd(origin, \"/\");\n      region = sonarCloudActiveEnvironment.getRegionOrThrow(serverUrl);\n    } else {\n      serverUrl = params.get(\"server\");\n    }\n    return new ShowIssueQuery(serverUrl, params.get(\"project\"), params.get(\"issue\"), params.get(\"branch\"),\n      params.get(\"pullRequest\"), params.get(\"tokenName\"), params.get(\"tokenValue\"), params.get(\"organizationKey\"), region, isSonarCloud);\n  }\n\n  @VisibleForTesting\n  public static class ShowIssueQuery {\n\n    private final String serverUrl;\n    private final String projectKey;\n    private final String issueKey;\n    @Nullable\n    private final String branch;\n    @Nullable\n    private final String pullRequest;\n    @Nullable\n    private final String tokenName;\n    @Nullable\n    private final String tokenValue;\n    @Nullable\n    private final String organizationKey;\n    @Nullable\n    private final SonarCloudRegion region;\n    private final boolean isSonarCloud;\n\n    public ShowIssueQuery(@Nullable String serverUrl, String projectKey, String issueKey, @Nullable String branch, @Nullable String pullRequest,\n      @Nullable String tokenName, @Nullable String tokenValue, @Nullable String organizationKey, @Nullable SonarCloudRegion region, boolean isSonarCloud) {\n      this.serverUrl = serverUrl;\n      this.projectKey = projectKey;\n      this.issueKey = issueKey;\n      this.branch = branch;\n      this.pullRequest = pullRequest;\n      this.tokenName = tokenName;\n      this.tokenValue = tokenValue;\n      this.organizationKey = organizationKey;\n      this.region = region != null ? region : SonarCloudRegion.EU;\n      this.isSonarCloud = isSonarCloud;\n    }\n\n    public boolean isValid() {\n      return isNotBlank(projectKey) && isNotBlank(issueKey)\n        && (isSonarCloud || isNotBlank(serverUrl))\n        && (!isSonarCloud || isNotBlank(organizationKey))\n        && isPullRequestParamValid() && isTokenValid();\n    }\n\n    public boolean isPullRequestParamValid() {\n      if (pullRequest != null) {\n        return isNotEmpty(pullRequest);\n      }\n      return true;\n    }\n\n    /**\n     * Either we get a token combination or we don't get a token combination: There is nothing in between\n     */\n    public boolean isTokenValid() {\n      if (tokenName != null && tokenValue != null) {\n        return isNotEmpty(tokenName) && isNotEmpty(tokenValue);\n      }\n\n      return tokenName == null && tokenValue == null;\n    }\n\n    public String getServerUrl() {\n      return serverUrl;\n    }\n\n    public String getProjectKey() {\n      return projectKey;\n    }\n\n    @Nullable\n    public String getOrganizationKey() {\n      return organizationKey;\n    }\n\n    public String getIssueKey() {\n      return issueKey;\n    }\n\n    @Nullable\n    public String getBranch() {\n      return branch;\n    }\n\n    @Nullable\n    public String getPullRequest() {\n      return pullRequest;\n    }\n\n    @Nullable\n    public String getTokenName() {\n      return tokenName;\n    }\n\n    @Nullable\n    public String getTokenValue() {\n      return tokenValue;\n    }\n\n    @Nullable\n    public SonarCloudRegion getRegion() {\n      return region;\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/handler/StatusRequestHandler.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server.handler;\n\nimport com.google.gson.Gson;\nimport com.google.gson.annotations.Expose;\nimport java.io.IOException;\nimport org.apache.hc.core5.http.ClassicHttpRequest;\nimport org.apache.hc.core5.http.ClassicHttpResponse;\nimport org.apache.hc.core5.http.ContentType;\nimport org.apache.hc.core5.http.HttpException;\nimport org.apache.hc.core5.http.HttpStatus;\nimport org.apache.hc.core5.http.Method;\nimport org.apache.hc.core5.http.io.HttpRequestHandler;\nimport org.apache.hc.core5.http.io.entity.StringEntity;\nimport org.apache.hc.core5.http.protocol.HttpContext;\nimport org.sonarsource.sonarlint.core.embedded.server.AttributeUtils;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.ClientConstantInfoDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\n\npublic class StatusRequestHandler implements HttpRequestHandler {\n\n  private final SonarLintRpcClient client;\n  private final ConnectionConfigurationRepository repository;\n  private final ClientConstantInfoDto clientInfo;\n\n  public StatusRequestHandler(SonarLintRpcClient client, ConnectionConfigurationRepository repository, InitializeParams params) {\n    this.client = client;\n    this.repository = repository;\n    this.clientInfo = params.getClientConstantInfo();\n  }\n\n  @Override\n  public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException {\n    if (!Method.GET.isSame(request.getMethod())) {\n      response.setCode(HttpStatus.SC_BAD_REQUEST);\n      return;\n    }\n\n    boolean trustedServer = isTrustedServer(AttributeUtils.getOrigin(context));\n\n    var description = getDescription(trustedServer);\n    var capabilities = new CapabilitiesResponse(true);\n    // We need a token when the requesting server is not a trusted one (in order to automatically create a connection).\n    response.setEntity(new StringEntity(new Gson().toJson(new StatusResponse(clientInfo.getName(), description, !trustedServer, capabilities)), ContentType.APPLICATION_JSON));\n\n  }\n\n  private String getDescription(boolean trustedServer) {\n    if (trustedServer) {\n      var getClientInfoResponse = client.getClientLiveInfo().join();\n      return getClientInfoResponse.getDescription();\n    }\n    return \"\";\n  }\n\n  private boolean isTrustedServer(String serverOrigin) {\n    return repository.hasConnectionWithOrigin(serverOrigin);\n  }\n\n  private record StatusResponse(@Expose String ideName, @Expose String description, @Expose boolean needsToken,\n                                @Expose CapabilitiesResponse capabilities) { }\n\n  private record CapabilitiesResponse(@Expose boolean canOpenFixSuggestion) { }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/handler/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.embedded.server.handler;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.embedded.server;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/event/BindingConfigChangedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.event;\n\nimport org.sonarsource.sonarlint.core.repository.config.BindingConfiguration;\n\npublic record BindingConfigChangedEvent(String configScopeId, BindingConfiguration previousConfig, BindingConfiguration newConfig) {\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/event/ConfigurationScopeRemovedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.event;\n\nimport org.sonarsource.sonarlint.core.repository.config.BindingConfiguration;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationScope;\n\npublic record ConfigurationScopeRemovedEvent(ConfigurationScope removedConfigurationScope, BindingConfiguration removedBindingConfiguration) {\n  public String getRemovedConfigurationScopeId() {\n    return removedConfigurationScope.id();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/event/ConfigurationScopesAddedWithBindingEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.event;\n\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationScopeWithBinding;\n\npublic record ConfigurationScopesAddedWithBindingEvent(Set<ConfigurationScopeWithBinding> addedConfigurationScopes) {\n\n  public Set<String> getConfigScopeIds() {\n    return addedConfigurationScopes.stream()\n      .map(configScope -> configScope.scope().id())\n      .collect(Collectors.toSet());\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/event/ConnectionConfigurationAddedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.event;\n\nimport org.sonarsource.sonarlint.core.commons.ConnectionKind;\n\npublic record ConnectionConfigurationAddedEvent(String addedConnectionId, ConnectionKind connectionKind) {\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/event/ConnectionConfigurationRemovedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.event;\n\npublic record ConnectionConfigurationRemovedEvent(String removedConnectionId) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/event/ConnectionConfigurationUpdatedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.event;\n\npublic record ConnectionConfigurationUpdatedEvent(String updatedConnectionId) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/event/ConnectionCredentialsChangedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.event;\n\npublic class ConnectionCredentialsChangedEvent {\n\n  private final String connectionId;\n\n  public ConnectionCredentialsChangedEvent(String connectionId) {\n    this.connectionId = connectionId;\n  }\n\n  public String getConnectionId() {\n    return connectionId;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/event/DependencyRisksSynchronizedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.event;\n\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerDependencyRisk;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.UpdateSummary;\n\npublic record DependencyRisksSynchronizedEvent(String connectionId, String sonarProjectKey, String sonarBranch, UpdateSummary<ServerDependencyRisk> summary) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/event/FixSuggestionReceivedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.event;\n\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AiSuggestionSource;\n\npublic record FixSuggestionReceivedEvent(String fixSuggestionId, AiSuggestionSource source, int snippetsCount, boolean wasGeneratedFromIde) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/event/LocalOnlyIssueStatusChangedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.event;\n\nimport org.sonarsource.sonarlint.core.commons.LocalOnlyIssue;\n\npublic class LocalOnlyIssueStatusChangedEvent {\n  private final LocalOnlyIssue issue;\n\n  public LocalOnlyIssueStatusChangedEvent(LocalOnlyIssue issue) {\n    this.issue = issue;\n  }\n\n  public LocalOnlyIssue getIssue() {\n    return issue;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/event/MatchingSessionEndedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.event;\n\npublic record MatchingSessionEndedEvent(long newIssuesFound, long issuesFixed) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/event/PluginStatusUpdateEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.event;\n\nimport java.util.Collection;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.plugin.PluginStatus;\n\npublic record PluginStatusUpdateEvent(\n  @Nullable String connectionId,\n  Collection<PluginStatus> newStatuses) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/event/ServerIssueStatusChangedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.event;\n\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerFinding;\n\npublic class ServerIssueStatusChangedEvent {\n  private final String connectionId;\n  private final String projectKey;\n  private final ServerFinding finding;\n\n  public ServerIssueStatusChangedEvent(String connectionId, String projectKey, ServerFinding finding) {\n    this.connectionId = connectionId;\n    this.projectKey = projectKey;\n    this.finding = finding;\n  }\n\n  public String getConnectionId() {\n    return connectionId;\n  }\n\n  public String getProjectKey() {\n    return projectKey;\n  }\n\n  public ServerFinding getFinding() {\n    return finding;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/event/SonarServerEventReceivedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.event;\n\nimport org.sonarsource.sonarlint.core.serverapi.push.SonarServerEvent;\n\npublic class SonarServerEventReceivedEvent {\n  private final String connectionId;\n  private final SonarServerEvent event;\n\n  public SonarServerEventReceivedEvent(String connectionId, SonarServerEvent event) {\n    this.connectionId = connectionId;\n    this.event = event;\n  }\n\n  public String getConnectionId() {\n    return connectionId;\n  }\n\n  public SonarServerEvent getEvent() {\n    return event;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/event/TaintVulnerabilitiesSynchronizedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.event;\n\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerTaintIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.UpdateSummary;\n\npublic class TaintVulnerabilitiesSynchronizedEvent {\n  private final String connectionId;\n  private final String sonarProjectKey;\n  private final String sonarBranch;\n  private final UpdateSummary<ServerTaintIssue> summary;\n\n  public TaintVulnerabilitiesSynchronizedEvent(String connectionId, String sonarProjectKey, String sonarBranch, UpdateSummary<ServerTaintIssue> summary) {\n    this.connectionId = connectionId;\n    this.sonarProjectKey = sonarProjectKey;\n    this.sonarBranch = sonarBranch;\n    this.summary = summary;\n  }\n\n  public String getConnectionId() {\n    return connectionId;\n  }\n\n  public String getSonarProjectKey() {\n    return sonarProjectKey;\n  }\n\n  public String getSonarBranch() {\n    return sonarBranch;\n  }\n\n  public UpdateSummary<ServerTaintIssue> getSummary() {\n    return summary;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/event/TelemetryUpdatedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.event;\n\npublic record TelemetryUpdatedEvent(boolean isTelemetryEnabled) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/event/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.event;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/file/FilePathTranslation.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.file;\n\nimport java.nio.file.Path;\n\npublic class FilePathTranslation {\n  private final Path idePathPrefix;\n  private final Path serverPathPrefix;\n\n  public FilePathTranslation(Path idePathPrefix, Path serverPathPrefix) {\n    this.idePathPrefix = idePathPrefix;\n    this.serverPathPrefix = serverPathPrefix;\n  }\n\n  public Path getIdePathPrefix() {\n    return idePathPrefix;\n  }\n\n  public Path getServerPathPrefix() {\n    return serverPathPrefix;\n  }\n\n  public Path serverToIdePath(Path serverFilePath) {\n    if (!serverFilePath.toString().startsWith(serverPathPrefix.toString())) {\n      return serverFilePath;\n    }\n    var localPrefixLen = serverPathPrefix.toString().length();\n    if (localPrefixLen > 0) {\n      localPrefixLen++;\n    }\n    return idePathPrefix.resolve(serverFilePath.toString().substring(localPrefixLen));\n  }\n\n  public Path ideToServerPath(Path idePath) {\n    if (!idePath.toString().startsWith(idePathPrefix.toString())) {\n      return idePath;\n    }\n    var localPrefixLen = idePathPrefix.toString().length();\n    if (localPrefixLen > 0) {\n      localPrefixLen++;\n    }\n    return serverPathPrefix.resolve(idePath.toString().substring(localPrefixLen));\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/file/PathTranslationService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.file;\n\nimport jakarta.annotation.PreDestroy;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.Optional;\nimport javax.annotation.CheckForNull;\nimport org.sonarsource.sonarlint.core.SonarLintMDC;\nimport org.sonarsource.sonarlint.core.branch.MatchedSonarProjectBranchChangedEvent;\nimport org.sonarsource.sonarlint.core.commons.SmartCancelableLoadingCache;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.event.BindingConfigChangedEvent;\nimport org.sonarsource.sonarlint.core.event.ConfigurationScopeRemovedEvent;\nimport org.sonarsource.sonarlint.core.fs.ClientFile;\nimport org.sonarsource.sonarlint.core.fs.ClientFileSystemService;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.serverconnection.prefix.FileTreeMatcher;\nimport org.springframework.context.event.EventListener;\n\n/**\n * The path translation service is responsible for matching the files on the server with the files on the client.\n * This is only used in connected mode.\n * A debounce mechanism is used to avoid too many requests to the server.\n */\npublic class PathTranslationService {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final ClientFileSystemService clientFs;\n  private final ConfigurationRepository configurationRepository;\n  private final ServerFilePathsProvider serverFilePathsProvider;\n  private final SmartCancelableLoadingCache<String, FilePathTranslation> cachedPathsTranslationByConfigScope =\n    new SmartCancelableLoadingCache<>(\"sonarlint-path-translation\", this::computePaths, (key, oldValue, newValue) -> {\n    });\n\n  public PathTranslationService(ClientFileSystemService clientFs, ConfigurationRepository configurationRepository, ServerFilePathsProvider serverFilePathsProvider) {\n    this.clientFs = clientFs;\n    this.configurationRepository = configurationRepository;\n    this.serverFilePathsProvider = serverFilePathsProvider;\n  }\n\n  @CheckForNull\n  private FilePathTranslation computePaths(String configScopeId, SonarLintCancelMonitor cancelMonitor) {\n    SonarLintMDC.putConfigScopeId(configScopeId);\n    LOG.debug(\"Computing paths translation for config scope '{}'...\", configScopeId);\n    var fileMatcher = new FileTreeMatcher();\n    var binding = configurationRepository.getEffectiveBinding(configScopeId).orElse(null);\n    if (binding == null) {\n      LOG.debug(\"Config scope '{}' does not exist or is not bound\", configScopeId);\n      return null;\n    }\n    return serverFilePathsProvider.getServerPaths(binding, cancelMonitor)\n      .map(paths -> matchPaths(configScopeId, fileMatcher, paths))\n      .orElse(null);\n  }\n\n  private FilePathTranslation matchPaths(String configScopeId, FileTreeMatcher fileMatcher, List<Path> serverFilePaths) {\n    LOG.debug(\"Starting matching paths for config scope '{}'...\", configScopeId);\n    var localFilePaths = clientFs.getFiles(configScopeId);\n    if (localFilePaths.isEmpty()) {\n      LOG.debug(\"No client files for config scope '{}'. Skipping path matching.\", configScopeId);\n      // Maybe a config scope without files, or the filesystem has not been initialized yet\n      return new FilePathTranslation(Paths.get(\"\"), Paths.get(\"\"));\n    }\n    var match = fileMatcher.match(serverFilePaths, localFilePaths.stream().map(ClientFile::getClientRelativePath).toList());\n    LOG.debug(\"Matched paths for config scope '{}':\\n  * idePrefix={}\\n  * serverPrefix={}\", configScopeId, match.idePrefix(), match.sqPrefix());\n    return new FilePathTranslation(match.idePrefix(), match.sqPrefix());\n  }\n\n  @EventListener\n  public void onConfigurationScopeRemoved(ConfigurationScopeRemovedEvent event) {\n    cachedPathsTranslationByConfigScope.clear(event.getRemovedConfigurationScopeId());\n  }\n\n  @EventListener\n  public void onBindingChanged(BindingConfigChangedEvent event) {\n    var configScopeId = event.configScopeId();\n    cachedPathsTranslationByConfigScope.refreshAsync(configScopeId);\n  }\n\n  @EventListener\n  public void onBranchChanged(MatchedSonarProjectBranchChangedEvent event) {\n    var configScopeId = event.getConfigurationScopeId();\n    cachedPathsTranslationByConfigScope.refreshAsync(configScopeId);\n  }\n\n  public Optional<FilePathTranslation> getOrComputePathTranslation(String configurationScopeId) {\n    return Optional.ofNullable(cachedPathsTranslationByConfigScope.get(configurationScopeId));\n  }\n\n  @PreDestroy\n  public void shutdown() {\n    cachedPathsTranslationByConfigScope.close();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProvider.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.file;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.cache.Cache;\nimport com.google.common.cache.CacheBuilder;\nimport java.io.BufferedWriter;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.nio.charset.Charset;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.UUID;\nimport java.util.concurrent.CancellationException;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.UserPaths;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\n\npublic class ServerFilePathsProvider {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final SonarQubeClientManager sonarQubeClientManager;\n  private final Map<Binding, Path> cachedResponseFilePathByBinding = new HashMap<>();\n  private final Path cacheDirectoryPath;\n  private final Cache<Binding, List<Path>> temporaryInMemoryFilePathCacheByBinding;\n\n  public ServerFilePathsProvider(SonarQubeClientManager sonarQubeClientManager, UserPaths userPaths) {\n    this.sonarQubeClientManager = sonarQubeClientManager;\n    this.cacheDirectoryPath = userPaths.getStorageRoot().resolve(\"cache\");\n    this.temporaryInMemoryFilePathCacheByBinding = CacheBuilder.newBuilder()\n      .expireAfterWrite(Duration.of(1, ChronoUnit.MINUTES))\n      .maximumSize(3)\n      .build();\n\n    clearCachePath();\n  }\n\n  private void clearCachePath() {\n    if (!cacheDirectoryPath.toFile().exists()) {\n      return;\n    }\n    try {\n      FileUtils.deleteDirectory(cacheDirectoryPath.toFile());\n    } catch (IOException e) {\n      LOG.debug(\"Error occurred while deleting a cache file\", e);\n    }\n  }\n\n  Optional<List<Path>> getServerPaths(Binding binding, SonarLintCancelMonitor cancelMonitor) {\n    return getPathsFromInMemoryCache(binding)\n      .or(() -> getPathsFromFileCache(binding))\n      .or(() -> fetchPathsFromServer(binding, cancelMonitor));\n  }\n\n  private Optional<List<Path>> getPathsFromInMemoryCache(Binding binding) {\n    return Optional.ofNullable(temporaryInMemoryFilePathCacheByBinding.getIfPresent(binding));\n  }\n\n  private Optional<List<Path>> getPathsFromFileCache(Binding binding) {\n    return Optional.ofNullable(cachedResponseFilePathByBinding.get(binding))\n      .filter(path -> path.toFile().exists())\n      .map(path -> {\n        List<Path> paths = readServerPathsFromFile(path);\n        putToInMemoryCache(binding, paths);\n        return paths;\n      });\n  }\n\n  private Optional<List<Path>> fetchPathsFromServer(Binding binding, SonarLintCancelMonitor cancelMonitor) {\n    try {\n      return sonarQubeClientManager.withActiveClientAndReturn(binding.connectionId(), serverApi -> {\n        List<Path> paths = fetchPathsFromServer(serverApi, binding.sonarProjectKey(), cancelMonitor);\n        cacheServerPaths(binding, paths);\n        return paths;\n      });\n    } catch (CancellationException e) {\n      throw e;\n    } catch (Exception e) {\n      LOG.debug(\"Error while getting server file paths for project '{}'\", binding.sonarProjectKey(), e);\n      return Optional.empty();\n    }\n  }\n\n  private static List<Path> readServerPathsFromFile(Path responsePath) {\n    try {\n      return Files.readAllLines(responsePath).stream().map(Paths::get).toList();\n    } catch (IOException e) {\n      LOG.debug(\"Error occurred while reading the file server path response file cache {}\", responsePath);\n      return Collections.emptyList();\n    }\n  }\n\n  private void putToInMemoryCache(Binding binding, List<Path> paths) {\n    temporaryInMemoryFilePathCacheByBinding.put(binding, paths);\n  }\n\n  private static List<Path> fetchPathsFromServer(ServerApi serverApi, String projectKey, SonarLintCancelMonitor cancelMonitor) {\n    return serverApi.component().getAllFileKeys(projectKey, cancelMonitor).stream()\n      .map(fileKey -> StringUtils.substringAfterLast(fileKey, \":\"))\n      .map(Paths::get)\n      .toList();\n  }\n\n  private void cacheServerPaths(Binding binding, List<Path> paths) {\n    var fileName = UUID.randomUUID().toString();\n    var filePath = cacheDirectoryPath.resolve(fileName);\n    try {\n      Files.createDirectories(cacheDirectoryPath);\n      writeToFile(filePath, paths);\n      cachedResponseFilePathByBinding.put(binding, filePath);\n      putToInMemoryCache(binding, paths);\n    } catch (IOException e) {\n      LOG.debug(\"Error occurred while writing the cache file\", e);\n    }\n  }\n\n  private static void writeToFile(Path filePath, List<Path> paths) throws IOException {\n    try (var bufferedWriter = new BufferedWriter(new FileWriter(filePath.toFile(), Charset.defaultCharset()))) {\n      for (Path path : paths) {\n        bufferedWriter.write(path + System.lineSeparator());\n      }\n    }\n  }\n\n  @VisibleForTesting\n  void clearInMemoryCache() {\n    temporaryInMemoryFilePathCacheByBinding.invalidateAll();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/file/WindowsShortcutUtils.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.file;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.net.URI;\nimport java.util.Arrays;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\nimport static org.apache.commons.lang3.ArrayUtils.reverse;\n\npublic class WindowsShortcutUtils {\n  // Based on Windows specification the magic number is 0x0000004C that must be tested with both big and little endian\n  // as it might differ based on the architecture / OS.\n  private static final byte[] WINDOWS_SHORTCUT_MAGIC_NUMBER = new byte[] {0, 0, 0, 76};\n  \n  private WindowsShortcutUtils() {\n    // utility class\n  }\n\n  /**\n   *  Checks whether a file provided by URI is a Windows shortcut or not. These differ from actual (sym)links on\n   *  Windows as they are like an object containing the pointer to the other resource instead of pointing to the\n   *  resource directly.\n   */\n  public static boolean isWindowsShortcut(URI uri) {\n    // Based on the Windows specification the shortcuts have this file suffix, when changing the file suffix they won't\n    // work anymore. So if users would create a shortcut, then rename it and have it in the scope of SonarLint this\n    // would fail but that is fine.\n    if (!uri.toString().contains(\".lnk\")) {\n      return false;\n    }\n    \n    // If the filename ends with \".lnk\" we check the magic number in order to determine it to actually be a windows\n    // shortcut. This is expensive and therefore will actually only do it on files that match this filter!\n    var magicNumber = new byte[4];\n    try (var is = new FileInputStream(new File(uri))) {\n      if (is.read(magicNumber) != magicNumber.length) {\n        // We can only read 0-3 bytes, therefore it cannot be a Windows shortcut. No idea what kind of file this might\n        // be (e.g. a text file with 3 characters?) but hey, they gave it a \".lnk\" suffix and mimicked a shortcut so\n        // they probably know better.\n        return false;\n      }\n      \n      // Check big endian\n      if (Arrays.equals(WINDOWS_SHORTCUT_MAGIC_NUMBER, magicNumber)) {\n        return true;\n      }\n\n      // Check little endian\n      reverse(magicNumber);\n      return Arrays.equals(WINDOWS_SHORTCUT_MAGIC_NUMBER, magicNumber);\n    } catch (IOException err) {\n      SonarLintLogger.get().debug(\"Cannot check whether '\" + uri + \"' is a Windows shortcut, assuming it is not.\");\n    }\n    \n    return false;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/file/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.file;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/fs/ClientFile.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.fs;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.nio.charset.Charset;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport javax.annotation.Nullable;\nimport org.apache.commons.compress.utils.FileNameUtils;\nimport org.apache.commons.io.ByteOrderMark;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.commons.io.input.BOMInputStream;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.util.FileUtils;\n\npublic class ClientFile {\n\n  private static final String SONARLINT_FOLDER_NAME = \".sonarlint\";\n\n  /**\n   * Unique identifier for this file on the client side\n   */\n  private final URI uri;\n\n  private final String configScopeId;\n\n  /**\n   * Relative path for this file on the client side. We use the {@link Path} class for convenience for filename separators,\n   * but it is not necessary to have a file on the filesystem.\n   */\n  private final Path relativePath;\n\n  /**\n   * For some clients, deciding if a file is a test is costly, and will be computed only when a file is opened in an editor.\n   * null means unknown\n   */\n  @Nullable\n  private final Boolean isTest;\n\n  @Nullable\n  private final Charset charset;\n\n  /**\n   * The absolute path on the local filesystem, if available.\n   */\n  @Nullable\n  private final Path fsPath;\n  @Nullable\n  private final SonarLanguage detectedLanguage;\n\n  /**\n   * Tell if the file content is flushed on disk?\n   * If the file is dirty, it means that the content is not flushed on disk, and the backend get the content from the client.\n   */\n  private boolean isDirty;\n\n  /**\n   * When the file is dirty, the content should be provided by the client.\n   */\n  @Nullable\n  private String clientProvidedContent;\n\n  private final boolean isUserDefined;\n\n  public ClientFile(URI uri, String configScopeId, Path relativePath, @Nullable Boolean isTest, @Nullable Charset charset, @Nullable Path fsPath,\n    @Nullable SonarLanguage detectedLanguage, boolean isUserDefined) {\n    this.uri = uri;\n    this.configScopeId = configScopeId;\n    this.relativePath = relativePath;\n    this.isTest = isTest;\n    this.charset = charset;\n    this.fsPath = fsPath;\n    this.detectedLanguage = detectedLanguage;\n    this.isUserDefined = isUserDefined;\n  }\n\n  public Path getClientRelativePath() {\n    return relativePath;\n  }\n\n  public String getFileName() {\n    return relativePath.getFileName().toString();\n  }\n\n  public URI getUri() {\n    return uri;\n  }\n\n  public boolean isDirty() {\n    return isDirty;\n  }\n\n  public String getContent() {\n    if (isDirty) {\n      return clientProvidedContent;\n    }\n    var charsetToUse = getCharset();\n    try (var inputStream = inputStream()) {\n      return IOUtils.toString(inputStream, charsetToUse);\n    } catch (IOException e) {\n      throw new IllegalStateException(\"Unable to read file \" + fsPath + \"content with charset \" + charsetToUse, e);\n    }\n  }\n\n  public InputStream inputStream() throws IOException {\n    if (isDirty && clientProvidedContent != null) {\n      return new ByteArrayInputStream(clientProvidedContent.getBytes(getCharset()));\n    }\n    if (fsPath == null) {\n      throw new IllegalStateException(\"File \" + uri + \" is not dirty or does not have content but has no OS Path defined\");\n    }\n    return BOMInputStream.builder().setInputStream(Files.newInputStream(fsPath))\n      // order list of BOMs by length descending, as anyway a sort is made in the constructor\n      .setByteOrderMarks(ByteOrderMark.UTF_32LE, ByteOrderMark.UTF_32BE, ByteOrderMark.UTF_8, ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_16BE).get();\n  }\n\n  public Charset getCharset() {\n    return charset != null ? charset : Charset.defaultCharset();\n  }\n\n  public String getConfigScopeId() {\n    return configScopeId;\n  }\n\n  public void setDirty(String content) {\n    this.isDirty = true;\n    this.clientProvidedContent = content;\n  }\n\n  public void setClean() {\n    this.isDirty = false;\n    this.clientProvidedContent = null;\n  }\n\n  public boolean isLargerThan(long size) throws IOException {\n    if (isDirty && clientProvidedContent != null) {\n      return clientProvidedContent.getBytes(getCharset()).length > size;\n    } else {\n      var localPath = FileUtils.getFilePathFromUri(uri);\n      if (Files.exists(localPath)) {\n        return Files.size(localPath) > size;\n      }\n    }\n    return false;\n  }\n\n  public boolean isSonarlintConfigurationFile() {\n    // Considering .sonarlint/*.json for compatibility with settings exported from Visual Studio\n    return isInDotSonarLintFolder() && hasJsonExtension();\n  }\n\n  private boolean isInDotSonarLintFolder() {\n    var sonarlintPath = getClientRelativePath().getParent();\n    return sonarlintPath != null && SONARLINT_FOLDER_NAME.equals(sonarlintPath.getFileName().toString());\n  }\n\n  private boolean hasJsonExtension() {\n    return \"json\".equals(FileNameUtils.getExtension(getClientRelativePath()));\n  }\n\n  public boolean isTest() {\n    return Boolean.TRUE == isTest;\n  }\n\n  @Nullable\n  public SonarLanguage getDetectedLanguage() {\n    return detectedLanguage;\n  }\n\n  @Override\n  public String toString() {\n    return uri.toString();\n  }\n\n  public boolean isUserDefined() {\n    return isUserDefined;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/fs/ClientFileSystemService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.fs;\n\nimport java.net.URI;\nimport java.nio.charset.Charset;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.stream.Collectors;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport javax.annotation.PreDestroy;\nimport org.sonarsource.sonarlint.core.commons.SmartCancelableLoadingCache;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.event.ConfigurationScopeRemovedEvent;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidUpdateFileSystemParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fs.GetBaseDirParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fs.ListFilesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryService;\nimport org.springframework.context.ApplicationEventPublisher;\nimport org.springframework.context.event.EventListener;\n\npublic class ClientFileSystemService {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final SonarLintRpcClient rpcClient;\n  private final ApplicationEventPublisher eventPublisher;\n  private final Map<URI, ClientFile> filesByUri = new ConcurrentHashMap<>();\n  private final Map<String, Path> baseDirPerConfigScopeId = new ConcurrentHashMap<>();\n  private final OpenFilesRepository openFilesRepository;\n  private final TelemetryService telemetryService;\n  private final SmartCancelableLoadingCache<String, Map<URI, ClientFile>> filesByConfigScopeIdCache =\n    new SmartCancelableLoadingCache<>(\"sonarlint-filesystem\", this::initializeFileSystem);\n\n  public ClientFileSystemService(SonarLintRpcClient rpcClient, ApplicationEventPublisher eventPublisher, OpenFilesRepository openFilesRepository,\n    TelemetryService telemetryService) {\n    this.rpcClient = rpcClient;\n    this.eventPublisher = eventPublisher;\n    this.openFilesRepository = openFilesRepository;\n    this.telemetryService = telemetryService;\n  }\n\n  public List<ClientFile> getFiles(String configScopeId) {\n    return List.copyOf(filesByConfigScopeIdCache.get(configScopeId).values());\n  }\n\n  private static ClientFile fromDto(ClientFileDto clientFileDto) {\n    var charset = charsetFromDto(clientFileDto.getCharset());\n    var forcedLanguage = clientFileDto.getDetectedLanguage();\n    var forcedSonarLanguage = forcedLanguage == null ? null : SonarLanguage.valueOf(forcedLanguage.name());\n    var file = new ClientFile(clientFileDto.getUri(), clientFileDto.getConfigScopeId(),\n      clientFileDto.getIdeRelativePath(),\n      clientFileDto.isTest(),\n      charset,\n      clientFileDto.getFsPath(),\n      forcedSonarLanguage,\n      clientFileDto.isUserDefined());\n    if (clientFileDto.getContent() != null) {\n      file.setDirty(clientFileDto.getContent());\n    }\n    return file;\n  }\n\n  @Nullable\n  private static Charset charsetFromDto(@Nullable String dtoCharset) {\n    if (dtoCharset == null) {\n      return null;\n    }\n    try {\n      return Charset.forName(dtoCharset);\n    } catch (Exception e) {\n      return null;\n    }\n  }\n\n  public List<ClientFile> findFilesByNamesInScope(String configScopeId, List<String> filenames) {\n    return getFiles(configScopeId).stream()\n      .filter(f -> filenames.contains(f.getClientRelativePath().getFileName().toString()))\n      .toList();\n  }\n\n  public List<ClientFile> findSonarlintConfigurationFilesByScope(String configScopeId) {\n    return getFiles(configScopeId).stream()\n      .filter(ClientFile::isSonarlintConfigurationFile)\n      .toList();\n  }\n\n  public Map<URI, ClientFile> initializeFileSystem(String configScopeId, SonarLintCancelMonitor cancelMonitor) {\n    var result = new ConcurrentHashMap<URI, ClientFile>();\n    var files = getClientFileDtos(configScopeId, cancelMonitor);\n    files.forEach(clientFileDto -> {\n      var clientFile = fromDto(clientFileDto);\n      filesByUri.put(clientFileDto.getUri(), clientFile);\n      result.put(clientFileDto.getUri(), clientFile);\n    });\n    return result;\n  }\n\n  private List<ClientFileDto> getClientFileDtos(String configScopeId, SonarLintCancelMonitor cancelMonitor) {\n    var startTime = System.currentTimeMillis();\n    var future = rpcClient.listFiles(new ListFilesParams(configScopeId));\n    cancelMonitor.onCancel(() -> future.cancel(true));\n    var files = future.join().getFiles();\n    var endTime = System.currentTimeMillis();\n    telemetryService.updateListFilesPerformance(files.size(), endTime - startTime);\n    return files;\n  }\n\n  public void didUpdateFileSystem(DidUpdateFileSystemParams params) {\n    var removed = new ArrayList<ClientFile>();\n    params.getRemovedFiles().forEach(uri -> {\n      var clientFile = filesByUri.remove(uri);\n      if (clientFile != null) {\n        filesByConfigScopeIdCache.get(clientFile.getConfigScopeId()).remove(uri);\n        removed.add(clientFile);\n      }\n    });\n\n    var added = new ArrayList<ClientFile>();\n    params.getAddedFiles().forEach(clientFileDto -> {\n      var clientFile = fromDto(clientFileDto);\n      var previousFile = filesByUri.put(clientFileDto.getUri(), clientFile);\n      // We only send send the ADDED event for files that were actually added (not existing before)\n      if (previousFile == null) {\n        added.add(clientFile);\n      }\n      var byScope = filesByConfigScopeIdCache.get(clientFileDto.getConfigScopeId());\n      byScope.put(clientFileDto.getUri(), clientFile);\n    });\n\n    var updated = new ArrayList<ClientFile>();\n    params.getChangedFiles().forEach(clientFileDto -> {\n      var clientFile = fromDto(clientFileDto);\n      var previousFile = filesByUri.put(clientFileDto.getUri(), clientFile);\n      // Modifying an unknown file is equals to adding it\n      if (previousFile != null) {\n        updated.add(clientFile);\n      } else {\n        added.add(clientFile);\n      }\n      var byScope = filesByConfigScopeIdCache.get(clientFileDto.getConfigScopeId());\n      byScope.put(clientFileDto.getUri(), clientFile);\n    });\n\n    eventPublisher.publishEvent(new FileSystemUpdatedEvent(removed, added, updated));\n  }\n\n  @EventListener\n  public void onConfigurationScopeRemoved(ConfigurationScopeRemovedEvent event) {\n    var removedFilesByURI = filesByConfigScopeIdCache.get(event.getRemovedConfigurationScopeId());\n    filesByConfigScopeIdCache.clear(event.getRemovedConfigurationScopeId());\n    if (removedFilesByURI != null) {\n      removedFilesByURI.keySet().forEach(filesByUri::remove);\n    }\n  }\n\n  @PreDestroy\n  public void shutdown() {\n    filesByConfigScopeIdCache.close();\n  }\n\n  /**\n   * This will trigger loading the FS from the client if needed\n   */\n  @CheckForNull\n  public ClientFile getClientFiles(String configScopeId, URI fileUri) {\n    return filesByConfigScopeIdCache.get(configScopeId).get(fileUri);\n  }\n\n  /**\n   * This will NOT trigger loading the FS from the client\n   */\n  @CheckForNull\n  public ClientFile getClientFile(URI fileUri) {\n    return filesByUri.get(fileUri);\n  }\n\n  @CheckForNull\n  public Path getBaseDir(String configurationScopeId) {\n    return baseDirPerConfigScopeId.computeIfAbsent(configurationScopeId, k -> {\n      try {\n        return rpcClient.getBaseDir(new GetBaseDirParams(configurationScopeId)).join().getBaseDir();\n      } catch (Exception e) {\n        LOG.error(\"Error when getting the base dir from the client\", e);\n        return null;\n      }\n    });\n  }\n\n  public void didOpenFile(String configurationScopeId, URI fileUri) {\n    var isNewlyOpenedFile = openFilesRepository.considerOpened(configurationScopeId, fileUri);\n    if (isNewlyOpenedFile) {\n      eventPublisher.publishEvent(new FileOpenedEvent(configurationScopeId, fileUri));\n    }\n  }\n\n  public void didCloseFile(String configurationScopeId, URI fileUri) {\n    openFilesRepository.considerClosed(configurationScopeId, fileUri);\n  }\n\n  public Map<String, Set<URI>> groupFilesByConfigScope(Set<URI> fileUris) {\n    return fileUris.stream()\n      .map(filesByUri::get)\n      .filter(Objects::nonNull)\n      .collect(Collectors.groupingBy(\n        ClientFile::getConfigScopeId,\n        Collectors.mapping(\n          ClientFile::getUri,\n          Collectors.toSet()\n        )\n      ));\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/fs/FileExclusionService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.fs;\n\nimport java.net.URI;\nimport java.nio.file.FileSystemNotFoundException;\nimport java.nio.file.FileSystems;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.PathMatcher;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.function.Consumer;\nimport java.util.function.Predicate;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonar.api.CoreProperties;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonarsource.sonarlint.core.ServerFileExclusions;\nimport org.sonarsource.sonarlint.core.plugin.commons.sonarapi.MapSettings;\nimport org.sonarsource.sonarlint.core.commons.SmartCancelableLoadingCache;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.commons.util.FileUtils;\nimport org.sonarsource.sonarlint.core.event.BindingConfigChangedEvent;\nimport org.sonarsource.sonarlint.core.file.PathTranslationService;\nimport org.sonarsource.sonarlint.core.file.WindowsShortcutUtils;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.FileStatusDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.GetFileExclusionsParams;\nimport org.sonarsource.sonarlint.core.serverconnection.AnalyzerConfiguration;\nimport org.sonarsource.sonarlint.core.serverconnection.IssueStorePaths;\nimport org.sonarsource.sonarlint.core.serverconnection.SonarServerSettingsChangedEvent;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.StorageException;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.springframework.context.event.EventListener;\n\nimport static java.util.Objects.requireNonNull;\nimport static org.sonarsource.sonarlint.core.commons.util.git.GitService.createSonarLintGitIgnore;\n\npublic class FileExclusionService {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  // 5 MB\n  private static final long MAX_AUTO_ANALYSIS_FILE_SIZE_BYTES = 5L * 1024 * 1024;\n\n  // See org.sonar.api.scan.filesystem.FileExclusions\n  private static final Set<String> ALL_EXCLUSION_RELATED_SETTINGS = Set.of(\n    CoreProperties.PROJECT_INCLUSIONS_PROPERTY,\n    CoreProperties.PROJECT_TEST_INCLUSIONS_PROPERTY,\n    CoreProperties.GLOBAL_EXCLUSIONS_PROPERTY,\n    CoreProperties.PROJECT_EXCLUSIONS_PROPERTY,\n    CoreProperties.GLOBAL_TEST_EXCLUSIONS_PROPERTY,\n    CoreProperties.PROJECT_TEST_EXCLUSIONS_PROPERTY);\n\n  private final ConfigurationRepository configRepo;\n  private final StorageService storageService;\n  private final PathTranslationService pathTranslationService;\n  private final ClientFileSystemService clientFileSystemService;\n  private final SonarLintRpcClient client;\n\n  private final SmartCancelableLoadingCache<URI, Boolean> serverExclusionByUriCache = new SmartCancelableLoadingCache<>(\"sonarlint-file-exclusions\", this::computeIfExcluded,\n    (key, oldValue, newValue) -> {});\n\n  public FileExclusionService(ConfigurationRepository configRepo, StorageService storageService, PathTranslationService pathTranslationService,\n    ClientFileSystemService clientFileSystemService, SonarLintRpcClient client) {\n    this.configRepo = configRepo;\n    this.storageService = storageService;\n    this.pathTranslationService = pathTranslationService;\n    this.clientFileSystemService = clientFileSystemService;\n    this.client = client;\n  }\n\n  public boolean computeIfExcluded(URI fileUri, SonarLintCancelMonitor cancelMonitor) {\n    LOG.debug(\"Computing file exclusion for uri '{}'\", fileUri);\n    var clientFile = clientFileSystemService.getClientFile(fileUri);\n    if (clientFile == null) {\n      LOG.debug(\"Unable to find client file for uri {}\", fileUri);\n      return false;\n    }\n    var configScope = clientFile.getConfigScopeId();\n    var effectiveBindingOpt = configRepo.getEffectiveBinding(configScope);\n    if (effectiveBindingOpt.isEmpty()) {\n      return false;\n    }\n    var analyzerStorage = storageService.connection(effectiveBindingOpt.get().connectionId())\n      .project(effectiveBindingOpt.get().sonarProjectKey())\n      .analyzerConfiguration();\n    if (!analyzerStorage.isValid()) {\n      LOG.warn(\"Unable to read settings in local storage, analysis storage is not ready\");\n      return false;\n    }\n    AnalyzerConfiguration analyzerConfig;\n    try {\n      analyzerConfig = analyzerStorage.read();\n    } catch (StorageException e) {\n      LOG.debug(\"Unable to read settings in local storage\", e);\n      return false;\n    }\n    var settings = new MapSettings(analyzerConfig.getSettings().getAll());\n    var exclusionFilters = new ServerFileExclusions(settings.asConfig());\n    exclusionFilters.prepare();\n    var idePath = clientFile.getClientRelativePath();\n    var pathTranslation = pathTranslationService.getOrComputePathTranslation(configScope);\n    Path serverPath;\n    if (pathTranslation.isPresent()) {\n      serverPath = IssueStorePaths.idePathToServerPath(pathTranslation.get().getIdePathPrefix(), pathTranslation.get().getServerPathPrefix(), idePath);\n      if (serverPath == null) {\n        // we can't map it to a Sonar server path, so just apply exclusions to the original ide path\n        serverPath = idePath;\n      }\n    } else {\n      serverPath = idePath;\n    }\n    var type = clientFile.isTest() ? InputFile.Type.TEST : InputFile.Type.MAIN;\n    var result = !exclusionFilters.accept(serverPath.toString(), type);\n    LOG.debug(\"File exclusion for uri '{}' is {}\", fileUri, result);\n    return result;\n  }\n\n  @EventListener\n  public void onBindingChanged(BindingConfigChangedEvent event) {\n    if (event.newConfig().isBound()) {\n      var connectionId = requireNonNull(event.newConfig().connectionId());\n      var projectKey = requireNonNull(event.newConfig().sonarProjectKey());\n      // do not recompute exclusions if storage does not yet contain settings (will be done by onFileExclusionSettingsChanged later)\n      if (storageService.connection(connectionId).project(projectKey).analyzerConfiguration().isValid()) {\n        LOG.debug(\"Binding changed for config scope '{}', recompute file exclusions...\", event.configScopeId());\n        forEachFileInScopeAndInheritedDescendants(event.configScopeId(), f -> serverExclusionByUriCache.refreshAsync(f.getUri()));\n      }\n    } else {\n      LOG.debug(\"Binding removed for config scope '{}', clearing file exclusions...\", event.configScopeId());\n      forEachFileInScopeAndInheritedDescendants(event.configScopeId(), f -> serverExclusionByUriCache.clear(f.getUri()));\n    }\n  }\n\n  private void forEachFileInScopeAndInheritedDescendants(String rootScopeId, Consumer<ClientFile> action) {\n    Stream.concat(Stream.of(rootScopeId), configRepo.getChildrenWithInheritedBinding(rootScopeId).stream())\n      .forEach(scopeId -> clientFileSystemService.getFiles(scopeId).forEach(action));\n  }\n\n  @EventListener\n  public void onFileSystemUpdated(FileSystemUpdatedEvent event) {\n    event.getRemoved().forEach(f -> serverExclusionByUriCache.clear(f.getUri()));\n    // We could try to be more efficient by looking at changed files, and deciding if we need to invalidate or not based on changed\n    // attributes (relative path, isTest). But it's probably not worth the effort.\n    Stream.concat(event.getAdded().stream(), event.getUpdated().stream())\n      .forEach(f -> serverExclusionByUriCache.refreshAsync(f.getUri()));\n  }\n\n  @EventListener\n  public void onFileExclusionSettingsChanged(SonarServerSettingsChangedEvent event) {\n    var settingsDiff = event.updatedSettingsValueByKey();\n    if (isFileExclusionSettingsDifferent(settingsDiff)) {\n      LOG.debug(\"File exclusion settings changed, recompute all file exclusions...\");\n      event.configScopeIds().forEach(configScopeId -> clientFileSystemService.getFiles(configScopeId)\n        .forEach(f -> serverExclusionByUriCache.refreshAsync(f.getUri())));\n    }\n  }\n\n  private static boolean isFileExclusionSettingsDifferent(Map<String, String> updatedSettingsValueByKey) {\n    return ALL_EXCLUSION_RELATED_SETTINGS.stream().anyMatch(updatedSettingsValueByKey::containsKey);\n  }\n\n  public Map<URI, FileStatusDto> getFilesStatus(Map<String, List<URI>> fileUrisByConfigScope) {\n    var result = new HashMap<URI, FileStatusDto>();\n    for (var entry : fileUrisByConfigScope.entrySet()) {\n      var configScopeId = entry.getKey();\n      var baseDir = clientFileSystemService.getBaseDir(configScopeId);\n      var files = new HashSet<>(entry.getValue());\n      var filteredFileUris = filterOutExcludedFiles(configScopeId, baseDir, files).stream().map(ClientFile::getUri).collect(Collectors.toSet());\n      files.forEach(uri -> result.put(uri, new FileStatusDto(!filteredFileUris.contains(uri))));\n    }\n    return result;\n  }\n\n  public boolean isExcludedFromServer(URI fileUri) {\n    return Boolean.TRUE.equals(serverExclusionByUriCache.get(fileUri));\n  }\n\n  public List<ClientFile> filterOutExcludedFiles(String configurationScopeId, @Nullable Path baseDir, Set<URI> files) {\n    var sonarLintGitIgnore = createSonarLintGitIgnore(baseDir);\n    // INFO: When there are additional filters coming at some point, add them here and log them down below as well!\n    var filteredURIsFromServerExclusionService = new ArrayList<URI>();\n    var filteredURIsFromGitIgnore = new ArrayList<URI>();\n    var filteredURIsNotUserDefined = new ArrayList<URI>();\n    var filteredURIsFromSymbolicLink = new ArrayList<URI>();\n    var filteredURIsFromWindowsShortcut = new ArrayList<URI>();\n    var filteredURIsNoFile = new ArrayList<URI>();\n    var filteredURIsTooLarge = new ArrayList<URI>();\n\n    var filesToExclude = files;\n\n    if (configRepo.getEffectiveBinding(configurationScopeId).isEmpty()) {\n      // client-defined file exclusions only apply in standalone mode\n      filesToExclude = filterOutClientExcludedFiles(configurationScopeId, files);\n    }\n\n    // Do the actual filtering and in case of a filtered out URI, save them for later logging!\n    var actualFilesToAnalyze = filesToExclude\n      .stream()\n      .map(uri -> {\n        var file = findFile(configurationScopeId, uri);\n        if (file == null) {\n          filteredURIsNoFile.add(uri);\n        }\n        return file;\n      })\n      .filter(Objects::nonNull)\n      .filter(file -> {\n        if (isExcludedFromServer(file.getUri())) {\n          filteredURIsFromServerExclusionService.add(file.getUri());\n          return false;\n        }\n        return true;\n      })\n      .filter(file -> {\n        if (sonarLintGitIgnore.isFileIgnored(file.getClientRelativePath())) {\n          filteredURIsFromGitIgnore.add(file.getUri());\n          return false;\n        }\n        return true;\n      })\n      .filter(file -> {\n        if (!file.isUserDefined()) {\n          filteredURIsNotUserDefined.add(file.getUri());\n          return false;\n        }\n        return true;\n      })\n      .filter(file -> {\n        try {\n          if (file.isLargerThan(MAX_AUTO_ANALYSIS_FILE_SIZE_BYTES)) {\n            filteredURIsTooLarge.add(file.getUri());\n            return false;\n          }\n        } catch (Exception e) {\n          // Could not determine the size, include the file by default\n        }\n        return true;\n      })\n      .filter(file -> {\n        // On Schemes like \"temp\" (used by IntelliJ) or \"rse\" (Eclipse Remote System Explorer),\n        // the check for a symbolic link or Windows shortcut will fail as these file systems cannot be resolved for the operations.\n        // If this happens, we won't exclude the file as the chance for someone to use a protocol with such a scheme while also using\n        // symbolic links or Windows shortcuts should be near zero, and this is less error-prone than excluding the\n        try {\n          var uri = file.getUri();\n          if (Files.isSymbolicLink(FileUtils.getFilePathFromUri(uri))) {\n            filteredURIsFromSymbolicLink.add(uri);\n            return false;\n          } else if (WindowsShortcutUtils.isWindowsShortcut(uri)) {\n            filteredURIsFromWindowsShortcut.add(uri);\n            return false;\n          }\n          return true;\n        } catch (FileSystemNotFoundException err) {\n          LOG.debug(\"Checking for symbolic links or Windows shortcuts in the file system is not possible for the URI '\" + file\n            + \"'. Therefore skipping the checks due to the underlying protocol / its scheme.\", err);\n          return true;\n        }\n      })\n      .toList();\n\n    // Log all the filtered out URIs but not for the filters where there were none\n    logFilteredURIs(\"Filtered out URIs based on the server exclusion service\", filteredURIsFromServerExclusionService);\n    logFilteredURIs(\"Filtered out URIs ignored by Git\", filteredURIsFromGitIgnore);\n    logFilteredURIs(\"Filtered out URIs not user-defined\", filteredURIsNotUserDefined);\n    logFilteredURIs(\"Filtered out URIs exceeding max allowed size\", filteredURIsTooLarge);\n    logFilteredURIs(\"Filtered out URIs that are symbolic links\", filteredURIsFromSymbolicLink);\n    logFilteredURIs(\"Filtered out URIs that are Windows shortcuts\", filteredURIsFromWindowsShortcut);\n    logFilteredURIs(\"Filtered out URIs having no file\", filteredURIsNoFile);\n\n    return actualFilesToAnalyze;\n  }\n\n  @CheckForNull\n  private ClientFile findFile(String configScopeId, URI fileUriToAnalyze) {\n    var clientFile = clientFileSystemService.getClientFiles(configScopeId, fileUriToAnalyze);\n    if (clientFile == null) {\n      LOG.error(\"File to analyze was not found in the file system: {}\", fileUriToAnalyze);\n      return null;\n    }\n    return clientFile;\n  }\n\n  private void logFilteredURIs(String reason, ArrayList<URI> uris) {\n    if (!uris.isEmpty()) {\n      SonarLintLogger.get().debug(reason + \": \" + String.join(\", \", uris.stream().map(Object::toString).toList()));\n    }\n  }\n\n  private Set<URI> filterOutClientExcludedFiles(String configurationScopeId, Set<URI> files) {\n    var fileExclusionsGlobPatterns = getClientFileExclusionPatterns(configurationScopeId);\n    var matchers = parseGlobPatterns(fileExclusionsGlobPatterns);\n    Predicate<URI> fileExclusionFilter = uri -> matchers.stream().noneMatch(matcher -> matcher.matches(Paths.get(uri)));\n\n    return files.stream()\n      .filter(fileExclusionFilter)\n      .collect(Collectors.toSet());\n  }\n\n  private Set<String> getClientFileExclusionPatterns(String configurationScopeId) {\n    try {\n      return client.getFileExclusions(new GetFileExclusionsParams(configurationScopeId)).join().getFileExclusionPatterns();\n    } catch (Exception e) {\n      LOG.error(\"Error when requesting the file exclusions\", e);\n      return Collections.emptySet();\n    }\n  }\n\n  private static List<PathMatcher> parseGlobPatterns(Set<String> globPatterns) {\n    var fs = FileSystems.getDefault();\n\n    List<PathMatcher> parsedMatchers = new ArrayList<>(globPatterns.size());\n    for (String pattern : globPatterns) {\n      try {\n        parsedMatchers.add(fs.getPathMatcher(\"glob:\" + pattern));\n      } catch (Exception e) {\n        // ignore invalid patterns, skip them\n      }\n    }\n    return parsedMatchers;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/fs/FileOpenedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.fs;\n\nimport java.net.URI;\n\npublic record FileOpenedEvent(String configurationScopeId, URI fileUri) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/fs/FileSystemUpdatedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.fs;\n\nimport java.util.List;\nimport java.util.stream.Stream;\n\npublic class FileSystemUpdatedEvent {\n\n  private final List<ClientFile> removed;\n  private final List<ClientFile> added;\n  private final List<ClientFile> updated;\n\n  public FileSystemUpdatedEvent(List<ClientFile> removed, List<ClientFile> added, List<ClientFile> updated) {\n    this.removed = removed;\n    this.added = added;\n    this.updated = updated;\n  }\n\n  public List<ClientFile> getRemoved() {\n    return removed;\n  }\n\n  public List<ClientFile> getAdded() {\n    return added;\n  }\n\n  public List<ClientFile> getUpdated() {\n    return updated;\n  }\n\n  public List<ClientFile> getAddedOrUpdated() {\n    return Stream.concat(getAdded().stream(), getUpdated().stream())\n      .toList();\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/fs/OpenFilesRepository.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.fs;\n\nimport java.net.URI;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.stream.Collectors;\n\npublic class OpenFilesRepository {\n  private final Map<String, Set<URI>> openFilesByConfigScopeId = new ConcurrentHashMap<>();\n\n  /**\n   * @return true if the file was previously not considered open; it is a newly opened file\n   */\n  public boolean considerOpened(String configurationScopeId, URI fileUri) {\n    var openFiles = openFilesByConfigScopeId.computeIfAbsent(configurationScopeId, k -> new HashSet<>());\n    return openFiles.add(fileUri);\n  }\n\n  public void considerClosed(String configurationScopeId, URI fileUri) {\n    var openFiles = openFilesByConfigScopeId.get(configurationScopeId);\n    if (openFiles != null) {\n      openFiles.remove(fileUri);\n    }\n  }\n\n  public Set<URI> getOpenFilesAmong(String configurationScopeId, Set<URI> fileUris) {\n    var openFiles = openFilesByConfigScopeId.getOrDefault(configurationScopeId, Set.of());\n    return openFiles.stream().filter(fileUris::contains).collect(Collectors.toSet());\n  }\n\n  public Map<String, Set<URI>> getOpenFilesByConfigScopeId() {\n    return openFilesByConfigScopeId;\n  }\n\n  public Set<URI> getOpenFilesForConfigScope(String configurationScopeId) {\n    return openFilesByConfigScopeId.getOrDefault(configurationScopeId, Set.of());\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/fs/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.fs;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/hotspot/HotspotService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.hotspot;\n\nimport java.util.List;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseError;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.branch.SonarProjectBranchTrackingService;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.event.SonarServerEventReceivedEvent;\nimport org.sonarsource.sonarlint.core.reporting.FindingReportingService;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.CheckLocalDetectionSupportedResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.CheckStatusChangePermittedResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.HotspotStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.OpenUrlInBrowserParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.RaisedHotspotDto;\nimport org.sonarsource.sonarlint.core.serverapi.EndpointParams;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\nimport org.sonarsource.sonarlint.core.serverapi.UrlUtils;\nimport org.sonarsource.sonarlint.core.serverapi.hotspot.ServerHotspot;\nimport org.sonarsource.sonarlint.core.serverapi.push.SecurityHotspotChangedEvent;\nimport org.sonarsource.sonarlint.core.serverapi.push.SecurityHotspotClosedEvent;\nimport org.sonarsource.sonarlint.core.serverapi.push.SecurityHotspotRaisedEvent;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryService;\nimport org.sonarsource.sonarlint.core.tracking.TaintVulnerabilityTrackingService;\nimport org.springframework.context.event.EventListener;\n\npublic class HotspotService {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final String NO_BINDING_REASON = \"The project is not bound, please bind it to SonarQube (Server, Cloud)\";\n  private static final String REVIEW_STATUS_UPDATE_PERMISSION_MISSING_REASON = \"Changing a hotspot's status requires the 'Administer Security Hotspot' permission.\";\n\n  private final SonarLintRpcClient client;\n  private final ConfigurationRepository configurationRepository;\n  private final ConnectionConfigurationRepository connectionRepository;\n\n  private final SonarQubeClientManager sonarQubeClientManager;\n  private final TelemetryService telemetryService;\n  private final SonarProjectBranchTrackingService branchTrackingService;\n  private final FindingReportingService findingReportingService;\n  private final StorageService storageService;\n\n  public HotspotService(SonarLintRpcClient client, StorageService storageService, ConfigurationRepository configurationRepository,\n    ConnectionConfigurationRepository connectionRepository, SonarQubeClientManager sonarQubeClientManager, TelemetryService telemetryService,\n    SonarProjectBranchTrackingService branchTrackingService, FindingReportingService findingReportingService) {\n    this.client = client;\n    this.storageService = storageService;\n    this.configurationRepository = configurationRepository;\n    this.connectionRepository = connectionRepository;\n    this.sonarQubeClientManager = sonarQubeClientManager;\n    this.telemetryService = telemetryService;\n    this.branchTrackingService = branchTrackingService;\n    this.findingReportingService = findingReportingService;\n  }\n\n  public void openHotspotInBrowser(String configScopeId, String hotspotKey) {\n    var effectiveBinding = configurationRepository.getEffectiveBinding(configScopeId);\n    var endpointParams = effectiveBinding.flatMap(binding -> connectionRepository.getEndpointParams(binding.connectionId()));\n    if (effectiveBinding.isEmpty() || endpointParams.isEmpty()) {\n      LOG.warn(\"Configuration scope {} is not bound properly, unable to open hotspot\", configScopeId);\n      return;\n    }\n    var branchName = branchTrackingService.awaitEffectiveSonarProjectBranch(configScopeId);\n    if (branchName.isEmpty()) {\n      LOG.warn(\"Configuration scope {} has no matching branch, unable to open hotspot\", configScopeId);\n      return;\n    }\n\n    var url = buildHotspotUrl(effectiveBinding.get().sonarProjectKey(), branchName.get(), hotspotKey, endpointParams.get());\n\n    client.openUrlInBrowser(new OpenUrlInBrowserParams(url));\n\n    telemetryService.hotspotOpenedInBrowser();\n  }\n\n  public CheckLocalDetectionSupportedResponse checkLocalDetectionSupported(String configScopeId) {\n    var configScope = configurationRepository.getConfigurationScope(configScopeId);\n    if (configScope == null) {\n      var error = new ResponseError(SonarLintRpcErrorCode.CONFIG_SCOPE_NOT_FOUND, \"The provided configuration scope does not exist: \" + configScopeId, configScopeId);\n      throw new ResponseErrorException(error);\n    }\n    var effectiveBinding = configurationRepository.getEffectiveBinding(configScopeId);\n    if (effectiveBinding.isEmpty()) {\n      return new CheckLocalDetectionSupportedResponse(false, NO_BINDING_REASON);\n    }\n    var connectionId = effectiveBinding.get().connectionId();\n    if (connectionRepository.getConnectionById(connectionId) == null) {\n      var error = new ResponseError(SonarLintRpcErrorCode.CONNECTION_NOT_FOUND, \"The provided configuration scope is bound to an unknown connection: \" + connectionId,\n        connectionId);\n      throw new ResponseErrorException(error);\n    }\n\n    return new CheckLocalDetectionSupportedResponse(true, null);\n  }\n\n  public CheckStatusChangePermittedResponse checkStatusChangePermitted(String connectionId, String hotspotKey, SonarLintCancelMonitor cancelMonitor) {\n    // fixme add getConnectionByIdOrThrow\n    var connection = connectionRepository.getConnectionById(connectionId);\n    var r = sonarQubeClientManager.getValidClientOrThrow(connectionId)\n      .withClientApiAndReturn(serverApi -> serverApi.hotspot().show(hotspotKey, cancelMonitor));\n    var allowedStatuses = HotspotReviewStatus.allowedStatusesOn(connection.getKind());\n    // canChangeStatus is false when the 'Administer Hotspots' permission is missing\n    // normally the 'Browse' permission is also required, but we assume it's present as the client knows the hotspot key\n    return toResponse(r.canChangeStatus, allowedStatuses);\n  }\n\n  private static CheckStatusChangePermittedResponse toResponse(boolean canChangeStatus, List<HotspotReviewStatus> coreStatuses) {\n    return new CheckStatusChangePermittedResponse(canChangeStatus,\n      canChangeStatus ? null : REVIEW_STATUS_UPDATE_PERMISSION_MISSING_REASON,\n      coreStatuses.stream().map(s -> HotspotStatus.valueOf(s.name()))\n        // respect ordering of the client-api enum for the UI\n        .sorted()\n        .toList());\n  }\n\n  public void changeStatus(String configurationScopeId, String hotspotKey, HotspotReviewStatus newStatus, SonarLintCancelMonitor cancelMonitor) {\n    var effectiveBindingOpt = configurationRepository.getEffectiveBinding(configurationScopeId);\n    if (effectiveBindingOpt.isEmpty()) {\n      LOG.debug(\"No binding for config scope {}\", configurationScopeId);\n      return;\n    }\n    sonarQubeClientManager.withActiveClient(effectiveBindingOpt.get().connectionId(), serverApi -> {\n      serverApi.hotspot().changeStatus(hotspotKey, newStatus, cancelMonitor);\n      saveStatusInStorage(effectiveBindingOpt.get(), hotspotKey, newStatus);\n      telemetryService.hotspotStatusChanged();\n    });\n  }\n\n  private void saveStatusInStorage(Binding binding, String hotspotKey, HotspotReviewStatus newStatus) {\n    storageService.binding(binding)\n      .findings()\n      .changeHotspotStatus(hotspotKey, newStatus);\n  }\n\n  static String buildHotspotUrl(String projectKey, String branch, String hotspotKey, EndpointParams endpointParams) {\n    var relativePath = (endpointParams.isSonarCloud() ? \"/project/security_hotspots?id=\" : \"/security_hotspots?id=\")\n      + UrlUtils.urlEncode(projectKey)\n      + \"&branch=\"\n      + UrlUtils.urlEncode(branch)\n      + \"&hotspots=\"\n      + UrlUtils.urlEncode(hotspotKey);\n\n    return ServerApiHelper.concat(endpointParams.getBaseUrl(), relativePath);\n  }\n\n  @EventListener\n  public void onServerEventReceived(SonarServerEventReceivedEvent event) {\n    var connectionId = event.getConnectionId();\n    var serverEvent = event.getEvent();\n    if (serverEvent instanceof SecurityHotspotChangedEvent hotspotChangedEvent) {\n      updateStorage(connectionId, hotspotChangedEvent);\n      republishPreviouslyRaisedHotspots(connectionId, hotspotChangedEvent);\n    } else if (serverEvent instanceof SecurityHotspotClosedEvent hotspotClosedEvent) {\n      updateStorage(connectionId, hotspotClosedEvent);\n      republishPreviouslyRaisedHotspots(connectionId, hotspotClosedEvent);\n    } else if (serverEvent instanceof SecurityHotspotRaisedEvent hotspotRaisedEvent) {\n      // We could try to match with an existing hotspot. But we don't do it because we don't invest in hotspots right now.\n      updateStorage(connectionId, hotspotRaisedEvent);\n    }\n  }\n\n  private void updateStorage(String connectionId, SecurityHotspotRaisedEvent event) {\n    var hotspot = new ServerHotspot(\n      event.getHotspotKey(),\n      event.getRuleKey(),\n      event.getMainLocation().getMessage(),\n      event.getMainLocation().getFilePath(),\n      TaintVulnerabilityTrackingService.adapt(event.getMainLocation().getTextRange()),\n      event.getCreationDate(),\n      event.getStatus(),\n      event.getVulnerabilityProbability(),\n      null);\n    var projectKey = event.getProjectKey();\n    storageService.connection(connectionId).project(projectKey).findings().insert(event.getBranch(), hotspot);\n  }\n\n  private void updateStorage(String connectionId, SecurityHotspotClosedEvent event) {\n    var projectKey = event.getProjectKey();\n    storageService.connection(connectionId).project(projectKey).findings().deleteHotspot(event.getHotspotKey());\n  }\n\n  private void updateStorage(String connectionId, SecurityHotspotChangedEvent event) {\n    var projectKey = event.getProjectKey();\n    storageService.connection(connectionId).project(projectKey).findings().updateHotspot(event.getHotspotKey(), hotspot -> {\n      var status = event.getStatus();\n      if (status != null) {\n        hotspot.setStatus(status);\n      }\n      var assignee = event.getAssignee();\n      if (assignee != null) {\n        hotspot.setAssignee(assignee);\n      }\n    });\n  }\n\n  private void republishPreviouslyRaisedHotspots(String connectionId, SecurityHotspotChangedEvent event) {\n    var boundScopes = configurationRepository.getBoundScopesToConnectionAndSonarProject(connectionId, event.getProjectKey());\n    boundScopes.forEach(scope -> {\n      var scopeId = scope.getConfigScopeId();\n      findingReportingService.updateAndReportHotspots(scopeId,\n        raisedHotspotDto -> changedHotspotUpdater(raisedHotspotDto, event));\n    });\n  }\n\n  private static RaisedHotspotDto changedHotspotUpdater(RaisedHotspotDto raisedHotspotDto, SecurityHotspotChangedEvent event) {\n    if (event.getHotspotKey().equals(raisedHotspotDto.getServerKey())) {\n      return raisedHotspotDto.withHotspotStatusAndResolution(HotspotStatus.valueOf(event.getStatus().name()), event.getStatus().isResolved());\n    }\n    return raisedHotspotDto;\n  }\n\n  private void republishPreviouslyRaisedHotspots(String connectionId, SecurityHotspotClosedEvent event) {\n    var boundScopes = configurationRepository.getBoundScopesToConnectionAndSonarProject(connectionId, event.getProjectKey());\n    boundScopes.forEach(scope -> {\n      var scopeId = scope.getConfigScopeId();\n      findingReportingService.updateAndReportHotspots(scopeId,\n        raisedHotspotDto -> closedHotspotUpdater(raisedHotspotDto, event));\n    });\n  }\n\n  private static RaisedHotspotDto closedHotspotUpdater(RaisedHotspotDto raisedHotspotDto, SecurityHotspotClosedEvent event) {\n    if (event.getHotspotKey().equals(raisedHotspotDto.getServerKey())) {\n      return null;\n    }\n    return raisedHotspotDto;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/hotspot/HotspotStatusChangeException.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.hotspot;\n\npublic class HotspotStatusChangeException extends RuntimeException {\n  public HotspotStatusChangeException(Throwable cause) {\n    super(\"Cannot change status on the hotspot\", cause);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/hotspot/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.hotspot;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/http/AskClientCertificatePredicate.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.http;\n\nimport java.util.Arrays;\nimport java.util.concurrent.ExecutionException;\nimport java.util.function.Predicate;\nimport nl.altindag.ssl.model.TrustManagerParameters;\nimport nl.altindag.ssl.util.CertificateUtils;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.CheckServerTrustedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.X509CertificateDto;\n\npublic class AskClientCertificatePredicate implements Predicate<TrustManagerParameters> {\n\n  private final SonarLintRpcClient client;\n\n  public AskClientCertificatePredicate(SonarLintRpcClient client) {\n    this.client = client;\n  }\n\n  @Override\n  public boolean test(TrustManagerParameters trustManagerParameters) {\n    try {\n      return client\n        .checkServerTrusted(new CheckServerTrustedParams(\n          Arrays.stream(trustManagerParameters.getChain())\n            .map(c -> new X509CertificateDto(CertificateUtils.convertToPem(c))).toList(),\n          trustManagerParameters.getAuthType()))\n        .get()\n        .isTrusted();\n    } catch (InterruptedException ex) {\n      Thread.currentThread().interrupt();\n      return false;\n    } catch (ExecutionException ex) {\n      throw new RuntimeException(ex);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/http/ClientProxyCredentialsProvider.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.http;\n\nimport java.net.MalformedURLException;\nimport java.net.URISyntaxException;\nimport java.util.concurrent.ExecutionException;\nimport javax.annotation.Nullable;\nimport org.apache.hc.client5.http.auth.AuthScope;\nimport org.apache.hc.client5.http.auth.Credentials;\nimport org.apache.hc.client5.http.auth.CredentialsProvider;\nimport org.apache.hc.client5.http.auth.UsernamePasswordCredentials;\nimport org.apache.hc.client5.http.protocol.HttpClientContext;\nimport org.apache.hc.core5.http.URIScheme;\nimport org.apache.hc.core5.http.protocol.HttpContext;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.GetProxyPasswordAuthenticationParams;\n\n/**\n * Inspired by {@link org.apache.hc.client5.http.impl.auth.SystemDefaultCredentialsProvider} but asking client instead of\n * asking JDK\n */\npublic class ClientProxyCredentialsProvider implements CredentialsProvider {\n\n  private final SonarLintLogger logger = SonarLintLogger.get();\n  private final SonarLintRpcClient client;\n\n  public ClientProxyCredentialsProvider(SonarLintRpcClient client) {\n    this.client = client;\n  }\n\n  @Override\n  public Credentials getCredentials(AuthScope authScope, @Nullable HttpContext context) {\n    var host = authScope.getHost();\n    if (host == null || context == null) {\n      return null;\n    }\n    try {\n      var targetHostURI = HttpClientContext.adapt(context).getRequest().getUri();\n      var protocol = getProtocol(authScope);\n      var response = client.getProxyPasswordAuthentication(\n        new GetProxyPasswordAuthenticationParams(host, authScope.getPort(), protocol,\n          authScope.getRealm(), authScope.getSchemeName(), targetHostURI.toURL()))\n        .get();\n      var proxyUser = response.getProxyUser();\n      if (proxyUser != null) {\n        var proxyPassword = response.getProxyPassword();\n        return new UsernamePasswordCredentials(proxyUser, proxyPassword != null ? proxyPassword.toCharArray() : null);\n      }\n    } catch (InterruptedException e) {\n      Thread.currentThread().interrupt();\n      logger.warn(\"Interrupted!\", e);\n    } catch (URISyntaxException | MalformedURLException | ExecutionException e) {\n      logger.warn(\"Unable to get proxy credentials from the client\", e);\n    }\n    return null;\n  }\n\n  private static String getProtocol(AuthScope authScope) {\n    String protocol;\n    if (authScope.getProtocol() != null) {\n      protocol = authScope.getProtocol();\n    } else {\n      protocol = (authScope.getPort() == 443 ? URIScheme.HTTPS.id : URIScheme.HTTP.id);\n    }\n    return protocol;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/http/ClientProxySelector.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.http;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.net.Proxy;\nimport java.net.ProxySelector;\nimport java.net.SocketAddress;\nimport java.net.URI;\nimport java.util.List;\nimport java.util.concurrent.ExecutionException;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.SelectProxiesParams;\n\npublic class ClientProxySelector extends ProxySelector {\n\n  private final SonarLintLogger logger = SonarLintLogger.get();\n  private final SonarLintRpcClient client;\n\n  public ClientProxySelector(SonarLintRpcClient client) {\n    this.client = client;\n  }\n\n  @Override\n  public List<Proxy> select(URI uri) {\n    try {\n      return client.selectProxies(new SelectProxiesParams(uri)).get().getProxies().stream()\n        .map(p -> {\n          if (p.getType() == Proxy.Type.DIRECT) {\n            return Proxy.NO_PROXY;\n          } else {\n            if (p.getPort() < 0 || p.getPort() > 65535) {\n              throw new IllegalStateException(\"Port is outside the valid range for hostname: \" + p.getHostname());\n            }\n            return new Proxy(p.getType(), new InetSocketAddress(p.getHostname(), p.getPort()));\n          }\n        })\n        .toList();\n    } catch (InterruptedException e) {\n      Thread.currentThread().interrupt();\n      logger.warn(\"Interrupted!\", e);\n    } catch (IllegalStateException | ExecutionException e) {\n      logger.warn(\"Unable to get proxy\", e);\n    }\n    return List.of();\n  }\n\n  @Override\n  public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {\n    // Intentionally left blank: connection failures are handled by the HTTP client, no additional action required here\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/http/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.http;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/issue/IssueNotFoundException.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.issue;\n\nimport java.util.UUID;\n\npublic class IssueNotFoundException extends Exception {\n  private final UUID issueKey;\n\n  public IssueNotFoundException(String message, UUID issueKey) {\n    super(message);\n    this.issueKey = issueKey;\n  }\n\n  public UUID getIssueKey() {\n    return issueKey;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/issue/IssueService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.issue;\n\nimport jakarta.annotation.PostConstruct;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.EnumMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.function.UnaryOperator;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseError;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.active.rules.ActiveRulesService;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.LocalOnlyIssue;\nimport org.sonarsource.sonarlint.core.commons.NewCodeDefinition;\nimport org.sonarsource.sonarlint.core.commons.Transition;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.event.LocalOnlyIssueStatusChangedEvent;\nimport org.sonarsource.sonarlint.core.event.ServerIssueStatusChangedEvent;\nimport org.sonarsource.sonarlint.core.event.SonarServerEventReceivedEvent;\nimport org.sonarsource.sonarlint.core.local.only.XodusLocalOnlyIssueStorageService;\nimport org.sonarsource.sonarlint.core.mode.SeverityModeService;\nimport org.sonarsource.sonarlint.core.newcode.NewCodeService;\nimport org.sonarsource.sonarlint.core.remediation.aicodefix.AiCodeFixService;\nimport org.sonarsource.sonarlint.core.reporting.FindingReportingService;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.CheckStatusChangePermittedResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.EffectiveIssueDetailsDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ReopenAllIssuesForFileParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ResolutionStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.ImpactDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedFindingDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.RuleType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.rules.RuleDetails;\nimport org.sonarsource.sonarlint.core.rules.RuleDetailsAdapter;\nimport org.sonarsource.sonarlint.core.rules.RuleNotFoundException;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.exception.NotFoundException;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues;\nimport org.sonarsource.sonarlint.core.serverapi.push.IssueChangedEvent;\nimport org.sonarsource.sonarlint.core.serverconnection.ServerInfoSynchronizer;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.LocalOnlyIssuesRepository;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ProjectServerIssueStore;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.sonarsource.sonarlint.core.tracking.LocalOnlyIssueRepository;\nimport org.sonarsource.sonarlint.core.tracking.TaintVulnerabilityTrackingService;\nimport org.springframework.context.ApplicationEventPublisher;\nimport org.springframework.context.event.EventListener;\n\npublic class IssueService {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private static final String STATUS_CHANGE_PERMISSION_MISSING_REASON = \"Marking an issue as resolved requires the 'Administer Issues' permission\";\n  private static final String UNSUPPORTED_SQ_VERSION_REASON = \"Marking a local-only issue as resolved requires SonarQube Server 10.2+\";\n  private static final Version SQ_ANTICIPATED_TRANSITIONS_MIN_VERSION = Version.create(\"10.2\");\n\n  /**\n   * With SQ 10.4 the transitions changed from \"Won't fix\" to \"Accept\"\n   */\n  private static final Version SQ_ACCEPTED_TRANSITION_MIN_VERSION = Version.create(\"10.4\");\n  private static final List<ResolutionStatus> NEW_RESOLUTION_STATUSES = List.of(ResolutionStatus.ACCEPT, ResolutionStatus.FALSE_POSITIVE);\n  private static final List<ResolutionStatus> OLD_RESOLUTION_STATUSES = List.of(ResolutionStatus.WONT_FIX, ResolutionStatus.FALSE_POSITIVE);\n  private static final Map<ResolutionStatus, Transition> transitionByResolutionStatus = Map.of(\n    ResolutionStatus.ACCEPT, Transition.ACCEPT,\n    ResolutionStatus.WONT_FIX, Transition.WONT_FIX,\n    ResolutionStatus.FALSE_POSITIVE, Transition.FALSE_POSITIVE);\n\n  private final ConfigurationRepository configurationRepository;\n  private final SonarQubeClientManager sonarQubeClientManager;\n  private final StorageService storageService;\n  private final XodusLocalOnlyIssueStorageService localOnlyIssueStorageService;\n  private final LocalOnlyIssueRepository localOnlyIssueRepository;\n  private final ApplicationEventPublisher eventPublisher;\n  private final FindingReportingService findingReportingService;\n  private final SeverityModeService severityModeService;\n  private final NewCodeService newCodeService;\n  private final ActiveRulesService activeRulesService;\n  private final TaintVulnerabilityTrackingService taintVulnerabilityTrackingService;\n  private final AiCodeFixService aiCodeFixService;\n  private final LocalOnlyIssuesRepository localOnlyIssuesRepository;\n\n  public IssueService(ConfigurationRepository configurationRepository, SonarQubeClientManager sonarQubeClientManager, StorageService storageService,\n    XodusLocalOnlyIssueStorageService localOnlyIssueStorageService, LocalOnlyIssueRepository localOnlyIssueRepository, ApplicationEventPublisher eventPublisher,\n    FindingReportingService findingReportingService, SeverityModeService severityModeService, NewCodeService newCodeService, ActiveRulesService activeRulesService,\n    TaintVulnerabilityTrackingService taintVulnerabilityTrackingService, AiCodeFixService aiCodeFixService, LocalOnlyIssuesRepository localOnlyIssuesRepository) {\n    this.configurationRepository = configurationRepository;\n    this.sonarQubeClientManager = sonarQubeClientManager;\n    this.storageService = storageService;\n    this.localOnlyIssueStorageService = localOnlyIssueStorageService;\n    this.localOnlyIssueRepository = localOnlyIssueRepository;\n    this.eventPublisher = eventPublisher;\n    this.findingReportingService = findingReportingService;\n    this.severityModeService = severityModeService;\n    this.newCodeService = newCodeService;\n    this.activeRulesService = activeRulesService;\n    this.taintVulnerabilityTrackingService = taintVulnerabilityTrackingService;\n    this.aiCodeFixService = aiCodeFixService;\n    this.localOnlyIssuesRepository = localOnlyIssuesRepository;\n  }\n\n  @PostConstruct\n  public void migrateData() {\n    if (localOnlyIssueStorageService.exists()) {\n      try {\n        LOG.info(\"Migrating the Xodus local-only issues to H2\");\n        var migrationStart = System.currentTimeMillis();\n        var xodusLocalOnlyIssueStore = localOnlyIssueStorageService.get();\n        var issuesPerConfigScope = xodusLocalOnlyIssueStore.loadAll();\n        localOnlyIssuesRepository.storeIssues(issuesPerConfigScope);\n        LOG.info(\"Migrated Xodus local-only issues to H2, took {}ms\", System.currentTimeMillis() - migrationStart);\n      } catch (Exception e) {\n        LOG.error(\"Unable to migrate local-only findings, will use fresh DB\", e);\n      }\n    }\n    // always call to remove lingering temporary files\n    localOnlyIssueStorageService.delete();\n  }\n\n  public void changeStatus(String configurationScopeId, String issueKey, ResolutionStatus newStatus, boolean isTaintIssue, SonarLintCancelMonitor cancelMonitor) {\n    var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId);\n    var serverConnection = sonarQubeClientManager.getValidClientOrThrow(binding.connectionId());\n    var reviewStatus = transitionByResolutionStatus.get(newStatus);\n    var projectServerIssueStore = storageService.binding(binding).findings();\n    boolean isServerIssue = projectServerIssueStore.containsIssue(issueKey);\n    if (isServerIssue) {\n      serverConnection.withClientApi(serverApi -> serverApi.issue().changeStatus(issueKey, reviewStatus, cancelMonitor));\n      projectServerIssueStore.updateIssueResolutionStatus(issueKey, isTaintIssue, true)\n        .ifPresent(issue -> eventPublisher.publishEvent(new ServerIssueStatusChangedEvent(binding.connectionId(), binding.sonarProjectKey(), issue)));\n    } else {\n      var localIssueOpt = asUUID(issueKey).flatMap(localOnlyIssueRepository::findByKey);\n      if (localIssueOpt.isEmpty()) {\n        // this happens in case if VS client trying to change status of the issue for Roslyn analysed language\n        // since analysis was executed outside the backend on the client side we trust client to provide valid issue key\n        try {\n          serverConnection.withClientApi(serverApi -> serverApi.issue().changeStatus(issueKey, reviewStatus, cancelMonitor));\n          return;\n        } catch (NotFoundException ex) {\n          throw issueNotFoundException(issueKey);\n        }\n      }\n      var coreStatus = org.sonarsource.sonarlint.core.commons.IssueStatus.valueOf(newStatus.name());\n      var issue = localIssueOpt.get();\n      issue.resolve(coreStatus);\n      var allIssues = localOnlyIssuesRepository.loadAll(configurationScopeId);\n      serverConnection.withClientApi(serverApi -> serverApi.issue()\n        .anticipatedTransitions(binding.sonarProjectKey(), concat(allIssues, issue), cancelMonitor));\n      localOnlyIssuesRepository.storeLocalOnlyIssue(configurationScopeId, issue);\n      eventPublisher.publishEvent(new LocalOnlyIssueStatusChangedEvent(issue));\n    }\n  }\n\n  private static List<LocalOnlyIssue> concat(List<LocalOnlyIssue> issues, LocalOnlyIssue issue) {\n    return Stream.concat(issues.stream(), Stream.of(issue)).toList();\n  }\n\n  private static List<LocalOnlyIssue> subtract(List<LocalOnlyIssue> allIssues, List<LocalOnlyIssue> issueToSubtract) {\n    return allIssues.stream()\n      .filter(it -> issueToSubtract.stream().noneMatch(issue -> issue.getId().equals(it.getId())))\n      .toList();\n  }\n\n  public boolean checkAnticipatedStatusChangeSupported(String configScopeId) {\n    var binding = configurationRepository.getEffectiveBindingOrThrow(configScopeId);\n    var connectionId = binding.connectionId();\n    return sonarQubeClientManager.getValidClientOrThrow(binding.connectionId())\n      .withClientApiAndReturn(serverApi -> checkAnticipatedStatusChangeSupported(serverApi, connectionId));\n  }\n\n  /**\n   * Check if the anticipated transitions are supported on the server side (requires SonarQube 10.2+)\n   *\n   * @param api          used for checking if server is a SonarQube instance\n   * @param connectionId required to get the version information from the server\n   * @return whether server is SonarQube instance and matches version requirement\n   */\n  private boolean checkAnticipatedStatusChangeSupported(ServerApi api, String connectionId) {\n    return !api.isSonarCloud() && storageService.connection(connectionId).serverInfo().read()\n      .map(version -> version.version().satisfiesMinRequirement(SQ_ANTICIPATED_TRANSITIONS_MIN_VERSION))\n      .orElse(false);\n  }\n\n  public CheckStatusChangePermittedResponse checkStatusChangePermitted(String connectionId, String issueKey, SonarLintCancelMonitor cancelMonitor) {\n    return sonarQubeClientManager.getValidClientOrThrow(connectionId).withClientApiAndReturn(serverApi -> asUUID(issueKey)\n      .flatMap(localOnlyIssueRepository::findByKey)\n      .map(r -> {\n        // For anticipated issues we currently don't get the information from SonarQube (as there is no web API\n        // endpoint) regarding the available transitions. SonarCloud doesn't provide it currently anyway. That's why we\n        // have to rely on the version check for SonarQube (>= 10.2 / >=10.4)\n        List<ResolutionStatus> statuses = List.of();\n        if (checkAnticipatedStatusChangeSupported(serverApi, connectionId)) {\n          var is104orNewer = !serverApi.isSonarCloud() && is104orNewer(connectionId, serverApi, cancelMonitor);\n          statuses = is104orNewer ? NEW_RESOLUTION_STATUSES : OLD_RESOLUTION_STATUSES;\n        }\n\n        return toResponse(statuses, UNSUPPORTED_SQ_VERSION_REASON);\n      })\n      .orElseGet(() -> {\n        var issue = serverApi.issue().searchByKey(issueKey, cancelMonitor);\n        return toResponse(getAdministerIssueTransitions(issue), STATUS_CHANGE_PERMISSION_MISSING_REASON);\n      }));\n  }\n\n  /**\n   * For checking whether SonarQube is already on 10.4 or not. NEVER apply to SonarCloud as their version differs!\n   */\n  private boolean is104orNewer(String connectionId, ServerApi serverApi, SonarLintCancelMonitor cancelMonitor) {\n    var serverVersionSynchronizer = new ServerInfoSynchronizer(storageService.connection(connectionId));\n    var serverVersion = serverVersionSynchronizer.readOrSynchronizeServerInfo(serverApi, cancelMonitor);\n    return serverVersion.version().compareToIgnoreQualifier(SQ_ACCEPTED_TRANSITION_MIN_VERSION) >= 0;\n  }\n\n  private static CheckStatusChangePermittedResponse toResponse(List<ResolutionStatus> statuses, String reason) {\n    var permitted = !statuses.isEmpty();\n\n    // No status available means it is not permitted or not supported (e.g. SonarCloud for anticipated issues)\n    return new CheckStatusChangePermittedResponse(permitted, permitted ? null : reason, statuses);\n  }\n\n  private static List<ResolutionStatus> getAdministerIssueTransitions(Issues.Issue issue) {\n    // the 2 required transitions are not available when the 'Administer Issues' permission is missing\n    // normally the 'Browse' permission is also required, but we assume it's present as the client knows the issue key\n    var possibleTransitions = new HashSet<>(issue.getTransitions().getTransitionsList());\n\n    if (possibleTransitions.containsAll(toTransitionStatus(NEW_RESOLUTION_STATUSES))) {\n      return NEW_RESOLUTION_STATUSES;\n    }\n\n    // No transitions meaning you're not allowed. That's it.\n    return possibleTransitions.containsAll(toTransitionStatus(OLD_RESOLUTION_STATUSES))\n      ? OLD_RESOLUTION_STATUSES\n      : List.of();\n  }\n\n  private static Set<String> toTransitionStatus(List<ResolutionStatus> resolutions) {\n    return resolutions.stream()\n      .map(resolution -> transitionByResolutionStatus.get(resolution).getStatus())\n      .collect(Collectors.toSet());\n  }\n\n  public void addComment(String configurationScopeId, String issueKey, String text, SonarLintCancelMonitor cancelMonitor) {\n    var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId);\n    var projectServerIssueStore = storageService.binding(binding).findings();\n    boolean isServerIssue = projectServerIssueStore.containsIssue(issueKey);\n    if (isServerIssue) {\n      addCommentOnServerIssue(configurationScopeId, issueKey, text, cancelMonitor);\n    } else {\n      var optionalId = asUUID(issueKey);\n      if (optionalId.isPresent()) {\n        setCommentOnLocalOnlyIssue(configurationScopeId, optionalId.get(), text, cancelMonitor);\n      } else {\n        // this happens in case if VS client trying to add comment of the issue for Roslyn analysed language\n        // since analysis was executed outside the backend on the client side we trust client to provide valid issue key\n        try {\n          addCommentOnServerIssue(configurationScopeId, issueKey, text, cancelMonitor);\n        } catch (NotFoundException ex) {\n          throw issueNotFoundException(issueKey);\n        }\n      }\n    }\n  }\n\n  public boolean reopenIssue(String configurationScopeId, String issueId, boolean isTaintIssue, SonarLintCancelMonitor cancelMonitor) {\n    var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId);\n    var projectServerIssueStore = storageService.binding(binding).findings();\n    boolean isServerIssue = projectServerIssueStore.containsIssue(issueId);\n    if (isServerIssue) {\n      return sonarQubeClientManager.getValidClientOrThrow(binding.connectionId())\n        .withClientApiAndReturn(serverApi -> reopenServerIssue(serverApi, binding, issueId, projectServerIssueStore, isTaintIssue, cancelMonitor));\n    } else {\n      return reopenLocalIssue(issueId, configurationScopeId, cancelMonitor);\n    }\n  }\n\n  public boolean reopenAllIssuesForFile(ReopenAllIssuesForFileParams params, SonarLintCancelMonitor cancelMonitor) {\n    var configurationScopeId = params.getConfigurationScopeId();\n    var ideRelativePath = params.getIdeRelativePath();\n    var allIssues = localOnlyIssuesRepository.loadAll(configurationScopeId);\n    var issuesForFile = localOnlyIssuesRepository.loadForFile(configurationScopeId, ideRelativePath);\n    var issuesToSync = subtract(allIssues, issuesForFile);\n    var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId);\n    sonarQubeClientManager.getValidClientOrThrow(binding.connectionId())\n      .withClientApi(serverApi -> serverApi.issue().anticipatedTransitions(binding.sonarProjectKey(), issuesToSync, cancelMonitor));\n    return localOnlyIssuesRepository.removeAllIssuesForFile(configurationScopeId, ideRelativePath);\n  }\n\n  private void removeIssueOnServer(String configurationScopeId, UUID issueId, SonarLintCancelMonitor cancelMonitor) {\n    var allIssues = localOnlyIssuesRepository.loadAll(configurationScopeId);\n    var issuesToSync = allIssues.stream().filter(it -> !it.getId().equals(issueId)).toList();\n    var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId);\n    sonarQubeClientManager.getValidClientOrThrow(binding.connectionId())\n      .withClientApi(serverApi -> serverApi.issue().anticipatedTransitions(binding.sonarProjectKey(), issuesToSync, cancelMonitor));\n  }\n\n  private void setCommentOnLocalOnlyIssue(String configurationScopeId, UUID issueId, String comment, SonarLintCancelMonitor cancelMonitor) {\n    var optionalLocalOnlyIssue = localOnlyIssuesRepository.find(issueId);\n    if (optionalLocalOnlyIssue.isPresent()) {\n      var commentedIssue = optionalLocalOnlyIssue.get();\n      var resolution = commentedIssue.getResolution();\n      if (resolution != null) {\n        resolution.setComment(comment);\n        var issuesToSync = new ArrayList<>(localOnlyIssuesRepository.loadAll(configurationScopeId));\n        issuesToSync.replaceAll(issue -> issue.getId().equals(issueId) ? commentedIssue : issue);\n        var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId);\n        sonarQubeClientManager.getValidClientOrThrow(binding.connectionId())\n          .withClientApi(serverApi -> serverApi.issue().anticipatedTransitions(binding.sonarProjectKey(), issuesToSync, cancelMonitor));\n        localOnlyIssuesRepository.storeLocalOnlyIssue(configurationScopeId, commentedIssue);\n      }\n    } else {\n      throw issueNotFoundException(issueId.toString());\n    }\n  }\n\n  private static ResponseErrorException issueNotFoundException(String issueId) {\n    var error = new ResponseError(SonarLintRpcErrorCode.ISSUE_NOT_FOUND, \"Issue key \" + issueId + \" was not found\", issueId);\n    throw new ResponseErrorException(error);\n  }\n\n  private void addCommentOnServerIssue(String configurationScopeId, String issueKey, String comment, SonarLintCancelMonitor cancelMonitor) {\n    var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId);\n    sonarQubeClientManager.getValidClientOrThrow(binding.connectionId())\n      .withClientApi(serverApi -> serverApi.issue().addComment(issueKey, comment, cancelMonitor));\n  }\n\n  private boolean reopenServerIssue(ServerApi connection, Binding binding, String issueId, ProjectServerIssueStore projectServerIssueStore, boolean isTaintIssue,\n    SonarLintCancelMonitor cancelMonitor) {\n    connection.issue().changeStatus(issueId, Transition.REOPEN, cancelMonitor);\n    var serverIssue = projectServerIssueStore.updateIssueResolutionStatus(issueId, isTaintIssue, false);\n    serverIssue.ifPresent(issue -> eventPublisher.publishEvent(new ServerIssueStatusChangedEvent(binding.connectionId(), binding.sonarProjectKey(), issue)));\n    return true;\n  }\n\n  private boolean reopenLocalIssue(String issueId, String configurationScopeId, SonarLintCancelMonitor cancelMonitor) {\n    var issueUuidOptional = asUUID(issueId);\n    if (issueUuidOptional.isEmpty()) {\n      return false;\n    }\n    var issueUuid = issueUuidOptional.get();\n    removeIssueOnServer(configurationScopeId, issueUuid, cancelMonitor);\n    return localOnlyIssuesRepository.removeIssue(issueUuid);\n  }\n\n  public EffectiveIssueDetailsDto getEffectiveIssueDetails(String configurationScopeId, UUID findingId, SonarLintCancelMonitor cancelMonitor)\n    throws IssueNotFoundException, RuleNotFoundException {\n    var effectiveBinding = configurationRepository.getEffectiveBinding(configurationScopeId);\n    String connectionId = null;\n    if (effectiveBinding.isPresent()) {\n      connectionId = effectiveBinding.get().connectionId();\n    }\n    var isMQRMode = severityModeService.isMQRModeForConnection(connectionId);\n    var newCodeDefinition = newCodeService.getFullNewCodeDefinition(configurationScopeId).orElseGet(NewCodeDefinition::withAlwaysNew);\n    var aiCodeFixFeature = effectiveBinding.flatMap(aiCodeFixService::getFeature);\n    var maybeIssue = findingReportingService.findReportedIssue(findingId, newCodeDefinition, isMQRMode, aiCodeFixFeature);\n    var maybeHotspot = findingReportingService.findReportedHotspot(findingId, newCodeDefinition, isMQRMode);\n    var maybeTaint = taintVulnerabilityTrackingService.getTaintVulnerability(configurationScopeId, findingId, cancelMonitor);\n\n    if (maybeIssue != null) {\n      return getFindingDetails(maybeIssue, configurationScopeId, cancelMonitor);\n    } else if (maybeHotspot != null) {\n      return getFindingDetails(maybeHotspot, configurationScopeId, cancelMonitor);\n    } else if (maybeTaint.isPresent()) {\n      return getTaintDetails(maybeTaint.get(), configurationScopeId, cancelMonitor);\n    }\n    throw new IssueNotFoundException(\"Failed to retrieve finding details. Finding with key '\"\n      + findingId + \"' not found.\", findingId);\n  }\n\n  private EffectiveIssueDetailsDto getFindingDetails(RaisedFindingDto finding, String configurationScopeId, SonarLintCancelMonitor cancelMonitor) throws RuleNotFoundException {\n    var ruleKey = finding.getRuleKey();\n    var ruleDetails = activeRulesService.getActiveRuleDetails(configurationScopeId, ruleKey, cancelMonitor);\n    var ruleDetailsEnrichedWithActualIssueSeverity = RuleDetails.merging(ruleDetails, finding);\n    var effectiveRuleDetails = RuleDetailsAdapter.transform(ruleDetailsEnrichedWithActualIssueSeverity, finding.getRuleDescriptionContextKey());\n    return new EffectiveIssueDetailsDto(ruleKey, effectiveRuleDetails.getName(), effectiveRuleDetails.getLanguage(),\n      // users cannot customize vulnerability probability\n      effectiveRuleDetails.getVulnerabilityProbability(),\n      effectiveRuleDetails.getDescription(), effectiveRuleDetails.getParams(), finding.getSeverityMode(), finding.getRuleDescriptionContextKey());\n  }\n\n  private EffectiveIssueDetailsDto getTaintDetails(TaintVulnerabilityDto finding, String configurationScopeId, SonarLintCancelMonitor cancelMonitor) throws RuleNotFoundException {\n    var ruleKey = finding.getRuleKey();\n    var ruleDetails = activeRulesService.getActiveRuleDetails(configurationScopeId, ruleKey, cancelMonitor);\n    var ruleDetailsEnrichedWithActualIssueSeverity = RuleDetails.merging(ruleDetails, finding);\n    var effectiveRuleDetails = RuleDetailsAdapter.transform(ruleDetailsEnrichedWithActualIssueSeverity, finding.getRuleDescriptionContextKey());\n    return new EffectiveIssueDetailsDto(ruleKey, effectiveRuleDetails.getName(), effectiveRuleDetails.getLanguage(),\n      effectiveRuleDetails.getVulnerabilityProbability(),\n      effectiveRuleDetails.getDescription(), effectiveRuleDetails.getParams(), finding.getSeverityMode(), finding.getRuleDescriptionContextKey());\n  }\n\n  @EventListener\n  public void onServerEventReceived(SonarServerEventReceivedEvent eventReceived) {\n    var connectionId = eventReceived.getConnectionId();\n    var serverEvent = eventReceived.getEvent();\n    if (serverEvent instanceof IssueChangedEvent issueChangedEvent) {\n      handleEvent(connectionId, issueChangedEvent);\n    }\n  }\n\n  private void handleEvent(String connectionId, IssueChangedEvent event) {\n    updateProjectIssueStorage(connectionId, event);\n    republishPreviouslyRaisedIssues(connectionId, event);\n  }\n\n  private void republishPreviouslyRaisedIssues(String connectionId, IssueChangedEvent event) {\n    var isMQRMode = severityModeService.isMQRModeForConnection(connectionId);\n    var boundScopes = configurationRepository.getBoundScopesToConnectionAndSonarProject(connectionId, event.getProjectKey());\n    boundScopes.forEach(scope -> {\n      var scopeId = scope.getConfigScopeId();\n      findingReportingService.updateAndReportIssues(scopeId, previouslyRaisedIssue -> raisedIssueUpdater(previouslyRaisedIssue, isMQRMode, event));\n    });\n  }\n\n  public static RaisedIssueDto raisedIssueUpdater(RaisedIssueDto previouslyRaisedIssue, boolean isMQRMode, IssueChangedEvent event) {\n    var updatedIssue = previouslyRaisedIssue;\n    var resolved = event.getResolved();\n    var userSeverity = event.getUserSeverity();\n    var userType = event.getUserType();\n    var impactedIssueKeys = event.getImpactedIssues().stream().map(IssueChangedEvent.Issue::getIssueKey).collect(Collectors.toSet());\n    if (resolved != null) {\n      UnaryOperator<RaisedIssueDto> issueUpdater = it -> it.builder().withResolution(resolved).buildIssue();\n      updatedIssue = updateIssue(updatedIssue, impactedIssueKeys, issueUpdater);\n    }\n    if (updatedIssue.getSeverityMode().isLeft()) {\n      // if the event does not match the local severity mode, we skip updating as we would only have partial information\n      // the data will be updated at the next sync\n      var standardModeDetails = updatedIssue.getSeverityMode().getLeft();\n      if (userSeverity != null) {\n        UnaryOperator<RaisedIssueDto> issueUpdater = it -> it.builder().withStandardModeDetails(IssueSeverity.valueOf(userSeverity.name()), standardModeDetails.getType())\n          .buildIssue();\n        updatedIssue = updateIssue(updatedIssue, impactedIssueKeys, issueUpdater);\n      }\n      if (userType != null) {\n        UnaryOperator<RaisedIssueDto> issueUpdater = it -> it.builder().withStandardModeDetails(standardModeDetails.getSeverity(), RuleType.valueOf(userType.name()))\n          .buildIssue();\n        updatedIssue = updateIssue(updatedIssue, impactedIssueKeys, issueUpdater);\n      }\n    }\n    for (var issue : event.getImpactedIssues()) {\n      if (!issue.getImpacts().isEmpty() && isMQRMode && updatedIssue.getSeverityMode().isRight()) {\n        var mqrModeDetails = updatedIssue.getSeverityMode().getRight();\n        var impacts = issue.getImpacts().entrySet().stream()\n          .map(impact -> new ImpactDto(\n            SoftwareQuality.valueOf(impact.getKey().name()),\n            org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity.valueOf(impact.getValue().name())))\n          .toList();\n        UnaryOperator<RaisedIssueDto> issueUpdater = it -> it.builder()\n          .withMQRModeDetails(mqrModeDetails.getCleanCodeAttribute(), mergeImpacts(it.getSeverityMode().getRight().getImpacts(), impacts)).buildIssue();\n        updatedIssue = updateIssue(updatedIssue, impactedIssueKeys, issueUpdater);\n      }\n\n    }\n    return updatedIssue;\n  }\n\n  private static List<ImpactDto> mergeImpacts(List<ImpactDto> currentImpacts, List<ImpactDto> overriddenImpacts) {\n    var mergedImpacts = new ArrayList<>(currentImpacts);\n    for (var impact : overriddenImpacts) {\n      mergedImpacts.removeIf(i -> i.getSoftwareQuality().equals(impact.getSoftwareQuality()));\n      mergedImpacts.add(new ImpactDto(impact.getSoftwareQuality(), impact.getImpactSeverity()));\n    }\n\n    return mergedImpacts;\n  }\n\n  private static RaisedIssueDto updateIssue(RaisedIssueDto issue, Set<String> impactedIssueKeys, UnaryOperator<RaisedIssueDto> issueUpdater) {\n    var serverKey = issue.getServerKey();\n    if (serverKey != null && impactedIssueKeys.contains(serverKey)) {\n      return issueUpdater.apply(issue);\n    }\n    return issue;\n  }\n\n  private void updateProjectIssueStorage(String connectionId, IssueChangedEvent event) {\n    var findingsStorage = storageService.connection(connectionId).project(event.getProjectKey()).findings();\n    event.getImpactedIssues().forEach(issue -> findingsStorage.updateIssue(issue.getIssueKey(), storedIssue -> {\n      var userSeverity = event.getUserSeverity();\n      if (userSeverity != null) {\n        storedIssue.setUserSeverity(userSeverity);\n      }\n      var userType = event.getUserType();\n      if (userType != null) {\n        storedIssue.setType(userType);\n      }\n      var resolved = event.getResolved();\n      if (resolved != null) {\n        storedIssue.setResolved(resolved);\n      }\n      var impacts = issue.getImpacts();\n      if (!impacts.isEmpty()) {\n        storedIssue.setImpacts(mergeImpacts(storedIssue.getImpacts(), impacts));\n      }\n    }));\n  }\n\n  private static Map<org.sonarsource.sonarlint.core.commons.SoftwareQuality, ImpactSeverity> mergeImpacts(\n    Map<org.sonarsource.sonarlint.core.commons.SoftwareQuality, ImpactSeverity> defaultImpacts,\n    Map<org.sonarsource.sonarlint.core.commons.SoftwareQuality, ImpactSeverity> overriddenImpacts) {\n    var mergedImpacts = new EnumMap<org.sonarsource.sonarlint.core.commons.SoftwareQuality, ImpactSeverity>(org.sonarsource.sonarlint.core.commons.SoftwareQuality.class);\n    if (!defaultImpacts.isEmpty()) {\n      mergedImpacts = new EnumMap<>(defaultImpacts);\n    }\n\n    for (var entry : overriddenImpacts.entrySet()) {\n      var quality = org.sonarsource.sonarlint.core.commons.SoftwareQuality.valueOf(entry.getKey().name());\n      var severity = ImpactSeverity.mapSeverity(entry.getValue().name());\n      mergedImpacts.put(quality, severity);\n    }\n\n    return Collections.unmodifiableMap(mergedImpacts);\n  }\n\n  private static Optional<UUID> asUUID(String key) {\n    try {\n      return Optional.of(UUID.fromString(key));\n    } catch (Exception e) {\n      return Optional.empty();\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/issue/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.issue;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/labs/IdeLabsHttpClient.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.labs;\n\nimport com.google.gson.Gson;\nimport org.sonarsource.sonarlint.core.http.HttpClient;\nimport org.sonarsource.sonarlint.core.http.HttpClientProvider;\nimport org.springframework.beans.factory.annotation.Qualifier;\n\npublic class IdeLabsHttpClient {\n  private final HttpClient httpClient;\n  private final String labsSubscriptionEndpoint;\n\n  private final Gson gson = new Gson();\n\n  public IdeLabsHttpClient(HttpClientProvider httpClientProvider, @Qualifier(\"labsSubscriptionEndpoint\") String labsSubscriptionEndpoint) {\n    this.httpClient = httpClientProvider.getHttpClientWithoutAuth();\n    this.labsSubscriptionEndpoint = labsSubscriptionEndpoint;\n  }\n\n  public HttpClient.Response join(String email, String ideName) {\n    var requestBody = gson.toJson(new IdeLabsSubscriptionRequestPayload(email, ideName));\n\n    return httpClient.post(labsSubscriptionEndpoint, \"application/json\", requestBody);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/labs/IdeLabsService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.labs;\n\nimport com.google.gson.Gson;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.labs.JoinIdeLabsProgramResponse;\n\npublic class IdeLabsService {\n\n  private final IdeLabsHttpClient labsHttpClient;\n  private final Gson gson = new Gson();\n\n  public IdeLabsService(IdeLabsHttpClient labsHttpClient) {\n    this.labsHttpClient = labsHttpClient;\n  }\n\n  public JoinIdeLabsProgramResponse joinIdeLabsProgram(String email, String ideName) {\n    try (var response = labsHttpClient.join(email, ideName)) {\n      if (!response.isSuccessful()) {  \n        return new JoinIdeLabsProgramResponse(false, \"An unexpected error occurred. Server responded with status code: \" + response.code());  \n      }\n\n      var responseBody = gson.fromJson(response.bodyAsString(), IdeLabsSubscriptionResponseBody.class);\n      if (!responseBody.validEmail()) {\n        return new JoinIdeLabsProgramResponse(false, \"The provided email address is not valid. Please enter a valid email address.\");\n      }\n\n      return new JoinIdeLabsProgramResponse(true, null);\n    } catch (Exception e) {\n      return new JoinIdeLabsProgramResponse(false, \"An unexpected error occurred: \" + e.getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/labs/IdeLabsSpringConfig.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.labs;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Import;\n\n\n@Configuration\n@Import({IdeLabsHttpClient.class, IdeLabsService.class})\npublic class IdeLabsSpringConfig {\n  public static final String PROPERTY_IDE_LABS_SUBSCRIPTION_URL = \"sonarlint.internal.labs.subscription.url\";\n  public static final String IDE_LABS_SUBSCRIPTION_URL = \"https://discover.sonarsource.com/sq-ide-labs.json\";\n\n  @Bean(name = \"labsSubscriptionEndpoint\")\n  String provideLabsSubscriptionEndpoint() {\n    return System.getProperty(PROPERTY_IDE_LABS_SUBSCRIPTION_URL, IDE_LABS_SUBSCRIPTION_URL);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/labs/IdeLabsSubscriptionRequestPayload.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.labs;\n\npublic record IdeLabsSubscriptionRequestPayload(String email, String source) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/labs/IdeLabsSubscriptionResponseBody.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.labs;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic record IdeLabsSubscriptionResponseBody(@SerializedName(\"valid_email\") boolean validEmail) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/labs/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.labs;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/languages/LanguageSupportRepository.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.languages;\n\nimport java.util.Collection;\nimport java.util.EnumSet;\nimport java.util.List;\nimport java.util.Set;\nimport org.jetbrains.annotations.NotNull;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\n\npublic class LanguageSupportRepository {\n  private static final EnumSet<SonarLanguage> LANGUAGES_RAISING_TAINT_VULNERABILITIES = EnumSet.of(SonarLanguage.CS, SonarLanguage.JAVA, SonarLanguage.JS, SonarLanguage.TS,\n    SonarLanguage.PHP, SonarLanguage.PYTHON);\n  private final EnumSet<SonarLanguage> enabledLanguagesInStandaloneMode;\n  private final EnumSet<SonarLanguage> extraEnabledLanguagesInConnectedMode;\n  private final EnumSet<SonarLanguage> enabledLanguagesInConnectedMode;\n\n  public LanguageSupportRepository(InitializeParams params) {\n    this.enabledLanguagesInStandaloneMode = toEnumSet(adaptLanguage(params.getEnabledLanguagesInStandaloneMode()), SonarLanguage.class);\n    this.enabledLanguagesInConnectedMode = EnumSet.copyOf(this.enabledLanguagesInStandaloneMode);\n    this.extraEnabledLanguagesInConnectedMode = toEnumSet(adaptLanguage(params.getExtraEnabledLanguagesInConnectedMode()), SonarLanguage.class);\n    this.enabledLanguagesInConnectedMode.addAll(extraEnabledLanguagesInConnectedMode);\n  }\n\n  @NotNull\n  private static List<SonarLanguage> adaptLanguage(Set<Language> languagesDto) {\n    return languagesDto.stream().map(e -> SonarLanguage.valueOf(e.name())).toList();\n  }\n\n  private static <T extends Enum<T>> EnumSet<T> toEnumSet(Collection<T> collection, Class<T> clazz) {\n    return collection.isEmpty() ? EnumSet.noneOf(clazz) : EnumSet.copyOf(collection);\n  }\n\n  public Set<SonarLanguage> getEnabledLanguagesInStandaloneMode() {\n    return enabledLanguagesInStandaloneMode;\n  }\n\n  public Set<SonarLanguage> getEnabledLanguagesInConnectedMode() {\n    return enabledLanguagesInConnectedMode;\n  }\n\n  public boolean areTaintVulnerabilitiesSupported() {\n    var intersection = EnumSet.copyOf(LANGUAGES_RAISING_TAINT_VULNERABILITIES);\n    intersection.retainAll(enabledLanguagesInConnectedMode);\n    return !intersection.isEmpty();\n  }\n\n  public boolean isEnabledOnlyInConnectedMode(SonarLanguage language) {\n    return extraEnabledLanguagesInConnectedMode.contains(language);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/languages/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.languages;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/local/only/IssueStatusBinding.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.local.only;\n\nimport java.io.ByteArrayInputStream;\nimport jetbrains.exodus.bindings.BindingUtils;\nimport jetbrains.exodus.bindings.ComparableBinding;\nimport jetbrains.exodus.util.LightOutputStream;\nimport org.jetbrains.annotations.NotNull;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\n\npublic class IssueStatusBinding extends ComparableBinding {\n\n  @Override\n  public Comparable readObject(@NotNull ByteArrayInputStream stream) {\n    return IssueStatus.values()[BindingUtils.readInt(stream)];\n  }\n\n  @Override\n  public void writeObject(@NotNull LightOutputStream output, @NotNull Comparable object) {\n    final var cPair = (IssueStatus) object;\n    output.writeUnsignedInt(cPair.ordinal() ^ 0x80_000_000);\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/local/only/XodusLocalOnlyIssueStorageService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.local.only;\n\nimport jakarta.annotation.PreDestroy;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport org.apache.commons.io.FileUtils;\nimport org.sonarsource.sonarlint.core.UserPaths;\n\nimport static org.sonarsource.sonarlint.core.commons.storage.XodusPurgeUtils.deleteInFolderWithPattern;\nimport static org.sonarsource.sonarlint.core.local.only.XodusLocalOnlyIssueStore.LOCAL_ONLY_ISSUE;\n\npublic class XodusLocalOnlyIssueStorageService {\n\n  private final Path projectsStorageBaseDir;\n  private final Path workDir;\n  private XodusLocalOnlyIssueStore localOnlyIssueStore;\n\n  public XodusLocalOnlyIssueStorageService(UserPaths userPaths) {\n    this.projectsStorageBaseDir = userPaths.getStorageRoot();\n    this.workDir = userPaths.getWorkDir();\n  }\n\n  public boolean exists() {\n    return Files.exists(projectsStorageBaseDir.resolve(XodusLocalOnlyIssueStore.BACKUP_TAR_GZ));\n  }\n\n  public XodusLocalOnlyIssueStore get() {\n    if (localOnlyIssueStore == null) {\n      try {\n        localOnlyIssueStore = new XodusLocalOnlyIssueStore(projectsStorageBaseDir, workDir);\n        return localOnlyIssueStore;\n      } catch (IOException e) {\n        throw new IllegalStateException(\"Unable to create local-only issue database\", e);\n      }\n    }\n    return localOnlyIssueStore;\n  }\n\n  @PreDestroy\n  public void close() {\n    if (localOnlyIssueStore != null) {\n      localOnlyIssueStore.close();\n    }\n  }\n\n  public void delete() {\n    if (localOnlyIssueStore != null) {\n      localOnlyIssueStore.close();\n      localOnlyIssueStore = null;\n    }\n    FileUtils.deleteQuietly(projectsStorageBaseDir.resolve(XodusLocalOnlyIssueStore.BACKUP_TAR_GZ).toFile());\n    deleteInFolderWithPattern(workDir, LOCAL_ONLY_ISSUE + \"*\");\n    deleteInFolderWithPattern(projectsStorageBaseDir, \"local_only_issue_backup*\");\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/local/only/XodusLocalOnlyIssueStore.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.local.only;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.stream.StreamSupport;\nimport jetbrains.exodus.entitystore.Entity;\nimport jetbrains.exodus.entitystore.PersistentEntityStore;\nimport jetbrains.exodus.entitystore.PersistentEntityStores;\nimport jetbrains.exodus.env.EnvironmentConfig;\nimport jetbrains.exodus.env.Environments;\nimport org.apache.commons.io.FileUtils;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.LineWithHash;\nimport org.sonarsource.sonarlint.core.commons.LocalOnlyIssue;\nimport org.sonarsource.sonarlint.core.commons.LocalOnlyIssueResolution;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.InstantBinding;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.TarGzUtils;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.UuidBinding;\n\nimport static java.util.Objects.requireNonNull;\nimport static java.util.stream.Collectors.flatMapping;\nimport static java.util.stream.Collectors.groupingBy;\nimport static java.util.stream.Collectors.toList;\n\npublic class XodusLocalOnlyIssueStore {\n\n  static final String LOCAL_ONLY_ISSUE = \"xodus-local-only-issue-store\";\n  private static final String CONFIGURATION_SCOPE_ID_ENTITY_TYPE = \"Scope\";\n  private static final String CONFIGURATION_SCOPE_ID_TO_FILES_LINK_NAME = \"files\";\n  private static final String PATH_PROPERTY_NAME = \"path\";\n  private static final String NAME_PROPERTY_NAME = \"name\";\n  private static final String FILE_TO_ISSUES_LINK_NAME = \"issues\";\n  private static final String UUID_PROPERTY_NAME = \"uuid\";\n  private static final String ISSUE_TO_FILE_LINK_NAME = \"file\";\n  private static final String COMMENT_PROPERTY_NAME = \"comment\";\n  private static final String RESOLVED_STATUS_PROPERTY_NAME = \"resolvedStatus\";\n  private static final String RESOLUTION_DATE_PROPERTY_NAME = \"resolvedDate\";\n  private static final String RULE_KEY_PROPERTY_NAME = \"ruleKey\";\n  private static final String RANGE_HASH_PROPERTY_NAME = \"rangeHash\";\n  private static final String LINE_HASH_PROPERTY_NAME = \"lineHash\";\n  private static final String START_LINE_PROPERTY_NAME = \"startLine\";\n  private static final String START_LINE_OFFSET_PROPERTY_NAME = \"startLineOffset\";\n  private static final String END_LINE_PROPERTY_NAME = \"endLine\";\n  private static final String END_LINE_OFFSET_PROPERTY_NAME = \"endLineOffset\";\n  private static final String MESSAGE_BLOB_NAME = \"message\";\n  static final String BACKUP_TAR_GZ = \"local_only_issue_backup.tar.gz\";\n  private final PersistentEntityStore entityStore;\n  private final Path xodusDbDir;\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  public XodusLocalOnlyIssueStore(Path backupDir, Path workDir) throws IOException {\n    xodusDbDir = Files.createTempDirectory(workDir, LOCAL_ONLY_ISSUE);\n    var backupFile = backupDir.resolve(BACKUP_TAR_GZ);\n    if (Files.isRegularFile(backupFile)) {\n      LOG.debug(\"Restoring previous local-only issue database from {}\", backupFile);\n      try {\n        TarGzUtils.extractTarGz(backupFile, xodusDbDir);\n      } catch (Exception e) {\n        LOG.error(\"Unable to restore local-only issue backup {}\", backupFile);\n      }\n    }\n    LOG.debug(\"Starting local-only issue database from {}\", xodusDbDir);\n    this.entityStore = buildEntityStore();\n    entityStore.executeInTransaction(txn -> {\n      entityStore.registerCustomPropertyType(txn, Instant.class, new InstantBinding());\n      entityStore.registerCustomPropertyType(txn, UUID.class, new UuidBinding());\n      entityStore.registerCustomPropertyType(txn, IssueStatus.class, new IssueStatusBinding());\n    });\n  }\n\n  public Map<String, List<LocalOnlyIssue>> loadAll() {\n    return entityStore.computeInReadonlyTransaction(txn -> StreamSupport.stream(txn.getAll(CONFIGURATION_SCOPE_ID_ENTITY_TYPE).spliterator(), false)\n      .collect(groupingBy(\n        e -> (String) requireNonNull(e.getProperty(NAME_PROPERTY_NAME)),\n        flatMapping(e -> StreamSupport.stream(e.getLinks(CONFIGURATION_SCOPE_ID_TO_FILES_LINK_NAME).spliterator(), false)\n          .flatMap(file -> StreamSupport.stream(file.getLinks(XodusLocalOnlyIssueStore.FILE_TO_ISSUES_LINK_NAME).spliterator(), false)\n            .map(XodusLocalOnlyIssueStore::adapt)),\n          toList()))));\n  }\n\n  private static LocalOnlyIssue adapt(Entity storedIssue) {\n    var filePath = (String) requireNonNull(storedIssue.getLink(ISSUE_TO_FILE_LINK_NAME).getProperty(PATH_PROPERTY_NAME));\n    var uuid = (UUID) requireNonNull(storedIssue.getProperty(UUID_PROPERTY_NAME));\n    var status = (IssueStatus) requireNonNull(storedIssue.getProperty(RESOLVED_STATUS_PROPERTY_NAME));\n    var resolvedDate = (Instant) requireNonNull(storedIssue.getProperty(RESOLUTION_DATE_PROPERTY_NAME));\n    var ruleKey = (String) requireNonNull(storedIssue.getProperty(RULE_KEY_PROPERTY_NAME));\n    var msg = requireNonNull(storedIssue.getBlobString(MESSAGE_BLOB_NAME));\n    var comment = storedIssue.getBlobString(COMMENT_PROPERTY_NAME);\n    var startLine = (Integer) storedIssue.getProperty(START_LINE_PROPERTY_NAME);\n\n    TextRangeWithHash textRange = null;\n    LineWithHash lineWithHash = null;\n    if (startLine != null) {\n      var rangeHash = (String) storedIssue.getProperty(RANGE_HASH_PROPERTY_NAME);\n      if (rangeHash != null) {\n        var startLineOffset = (Integer) storedIssue.getProperty(START_LINE_OFFSET_PROPERTY_NAME);\n        var endLine = (Integer) storedIssue.getProperty(END_LINE_PROPERTY_NAME);\n        var endLineOffset = (Integer) storedIssue.getProperty(END_LINE_OFFSET_PROPERTY_NAME);\n        textRange = new TextRangeWithHash(startLine, startLineOffset, endLine, endLineOffset, rangeHash);\n      }\n      var lineHash = (String) storedIssue.getProperty(LINE_HASH_PROPERTY_NAME);\n      if (lineHash != null) {\n        lineWithHash = new LineWithHash(startLine, lineHash);\n      }\n    }\n    return new LocalOnlyIssue(\n      uuid,\n      Path.of(filePath),\n      textRange,\n      lineWithHash,\n      ruleKey,\n      msg,\n      new LocalOnlyIssueResolution(status, resolvedDate, comment));\n  }\n\n  private PersistentEntityStore buildEntityStore() {\n    var environment = Environments.newInstance(xodusDbDir.toAbsolutePath().toFile(), new EnvironmentConfig()\n      .setLogAllowRemote(true)\n      .setLogAllowRemovable(true)\n      .setLogAllowRamDisk(true));\n    var entityStoreImpl = PersistentEntityStores.newInstance(environment);\n    entityStoreImpl.setCloseEnvironment(true);\n    return entityStoreImpl;\n  }\n\n  public void close() {\n    entityStore.close();\n    FileUtils.deleteQuietly(xodusDbDir.toFile());\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/local/only/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.local.only;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/log/LogService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.log;\n\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.log.LogLevel;\n\npublic class LogService {\n\n  public void setLogLevel(LogLevel newLevel) {\n    SonarLintLogger.get().setLevel(convert(newLevel));\n  }\n\n  public static LogOutput.Level convert(LogLevel level) {\n    return switch (level) {\n      case OFF -> LogOutput.Level.OFF;\n      case ERROR -> LogOutput.Level.ERROR;\n      case INFO -> LogOutput.Level.INFO;\n      case WARN -> LogOutput.Level.WARN;\n      case DEBUG -> LogOutput.Level.DEBUG;\n      case TRACE -> LogOutput.Level.TRACE;\n    };\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/log/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.log;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/mode/SeverityModeService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.mode;\n\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.ConnectionKind;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.serverconnection.StoredServerInfo;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\n\npublic class SeverityModeService {\n\n  private final StorageService storageService;\n  private final ConnectionConfigurationRepository connectionConfigurationRepository;\n\n  public SeverityModeService(StorageService storageService, ConnectionConfigurationRepository connectionConfigurationRepository) {\n    this.storageService = storageService;\n    this.connectionConfigurationRepository = connectionConfigurationRepository;\n  }\n\n  public boolean isMQRModeForConnection(@Nullable String connectionId) {\n    if (connectionId == null) {\n      return true;\n    }\n    var connection = connectionConfigurationRepository.getConnectionById(connectionId);\n    if (connection == null) {\n      throw new IllegalArgumentException(\"Connection with id '\" + connectionId + \"' not found\");\n    }\n    if (connection.getKind() == ConnectionKind.SONARCLOUD) {\n      return true;\n    }\n    return storageService.connection(connectionId).serverInfo().read()\n      .map(StoredServerInfo::shouldConsiderMultiQualityModeEnabled)\n      // if no storage, use MQR\n      .orElse(true);\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/mode/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.mode;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/monitoring/MonitoringInitializationParams.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.monitoring;\n\npublic record MonitoringInitializationParams(\n  boolean monitoringEnabled,\n  boolean isTelemetryEnabled,\n  String productKey,\n  String sonarQubeForIdeVersion,\n  String ideVersion\n) {}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/monitoring/MonitoringService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.monitoring;\n\nimport io.sentry.Hint;\nimport io.sentry.Sentry;\nimport io.sentry.SentryBaseEvent;\nimport io.sentry.SentryOptions;\nimport io.sentry.protocol.User;\nimport jakarta.inject.Inject;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.sonarsource.sonarlint.core.commons.SonarLintCoreVersion;\nimport org.sonarsource.sonarlint.core.commons.dogfood.DogfoodEnvironmentDetectionService;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.tracing.Trace;\nimport org.sonarsource.sonarlint.core.event.TelemetryUpdatedEvent;\nimport org.springframework.context.event.EventListener;\n\npublic class MonitoringService {\n\n  public static final String DSN_PROPERTY = \"sonarlint.internal.monitoring.dsn\";\n  private static final String DSN_DEFAULT = \"https://ad1c1fe3cb2b12fc2d191ecd25f89866@o1316750.ingest.us.sentry.io/4508201175089152\";\n\n  public static final String TRACES_SAMPLE_RATE_PROPERTY = \"sonarlint.internal.monitoring.tracesSampleRate\";\n  private static final double TRACES_SAMPLE_RATE_DEFAULT = 0D;\n  private static final double TRACES_SAMPLE_RATE_DOGFOOD_DEFAULT = 0.01D;\n\n  private static final String ENVIRONMENT_PRODUCTION = \"production\";\n  private static final String ENVIRONMENT_DOGFOOD = \"dogfood\";\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final MonitoringInitializationParams initializeParams;\n  private final DogfoodEnvironmentDetectionService dogfoodEnvDetectionService;\n  private final MonitoringUserIdStore userIdStore;\n\n  private boolean active;\n\n  @Inject\n  public MonitoringService(MonitoringInitializationParams initializeParams, DogfoodEnvironmentDetectionService dogfoodEnvDetectionService,\n    MonitoringUserIdStore userIdStore) {\n    this.initializeParams = initializeParams;\n    this.dogfoodEnvDetectionService = dogfoodEnvDetectionService;\n    this.userIdStore = userIdStore;\n\n    this.startIfNeeded();\n  }\n\n  public void startIfNeeded() {\n    if (!initializeParams.monitoringEnabled()) {\n      LOG.info(\"Monitoring is disabled by feature flag.\");\n      return;\n    }\n    if (shouldInitializeSentry()) {\n      LOG.info(\"Initializing Sentry\");\n      start();\n    }\n  }\n\n  private boolean shouldInitializeSentry() {\n    return dogfoodEnvDetectionService.isDogfoodEnvironment() || initializeParams.isTelemetryEnabled();\n  }\n\n  private void start() {\n    Sentry.init(this::configure);\n    userIdStore.getOrCreate().ifPresent(userId -> {\n      var user = new User();\n      user.setId(userId.toString());\n      Sentry.setUser(user);\n    });\n    active = true;\n  }\n\n  public boolean isActive() {\n    return active;\n  }\n\n  private void configure(SentryOptions sentryOptions) {\n    sentryOptions.setDsn(getDsn());\n    sentryOptions.setRelease(SonarLintCoreVersion.getLibraryVersion());\n    sentryOptions.setEnvironment(getEnvironment());\n    sentryOptions.setTag(\"productKey\", initializeParams.productKey());\n    sentryOptions.setTag(\"sonarQubeForIDEVersion\", initializeParams.sonarQubeForIdeVersion());\n    sentryOptions.setTag(\"ideVersion\", initializeParams.ideVersion());\n    sentryOptions.setTag(\"platform\", SystemUtils.OS_NAME);\n    sentryOptions.setTag(\"architecture\", SystemUtils.OS_ARCH);\n    sentryOptions.addInAppInclude(\"org.sonarsource.sonarlint\");\n    sentryOptions.setTracesSampleRate(getTracesSampleRate());\n    addCaptureIgnoreRule(sentryOptions, \"(?s)com\\\\.sonar\\\\.sslr\\\\.api\\\\.RecognitionException.*\");\n    addCaptureIgnoreRule(sentryOptions, \"(?s)com\\\\.sonar\\\\.sslr\\\\.impl\\\\.LexerException.*\");\n    sentryOptions.setBeforeSend(MonitoringService::beforeSend);\n    sentryOptions.setBeforeSendTransaction(MonitoringService::beforeSend);\n  }\n\n  private String getEnvironment() {\n    if (dogfoodEnvDetectionService.isDogfoodEnvironment()) {\n      return ENVIRONMENT_DOGFOOD;\n    }\n\n    return ENVIRONMENT_PRODUCTION;\n  }\n\n  private static <T extends SentryBaseEvent> T beforeSend(T event, Hint hint) {\n    event.setServerName(null);\n    return event;\n  }\n\n  private static String getDsn() {\n    return System.getProperty(DSN_PROPERTY, DSN_DEFAULT);\n  }\n\n  private double getTracesSampleRate() {\n    try {\n      var sampleRateFromSystemProperty = System.getProperty(TRACES_SAMPLE_RATE_PROPERTY);\n      var parsedSampleRate = Double.parseDouble(sampleRateFromSystemProperty);\n      LOG.debug(\"Overriding trace sample rate with value from system property: {}\", parsedSampleRate);\n      return parsedSampleRate;\n    } catch (RuntimeException e) {\n      var sampleRate = TRACES_SAMPLE_RATE_DEFAULT;\n      if (dogfoodEnvDetectionService.isDogfoodEnvironment()) {\n        sampleRate = TRACES_SAMPLE_RATE_DOGFOOD_DEFAULT;\n      }\n      LOG.debug(\"Using default trace sample rate: {}\", sampleRate);\n      return sampleRate;\n    }\n  }\n\n  /**\n   * To ignore exceptions, it's better to use {@link SentryOptions#addIgnoredExceptionForType}, but it accepts Class type\n   * and this is the workaround for the case when Exception class is not in the classpath\n   *\n   * @param regex this should be the regex satisfying java.util.regex.Pattern spec\n   */\n  private static void addCaptureIgnoreRule(SentryOptions sentryOptions, String regex) {\n    sentryOptions.addIgnoredError(regex);\n  }\n\n  public Trace newTrace(String name, String operation) {\n    return Trace.begin(name, operation);\n  }\n\n  @EventListener\n  public void onTelemetryUpdated(TelemetryUpdatedEvent event) {\n    if (!event.isTelemetryEnabled()) {\n      Sentry.close();\n      active = false;\n    } else if (!active && initializeParams.monitoringEnabled() && shouldInitializeSentry()) {\n      LOG.info(\"Initializing Sentry after telemetry was enabled\");\n      start();\n    }\n\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/monitoring/MonitoringUserIdStore.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.monitoring;\n\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardOpenOption;\nimport java.util.Base64;\nimport java.util.Optional;\nimport java.util.UUID;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.UserPaths;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic class MonitoringUserIdStore {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final String USER_ID_FILE_NAME = \"id\";\n\n  private final Path path;\n  @Nullable\n  private volatile UUID cachedUserId;\n\n  public MonitoringUserIdStore(UserPaths userPaths) {\n    this.path = userPaths.getUserHome().resolve(USER_ID_FILE_NAME);\n  }\n\n  public synchronized Optional<UUID> getOrCreate() {\n    var cached = cachedUserId;\n    if (cached != null) {\n      return Optional.of(cached);\n    }\n    try {\n      Files.createDirectories(path.getParent());\n      try (var fileChannel = FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.SYNC);\n        var ignored = fileChannel.lock()) {\n        var userId = readOrCreateUserId(fileChannel);\n        cachedUserId = userId;\n        return Optional.of(userId);\n      }\n    } catch (Exception e) {\n      LOG.debug(\"Failed to read or create user ID\", e);\n      return Optional.empty();\n    }\n  }\n\n  private static UUID readOrCreateUserId(FileChannel fileChannel) throws IOException {\n    var existingId = readUserId(fileChannel);\n    if (existingId != null) {\n      return existingId;\n    }\n    var newId = UUID.randomUUID();\n    writeUserId(fileChannel, newId);\n    return newId;\n  }\n\n  @Nullable\n  private static UUID readUserId(FileChannel fileChannel) throws IOException {\n    if (fileChannel.size() == 0) {\n      return null;\n    }\n    var buffer = ByteBuffer.allocate((int) fileChannel.size());\n    fileChannel.read(buffer);\n    try {\n      var decoded = Base64.getDecoder().decode(buffer.array());\n      var content = new String(decoded, StandardCharsets.UTF_8).trim();\n      if (content.isEmpty()) {\n        return null;\n      }\n      return UUID.fromString(content);\n    } catch (IllegalArgumentException e) {\n      LOG.debug(\"Invalid encoded UUID in \" + USER_ID_FILE_NAME, e);\n      return null;\n    }\n  }\n\n  private static void writeUserId(FileChannel fileChannel, UUID userId) throws IOException {\n    fileChannel.truncate(0);\n    fileChannel.position(0);\n    var encoded = Base64.getEncoder().encode(userId.toString().getBytes(StandardCharsets.UTF_8));\n    fileChannel.write(ByteBuffer.wrap(encoded));\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/monitoring/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.monitoring;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/newcode/NewCodeService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.newcode;\n\nimport java.util.Optional;\nimport org.sonarsource.sonarlint.core.commons.NewCodeDefinition;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.newcode.GetNewCodeDefinitionResponse;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryService;\n\npublic class NewCodeService {\n  private static final NewCodeDefinition STANDALONE_NEW_CODE_DEFINITION = NewCodeDefinition.withExactNumberOfDays(30);\n  private final ConfigurationRepository configurationRepository;\n  private final StorageService storageService;\n  private final TelemetryService telemetryService;\n\n  public NewCodeService(ConfigurationRepository configurationRepository, StorageService storageService, TelemetryService telemetryService) {\n    this.configurationRepository = configurationRepository;\n    this.storageService = storageService;\n    this.telemetryService = telemetryService;\n  }\n\n  public GetNewCodeDefinitionResponse getNewCodeDefinition(String configScopeId) {\n    return getFullNewCodeDefinition(configScopeId)\n      .map(newCodeDefinition -> new GetNewCodeDefinitionResponse(newCodeDefinition.toString(), newCodeDefinition.isSupported()))\n      .orElse(new GetNewCodeDefinitionResponse(\"No new code definition found\", false));\n  }\n\n  public Optional<NewCodeDefinition> getFullNewCodeDefinition(String configScopeId) {\n    var effectiveBinding = configurationRepository.getEffectiveBinding(configScopeId);\n    if (effectiveBinding.isEmpty()) {\n      return Optional.of(STANDALONE_NEW_CODE_DEFINITION);\n    }\n    var binding = effectiveBinding.get();\n    var sonarProjectStorage = storageService.binding(binding);\n    return sonarProjectStorage.newCodeDefinition().read();\n  }\n\n  public void didToggleFocus() {\n    telemetryService.newCodeFocusChanged();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/newcode/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.newcode;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/nodejs/InstalledNodeJs.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.nodejs;\n\nimport java.nio.file.Path;\nimport org.sonarsource.sonarlint.core.commons.Version;\n\npublic class InstalledNodeJs {\n  private final Path path;\n  private final Version version;\n\n  public InstalledNodeJs(Path path, Version version) {\n    this.path = path;\n    this.version = version;\n  }\n\n  public Path getPath() {\n    return path;\n  }\n\n  public Version getVersion() {\n    return version;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/nodejs/NodeJsHelper.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.nodejs;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.regex.Pattern;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonar.api.utils.System2;\nimport org.sonar.api.utils.command.Command;\nimport org.sonar.api.utils.command.CommandException;\nimport org.sonar.api.utils.command.CommandExecutor;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic class NodeJsHelper {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private static final Pattern NODEJS_VERSION_PATTERN = Pattern.compile(\"v?(\\\\d+\\\\.\\\\d+\\\\.\\\\d+(?:-\\\\S+)?)\");\n  private final System2 system2;\n  private final Path pathHelperLocationOnMac;\n  private final CommandExecutor commandExecutor;\n\n  public NodeJsHelper() {\n    this(System2.INSTANCE, Paths.get(\"/usr/libexec/path_helper\"), CommandExecutor.create());\n  }\n\n  // For testing\n  NodeJsHelper(System2 system2, Path pathHelperLocationOnMac, CommandExecutor commandExecutor) {\n    this.system2 = system2;\n    this.pathHelperLocationOnMac = pathHelperLocationOnMac;\n    this.commandExecutor = commandExecutor;\n  }\n\n  @CheckForNull\n  public InstalledNodeJs autoDetect() {\n    return detect(null);\n  }\n\n  @CheckForNull\n  public InstalledNodeJs detect(@Nullable Path configuredNodejsPath) {\n    var detectedNodePath = locateNode(configuredNodejsPath);\n    if (detectedNodePath != null) {\n      var nodeJsVersion = readNodeVersion(detectedNodePath);\n      if (nodeJsVersion == null) {\n        LOG.warn(\"Unable to query node version\");\n      } else {\n        return new InstalledNodeJs(detectedNodePath, nodeJsVersion);\n      }\n    }\n    return null;\n  }\n\n  @CheckForNull\n  private Version readNodeVersion(Path detectedNodePath) {\n    LOG.debug(\"Checking node version...\");\n    String nodeVersionStr;\n    var forcedNodeVersion = System.getProperty(\"sonarlint.internal.nodejs.forcedVersion\");\n    if (forcedNodeVersion != null) {\n      nodeVersionStr = forcedNodeVersion;\n    } else {\n      var command = Command.create(detectedNodePath.toString()).addArgument(\"-v\");\n      nodeVersionStr = runSimpleCommand(command);\n    }\n    Version nodeJsVersion = null;\n    if (nodeVersionStr != null) {\n      var matcher = NODEJS_VERSION_PATTERN.matcher(nodeVersionStr);\n      if (matcher.matches()) {\n        var version = matcher.group(1);\n        nodeJsVersion = Version.create(version);\n        LOG.debug(\"Detected node version: {}\", nodeJsVersion);\n      } else {\n        LOG.debug(\"Unable to parse node version: {}\", nodeVersionStr);\n      }\n    }\n    return nodeJsVersion;\n  }\n\n  @CheckForNull\n  private Path locateNode(@Nullable Path configuredNodejsPath) {\n    if (configuredNodejsPath != null) {\n      LOG.debug(\"Node.js path provided by configuration: {}\", configuredNodejsPath);\n      return configuredNodejsPath;\n    }\n    LOG.debug(\"Looking for node in the PATH\");\n\n    var forcedPath = System.getProperty(\"sonarlint.internal.nodejs.forcedPath\");\n    String result;\n    if (forcedPath != null) {\n      result = forcedPath;\n    } else if (system2.isOsWindows()) {\n      result = runSimpleCommand(Command.create(\"C:\\\\Windows\\\\System32\\\\where.exe\").addArgument(\"$PATH:node.exe\"));\n    } else {\n      // INFO: Based on the Linux / macOS shell we require the full path as \"which\" is a built-in on some shells!\n      var which = Command.create(\"/usr/bin/which\").addArgument(\"node\");\n      computePathEnvForMacOs(which);\n      result = runSimpleCommand(which);\n    }\n    if (result != null) {\n      LOG.debug(\"Found node at {}\", result);\n      return Paths.get(result);\n    } else {\n      LOG.debug(\"Unable to locate node\");\n      return null;\n    }\n  }\n\n  private void computePathEnvForMacOs(Command which) {\n    if (system2.isOsMac() && Files.exists(pathHelperLocationOnMac)) {\n      var command = Command.create(pathHelperLocationOnMac.toString()).addArgument(\"-s\");\n      var pathHelperOutput = runSimpleCommand(command);\n      if (pathHelperOutput != null) {\n        var regex = Pattern.compile(\"^\\\\s*PATH=\\\"([^\\\"]+)\\\"; export PATH;?\\\\s*$\");\n        var matchResult = regex.matcher(pathHelperOutput);\n        if (matchResult.matches()) {\n          which.setEnvironmentVariable(\"PATH\", matchResult.group(1));\n        }\n      }\n    }\n  }\n\n  /**\n   * Run a simple command that should return a single line on stdout\n   */\n  @CheckForNull\n  private String runSimpleCommand(Command command) {\n    List<String> stdOut = new ArrayList<>();\n    List<String> stdErr = new ArrayList<>();\n    LOG.debug(\"Execute command '{}'...\", command);\n    int exitCode;\n    try {\n      exitCode = commandExecutor.execute(command, stdOut::add, stdErr::add, 10_000);\n    } catch (CommandException e) {\n      LOG.debug(\"Unable to execute the command\", e);\n      return null;\n    }\n    var msg = new StringBuilder(String.format(\"Command '%s' exited with %s\", command, exitCode));\n    if (!stdOut.isEmpty()) {\n      msg.append(\"\\nstdout: \").append(String.join(\"\\n\", stdOut));\n    }\n    if (!stdErr.isEmpty()) {\n      msg.append(\"\\nstderr: \").append(String.join(\"\\n\", stdErr));\n    }\n    LOG.debug(\"{}\", msg);\n    if (exitCode != 0 || stdOut.isEmpty()) {\n      return null;\n    }\n    return stdOut.get(0);\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/nodejs/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.nodejs;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/ArtifactSource.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin;\n\npublic enum ArtifactSource {\n\n  EMBEDDED,\n  ON_DEMAND,\n  SONARQUBE_SERVER,\n  SONARQUBE_CLOUD\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/DotnetSupport.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin;\n\nimport java.nio.file.Path;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\n\npublic class DotnetSupport {\n  @Nullable\n  private final Path actualCsharpAnalyzerPath;\n  private final boolean supportsCsharp;\n  private final boolean supportsVbNet;\n  private final boolean shouldUseCsharpEnterprise;\n  private final boolean shouldUseVbNetEnterprise;\n\n  DotnetSupport(InitializeParams initializeParams, @Nullable Path actualCsharpAnalyzerPath, boolean shouldUseCsharpEnterprise, boolean shouldUseVbNetEnterprise) {\n    supportsCsharp = initializeParams.getEnabledLanguagesInStandaloneMode().contains(Language.CS);\n    supportsVbNet = initializeParams.getEnabledLanguagesInStandaloneMode().contains(Language.VBNET);\n    this.actualCsharpAnalyzerPath = actualCsharpAnalyzerPath;\n    this.shouldUseCsharpEnterprise = shouldUseCsharpEnterprise;\n    this.shouldUseVbNetEnterprise = shouldUseVbNetEnterprise;\n  }\n\n  @Nullable\n  public Path getActualCsharpAnalyzerPath() {\n    return actualCsharpAnalyzerPath;\n  }\n\n  public boolean isSupportsCsharp() {\n    return supportsCsharp;\n  }\n\n  public boolean isSupportsVbNet() {\n    return supportsVbNet;\n  }\n\n  public boolean isShouldUseCsharpEnterprise() {\n    return shouldUseCsharpEnterprise;\n  }\n\n  public boolean isShouldUseVbNetEnterprise() {\n    return shouldUseVbNetEnterprise;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/PluginJarUtils.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin;\n\nimport java.nio.file.Path;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.plugin.commons.loading.PluginInfo;\n\npublic class PluginJarUtils {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private PluginJarUtils() {\n  }\n\n  @Nullable\n  public static Version readVersion(Path jarPath) {\n    try {\n      return PluginInfo.create(jarPath).getVersion();\n    } catch (Exception e) {\n      LOG.debug(\"Failed to read version from {}\", jarPath, e);\n      return null;\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/PluginLifecycleService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin;\n\nimport org.sonarsource.sonarlint.core.active.rules.ActiveRulesService;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.repository.rules.RulesRepository;\n\n/**\n * Coordinates plugin lifecycle operations and cache eviction.\n * This service sits at a higher architectural level than PluginsService, RulesRepository,\n * and ActiveRulesService, orchestrating operations across these services without creating\n * circular dependencies.\n */\npublic class PluginLifecycleService {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final PluginsService pluginsService;\n  private final RulesRepository rulesRepository;\n  private final ActiveRulesService activeRulesService;\n\n  public PluginLifecycleService(PluginsService pluginsService, RulesRepository rulesRepository, ActiveRulesService activeRulesService) {\n    this.pluginsService = pluginsService;\n    this.rulesRepository = rulesRepository;\n    this.activeRulesService = activeRulesService;\n  }\n\n  public PluginsConfiguration reloadPluginsAndEvictCaches(String connectionId) {\n    LOG.debug(\"Reloading plugins and evicting all related caches for connection '{}'\", connectionId);\n\n    unloadPluginsAndEvictCaches(connectionId);\n    return pluginsService.getPlugins(connectionId);\n  }\n\n  public void unloadPluginsAndEvictCaches(String connectionId) {\n    LOG.debug(\"Unloading plugins and evicting all related caches for connection '{}'\", connectionId);\n\n    pluginsService.unloadPlugins(connectionId);\n    rulesRepository.evictFor(connectionId);\n    activeRulesService.evictFor(connectionId);\n  }\n\n  public PluginsConfiguration reloadEmbeddedPluginsAndEvictCaches() {\n    LOG.debug(\"Reloading embedded plugins and evicting all related caches\");\n\n    unloadEmbeddedPluginsAndEvictCaches();\n    return pluginsService.getEmbeddedPlugins();\n  }\n\n  public void unloadEmbeddedPluginsAndEvictCaches() {\n    LOG.debug(\"Unloading embedded plugins and evicting all related caches\");\n\n    pluginsService.unloadEmbeddedPlugins();\n    rulesRepository.evictEmbedded();\n    activeRulesService.evictStandalone();\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/PluginStatus.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin;\n\nimport java.nio.file.Path;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactOrigin;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactState;\n\n/**\n * @param pluginKey         the plugin key\n * @param language          language that this plugin provides analysis for\n * @param state             current state of the plugin at the backend\n * @param source            source where the plugin jar came from\n * @param actualVersion     used version of the plugin\n * @param overriddenVersion a version of the plugin that is overridden by the actualVersion, if any\n * @param path              path to the plugin jar on disk; populated for SYNCED/ACTIVE, null for DOWNLOADING/FAILED\n * @param serverVersion     version of the SonarQube Server that provided this plugin; {@code null} for non-server sources\n */\npublic record PluginStatus(\n  String pluginKey,\n  @Nullable SonarLanguage language,\n  ArtifactState state,\n  @Nullable ArtifactOrigin source,\n  @Nullable Version actualVersion,\n  @Nullable Version overriddenVersion,\n  @Nullable Path path,\n  @Nullable String serverVersion) {\n\n  public static PluginStatus forLanguage(SonarLanguage language, ArtifactState state,\n    @Nullable ArtifactOrigin source, @Nullable Version actual, @Nullable Version overridden, @Nullable Path path,\n    @Nullable String serverVersion) {\n    return new PluginStatus(language.getPlugin().getKey(), language, state, source, actual, overridden, path, serverVersion);\n  }\n\n  public static PluginStatus forCompanion(String pluginKey, ArtifactState state,\n    @Nullable ArtifactOrigin source, @Nullable Path path, @Nullable String serverVersion) {\n    return new PluginStatus(pluginKey, null, state, source, null, null, path, serverVersion);\n  }\n\n  public static PluginStatus unsupported(SonarLanguage language) {\n    return forLanguage(language, ArtifactState.UNSUPPORTED, null, null, null, null, null);\n  }\n\n  public static PluginStatus failed(SonarLanguage language) {\n    return forLanguage(language, ArtifactState.FAILED, null, null, null, null, null);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/PluginStatusMapper.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin;\n\nimport java.util.List;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactOrigin;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactState;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.ArtifactSourceDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.PluginStateDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.PluginStatusDto;\n\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\n\npublic class PluginStatusMapper {\n\n  private PluginStatusMapper() {\n  }\n\n  public static List<PluginStatusDto> toDto(List<PluginStatus> statuses) {\n    return statuses.stream().map(PluginStatusMapper::toDto).toList();\n  }\n\n  public static PluginStatusDto toDto(PluginStatus status) {\n    return new PluginStatusDto(\n      status.language() != null ? Language.valueOf(status.language().name()) : null,\n      status.language() != null ? status.language().getName() : null,\n      toDto(status.state()),\n      toDto(status.source()),\n      status.actualVersion() == null ? null : status.actualVersion().toString(),\n      status.overriddenVersion() == null ? null : status.overriddenVersion().toString(),\n      status.serverVersion()\n    );\n  }\n\n  public static PluginStateDto toDto(ArtifactState state) {\n    return switch (state) {\n      case ACTIVE -> PluginStateDto.ACTIVE;\n      case SYNCED -> PluginStateDto.SYNCED;\n      case DOWNLOADING -> PluginStateDto.DOWNLOADING;\n      case FAILED -> PluginStateDto.FAILED;\n      case PREMIUM -> PluginStateDto.PREMIUM;\n      case UNSUPPORTED -> PluginStateDto.UNSUPPORTED;\n    };\n  }\n\n  @Nullable\n  public static ArtifactSourceDto toDto(@Nullable ArtifactOrigin source) {\n    if (source == null) {\n      return null;\n    }\n    return switch (source) {\n      case EMBEDDED -> ArtifactSourceDto.EMBEDDED;\n      case ON_DEMAND -> ArtifactSourceDto.ON_DEMAND;\n      case SONARQUBE_SERVER -> ArtifactSourceDto.SONARQUBE_SERVER;\n      case SONARQUBE_CLOUD -> ArtifactSourceDto.SONARQUBE_CLOUD;\n    };\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/PluginStatusNotifierService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin;\n\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.BoundScope;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.plugin.DidChangePluginStatusesParams;\nimport org.springframework.context.event.EventListener;\n\npublic class PluginStatusNotifierService {\n\n  private final PluginsService pluginsService;\n  private final SonarLintRpcClient client;\n  private final ConfigurationRepository configurationRepository;\n\n  public PluginStatusNotifierService(PluginsService pluginsService, SonarLintRpcClient client, ConfigurationRepository configurationRepository) {\n    this.pluginsService = pluginsService;\n    this.client = client;\n    this.configurationRepository = configurationRepository;\n  }\n\n  @EventListener\n  public void onPluginStatusesChanged(PluginStatusesChangedEvent event) {\n    var connectionId = event.connectionId();\n    if (connectionId != null) {\n      // All affected scopes share the same connection: reuse the pre-computed statuses from the event\n      var statusDtos = PluginStatusMapper.toDto(event.pluginStatuses());\n      configurationRepository.getBoundScopesToConnection(connectionId).stream()\n        .map(BoundScope::getConfigScopeId)\n        .forEach(scopeId -> client.didChangePluginStatuses(new DidChangePluginStatusesParams(scopeId, statusDtos)));\n    } else {\n      // Embedded plugins changed: each scope may have a different effective connection, resolve per scope\n      for (var configScopeId : configurationRepository.getConfigScopeIds()) {\n        var effectiveConnectionId = configurationRepository.getEffectiveBinding(configScopeId)\n          .map(Binding::connectionId).orElse(null);\n        var newStatuses = pluginsService.getPluginStatuses(effectiveConnectionId);\n        client.didChangePluginStatuses(new DidChangePluginStatusesParams(configScopeId, PluginStatusMapper.toDto(newStatuses)));\n      }\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/PluginStatusesChangedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin;\n\nimport java.util.List;\nimport javax.annotation.Nullable;\n\n/**\n * Published when the loaded state of plugins changes for a given connection (or for embedded plugins when connectionId is null).\n * Carries the pre-computed plugin statuses to avoid redundant resolver traversal in listeners.\n */\npublic record PluginStatusesChangedEvent(@Nullable String connectionId, List<PluginStatus> pluginStatuses) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/PluginsConfiguration.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin;\n\nimport java.util.Map;\nimport org.sonarsource.sonarlint.core.plugin.commons.LoadedPlugins;\nimport org.sonarsource.sonarlint.core.plugin.loading.strategy.ArtifactsLoadingResult;\n\npublic record PluginsConfiguration(ArtifactsLoadingResult artifactsResult, LoadedPlugins plugins, Map<String, String> extraProperties) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/PluginsRepository.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin;\n\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.Map;\nimport java.util.Queue;\nimport java.util.concurrent.atomic.AtomicReference;\nimport javax.annotation.CheckForNull;\n\nimport static org.sonarsource.sonarlint.core.commons.IOExceptionUtils.throwFirstWithOtherSuppressed;\nimport static org.sonarsource.sonarlint.core.commons.IOExceptionUtils.tryAndCollectIOException;\n\npublic class PluginsRepository {\n  private final AtomicReference<PluginsConfiguration> embeddedPlugins = new AtomicReference<>();\n  private final Map<String, PluginsConfiguration> pluginsByConnectionId = new HashMap<>();\n\n  public void setEmbeddedPlugins(PluginsConfiguration config) {\n    this.embeddedPlugins.set(config);\n  }\n\n  @CheckForNull\n  public PluginsConfiguration getEmbeddedPlugins() {\n    return embeddedPlugins.get();\n  }\n\n  @CheckForNull\n  public PluginsConfiguration getPlugins(String connectionId) {\n    return pluginsByConnectionId.get(connectionId);\n  }\n\n  public void setPlugins(String connectionId, PluginsConfiguration config) {\n    pluginsByConnectionId.put(connectionId, config);\n  }\n\n  void unloadAllPlugins() throws IOException {\n    Queue<IOException> exceptions = new LinkedList<>();\n    var embedded = embeddedPlugins.get();\n    if (embedded != null) {\n      tryAndCollectIOException(embedded.plugins()::close, exceptions);\n      embeddedPlugins.set(null);\n    }\n    synchronized (pluginsByConnectionId) {\n      pluginsByConnectionId.values().forEach(config -> tryAndCollectIOException(config.plugins()::close, exceptions));\n      pluginsByConnectionId.clear();\n    }\n    throwFirstWithOtherSuppressed(exceptions);\n  }\n\n  public void unload(String connectionId) {\n    var config = pluginsByConnectionId.remove(connectionId);\n    if (config != null) {\n      try {\n        config.plugins().close();\n      } catch (IOException e) {\n        throw new IllegalStateException(\"Unable to unload plugins\", e);\n      }\n    }\n  }\n\n  public void unloadEmbedded() {\n    var config = embeddedPlugins.getAndSet(null);\n    if (config != null) {\n      try {\n        config.plugins().close();\n      } catch (IOException e) {\n        throw new IllegalStateException(\"Unable to unload embedded plugins\", e);\n      }\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/PluginsService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin;\n\nimport jakarta.annotation.PreDestroy;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.analysis.NodeJsService;\nimport org.sonarsource.sonarlint.core.commons.ConnectionKind;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\nimport org.sonarsource.sonarlint.core.plugin.commons.PluginsLoader;\nimport org.sonarsource.sonarlint.core.plugin.commons.loading.PluginRequirementsCheckResult;\nimport org.sonarsource.sonarlint.core.plugin.loading.strategy.ArtifactsLoadingResult;\nimport org.sonarsource.sonarlint.core.plugin.loading.strategy.ArtifactsLoadingStrategy;\nimport org.sonarsource.sonarlint.core.plugin.loading.strategy.ConnectedArtifactsLoadingStrategyFactory;\nimport org.sonarsource.sonarlint.core.plugin.loading.strategy.StandaloneArtifactsLoadingStrategy;\nimport org.sonarsource.sonarlint.core.plugin.skipped.SkippedPlugin;\nimport org.sonarsource.sonarlint.core.plugin.skipped.SkippedPluginsRepository;\nimport org.sonarsource.sonarlint.core.plugin.source.ResolvedArtifact;\nimport org.sonarsource.sonarlint.core.plugin.source.binaries.BinariesArtifactSource;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.serverconnection.StoredPlugin;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.sonarsource.sonarlint.core.sync.PluginsSynchronizedEvent;\nimport org.springframework.context.ApplicationEventPublisher;\n\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.DATAFLOW_BUG_DETECTION;\n\npublic class PluginsService {\n  private static final Version REPACKAGED_DOTNET_ANALYZER_MIN_SQ_VERSION = Version.create(\"10.8\");\n  public static final String CSHARP_ENTERPRISE_PLUGIN_ID = \"csharpenterprise\";\n  public static final String VBNET_ENTERPRISE_PLUGIN_ID = \"vbnetenterprise\";\n\n  private final SonarLintLogger logger = SonarLintLogger.get();\n  private final PluginsRepository pluginsRepository;\n  private final SkippedPluginsRepository skippedPluginsRepository;\n  private final StorageService storageService;\n  private final InitializeParams initializeParams;\n  private final ConnectionConfigurationRepository connectionConfigurationRepository;\n  private final NodeJsService nodeJsService;\n  private final boolean enableDataflowBugDetection;\n  private final ApplicationEventPublisher eventPublisher;\n  private final StandaloneArtifactsLoadingStrategy standaloneArtifactsLoadingStrategy;\n  private final ConnectedArtifactsLoadingStrategyFactory connectedArtifactsLoadingStrategyFactory;\n  private final BinariesArtifactSource binariesArtifactSource;\n\n  public PluginsService(PluginsRepository pluginsRepository, SkippedPluginsRepository skippedPluginsRepository,\n    StorageService storageService, InitializeParams params, ConnectionConfigurationRepository connectionConfigurationRepository,\n    NodeJsService nodeJsService, ApplicationEventPublisher eventPublisher,\n    StandaloneArtifactsLoadingStrategy standaloneArtifactsLoadingStrategy,\n    ConnectedArtifactsLoadingStrategyFactory connectedArtifactsLoadingStrategyFactory,\n    BinariesArtifactSource binariesArtifactSource) {\n    this.pluginsRepository = pluginsRepository;\n    this.skippedPluginsRepository = skippedPluginsRepository;\n    this.storageService = storageService;\n    this.enableDataflowBugDetection = params.getBackendCapabilities().contains(DATAFLOW_BUG_DETECTION);\n    this.initializeParams = params;\n    this.connectionConfigurationRepository = connectionConfigurationRepository;\n    this.nodeJsService = nodeJsService;\n    this.eventPublisher = eventPublisher;\n    this.standaloneArtifactsLoadingStrategy = standaloneArtifactsLoadingStrategy;\n    this.connectedArtifactsLoadingStrategyFactory = connectedArtifactsLoadingStrategyFactory;\n    this.binariesArtifactSource = binariesArtifactSource;\n  }\n\n  public List<PluginStatus> getPluginStatuses(@Nullable String connectionId) {\n    var plugins = connectionId == null ? getEmbeddedPlugins() : getPlugins(connectionId);\n    return getPluginStatuses(plugins.artifactsResult());\n  }\n\n  private static List<PluginStatus> getPluginStatuses(ArtifactsLoadingResult result) {\n    return Arrays.stream(SonarLanguage.values())\n      .map(language -> buildPluginStatus(language, result))\n      .toList();\n  }\n\n  private static PluginStatus buildPluginStatus(SonarLanguage language, ArtifactsLoadingResult result) {\n    var pluginKey = resolvePluginKey(language, result.resolvedArtifactsByKey());\n    return result.getResolvedArtifactByKey(pluginKey)\n      .map(artifact -> PluginStatus.forLanguage(language, artifact.state(), artifact.source(), artifact.version(), null, artifact.path(), null))\n      .orElseGet(() -> PluginStatus.unsupported(language));\n  }\n\n  private ArtifactsLoadingStrategy getPluginLoadingStrategy(@Nullable String connectionId) {\n    return connectionId != null ? connectedArtifactsLoadingStrategyFactory.getOrCreate(connectionId) : standaloneArtifactsLoadingStrategy;\n  }\n\n  /**\n   * Returns the effective plugin key for a language, preferring the enterprise variant if it is\n   * already present in the resolved map.\n   */\n  private static String resolvePluginKey(SonarLanguage language, Map<String, ResolvedArtifact> resolved) {\n    var baseKey = language.getPlugin().getKey();\n    var enterpriseKeys = SonarPlugin.findByKey(baseKey)\n      .map(SonarPlugin::getEnterpriseVariants)\n      .map(variants -> variants.stream().map(SonarPlugin::getKey).collect(Collectors.toSet()))\n      .orElseGet(Set::of);\n    return enterpriseKeys.stream()\n      .filter(resolved::containsKey)\n      .findFirst()\n      .orElse(baseKey);\n  }\n\n  public PluginsConfiguration getEmbeddedPlugins() {\n    var cached = pluginsRepository.getEmbeddedPlugins();\n    if (cached == null) {\n      cached = loadPlugins(null);\n      pluginsRepository.setEmbeddedPlugins(cached);\n      eventPublisher.publishEvent(new PluginStatusesChangedEvent(null, getPluginStatuses(cached.artifactsResult())));\n    }\n    return cached;\n  }\n\n  public PluginsConfiguration getPlugins(String connectionId) {\n    var cached = pluginsRepository.getPlugins(connectionId);\n    if (cached == null) {\n      cached = loadPlugins(connectionId);\n      pluginsRepository.setPlugins(connectionId, cached);\n      eventPublisher.publishEvent(new PluginStatusesChangedEvent(connectionId, getPluginStatuses(cached.artifactsResult())));\n    }\n    return cached;\n  }\n\n  private PluginsConfiguration loadPlugins(@Nullable String connectionId) {\n    var strategy = getPluginLoadingStrategy(connectionId);\n    var artifactsResult = strategy.resolveArtifacts();\n    artifactsResult.whenAllArtifactsDownloaded(() -> eventPublisher.publishEvent(new PluginsSynchronizedEvent(connectionId)));\n\n    var config = new PluginsLoader.Configuration(new HashSet<>(artifactsResult.getPluginPaths()), artifactsResult.enabledLanguages(),\n      enableDataflowBugDetection, nodeJsService.getActiveNodeJsVersion());\n    var pluginsLoadResult = new PluginsLoader().load(config, initializeParams.getDisabledPluginKeysForAnalysis());\n\n    var skippedPlugins = pluginsLoadResult.getPluginCheckResultByKeys().values().stream()\n      .filter(PluginRequirementsCheckResult::isSkipped)\n      .map(plugin -> new SkippedPlugin(plugin.getPlugin().getKey(), plugin.getSkipReason().get()))\n      .toList();\n    if (connectionId == null) {\n      skippedPluginsRepository.setSkippedEmbeddedPlugins(skippedPlugins);\n    } else {\n      skippedPluginsRepository.setSkippedPlugins(connectionId, skippedPlugins);\n    }\n\n    return new PluginsConfiguration(artifactsResult, pluginsLoadResult.getLoadedPlugins(), buildExtraProperties(connectionId, artifactsResult));\n  }\n\n  private Map<String, String> buildExtraProperties(@Nullable String connectionId, ArtifactsLoadingResult result) {\n    var properties = new HashMap<String, String>();\n    var dotnetSupport = getDotnetSupport(connectionId, result);\n    if (dotnetSupport.getActualCsharpAnalyzerPath() != null) {\n      properties.put(\"sonar.cs.internal.analyzerPath\", dotnetSupport.getActualCsharpAnalyzerPath().toString());\n    }\n    if (dotnetSupport.isSupportsCsharp()) {\n      properties.put(\"sonar.cs.internal.shouldUseCsharpEnterprise\", String.valueOf(dotnetSupport.isShouldUseCsharpEnterprise()));\n    }\n    if (dotnetSupport.isSupportsVbNet()) {\n      properties.put(\"sonar.cs.internal.shouldUseVbEnterprise\", String.valueOf(dotnetSupport.isShouldUseVbNetEnterprise()));\n    }\n    properties.putAll(binariesArtifactSource.getOmnisharpExtraProperties());\n    return properties;\n  }\n\n  public void unloadPlugins(String connectionId) {\n    logger.debug(\"Evict loaded plugins for connection '{}'\", connectionId);\n    pluginsRepository.unload(connectionId);\n    connectedArtifactsLoadingStrategyFactory.evict(connectionId);\n  }\n\n  public boolean shouldUseEnterpriseCSharpAnalyzer(String connectionId) {\n    return shouldUseEnterpriseDotNetAnalyzer(connectionId, CSHARP_ENTERPRISE_PLUGIN_ID);\n  }\n\n  private boolean shouldUseEnterpriseDotNetAnalyzer(String connectionId, String analyzerName) {\n    if (isSonarQubeCloud(connectionId)) {\n      return true;\n    } else {\n      var connectionStorage = storageService.connection(connectionId);\n      var serverInfo = connectionStorage.serverInfo().read();\n      if (serverInfo.isEmpty()) {\n        return false;\n      } else {\n        var serverVersion = serverInfo.get().version();\n        var supportsRepackagedDotnetAnalyzer = serverVersion.compareToIgnoreQualifier(REPACKAGED_DOTNET_ANALYZER_MIN_SQ_VERSION) >= 0;\n        var hasEnterprisePlugin = connectionStorage.plugins().getStoredPlugins().stream().map(StoredPlugin::getKey).anyMatch(analyzerName::equals);\n        return !supportsRepackagedDotnetAnalyzer || hasEnterprisePlugin;\n      }\n    }\n  }\n\n  private boolean isSonarQubeCloud(String connectionId) {\n    var connection = connectionConfigurationRepository.getConnectionById(connectionId);\n    return connection != null && connection.getKind() == ConnectionKind.SONARCLOUD;\n  }\n\n  public boolean shouldUseEnterpriseVbAnalyzer(String connectionId) {\n    return shouldUseEnterpriseDotNetAnalyzer(connectionId, VBNET_ENTERPRISE_PLUGIN_ID);\n  }\n\n  private DotnetSupport getDotnetSupport(@Nullable String connectionId, ArtifactsLoadingResult result) {\n    var ossPath = resolveOssCsharpAnalyzerPath(result);\n    if (connectionId == null) {\n      return new DotnetSupport(initializeParams, ossPath, false, false);\n    }\n    var useEnterpriseCs = shouldUseEnterpriseCSharpAnalyzer(connectionId);\n    var useEnterpriseVb = shouldUseEnterpriseVbAnalyzer(connectionId);\n    var actualPath = selectCsharpAnalyzerPath(connectionId, ossPath, useEnterpriseCs);\n    return new DotnetSupport(initializeParams, actualPath, useEnterpriseCs, useEnterpriseVb);\n  }\n\n  @Nullable\n  private static Path resolveOssCsharpAnalyzerPath(ArtifactsLoadingResult result) {\n    return result.getResolvedArtifactByKey(SonarPlugin.CS_OSS.getKey())\n      .map(ResolvedArtifact::path)\n      .orElse(null);\n  }\n\n  @Nullable\n  private Path selectCsharpAnalyzerPath(String connectionId, @Nullable Path ossPath, boolean useEnterprise) {\n    if (useEnterprise) {\n      return getStoredEnterprisePath(connectionId).orElse(ossPath);\n    }\n    return ossPath;\n  }\n\n  private Optional<Path> getStoredEnterprisePath(String connectionId) {\n    return Optional.ofNullable(storageService.connection(connectionId).plugins().getStoredPluginsByKey().get(CSHARP_ENTERPRISE_PLUGIN_ID))\n      .map(StoredPlugin::getJarPath);\n  }\n\n  public void unloadEmbeddedPlugins() {\n    logger.debug(\"Evict loaded embedded plugins\");\n    pluginsRepository.unloadEmbedded();\n  }\n\n  @PreDestroy\n  public void shutdown() throws IOException {\n    try {\n      pluginsRepository.unloadAllPlugins();\n    } catch (Exception e) {\n      SonarLintLogger.get().error(\"Error shutting down plugins service\", e);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/loading/strategy/ArtifactCandidate.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.loading.strategy;\n\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactSource;\nimport org.sonarsource.sonarlint.core.plugin.source.AvailableArtifact;\n\n/**\n * Pairs an {@link AvailableArtifact} with the {@link ArtifactSource} that won the priority\n * contest for its key. Used as the value type of the winner-map in both loading strategies.\n */\nrecord ArtifactCandidate(AvailableArtifact available, ArtifactSource source) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/loading/strategy/ArtifactsLoadingResult.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.loading.strategy;\n\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.CompletableFuture;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPluginDependency;\nimport org.sonarsource.sonarlint.core.plugin.source.ResolvedArtifact;\n\npublic record ArtifactsLoadingResult(Set<SonarLanguage> enabledLanguages, Map<String, ResolvedArtifact> resolvedArtifactsByKey) {\n\n  public Optional<ResolvedArtifact> getResolvedArtifactByKey(String key) {\n    return Optional.ofNullable(resolvedArtifactsByKey().get(key));\n  }\n\n  /**\n   * All artifacts must not be loaded, only real Sonar plugins.\n   */\n  public List<Path> getPluginPaths() {\n    var pluginPaths = new ArrayList<Path>();\n    for (var entry : resolvedArtifactsByKey.entrySet()) {\n      var key = entry.getKey();\n      var artifact = entry.getValue();\n      // only load artifacts that are ready on disk and not dependencies\n      if (artifact == null || artifact.path() == null || SonarPluginDependency.findByKey(key).isPresent()) {\n        continue;\n      }\n      // only load plugins whose required dependencies are also present on disk\n      if (areRequiredDependenciesPresent(key)) {\n        pluginPaths.add(artifact.path());\n      }\n    }\n    return pluginPaths;\n  }\n\n  private boolean areRequiredDependenciesPresent(String key) {\n    return SonarPlugin.findByKey(key)\n      .map(plugin -> plugin.getDependencies().stream()\n        .filter(dep -> !dep.optional())\n        .allMatch(dep -> {\n          var depArtifact = resolvedArtifactsByKey.get(dep.artifact().getKey());\n          return depArtifact != null && depArtifact.path() != null;\n        }))\n      .orElse(true);\n  }\n\n  public Optional<CompletableFuture<Void>> getAllDownloadsFuture() {\n    var pendingDownloads = resolvedArtifactsByKey.values().stream().map(ResolvedArtifact::downloadFuture)\n      .filter(Objects::nonNull)\n      .toList();\n    if (pendingDownloads.isEmpty()) {\n      return Optional.empty();\n    }\n    return Optional.of(CompletableFuture.allOf(pendingDownloads.toArray(new CompletableFuture[0])));\n  }\n\n  public void whenAllArtifactsDownloaded(Runnable runnable) {\n    getAllDownloadsFuture()\n      .ifPresent(future -> {\n        var logOutput = SonarLintLogger.get().getTargetForCopy();\n        future.thenRun(() -> {\n          SonarLintLogger.get().setTarget(logOutput);\n          runnable.run();\n        });\n      });\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/loading/strategy/ArtifactsLoadingStrategy.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.loading.strategy;\n\nimport org.sonarsource.sonarlint.core.plugin.PluginsService;\nimport org.sonarsource.sonarlint.core.plugin.source.ResolvedArtifact;\n\n/**\n * Defines how {@link org.sonarsource.sonarlint.core.plugin.source.ArtifactSource ArtifactSource}\n * instances are combined to produce the full set of resolved artifacts for a given context.\n *\n * <p>There are two implementations:\n * <ul>\n *   <li>{@link StandaloneArtifactsLoadingStrategy} — standalone mode, no server connection.</li>\n *   <li>{@link ConnectedArtifactsLoadingStrategy} — connected mode, one instance per connection.</li>\n * </ul>\n *\n * <p>Consumed by {@link PluginsService} to resolve artifacts without knowing the mode.\n * All complexity of listing sources, prioritizing, applying skip-lists, handling enterprise\n * variants and companion plugins stays hidden inside the implementation.</p>\n */\npublic interface ArtifactsLoadingStrategy {\n\n  /**\n   * Resolves all artifacts (plugins and plugin dependencies) from all managed sources.\n   * Higher-priority sources overwrite lower-priority ones for the same key.\n   * May schedule background downloads; entries with a {@code null} path are still being fetched.\n   *\n   * @return a map from artifact key to its current {@link ResolvedArtifact}\n   */\n  ArtifactsLoadingResult resolveArtifacts();\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/loading/strategy/BaseArtifactsLoadingStrategy.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.loading.strategy;\n\nimport java.util.Map;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPluginDependency;\n\n/**\n * Base class for artifact loading strategies. Provides shared filter passes that apply to both\n * standalone and connected mode.\n *\n * <p>Subclasses build a {@code LinkedHashMap<String, ArtifactCandidate>} winner-map using\n * their source-specific priority rules, then call {@link #removeOrphanDependencies} and\n * {@link #removeMissingRequiredDeps} before loading artifacts.</p>\n */\nabstract class BaseArtifactsLoadingStrategy implements ArtifactsLoadingStrategy {\n  protected BaseArtifactsLoadingStrategy() {\n    // only instantiable from subclasses\n  }\n\n\n  /**\n   * Removes dependency artifacts whose dependent plugin is not present in the candidate map.\n   *\n   * <p>A {@link SonarPluginDependency} with no corresponding dependent {@link SonarPlugin}\n   * in the map is an orphan and must not be loaded.</p>\n   */\n  protected static void removeOrphanDependencies(Map<String, ArtifactCandidate> candidates) {\n    candidates.entrySet().removeIf(e -> {\n      var sonarArtifact = e.getValue().available().sonarArtifact();\n      return sonarArtifact.isPresent()\n        && sonarArtifact.get() instanceof SonarPluginDependency dependency\n        && dependency.getDependents().stream().noneMatch(p -> candidates.containsKey(p.getKey()));\n    });\n  }\n\n  /**\n   * Removes plugins that are missing at least one required (non-optional) dependency in the\n   * candidate map.\n   */\n  protected static void removeMissingRequiredDeps(Map<String, ArtifactCandidate> candidates) {\n    candidates.entrySet().removeIf(e -> {\n      var sonarArtifact = e.getValue().available().sonarArtifact();\n      return sonarArtifact.isPresent()\n        && sonarArtifact.get() instanceof SonarPlugin plugin\n        && plugin.getDependencies().stream()\n          .filter(dep -> !dep.optional())\n          .anyMatch(dep -> !candidates.containsKey(dep.artifact().getKey()));\n    });\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/loading/strategy/ConnectedArtifactsLoadingStrategy.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.loading.strategy;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\nimport org.sonarsource.sonarlint.core.languages.LanguageSupportRepository;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactSource;\nimport org.sonarsource.sonarlint.core.plugin.source.AvailableArtifact;\nimport org.sonarsource.sonarlint.core.plugin.source.ResolvedArtifact;\nimport org.sonarsource.sonarlint.core.plugin.source.binaries.BinariesArtifactSource;\nimport org.sonarsource.sonarlint.core.plugin.source.embedded.EmbeddedPluginSource;\nimport org.sonarsource.sonarlint.core.plugin.source.server.ServerPluginSource;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\n\n/**\n * Artifacts loading strategy for connected mode (a specific connection).\n *\n * <p>One instance is created per connection and cached by\n * {@link ConnectedArtifactsLoadingStrategyFactory}.</p>\n *\n * <p>Sources, in ascending priority order:\n * <ol>\n *   <li>{@link BinariesArtifactSource} — on-demand downloadable artifacts (fallback).</li>\n *   <li>{@link ServerPluginSource} — artifacts synced from the server.</li>\n *   <li>{@link EmbeddedPluginSource} (connected) — JARs embedded in the IDE client (highest\n *       priority in normal circumstances).</li>\n * </ol>\n *\n * <p>{@link #resolveArtifacts()} uses a winner-map pattern: iterate sources in ascending\n * priority, last writer wins per key, then apply passes to correct the map before loading.\n *\n * <p>Connected-mode-specific passes (applied before the shared passes):\n * <ol>\n *   <li><b>Enterprise-variant deduplication</b>: when a different-key enterprise variant\n *       ({@code csharpenterprise}, {@code vbnetenterprise}) is present, the base key is removed\n *       so both are not loaded simultaneously.</li>\n *   <li><b>Enterprise priority override</b>: when the server reports a plugin as enterprise\n *       ({@link AvailableArtifact#isEnterprise()}), that plugin is forced to use the server\n *       source even if the embedded source would normally win. This applies to same-key\n *       enterprise plugins (GO, IAC) whose enterprise edition is served when the\n *       connection qualifies (SonarQube Server &ge; minimum version, or SonarQube Cloud).</li>\n * </ol>\n */\npublic class ConnectedArtifactsLoadingStrategy extends BaseArtifactsLoadingStrategy {\n  private final ServerPluginSource serverSource;\n  private final LanguageSupportRepository languageSupportRepository;\n  private final List<ArtifactSource> artifactSourcesSortedByAscendingPriority;\n\n  ConnectedArtifactsLoadingStrategy(InitializeParams params, BinariesArtifactSource binariesSource,\n    ServerPluginSource serverSource, LanguageSupportRepository languageSupportRepository) {\n    this.serverSource = serverSource;\n    this.languageSupportRepository = languageSupportRepository;\n    // Ascending priority: binaries (fallback) → server → embedded (highest)\n    this.artifactSourcesSortedByAscendingPriority = List.of(\n      binariesSource,\n      serverSource,\n      EmbeddedPluginSource.forConnected(params));\n  }\n\n  /**\n   * Resolves all artifacts from all sources using a winner-map pattern. May schedule background\n   * downloads.\n   *\n   * <p>Priority (highest wins in normal cases): embedded &gt; server &gt; binaries.\n   * Exception: enterprise server plugins beat embedded (see class Javadoc).</p>\n   */\n  @Override\n  public ArtifactsLoadingResult resolveArtifacts() {\n    var enabledLanguages = languageSupportRepository.getEnabledLanguagesInConnectedMode();\n\n    // Query server artifacts once; reused in normal pass and enterprise-override pass\n    var serverArtifacts = serverSource.listAvailableArtifacts(enabledLanguages);\n\n    // Winner-map: ascending priority, last writer wins per key\n    var candidates = new LinkedHashMap<String, ArtifactCandidate>();\n    for (var source : artifactSourcesSortedByAscendingPriority) {\n      var artifacts = (source == serverSource) ? serverArtifacts : source.listAvailableArtifacts(enabledLanguages);\n      for (var artifact : artifacts) {\n        candidates.put(artifact.key(), new ArtifactCandidate(artifact, source));\n      }\n    }\n\n    // Pass 1 (connected-specific): remove base keys superseded by a different-key enterprise variant\n    new ArrayList<>(candidates.keySet()).stream()\n      .filter(SonarPlugin::isEnterpriseVariant)\n      .forEach(entKey -> SonarPlugin.baseKeyFor(entKey).ifPresent(candidates::remove));\n\n    // Pass 2 (connected-specific): enterprise server plugins override even embedded\n    serverArtifacts.stream()\n      .filter(AvailableArtifact::isEnterprise)\n      .forEach(a -> candidates.computeIfPresent(a.key(), (k, existing) -> new ArtifactCandidate(existing.available(), serverSource)));\n\n    // Shared passes\n    removeOrphanDependencies(candidates);\n    removeMissingRequiredDeps(candidates);\n\n    // Group winning keys by source, then load once per source.\n    // Pre-populate all sources with empty sets so every source is always called (e.g. ServerPluginSource\n    // needs to be called even with an empty set to initialize its storage when nothing is downloaded).\n    var keysBySource = new HashMap<ArtifactSource, HashSet<String>>();\n    for (var source : artifactSourcesSortedByAscendingPriority) {\n      keysBySource.put(source, new HashSet<>());\n    }\n    candidates.forEach((key, candidate) -> keysBySource.get(candidate.source()).add(key));\n    var result = new LinkedHashMap<String, ResolvedArtifact>();\n    keysBySource.forEach((source, keys) -> result.putAll(source.load(keys).resolvedArtifactsByKey()));\n    return new ArtifactsLoadingResult(enabledLanguages, result);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/loading/strategy/ConnectedArtifactsLoadingStrategyFactory.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.loading.strategy;\n\nimport java.util.concurrent.ConcurrentHashMap;\nimport org.sonarsource.sonarlint.core.languages.LanguageSupportRepository;\nimport org.sonarsource.sonarlint.core.plugin.source.server.ServerPluginsCache;\nimport org.sonarsource.sonarlint.core.plugin.source.server.ServerPluginSource;\nimport org.sonarsource.sonarlint.core.plugin.source.server.ServerPluginDownloader;\nimport org.sonarsource.sonarlint.core.plugin.source.binaries.BinariesArtifactSource;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\n\n/**\n * Creates and caches {@link ConnectedArtifactsLoadingStrategy} instances, one per connection ID.\n *\n * <p>The cache ensures that the same strategy — and its underlying\n * {@link ServerPluginSource} — is reused across calls for the same connection, which is required\n * for consistent in-progress download tracking. Call {@link #evict(String)} when a connection is\n * removed.</p>\n */\npublic class ConnectedArtifactsLoadingStrategyFactory {\n\n  private final ConcurrentHashMap<String, ConnectedArtifactsLoadingStrategy> cache = new ConcurrentHashMap<>();\n\n  private final InitializeParams params;\n  private final BinariesArtifactSource binariesSource;\n  private final StorageService storageService;\n  private final ServerPluginsCache serverPluginsCache;\n  private final ServerPluginDownloader downloader;\n  private final LanguageSupportRepository languageSupportRepository;\n\n  public ConnectedArtifactsLoadingStrategyFactory(InitializeParams params,\n    BinariesArtifactSource binariesSource,\n    StorageService storageService,\n    ServerPluginsCache serverPluginsCache,\n    ServerPluginDownloader downloader,\n    LanguageSupportRepository languageSupportRepository) {\n    this.params = params;\n    this.binariesSource = binariesSource;\n    this.storageService = storageService;\n    this.serverPluginsCache = serverPluginsCache;\n    this.downloader = downloader;\n    this.languageSupportRepository = languageSupportRepository;\n  }\n\n  public ConnectedArtifactsLoadingStrategy getOrCreate(String connectionId) {\n    return cache.computeIfAbsent(connectionId, id -> {\n      var serverSource = new ServerPluginSource(id, storageService, serverPluginsCache, downloader);\n      return new ConnectedArtifactsLoadingStrategy(params, binariesSource, serverSource, languageSupportRepository);\n    });\n  }\n\n  public void evict(String connectionId) {\n    cache.remove(connectionId);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/loading/strategy/StandaloneArtifactsLoadingStrategy.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.loading.strategy;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\nimport org.sonarsource.sonarlint.core.languages.LanguageSupportRepository;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactSource;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactState;\nimport org.sonarsource.sonarlint.core.plugin.source.ResolvedArtifact;\nimport org.sonarsource.sonarlint.core.plugin.source.binaries.BinariesArtifactSource;\nimport org.sonarsource.sonarlint.core.plugin.source.embedded.EmbeddedPluginSource;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\n\n/**\n * Artifacts loading strategy for standalone (no-connection) mode.\n *\n * <p>Sources, in ascending priority order:\n * <ol>\n *   <li>{@link BinariesArtifactSource} — on-demand downloadable artifacts.</li>\n *   <li>{@link EmbeddedPluginSource} (standalone) — JARs embedded in the IDE client.</li>\n * </ol>\n *\n * <p>Languages available only in connected mode are reported as\n * {@link ArtifactState#PREMIUM} when no other source can provide them.</p>\n */\npublic class StandaloneArtifactsLoadingStrategy extends BaseArtifactsLoadingStrategy {\n\n  private final InitializeParams params;\n  private final BinariesArtifactSource binariesSource;\n  private final LanguageSupportRepository languageSupportRepository;\n  @Nullable\n  private List<ArtifactSource> artifactSourcesSortedByAscendingPriority;\n\n  public StandaloneArtifactsLoadingStrategy(InitializeParams params, BinariesArtifactSource binariesSource, LanguageSupportRepository languageSupportRepository) {\n    this.params = params;\n    this.binariesSource = binariesSource;\n    this.languageSupportRepository = languageSupportRepository;\n  }\n\n  private List<ArtifactSource> getArtifactSourcesByAscendingPriority() {\n    if (artifactSourcesSortedByAscendingPriority == null) {\n      // Ascending priority: binaries < embedded. Later source overwrites for the same key.\n      // EmbeddedPluginSource.forStandalone reads JAR manifests and may throw — defer until first use.\n      artifactSourcesSortedByAscendingPriority = List.of(binariesSource, EmbeddedPluginSource.forStandalone(params));\n    }\n    return artifactSourcesSortedByAscendingPriority;\n  }\n\n  /**\n   * Resolves all artifacts from standalone sources.\n   *\n   * <p>Priority (highest wins): embedded over binaries. Connected-only languages that\n   * cannot be provided by either source are reported as {@link ArtifactState#PREMIUM}.</p>\n   */\n  @Override\n  public ArtifactsLoadingResult resolveArtifacts() {\n    var enabledLanguages = languageSupportRepository.getEnabledLanguagesInStandaloneMode();\n\n    // Winner-map: ascending priority, last writer wins per key\n    var candidates = new LinkedHashMap<String, ArtifactCandidate>();\n    for (var source : getArtifactSourcesByAscendingPriority()) {\n      for (var artifact : source.listAvailableArtifacts(enabledLanguages)) {\n        candidates.put(artifact.key(), new ArtifactCandidate(artifact, source));\n      }\n    }\n\n    // remove base keys superseded by a different-key enterprise variant\n    new ArrayList<>(candidates.keySet()).stream()\n      .filter(SonarPlugin::isEnterpriseVariant)\n      .forEach(entKey -> SonarPlugin.baseKeyFor(entKey).ifPresent(candidates::remove));\n\n    removeOrphanDependencies(candidates);\n    removeMissingRequiredDeps(candidates);\n\n    // Group winning keys by source, then load once per source\n    var keysBySource = new HashMap<ArtifactSource, HashSet<String>>();\n    candidates.forEach((key, candidate) -> keysBySource.computeIfAbsent(candidate.source(), s -> new HashSet<>()).add(key));\n    var result = new LinkedHashMap<String, ResolvedArtifact>();\n    keysBySource.forEach((source, keys) -> result.putAll(source.load(keys).resolvedArtifactsByKey()));\n\n    // For each language not yet resolved and available only in connected mode, mark PREMIUM\n    for (var language : SonarLanguage.values()) {\n      var key = language.getPlugin().getKey();\n      if (!result.containsKey(key) && languageSupportRepository.isEnabledOnlyInConnectedMode(language)) {\n        result.put(key, ResolvedArtifact.premium());\n      }\n    }\n\n    return new ArtifactsLoadingResult(enabledLanguages, result);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/loading/strategy/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.plugin.loading.strategy;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.plugin;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/skipped/SkippedPlugin.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.skipped;\n\nimport org.sonarsource.sonarlint.core.plugin.commons.api.SkipReason;\n\npublic class SkippedPlugin {\n  private final String key;\n  private final SkipReason reason;\n\n  public SkippedPlugin(String key, SkipReason skipReason) {\n    this.key = key;\n    this.reason = skipReason;\n  }\n\n  public String getKey() {\n    return key;\n  }\n\n  public SkipReason getReason() {\n    return reason;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/skipped/SkippedPluginsNotifierService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.skipped;\n\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Set;\nimport javax.annotation.CheckForNull;\nimport org.sonarsource.sonarlint.core.analysis.AnalysisFinishedEvent;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.plugin.commons.api.SkipReason;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.plugin.DidSkipLoadingPluginParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.springframework.context.event.EventListener;\n\npublic class SkippedPluginsNotifierService {\n  private final SkippedPluginsRepository skippedPluginsRepository;\n  private final ConfigurationRepository configurationRepository;\n  private final SonarLintRpcClient client;\n  private final Set<String> alreadyNotifiedPluginKeys = new HashSet<>();\n\n  public SkippedPluginsNotifierService(SkippedPluginsRepository skippedPluginsRepository, ConfigurationRepository configurationRepository, SonarLintRpcClient client) {\n    this.skippedPluginsRepository = skippedPluginsRepository;\n    this.configurationRepository = configurationRepository;\n    this.client = client;\n  }\n\n  @EventListener\n  public void onAnalysisFinished(AnalysisFinishedEvent event) {\n    var detectedLanguages = event.getDetectedLanguages();\n    var configurationScopeId = event.getConfigurationScopeId();\n    var skippedPlugins = getSkippedPluginsToNotify(configurationScopeId);\n    if (skippedPlugins.isEmpty()) {\n      return;\n    }\n    notifyClientOfSkippedPlugins(configurationScopeId, detectedLanguages, skippedPlugins);\n  }\n\n  private void notifyClientOfSkippedPlugins(String configurationScopeId, Set<SonarLanguage> detectedLanguages, List<SkippedPlugin> skippedPlugins) {\n    detectedLanguages.stream().filter(Objects::nonNull)\n      .forEach(sonarLanguage -> skippedPlugins.stream().filter(p -> p.getKey().equals(sonarLanguage.getPlugin().getKey()))\n        .findFirst()\n        .ifPresent(skippedPlugin -> {\n          var skipReason = skippedPlugin.getReason();\n          if (skipReason instanceof SkipReason.UnsatisfiedRuntimeRequirement runtimeRequirement) {\n            var rpcLanguage = Language.valueOf(sonarLanguage.name());\n            var rpcSkipReason = runtimeRequirement.getRuntime() == SkipReason.UnsatisfiedRuntimeRequirement.RuntimeRequirement.JRE\n              ? DidSkipLoadingPluginParams.SkipReason.UNSATISFIED_JRE\n              : DidSkipLoadingPluginParams.SkipReason.UNSATISFIED_NODE_JS;\n            alreadyNotifiedPluginKeys.add(skippedPlugin.getKey());\n            client.didSkipLoadingPlugin(\n              new DidSkipLoadingPluginParams(configurationScopeId, rpcLanguage, rpcSkipReason, runtimeRequirement.getMinVersion(), runtimeRequirement.getCurrentVersion()));\n          }\n        }));\n  }\n\n  private List<SkippedPlugin> getSkippedPluginsToNotify(String configurationScopeId) {\n    var skippedPlugins = getSkippedPlugins(configurationScopeId);\n    if (skippedPlugins != null) {\n      return skippedPlugins.stream().filter(skippedPlugin -> !alreadyNotifiedPluginKeys.contains(skippedPlugin.getKey())).toList();\n    }\n    return List.of();\n  }\n\n  @CheckForNull\n  private List<SkippedPlugin> getSkippedPlugins(String configurationScopeId) {\n    return configurationRepository.getEffectiveBinding(configurationScopeId)\n      .map(binding -> skippedPluginsRepository.getSkippedPlugins(binding.connectionId()))\n      .orElseGet(skippedPluginsRepository::getSkippedEmbeddedPlugins);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/skipped/SkippedPluginsRepository.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.skipped;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport javax.annotation.CheckForNull;\n\npublic class SkippedPluginsRepository {\n  private List<SkippedPlugin> skippedEmbeddedPlugins;\n  private final Map<String, List<SkippedPlugin>> skippedPluginsByConnectionId = new HashMap<>();\n\n  public void setSkippedEmbeddedPlugins(List<SkippedPlugin> skippedPlugins) {\n    this.skippedEmbeddedPlugins = skippedPlugins;\n  }\n\n  @CheckForNull\n  public List<SkippedPlugin> getSkippedEmbeddedPlugins() {\n    return skippedEmbeddedPlugins;\n  }\n\n  public List<SkippedPlugin> getSkippedPlugins(String connectionId) {\n    return skippedPluginsByConnectionId.get(connectionId);\n  }\n\n  public void setSkippedPlugins(String connectionId, List<SkippedPlugin> skippedPlugins) {\n    skippedPluginsByConnectionId.put(connectionId, skippedPlugins);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/skipped/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.plugin.skipped;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/source/ArtifactKind.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source;\n\nimport org.sonarsource.sonarlint.core.plugin.PluginStatus;\n\n/**\n * Identifies where an artifact physically came from.\n * Used in {@link ResolvedArtifact} and {@link PluginStatus} to convey provenance.\n *\n * @see ArtifactSource the interface representing the provider of artifacts\n */\npublic enum ArtifactKind {\n\n  PLUGIN,\n\n  DEPENDENCY\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/source/ArtifactOrigin.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source;\n\nimport org.sonarsource.sonarlint.core.plugin.PluginStatus;\n\n/**\n * Identifies where an artifact physically came from.\n * Used in {@link ResolvedArtifact} and {@link PluginStatus} to convey provenance.\n *\n * @see ArtifactSource the interface representing the provider of artifacts\n */\npublic enum ArtifactOrigin {\n\n  /** Bundled inside the IDE extension distribution. */\n  EMBEDDED,\n\n  /** Downloaded on demand from binaries.sonarsource.com. */\n  ON_DEMAND,\n\n  /** Synchronized from a SonarQube Server connection. */\n  SONARQUBE_SERVER,\n\n  /** Synchronized from a SonarQube Cloud connection. */\n  SONARQUBE_CLOUD\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/source/ArtifactSource.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source;\n\nimport java.util.List;\nimport java.util.Set;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.plugin.source.binaries.BinariesArtifactSource;\nimport org.sonarsource.sonarlint.core.plugin.source.embedded.EmbeddedPluginSource;\nimport org.sonarsource.sonarlint.core.plugin.source.server.ServerPluginSource;\n\n/**\n * Represents one of the origins from which artifacts (plugins and plugin dependencies) can be\n * obtained: the client (embedded), public binaries on-demand, or a SonarQube/SonarQube Cloud\n * server (connected mode).\n *\n * <p>There are three concrete implementations:\n * <ul>\n *   <li>{@link EmbeddedPluginSource} — JARs bundled in the IDE extension.</li>\n *   <li>{@link BinariesArtifactSource} — artifacts downloadable from\n *       binaries.sonarsource.com.</li>\n *   <li>{@link ServerPluginSource} — artifacts synchronized from a connected server.</li>\n * </ul>\n *\n * <p>The two methods follow a list-then-act pattern:\n * <ul>\n *   <li>{@link #listAvailableArtifacts(Set)} is a pure query — no side effects, no downloads.</li>\n *   <li>{@link #load(Set)} is the action — given the full set of artifact keys that this source\n *       won in the priority contest, it ensures each artifact is available, scheduling background\n *       downloads when necessary. Receiving the complete set at once allows implementations (in\n *       particular {@link ServerPluginSource}) to take storage-level actions that require knowing\n *       all winners upfront (e.g. writing empty reference files for server plugins that were not\n *       selected). Keys absent from the returned {@link LoadResult} are silently ignored by the\n *       caller.</li>\n * </ul>\n */\npublic interface ArtifactSource {\n\n  /**\n   * Returns all artifacts known to this source for the given set of enabled languages, without triggering any downloads. This is a pure query.\n   * Implementations should return artifacts corresponding to enabled languages, and artifacts that are not tied to a specific language.\n   */\n  List<AvailableArtifact> listAvailableArtifacts(Set<SonarLanguage> enabledLanguages);\n\n  /**\n   * Ensures every artifact in {@code artifactKeys} is available from this source, scheduling\n   * background downloads when necessary. {@code artifactKeys} is the complete set of keys that\n   * this source won in the priority contest for the current load cycle, allowing implementations\n   * to reason about the full picture at once. A key may be absent from the returned\n   * {@link LoadResult} if this source cannot provide it. Resolved artifacts may carry the state\n   * {@link ArtifactState#DOWNLOADING} when a background download has been scheduled.\n   */\n  LoadResult load(Set<String> artifactKeys);\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/source/ArtifactState.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source;\n\npublic enum ArtifactState {\n\n  ACTIVE(\"Active\"),\n  SYNCED(\"Synced\"),\n  DOWNLOADING(\"Downloading…\"),\n  FAILED(\"Failed\"),\n  PREMIUM(\"Premium\"),\n  UNSUPPORTED(\"Unsupported\");\n\n  private final String name;\n\n  ArtifactState(String name) {\n    this.name = name;\n  }\n\n  public String getName() {\n    return name;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/source/AvailableArtifact.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source;\n\nimport java.util.Optional;\nimport java.util.Set;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarArtifact;\n\n/**\n * An artifact (plugin or plugin dependency) known to a given {@link ArtifactSource}.\n * Returned by {@link ArtifactSource#listAvailableArtifacts(Set)} as a pure query with no side\n * effects.\n *\n * <p>{@code isEnterprise} is {@code true} when the artifact is the enterprise edition of a\n * plugin on the current connection. Enterprise artifacts take priority over embedded sources\n * in {@code ConnectedArtifactsLoadingStrategy}.</p>\n */\npublic record AvailableArtifact(String key, @Nullable Version version, boolean isEnterprise, Optional<? extends SonarArtifact> sonarArtifact) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/source/LoadResult.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source;\n\nimport java.util.Map;\n\n/**\n * The result of a batch {@link ArtifactSource#load(java.util.Set)} call.\n *\n * <p>Wraps the resolved artifacts by key. Using a dedicated type instead of a raw map leaves\n * room for future fields (e.g. the set of artifacts that were available from this source but\n * not selected as winners, needed by {@code ServerPluginSource} to write empty reference files).</p>\n */\npublic record LoadResult(Map<String, ResolvedArtifact> resolvedArtifactsByKey) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/source/ResolvedArtifact.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source;\n\nimport java.nio.file.Path;\nimport java.util.concurrent.CompletableFuture;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.Version;\n\npublic record ResolvedArtifact(ArtifactState state, @Nullable Path path, @Nullable ArtifactOrigin source, @Nullable Version version,\n  @Nullable CompletableFuture<?> downloadFuture) {\n  public static ResolvedArtifact premium() {\n    return new ResolvedArtifact(ArtifactState.PREMIUM, null, null, null, null);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/source/UniqueTaskExecutor.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source;\n\nimport java.util.Map;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ExecutorService;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic class UniqueTaskExecutor {\n\n  private final Map<String, CompletableFuture<Void>> inProgress = new ConcurrentHashMap<>();\n  private final ExecutorService executor;\n\n  public UniqueTaskExecutor(ExecutorService executor) {\n    this.executor = executor;\n  }\n\n  public CompletableFuture<Void> scheduleIfAbsent(String key, Runnable task) {\n    return inProgress.computeIfAbsent(key, k -> {\n      var logOutput = SonarLintLogger.get().getTargetForCopy();\n      return CompletableFuture.runAsync(() -> {\n        SonarLintLogger.get().setTarget(logOutput);\n        try {\n          task.run();\n        } finally {\n          inProgress.remove(key);\n          SonarLintLogger.get().setTarget(null);\n        }\n      }, executor);\n    });\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/source/binaries/BinariesArtifact.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source.binaries;\n\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.Optional;\nimport java.util.Properties;\nimport java.util.Set;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarArtifact;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPluginDependency;\n\n@SuppressWarnings(\"java:S1192\")\npublic enum BinariesArtifact {\n\n  CFAMILY_PLUGIN(SonarPlugin.C_FAMILY, \"cfamily.version\", \"/CommercialDistribution/sonar-cfamily-plugin/sonar-cfamily-plugin-%s.jar\",\n    \"ondemand/sonar-cpp-plugin.jar.asc\"),\n  CSHARP_OSS(SonarPlugin.CS_OSS, \"cs.version\", \"/Distribution/sonar-csharp-plugin/sonar-csharp-plugin-%s.jar\",\n    \"ondemand/sonar-cs-plugin.jar.asc\"),\n  OMNISHARP_MONO(SonarPluginDependency.OMNISHARP_MONO, \"omnisharp.version\", \"/OmniSharp-Roslyn/%s/omnisharp-mono.tar.gz\",\n    \"ondemand/omnisharp-mono.tar.gz.asc\"),\n  OMNISHARP_NET472(SonarPluginDependency.OMNISHARP_NET472, \"omnisharp.version\", \"/OmniSharp-Roslyn/%s/omnisharp-net472.tar.gz\",\n    \"ondemand/omnisharp-net472.tar.gz.asc\"),\n  OMNISHARP_NET6(SonarPluginDependency.OMNISHARP_NET6, \"omnisharp.version\", \"/OmniSharp-Roslyn/%s/omnisharp-net6.0.tar.gz\",\n    \"ondemand/omnisharp-net6.0.tar.gz.asc\");\n\n  /** System property to override the download URL pattern for all artifacts, e.g. for testing with a mock server. */\n  public static final String PROPERTY_URL_PATTERN = \"sonarlint.ondemand.url\";\n\n  private static final String PROPERTIES_FILE = \"ondemand/plugins.properties\";\n  private static final String BINARIES_URL = \"https://binaries.sonarsource.com\";\n  private static final Properties VERSIONS = loadVersions();\n  private static final String TAR_GZ_EXTENSION = \".tar.gz\";\n\n  private final SonarArtifact artifact;\n  private final String versionKey;\n  private final String urlPattern;\n  private final String signatureResourcePath;\n\n  BinariesArtifact(SonarArtifact artifact, String versionKey, String urlPattern, String signatureResourcePath) {\n    this.artifact = artifact;\n    this.versionKey = versionKey;\n    this.urlPattern = urlPattern;\n    this.signatureResourcePath = signatureResourcePath;\n  }\n\n  public static Optional<BinariesArtifact> findByKey(@Nullable String key) {\n    return Arrays.stream(values()).filter(a -> a.artifactKey().equals(key)).findFirst();\n  }\n\n  public String version() {\n    if (!VERSIONS.containsKey(versionKey)) {\n      throw new IllegalStateException(\"Version is not set in properties for \" + artifactKey());\n    }\n    return VERSIONS.getProperty(versionKey);\n  }\n\n  public String urlPattern() {\n    var base = System.getProperty(PROPERTY_URL_PATTERN, BINARIES_URL);\n    return base + urlPattern;\n  }\n\n  public String artifactKey() {\n    return artifact.getKey();\n  }\n\n  public String signatureResourcePath() {\n    return signatureResourcePath;\n  }\n\n  public Set<SonarLanguage> getLanguages() {\n    return artifact.getLanguages();\n  }\n\n  public boolean isArchive() {\n    return urlPattern.endsWith(TAR_GZ_EXTENSION);\n  }\n\n  private static Properties loadVersions() {\n    try (var input = BinariesArtifact.class.getClassLoader().getResourceAsStream(PROPERTIES_FILE)) {\n      if (input == null) {\n        throw new IllegalStateException(\"Unable to find \" + PROPERTIES_FILE + \" on classpath\");\n      }\n      var properties = new Properties();\n      properties.load(input);\n      return properties;\n    } catch (IOException e) {\n      throw new IllegalStateException(\"Error loading plugin versions from \" + PROPERTIES_FILE, e);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/source/binaries/BinariesArtifactSource.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source.binaries;\n\nimport java.io.IOException;\nimport java.nio.file.AtomicMoveNotSupportedException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardCopyOption;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ExecutorService;\nimport org.apache.commons.io.FileUtils;\nimport org.sonarsource.sonarlint.core.UserPaths;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarArtifact;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPluginDependency;\nimport org.sonarsource.sonarlint.core.event.PluginStatusUpdateEvent;\nimport org.sonarsource.sonarlint.core.http.HttpClientProvider;\nimport org.sonarsource.sonarlint.core.plugin.PluginStatus;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactOrigin;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactSource;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactState;\nimport org.sonarsource.sonarlint.core.plugin.source.AvailableArtifact;\nimport org.sonarsource.sonarlint.core.plugin.source.LoadResult;\nimport org.sonarsource.sonarlint.core.plugin.source.ResolvedArtifact;\nimport org.sonarsource.sonarlint.core.plugin.source.UniqueTaskExecutor;\nimport org.springframework.beans.factory.annotation.Qualifier;\nimport org.springframework.context.ApplicationEventPublisher;\n\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.TarGzUtils.extractTarGz;\n\n/**\n * Artifact source backed by publicly downloadable artifacts from binaries.sonarsource.com.\n * Handles both plugins (CFamily, C# OSS) and plugin dependencies (OmniSharp distributions).\n *\n * <p>{@link #listAvailableArtifacts(Set)} is a pure query: it returns all known artifacts, with a\n * non-null {@code jarPath} only for those already cached and verified on disk. {@link #load(String)}\n * schedules a background download when the artifact is not yet cached, returning\n * {@link ArtifactState#DOWNLOADING} immediately. A {@link PluginStatusUpdateEvent} is published\n * when the download completes.</p>\n */\npublic class BinariesArtifactSource implements ArtifactSource {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final String CACHE_SUBDIR = \"ondemand-plugins\";\n\n  private final Path cacheBaseDirectory;\n  private final HttpClientProvider httpClientProvider;\n  private final BinariesSignatureVerifier signatureVerifier;\n  private final BinariesLocalCacheManager cacheManager;\n  private final ApplicationEventPublisher eventPublisher;\n  private final UniqueTaskExecutor uniqueTaskExecutor;\n\n  private final Map<String, Path> cachedArtifactPaths = new ConcurrentHashMap<>();\n\n  BinariesArtifactSource(UserPaths userPaths, HttpClientProvider httpClientProvider,\n    ApplicationEventPublisher eventPublisher, @Qualifier(\"pluginDownloadExecutor\") ExecutorService downloadExecutor,\n    BinariesSignatureVerifier signatureVerifier, BinariesLocalCacheManager binariesLocalCacheManager) {\n    this.cacheBaseDirectory = userPaths.getStorageRoot().resolve(CACHE_SUBDIR);\n    this.httpClientProvider = httpClientProvider;\n    this.signatureVerifier = signatureVerifier;\n    this.cacheManager = binariesLocalCacheManager;\n    this.eventPublisher = eventPublisher;\n    this.uniqueTaskExecutor = new UniqueTaskExecutor(downloadExecutor);\n  }\n\n  /**\n   * Returns all artifacts known to this source whose languages intersect {@code enabledLanguages}.\n   * No downloads triggered.\n   */\n  @Override\n  public List<AvailableArtifact> listAvailableArtifacts(Set<SonarLanguage> enabledLanguages) {\n    return Arrays.stream(BinariesArtifact.values())\n      .filter(artifact -> artifact.getLanguages().stream().anyMatch(enabledLanguages::contains))\n      .map(artifact -> new AvailableArtifact(artifact.artifactKey(), Version.create(artifact.version()), false,\n        SonarPlugin.findByKey(artifact.artifactKey()).<SonarArtifact>map(p -> p).or(() -> SonarPluginDependency.findByKey(artifact.artifactKey()))))\n      .toList();\n  }\n\n  @Override\n  public LoadResult load(Set<String> artifactKeys) {\n    var resolved = new HashMap<String, ResolvedArtifact>();\n    for (var key : artifactKeys) {\n      BinariesArtifact.findByKey(key).ifPresent(artifact -> {\n        var resolvedArtifact = findCachedArtifact(artifact)\n          .map(cached -> toActiveArtifact(artifact, cached.path()))\n          .orElseGet(() -> scheduleDownload(artifact));\n        resolved.put(key, resolvedArtifact);\n      });\n    }\n    return new LoadResult(resolved);\n  }\n\n  private ResolvedArtifact scheduleDownload(BinariesArtifact artifact) {\n    var downloadFuture = uniqueTaskExecutor.scheduleIfAbsent(artifact.artifactKey(), () -> downloadAndFireEvent(artifact));\n    return new ResolvedArtifact(ArtifactState.DOWNLOADING, null, null, null, downloadFuture);\n  }\n\n  private Optional<ResolvedArtifact> findCachedArtifact(BinariesArtifact artifact) {\n    var artifactKey = artifact.artifactKey();\n    var cached = cachedArtifactPaths.get(artifactKey);\n    if (cached != null && Files.exists(cached)) {\n      return Optional.of(toActiveArtifact(artifact, cached));\n    }\n    var pluginPath = buildArtifactLocalPath(artifact);\n    if (Files.exists(pluginPath)) {\n      if (isValidCache(pluginPath, artifact)) {\n        cachedArtifactPaths.put(artifactKey, pluginPath);\n        return Optional.of(toActiveArtifact(artifact, pluginPath));\n      }\n      LOG.warn(\"Invalid cached artifact {}, will re-download\", artifactKey);\n      FileUtils.deleteQuietly(pluginPath.toFile());\n    }\n    return Optional.empty();\n  }\n\n  /**\n   * Returns {@code true} when the cached artifact at {@code pluginPath} is considered valid and\n   * does not need to be re-downloaded.\n   *\n   * <p>For <b>JAR artifacts</b> the PGP signature is re-verified against the file on disk.\n   *\n   * <p>For <b>archive artifacts</b> (OmniSharp tar.gz distributions), {@code pluginPath} is the\n   * extracted directory. The PGP signature was already verified against the original archive at\n   * download time; the archive is deleted after extraction, so the signature cannot be re-checked.\n   * A non-empty directory is used as the completion marker instead.\n   */\n  private boolean isValidCache(Path pluginPath, BinariesArtifact artifact) {\n    if (artifact.isArchive()) {\n      try (var entries = Files.list(pluginPath)) {\n        return entries.findFirst().isPresent();\n      } catch (IOException e) {\n        LOG.warn(\"Could not read cached archive directory for {}\", artifact.artifactKey(), e);\n        return false;\n      }\n    }\n    return signatureVerifier.verify(pluginPath, artifact);\n  }\n\n  private static ResolvedArtifact toActiveArtifact(BinariesArtifact artifact, Path artifactPath) {\n    return new ResolvedArtifact(ArtifactState.ACTIVE, artifactPath, ArtifactOrigin.ON_DEMAND, Version.create(artifact.version()), null);\n  }\n\n  private Path downloadAndCache(BinariesArtifact artifact) throws IOException {\n    var pluginPath = buildArtifactLocalPath(artifact);\n    downloadAndVerify(artifact, pluginPath);\n    cacheManager.cleanupOldVersions(cacheBaseDirectory.resolve(artifact.artifactKey()), artifact.version());\n    cachedArtifactPaths.put(artifact.artifactKey(), pluginPath);\n    return pluginPath;\n  }\n\n  private void downloadAndFireEvent(BinariesArtifact artifact) {\n    try {\n      var path = downloadAndCache(artifact);\n      eventPublisher.publishEvent(new PluginStatusUpdateEvent(null, createSuccessStatuses(artifact, path)));\n    } catch (Exception e) {\n      LOG.error(\"Failed to download artifact with key {}\", artifact.artifactKey(), e);\n      eventPublisher.publishEvent(new PluginStatusUpdateEvent(null, createdFailedStatuses(artifact)));\n    }\n  }\n\n  private static List<PluginStatus> createSuccessStatuses(BinariesArtifact artifact, Path pluginPath) {\n    if (artifact.isArchive()) {\n      return List.of(PluginStatus.forCompanion(artifact.artifactKey(), ArtifactState.ACTIVE, ArtifactOrigin.ON_DEMAND, pluginPath, null));\n    }\n    var version = Version.create(artifact.version());\n    return artifact.getLanguages().stream()\n      .map(language -> PluginStatus.forLanguage(language, ArtifactState.ACTIVE, ArtifactOrigin.ON_DEMAND, version, null, pluginPath, null))\n      .toList();\n  }\n\n  private static List<PluginStatus> createdFailedStatuses(BinariesArtifact artifact) {\n    if (artifact.isArchive()) {\n      return List.of(PluginStatus.forCompanion(artifact.artifactKey(), ArtifactState.FAILED, null, null, null));\n    }\n    return artifact.getLanguages().stream()\n      .map(PluginStatus::failed)\n      .toList();\n  }\n\n  public Map<String, String> getOmnisharpExtraProperties() {\n    var properties = new HashMap<String, String>();\n    putIfCached(properties, SonarPluginDependency.OMNISHARP_MONO.getKey(), \"sonar.cs.internal.omnisharpMonoLocation\");\n    putIfCached(properties, SonarPluginDependency.OMNISHARP_NET472.getKey(), \"sonar.cs.internal.omnisharpWinLocation\");\n    putIfCached(properties, SonarPluginDependency.OMNISHARP_NET6.getKey(), \"sonar.cs.internal.omnisharpNet6Location\");\n    return properties;\n  }\n\n  private void putIfCached(Map<String, String> properties, String artifactKey, String propertyKey) {\n    var path = cachedArtifactPaths.get(artifactKey);\n    if (path != null && Files.exists(path)) {\n      properties.put(propertyKey, path.toString());\n    }\n  }\n\n  private void downloadAndVerify(BinariesArtifact artifact, Path targetPath) throws IOException {\n    Files.createDirectories(targetPath.getParent());\n    var tempFile = targetPath.getParent().resolve(targetPath.getFileName() + \".tmp\");\n    try {\n      downloadArtifact(artifact, tempFile);\n      if (!signatureVerifier.verify(tempFile, artifact)) {\n        throw new IOException(\"Signature verification failed for \" + artifact.artifactKey());\n      }\n      if (artifact.isArchive()) {\n        var tempExtractDir = targetPath.getParent().resolve(targetPath.getFileName() + \".extracting\");\n        try {\n          Files.createDirectories(tempExtractDir);\n          extractTarGz(tempFile, tempExtractDir);\n          moveAtomically(tempExtractDir, targetPath);\n        } finally {\n          FileUtils.deleteQuietly(tempExtractDir.toFile());\n        }\n      } else {\n        moveAtomically(tempFile, targetPath);\n      }\n      LOG.info(\"Successfully downloaded {} plugin version {}\", artifact.artifactKey(), artifact.version());\n    } finally {\n      Files.deleteIfExists(tempFile);\n    }\n  }\n\n  private static void moveAtomically(Path source, Path target) throws IOException {\n    try {\n      Files.move(source, target, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);\n    } catch (AtomicMoveNotSupportedException e) {\n      Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);\n    }\n  }\n\n  private void downloadArtifact(BinariesArtifact artifact, Path destination) throws IOException {\n    var url = String.format(artifact.urlPattern(), artifact.version());\n    var httpClient = httpClientProvider.getHttpClientWithoutAuth();\n    LOG.info(\"Downloading {} plugin version {} from {}\", artifact.artifactKey(), artifact.version(), url);\n    try (var response = httpClient.get(url)) {\n      if (!response.isSuccessful()) {\n        throw new IOException(\"Failed to download plugin: HTTP \" + response.code());\n      }\n      try (var inputStream = response.bodyAsStream()) {\n        FileUtils.copyInputStreamToFile(inputStream, destination.toFile());\n      }\n    }\n  }\n\n  private Path buildArtifactLocalPath(BinariesArtifact artifact) {\n    var artifactKey = artifact.artifactKey();\n    var version = artifact.version();\n    var base = cacheBaseDirectory.resolve(artifactKey).resolve(version);\n    if (artifact.isArchive()) {\n      return base;\n    }\n    return base.resolve(String.format(\"sonar-%s-plugin-%s.jar\", artifactKey, version));\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/source/binaries/BinariesLocalCacheManager.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source.binaries;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport org.apache.commons.io.FileUtils;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\n/**\n * Manages cleanup of old plugin versions from the cache.\n * Deletes version directories not modified within the last 60 days, skipping the current version.\n */\npublic class BinariesLocalCacheManager {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final long RETENTION_DAYS = 60;\n\n  /**\n   * Cleans up old plugin versions from the cache directory.\n   *\n   * @param cacheDirectory the base cache directory (e.g., {storageRoot}/cache/ondemand-plugins/cpp)\n   * @param currentVersion the current version to keep (not deleted)\n   */\n  void cleanupOldVersions(Path cacheDirectory, String currentVersion) {\n    if (!Files.isDirectory(cacheDirectory)) {\n      return;\n    }\n\n    var cutoffTime = Instant.now().minus(RETENTION_DAYS, ChronoUnit.DAYS);\n\n    try (var stream = Files.list(cacheDirectory)) {\n      stream.filter(Files::isDirectory)\n        .filter(versionDir -> !versionDir.getFileName().toString().equals(currentVersion))\n        .filter(versionDir -> isOlderThan(versionDir, cutoffTime))\n        .forEach(BinariesLocalCacheManager::deleteVersionDirectory);\n    } catch (IOException e) {\n      LOG.debug(\"Error cleaning up old plugin versions\", e);\n    }\n  }\n\n  private static boolean isOlderThan(Path directory, Instant cutoffTime) {\n    try {\n      var lastModified = Files.getLastModifiedTime(directory).toInstant();\n      return lastModified.isBefore(cutoffTime);\n    } catch (IOException e) {\n      LOG.debug(\"Failed to read last-modified time for plugin cache directory: {}\", directory, e);\n      return false;\n    }\n  }\n\n  private static void deleteVersionDirectory(Path directory) {\n    try {\n      FileUtils.deleteDirectory(directory.toFile());\n      LOG.debug(\"Deleted old plugin version: {}\", directory.getFileName());\n    } catch (Exception e) {\n      LOG.debug(\"Failed to delete old version directory: {}\", directory, e);\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/source/binaries/BinariesSignatureVerifier.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source.binaries;\n\nimport java.io.BufferedInputStream;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Path;\nimport org.bouncycastle.jce.provider.BouncyCastleProvider;\nimport org.bouncycastle.openpgp.PGPCompressedData;\nimport org.bouncycastle.openpgp.PGPException;\nimport org.bouncycastle.openpgp.PGPObjectFactory;\nimport org.bouncycastle.openpgp.PGPPublicKeyRingCollection;\nimport org.bouncycastle.openpgp.PGPSignatureList;\nimport org.bouncycastle.openpgp.PGPUtil;\nimport org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;\nimport org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\n/**\n * Verifies the PGP signature of downloaded artifacts using the SonarSource public key.\n */\npublic class BinariesSignatureVerifier {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final String SONAR_PUBLIC_KEY = \"ondemand/sonarsource-public.key\";\n  private static final BouncyCastleProvider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider();\n\n  boolean verify(Path artifactFile, BinariesArtifact artifact) {\n    return verify(artifactFile, artifact.signatureResourcePath());\n  }\n\n  boolean verify(Path artifactFile, String signatureResourcePath) {\n    var keyRing = loadPublicKeyRing();\n    if (keyRing == null) {\n      return false;\n    }\n    var isValid = verifyPgpSignature(artifactFile, signatureResourcePath, keyRing);\n    if (isValid) {\n      LOG.debug(\"Artifact file signature verified successfully\");\n    }\n    return isValid;\n  }\n\n  private PGPPublicKeyRingCollection loadPublicKeyRing() {\n    try (var keyStream = getClass().getClassLoader().getResourceAsStream(SONAR_PUBLIC_KEY)) {\n      if (keyStream == null) {\n        throw new FileNotFoundException(\"PGP key not found in resources: \" + SONAR_PUBLIC_KEY);\n      }\n\n      var decoder = PGPUtil.getDecoderStream(new BufferedInputStream(keyStream));\n      return new PGPPublicKeyRingCollection(decoder, new JcaKeyFingerprintCalculator());\n    } catch (IOException | PGPException e) {\n      LOG.error(\"Error loading public key ring\", e);\n      return null;\n    }\n  }\n\n  private InputStream loadBundledSignature(String signatureResourcePath) {\n    return getClass().getClassLoader().getResourceAsStream(signatureResourcePath);\n  }\n\n  private boolean verifyPgpSignature(Path dataFile, String signatureResourcePath, PGPPublicKeyRingCollection keyRing) {\n    try (var signatureStream = loadBundledSignature(signatureResourcePath)) {\n      if (signatureStream == null) {\n        LOG.error(\"Could not find bundled signature at resource path: {}\", signatureResourcePath);\n        return false;\n      }\n\n      try (var decoderStream = PGPUtil.getDecoderStream(new BufferedInputStream(signatureStream))) {\n        var pgpFact = new PGPObjectFactory(decoderStream, new JcaKeyFingerprintCalculator());\n\n        // Handle both compressed and uncompressed signature formats\n        var signatureList = extractSignatureList(pgpFact);\n        if (signatureList == null || signatureList.isEmpty()) {\n          LOG.error(\"No signatures found in signature file\");\n          return false;\n        }\n\n        var signature = signatureList.get(0);\n        var publicKey = keyRing.getPublicKey(signature.getKeyID());\n        if (publicKey == null) {\n          LOG.error(\"Public key not found for signature keyID={}\", signature.getKeyID());\n          return false;\n        }\n\n        signature.init(new JcaPGPContentVerifierBuilderProvider().setProvider(BOUNCY_CASTLE_PROVIDER), publicKey);\n\n        try (var dataIn = new FileInputStream(dataFile.toFile())) {\n          var buffer = new byte[8192];\n          int bytesRead;\n          while ((bytesRead = dataIn.read(buffer)) != -1) {\n            signature.update(buffer, 0, bytesRead);\n          }\n        }\n\n        return signature.verify();\n      }\n    } catch (IOException | PGPException e) {\n      LOG.error(\"Error verifying PGP signature\", e);\n      return false;\n    }\n  }\n\n  private static PGPSignatureList extractSignatureList(PGPObjectFactory pgpFact) {\n    try {\n      var obj = pgpFact.nextObject();\n      if (obj instanceof PGPCompressedData compressedData) {\n        var innerFactory = new PGPObjectFactory(compressedData.getDataStream(), new JcaKeyFingerprintCalculator());\n        var innerObj = innerFactory.nextObject();\n        if (innerObj instanceof PGPSignatureList signatureList) {\n          return signatureList;\n        }\n      } else if (obj instanceof PGPSignatureList signatureList) {\n        return signatureList;\n      }\n    } catch (IOException | PGPException e) {\n      LOG.error(\"Error extracting signature list\", e);\n    }\n    return null;\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/source/binaries/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.plugin.source.binaries;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/source/embedded/EmbeddedPluginSource.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source.embedded;\n\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\nimport org.sonarsource.sonarlint.core.plugin.PluginJarUtils;\nimport org.sonarsource.sonarlint.core.plugin.commons.loading.SonarPluginManifest;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactOrigin;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactSource;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactState;\nimport org.sonarsource.sonarlint.core.plugin.source.AvailableArtifact;\nimport org.sonarsource.sonarlint.core.plugin.source.LoadResult;\nimport org.sonarsource.sonarlint.core.plugin.source.ResolvedArtifact;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\n\n/**\n * Artifact source backed by JARs physically bundled (embedded) in the IDE client's distribution.\n * Does not do any filtering, trusts what the client provides. No downloads are ever triggered.\n *\n * <p>Use {@link #forStandalone(InitializeParams)} or {@link #forConnected(InitializeParams)} to\n * obtain an instance scoped to the appropriate mode.</p>\n */\npublic class EmbeddedPluginSource implements ArtifactSource {\n\n  private final Map<String, Path> embeddedPathsByKey;\n\n  private EmbeddedPluginSource(Map<String, Path> embeddedPathsByKey) {\n    this.embeddedPathsByKey = embeddedPathsByKey;\n  }\n\n  /**\n   * Returns a source backed by the standalone embedded plugin paths from {@code params}.\n   */\n  public static EmbeddedPluginSource forStandalone(InitializeParams params) {\n    return new EmbeddedPluginSource(buildPluginKeyToPathMap(params.getEmbeddedPluginPaths()));\n  }\n\n  /**\n   * Returns a source backed by the connected-mode embedded plugin paths from {@code params}.\n   */\n  public static EmbeddedPluginSource forConnected(InitializeParams params) {\n    return new EmbeddedPluginSource(params.getConnectedModeEmbeddedPluginPathsByKey());\n  }\n\n  /**\n   * Returns all artifacts physically embedded in the IDE client. No downloads are ever triggered.\n   * We ignore the enabledLanguages parameter for this source. We trust the clients to provide sensible embedded artifacts.\n   */\n  @Override\n  public List<AvailableArtifact> listAvailableArtifacts(Set<SonarLanguage> enabledLanguages) {\n    var result = new ArrayList<AvailableArtifact>();\n    for (var entry : embeddedPathsByKey.entrySet()) {\n      result.add(toAvailableArtifact(entry.getKey(), entry.getValue()));\n    }\n    return result;\n  }\n\n  @Override\n  public LoadResult load(Set<String> artifactKeys) {\n    var resolved = new HashMap<String, ResolvedArtifact>();\n    for (var key : artifactKeys) {\n      var path = embeddedPathsByKey.get(key);\n      if (path != null) {\n        resolved.put(key, new ResolvedArtifact(ArtifactState.ACTIVE, path, ArtifactOrigin.EMBEDDED, PluginJarUtils.readVersion(path), null));\n      }\n    }\n    return new LoadResult(resolved);\n  }\n\n  private static AvailableArtifact toAvailableArtifact(String key, Path path) {\n    var sonarPlugin = SonarPlugin.findByKey(key);\n    return new AvailableArtifact(key, PluginJarUtils.readVersion(path), SonarPlugin.isEnterpriseVariant(key), sonarPlugin);\n  }\n\n  private static Map<String, Path> buildPluginKeyToPathMap(Set<Path> embeddedPaths) {\n    return embeddedPaths.stream()\n      .collect(Collectors.toMap(\n        p -> SonarPluginManifest.fromJar(p).getKey(),\n        Function.identity(),\n        (existing, duplicate) -> {\n          throw new IllegalArgumentException(\"Multiple embedded plugins found with the same key for paths: \" + existing + \" and \" + duplicate);\n        }));\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/source/embedded/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.plugin.source.embedded;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/source/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.plugin.source;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/source/server/ServerPluginDownloader.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source.server;\n\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.commons.ConnectionKind;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.event.PluginStatusUpdateEvent;\nimport org.sonarsource.sonarlint.core.plugin.PluginJarUtils;\nimport org.sonarsource.sonarlint.core.plugin.PluginStatus;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactOrigin;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactState;\nimport org.sonarsource.sonarlint.core.plugin.source.UniqueTaskExecutor;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.serverapi.plugins.ServerPlugin;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.springframework.context.ApplicationEventPublisher;\n\n/**\n * Handles the background downloading of server plugins (both language and companion plugins).\n * Manages concurrent requests deduplication and publishes plugin status updates upon completion.\n */\npublic class ServerPluginDownloader {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final StorageService storageService;\n  private final SonarQubeClientManager sonarQubeClientManager;\n  private final ConnectionConfigurationRepository connectionConfigurationRepository;\n  private final ApplicationEventPublisher eventPublisher;\n  private final UniqueTaskExecutor uniqueTaskExecutor;\n\n  public ServerPluginDownloader(StorageService storageService, SonarQubeClientManager sonarQubeClientManager,\n    ConnectionConfigurationRepository connectionConfigurationRepository, ApplicationEventPublisher eventPublisher,\n    ExecutorService downloadExecutor) {\n    this.storageService = storageService;\n    this.sonarQubeClientManager = sonarQubeClientManager;\n    this.connectionConfigurationRepository = connectionConfigurationRepository;\n    this.eventPublisher = eventPublisher;\n    this.uniqueTaskExecutor = new UniqueTaskExecutor(downloadExecutor);\n  }\n\n  public CompletableFuture<Void> schedulePluginDownload(String connectionId, ServerPlugin serverPlugin) {\n    var sonarPlugin = SonarPlugin.findByKey(serverPlugin.getKey());\n    return sonarPlugin.isPresent() ? scheduleSonarPluginDownload(connectionId, serverPlugin, sonarPlugin.get())\n      : scheduleUnknownPluginDownload(connectionId, serverPlugin);\n  }\n\n  private CompletableFuture<Void> scheduleSonarPluginDownload(String connectionId, ServerPlugin serverPlugin, SonarPlugin sonarPlugin) {\n    var progressKey = connectionId + \":\" + serverPlugin.getKey();\n    return uniqueTaskExecutor.scheduleIfAbsent(progressKey, () -> asyncDownload(connectionId, serverPlugin, sonarPlugin));\n  }\n\n  private CompletableFuture<Void> scheduleUnknownPluginDownload(String connectionId, ServerPlugin plugin) {\n    var progressKey = connectionId + \":\" + plugin.getKey();\n    return uniqueTaskExecutor.scheduleIfAbsent(progressKey, () -> asyncUnknownPluginDownload(connectionId, plugin));\n  }\n\n  private void asyncDownload(String connectionId, ServerPlugin serverPlugin, SonarPlugin sonarPlugin) {\n    try {\n      downloadPluginAndFireEvent(connectionId, serverPlugin, sonarPlugin);\n    } catch (Exception e) {\n      LOG.error(\"Failed to download plugin '{}' for connection '{}'\", serverPlugin.getKey(), connectionId, e);\n      fireFailedEvent(connectionId, sonarPlugin);\n    }\n  }\n\n  private void asyncUnknownPluginDownload(String connectionId, ServerPlugin plugin) {\n    try {\n      downloadUnknownPluginAndFireEvent(connectionId, plugin);\n    } catch (Exception e) {\n      LOG.error(\"Failed to download companion plugin '{}' for connection '{}'\", plugin.getKey(), connectionId, e);\n      eventPublisher.publishEvent(new PluginStatusUpdateEvent(connectionId,\n        List.of(PluginStatus.forCompanion(plugin.getKey(), ArtifactState.FAILED, null, null, null))));\n    }\n  }\n\n  private void downloadPluginAndFireEvent(String connectionId, ServerPlugin serverPlugin, SonarPlugin sonarPlugin) {\n    var state = downloadPluginSync(connectionId, serverPlugin);\n    if (state == ArtifactState.SYNCED) {\n      var pluginKey = serverPlugin.getKey();\n      var storedPath = storageService.connection(connectionId).plugins().getStoredPluginPathsByKey().get(pluginKey);\n      var source = sourceFor(connectionId);\n      var version = storedPath != null ? PluginJarUtils.readVersion(storedPath) : null;\n      var statuses = sonarPlugin.getLanguages().stream()\n        .map(l -> PluginStatus.forLanguage(l, ArtifactState.SYNCED, source, version, null, storedPath, null))\n        .toList();\n      eventPublisher.publishEvent(new PluginStatusUpdateEvent(connectionId, statuses));\n    } else {\n      fireFailedEvent(connectionId, sonarPlugin);\n    }\n  }\n\n  private void downloadUnknownPluginAndFireEvent(String connectionId, ServerPlugin plugin) {\n    var state = downloadPluginSync(connectionId, plugin);\n    var storedPath = state == ArtifactState.SYNCED\n      ? storageService.connection(connectionId).plugins().getStoredPluginPathsByKey().get(plugin.getKey())\n      : null;\n    var source = sourceFor(connectionId);\n    eventPublisher.publishEvent(new PluginStatusUpdateEvent(connectionId,\n      List.of(PluginStatus.forCompanion(plugin.getKey(), state, source, storedPath, null))));\n  }\n\n  ArtifactState downloadPluginSync(String connectionId, ServerPlugin serverPlugin) {\n    var pluginKey = serverPlugin.getKey();\n    LOG.info(\"[SYNC] Downloading plugin '{}'\", serverPlugin.getFilename());\n    try {\n      var cancelMonitor = new SonarLintCancelMonitor();\n      sonarQubeClientManager.withActiveClient(connectionId,\n        api -> api.plugins().getPlugin(pluginKey,\n          binary -> storageService.connection(connectionId).plugins().store(serverPlugin, binary),\n          cancelMonitor));\n      return ArtifactState.SYNCED;\n    } catch (Exception e) {\n      LOG.error(\"Failed to download plugin '{}' for connection '{}'\", pluginKey, connectionId, e);\n      return ArtifactState.FAILED;\n    }\n  }\n\n  private void fireFailedEvent(String connectionId, SonarPlugin sonarPlugin) {\n    var statuses = sonarPlugin.getLanguages().stream()\n      .map(PluginStatus::failed)\n      .toList();\n    eventPublisher.publishEvent(new PluginStatusUpdateEvent(connectionId, statuses));\n  }\n\n  public ArtifactOrigin sourceFor(String connectionId) {\n    var connection = connectionConfigurationRepository.getConnectionById(connectionId);\n    var isSonarQubeCloud = connection != null && connection.getKind() == ConnectionKind.SONARCLOUD;\n    return isSonarQubeCloud ? ArtifactOrigin.SONARQUBE_CLOUD : ArtifactOrigin.SONARQUBE_SERVER;\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/source/server/ServerPluginSource.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source.server;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.plugins.EnterpriseReplacement;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\nimport org.sonarsource.sonarlint.core.plugin.PluginJarUtils;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactOrigin;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactSource;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactState;\nimport org.sonarsource.sonarlint.core.plugin.source.AvailableArtifact;\nimport org.sonarsource.sonarlint.core.plugin.source.LoadResult;\nimport org.sonarsource.sonarlint.core.plugin.source.ResolvedArtifact;\nimport org.sonarsource.sonarlint.core.serverapi.plugins.ServerPlugin;\nimport org.sonarsource.sonarlint.core.serverconnection.StoredPlugin;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\n\n/**\n * Artifact source backed by a specific SonarQube Server or SonarQube Cloud connection.\n *\n * <p>One instance is created per connection. The connection identifier is fixed at construction\n * time; no connection parameter is passed at call time.</p>\n *\n * <p>{@link #listAvailableArtifacts(Set)} queries the server for available plugins and filters\n * them by enabled languages and SonarLint compatibility. Language plugins are included if any of\n * their languages is enabled. Companion/unknown plugins are included if they are marked\n * {@code sonarLintSupported} by the server.</p>\n *\n * <p>The {@link AvailableArtifact#isEnterprise()} flag is set to {@code true} when the server\n * is serving the enterprise edition of a plugin on this connection:\n * <ul>\n *   <li>Different-key enterprise variants ({@code csharpenterprise}, {@code vbnetenterprise}):\n *       always enterprise.</li>\n *   <li>Same-key enterprise plugins (GO, IAC, TEXT): enterprise when the connection qualifies\n *       (SonarQube Cloud, or SonarQube Server &ge; the minimum version from\n *       {@link EnterpriseReplacement}).</li>\n * </ul>\n *\n * <p>{@link #load} resolves a plugin: it returns the stored artifact when already on disk with a\n * matching hash, or schedules a background download (returning\n * {@link ArtifactState#DOWNLOADING}). It does <em>not</em> apply the skip-list check — that is\n * the responsibility of the loading strategy that owns this source.</p>\n */\npublic class ServerPluginSource implements ArtifactSource {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final String PLUGIN_FETCH_ERROR = \"Could not fetch server plugin list for connection '{}'\";\n\n  private final String connectionId;\n  private final StorageService storageService;\n  private final ServerPluginsCache serverPluginsCache;\n  private final ServerPluginDownloader downloader;\n\n  public ServerPluginSource(String connectionId, StorageService storageService,\n    ServerPluginsCache serverPluginsCache, ServerPluginDownloader downloader) {\n    this.connectionId = connectionId;\n    this.storageService = storageService;\n    this.serverPluginsCache = serverPluginsCache;\n    this.downloader = downloader;\n  }\n\n  /**\n   * Returns all server plugins that are eligible:\n   * <ul>\n   *   <li>if a plugin is known (see {@link SonarPlugin}), at least one of its languages should be currently enabled\n   *   <li>if it is unknown, it needs to be SonarLint-Supported\n   * </ul>\n   */\n  @Override\n  public List<AvailableArtifact> listAvailableArtifacts(Set<SonarLanguage> enabledLanguages) {\n    return fetchServerPluginsSafely().stream()\n      .filter(plugin -> isEligible(plugin, enabledLanguages))\n      .map(plugin -> new AvailableArtifact(plugin.getKey(), null, isEnterprisePlugin(plugin.getKey()), SonarPlugin.findByKey(plugin.getKey())))\n      .toList();\n  }\n\n  private static boolean isEligible(ServerPlugin plugin, Set<SonarLanguage> enabledLanguages) {\n    return SonarPlugin.findByKey(plugin.getKey())\n      .map(sonarPlugin -> {\n        var languages = sonarPlugin.getLanguages();\n        return !languages.isEmpty() && languages.stream().anyMatch(lang -> lang.shouldSyncInConnectedMode() && enabledLanguages.contains(lang));\n      })\n      .orElseGet(plugin::isSonarLintSupported);\n  }\n\n  /**\n   * Returns {@code true} if the given plugin key is served as its enterprise edition on this\n   * connection.\n   *\n   * <p>Different-key enterprise variants ({@code csharpenterprise}, {@code vbnetenterprise}) are\n   * always enterprise. Same-key enterprise plugins (GO, IAC, TEXT) are enterprise when the\n   * connection is SonarQube Cloud, or when the stored server version meets the minimum.</p>\n   */\n  private boolean isEnterprisePlugin(String key) {\n    if (SonarPlugin.isEnterpriseVariant(key)) {\n      return true;\n    }\n    return SonarPlugin.findByKey(key)\n      .flatMap(SonarPlugin::getEnterpriseReplacement)\n      .map(this::hasEnterpriseReplacement)\n      .orElse(false);\n  }\n\n  private boolean hasEnterpriseReplacement(EnterpriseReplacement replacement) {\n    var source = downloader.sourceFor(connectionId);\n    if (source == ArtifactOrigin.SONARQUBE_CLOUD) {\n      return replacement.onSonarQubeCloud();\n    }\n    var replacementStartingInSonarQubeServerVersion = replacement.startingSonarQubeServerVersion();\n    return replacementStartingInSonarQubeServerVersion != null && storageService.connection(connectionId).serverInfo().read()\n      .map(info -> info.version().compareTo(replacementStartingInSonarQubeServerVersion) >= 0)\n      .orElse(false);\n  }\n\n  @Override\n  public LoadResult load(Set<String> artifactKeys) {\n    var storedPlugins = loadStoredPlugins();\n    var resolved = new HashMap<String, ResolvedArtifact>();\n    var serverAccessible = false;\n    List<ServerPlugin> expectedServerPlugins = List.of();\n    try {\n      var serverPluginsByKey = serverPluginsCache.getPlugins(connectionId).orElse(List.of())\n        .stream().collect(Collectors.toMap(ServerPlugin::getKey, Function.identity()));\n      serverAccessible = true;\n      for (var key : artifactKeys) {\n        var serverPlugin = serverPluginsByKey.get(key);\n        if (serverPlugin != null) {\n          resolved.put(key, resolveFromStorageOrSchedule(serverPlugin, storedPlugins, key));\n        } else {\n          findStoredPlugin(key, storedPlugins).map(s -> toResolvedArtifact(s.getJarPath()))\n            .ifPresent(r -> resolved.put(key, r));\n        }\n      }\n      expectedServerPlugins = artifactKeys.stream()\n        .filter(serverPluginsByKey::containsKey)\n        .map(key -> {\n          var serverPlugin = serverPluginsByKey.get(key);\n          // If the stored file already has the correct hash but a different filename (e.g. in tests),\n          // use the stored filename so cleanUpUnknownPlugins does not delete it.\n          return findStoredPlugin(key, storedPlugins)\n            .filter(stored -> stored.hasSameHash(serverPlugin))\n            .map(stored -> new ServerPlugin(key, serverPlugin.getHash(), stored.getJarPath().getFileName().toString(), serverPlugin.isSonarLintSupported()))\n            .orElse(serverPlugin);\n        })\n        .toList();\n    } catch (Exception e) {\n      LOG.debug(PLUGIN_FETCH_ERROR, connectionId);\n      for (var key : artifactKeys) {\n        findStoredPlugin(key, storedPlugins).map(s -> toResolvedArtifact(s.getJarPath()))\n          .ifPresent(r -> resolved.put(key, r));\n      }\n    }\n    if (serverAccessible) {\n      storageService.connection(connectionId).plugins().cleanUpUnknownPlugins(expectedServerPlugins);\n    }\n    return new LoadResult(resolved);\n  }\n\n  private ResolvedArtifact resolveFromStorageOrSchedule(ServerPlugin serverPlugin,\n    Map<String, StoredPlugin> storedPlugins, String pluginKey) {\n    var stored = findStoredPlugin(pluginKey, storedPlugins);\n    if (stored.isPresent() && stored.get().hasSameHash(serverPlugin)) {\n      LOG.debug(\"[SYNC] Code analyzer '{}' is up-to-date. Skip downloading it.\", pluginKey);\n      return toResolvedArtifact(stored.get().getJarPath());\n    }\n    var downloadFuture = downloader.schedulePluginDownload(connectionId, serverPlugin);\n    return new ResolvedArtifact(ArtifactState.DOWNLOADING, null, null, null, downloadFuture);\n  }\n\n  private static Optional<StoredPlugin> findStoredPlugin(String pluginKey, Map<String, StoredPlugin> storedPlugins) {\n    return Optional.ofNullable(storedPlugins.get(pluginKey))\n      .filter(plugin -> Files.exists(plugin.getJarPath()));\n  }\n\n  private ResolvedArtifact toResolvedArtifact(Path pluginPath) {\n    return new ResolvedArtifact(ArtifactState.SYNCED, pluginPath, downloader.sourceFor(connectionId), PluginJarUtils.readVersion(pluginPath), null);\n  }\n\n  private Map<String, StoredPlugin> loadStoredPlugins() {\n    try {\n      return storageService.connection(connectionId).plugins().getStoredPluginsByKey();\n    } catch (Exception e) {\n      return Collections.emptyMap();\n    }\n  }\n\n  private List<ServerPlugin> fetchServerPluginsSafely() {\n    try {\n      return serverPluginsCache.getPlugins(connectionId).orElse(List.of());\n    } catch (Exception e) {\n      LOG.debug(PLUGIN_FETCH_ERROR, connectionId);\n      return storedPluginsAsServerPlugins();\n    }\n  }\n\n  private List<ServerPlugin> storedPluginsAsServerPlugins() {\n    return loadStoredPlugins().values().stream()\n      .filter(s -> Files.exists(s.getJarPath()))\n      .map(s -> new ServerPlugin(s.getKey(), s.getHash(), s.getJarPath().getFileName().toString(), true))\n      .toList();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/source/server/ServerPluginsCache.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source.server;\n\nimport com.google.common.cache.Cache;\nimport com.google.common.cache.CacheBuilder;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationRemovedEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationUpdatedEvent;\nimport org.sonarsource.sonarlint.core.serverapi.plugins.ServerPlugin;\nimport org.springframework.context.event.EventListener;\n\npublic class ServerPluginsCache {\n\n  private final SonarQubeClientManager sonarQubeClientManager;\n\n  private final Cache<String, Optional<List<ServerPlugin>>> cache = CacheBuilder.newBuilder()\n    .expireAfterWrite(1, TimeUnit.HOURS)\n    .build();\n\n  public ServerPluginsCache(SonarQubeClientManager sonarQubeClientManager) {\n    this.sonarQubeClientManager = sonarQubeClientManager;\n  }\n\n  public Optional<List<ServerPlugin>> getPlugins(String connectionId) {\n    try {\n      return cache.get(connectionId, () -> fetch(connectionId));\n    } catch (ExecutionException e) {\n      throw new IllegalStateException(e.getCause());\n    }\n  }\n\n  private Optional<List<ServerPlugin>> fetch(String connectionId) {\n    return sonarQubeClientManager.withActiveClientAndReturn(connectionId,\n      api -> api.plugins().getInstalled(new SonarLintCancelMonitor()));\n  }\n\n  @EventListener\n  public void connectionRemoved(ConnectionConfigurationRemovedEvent event) {\n    cache.invalidate(event.removedConnectionId());\n  }\n\n  @EventListener\n  public void connectionUpdated(ConnectionConfigurationUpdatedEvent event) {\n    cache.invalidate(event.updatedConnectionId());\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/plugin/source/server/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.plugin.source.server;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/progress/ClientAwareProgressMonitor.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.progress;\n\nimport java.util.UUID;\nimport org.jetbrains.annotations.Nullable;\nimport org.sonarsource.sonarlint.core.commons.progress.ProgressMonitor;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.progress.ProgressEndNotification;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.progress.ProgressUpdateNotification;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.progress.ReportProgressParams;\n\npublic class ClientAwareProgressMonitor implements ProgressMonitor {\n  private final SonarLintRpcClient client;\n  private final UUID taskId;\n  private final SonarLintCancelMonitor cancelMonitor;\n\n  public ClientAwareProgressMonitor(SonarLintRpcClient client, UUID taskId, SonarLintCancelMonitor cancelMonitor) {\n    this.client = client;\n    this.taskId = taskId;\n    this.cancelMonitor = cancelMonitor;\n  }\n\n  @Override\n  public void notifyProgress(@Nullable String message, @Nullable Integer percentage) {\n    client.reportProgress(new ReportProgressParams(taskId.toString(), new ProgressUpdateNotification(message, percentage)));\n  }\n\n  @Override\n  public boolean isCanceled() {\n    return cancelMonitor.isCanceled();\n  }\n\n  @Override\n  public void cancel() {\n    cancelMonitor.cancel();\n  }\n\n  @Override\n  public void complete() {\n    client.reportProgress(new ReportProgressParams(taskId.toString(), new ProgressEndNotification()));\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/progress/ClientAwareTaskManager.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.progress;\n\nimport java.util.UUID;\nimport java.util.concurrent.ExecutionException;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.api.progress.CanceledException;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.ProgressMonitor;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.commons.progress.TaskManager;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.progress.StartProgressParams;\n\npublic class ClientAwareTaskManager extends TaskManager {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final SonarLintRpcClient client;\n\n  public ClientAwareTaskManager(SonarLintRpcClient client) {\n    this.client = client;\n  }\n\n  @Override\n  protected void startProgress(@Nullable String configurationScopeId, UUID taskId, String title, @Nullable String message, boolean indeterminate, boolean cancellable,\n    SonarLintCancelMonitor cancelMonitor) {\n    try {\n      client.startProgress(new StartProgressParams(taskId.toString(), configurationScopeId, title, message, indeterminate, cancellable)).get();\n    } catch (InterruptedException e) {\n      LOG.error(\"The progress report for the '\" + title + \"' was interrupted\", e);\n      Thread.currentThread().interrupt();\n      throw new CanceledException();\n    } catch (ExecutionException e) {\n      LOG.error(\"The client was unable to start progress, cause:\", e);\n      super.startProgress(configurationScopeId, taskId, title, message, indeterminate, cancellable, cancelMonitor);\n    }\n  }\n\n  @Override\n  protected ProgressMonitor createProgress(UUID taskId, SonarLintCancelMonitor cancelMonitor) {\n    return new ClientAwareProgressMonitor(client, taskId, cancelMonitor);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/progress/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.progress;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/promotion/LanguagePromotionService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.promotion;\n\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.analysis.AnalysisFinishedEvent;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.promotion.PromoteExtraEnabledLanguagesInConnectedModeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.springframework.context.event.EventListener;\n\npublic class LanguagePromotionService {\n  private final ConfigurationRepository configurationRepository;\n  private final Set<Language> extraEnabledLanguagesInConnectedMode;\n  private final SonarLintRpcClient client;\n\n  public LanguagePromotionService(ConfigurationRepository configurationRepository, InitializeParams initializeParams, SonarLintRpcClient client) {\n    this.configurationRepository = configurationRepository;\n    this.extraEnabledLanguagesInConnectedMode = initializeParams.getExtraEnabledLanguagesInConnectedMode();\n    this.client = client;\n  }\n\n  @EventListener\n  public void onAnalysisFinished(AnalysisFinishedEvent event) {\n    var configurationScopeId = event.getConfigurationScopeId();\n    if (isStandalone(configurationScopeId)) {\n      var languagesToPromote = getLanguagesToPromote(event.getDetectedLanguages());\n      if (!languagesToPromote.isEmpty()) {\n        client.promoteExtraEnabledLanguagesInConnectedMode(new PromoteExtraEnabledLanguagesInConnectedModeParams(configurationScopeId, languagesToPromote));\n      }\n    }\n  }\n\n  private boolean isStandalone(String configurationScopeId) {\n    return configurationRepository.getEffectiveBinding(configurationScopeId).isEmpty();\n  }\n\n  private Set<Language> getLanguagesToPromote(Set<SonarLanguage> detectedLanguages) {\n    var languagesToPromote = detectedLanguages.stream().map(sonarLanguage -> Language.valueOf(sonarLanguage.name())).collect(Collectors.toCollection(HashSet::new));\n    languagesToPromote.retainAll(extraEnabledLanguagesInConnectedMode);\n    return languagesToPromote;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/promotion/PromotionSpringConfig.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.promotion;\n\nimport java.nio.file.Path;\nimport org.sonarsource.sonarlint.core.UserPaths;\nimport org.sonarsource.sonarlint.core.promotion.campaign.CampaignService;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Import;\n\n@Configuration\n@Import({\n  LanguagePromotionService.class,\n  CampaignService.class\n})\npublic class PromotionSpringConfig {\n\n  @Bean\n  Path campaignsPath(UserPaths userPaths) {\n    return userPaths.getHomeIdeSpecificDir(\"campaigns\").resolve(\"campaigns\");\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/promotion/campaign/CampaignConstants.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.promotion.campaign;\n\npublic class CampaignConstants {\n\n  public static final String FEEDBACK_2026_01_CAMPAIGN = \"feedback_2026_01\";\n  private static final String JETBRAINS_MARKETPLACE = \"https://plugins.jetbrains.com/plugin/7973-sonarqube-for-ide/reviews\";\n  private static final String VS_MARKETPLACE = \"https://marketplace.visualstudio.com/items?itemName=SonarSource.SonarLintforVisualStudio2022&ssr=false#review-details\";\n  private static final String VSCODE_MARKETPLACE = \"https://marketplace.visualstudio.com/items?itemName=SonarSource.sonarlint-vscode&ssr=false#review-details\";\n  private static final String OPEN_VSX = \"https://open-vsx.org/extension/SonarSource/sonarlint-vscode/reviews\";\n  private static final String INTELLIJ_GOOGLE_FORM = \"https://forms.gle/kDyQ7sDyBfpPEBsy6\";\n  private static final String VISUAL_STUDIO_GOOGLE_FORM = \"https://forms.gle/LjKGKWECDdJw1PmU7\";\n  private static final String VS_CODE_GOOGLE_FORM = \"https://forms.gle/TncKAVK4EWM7z4RV6\";\n\n  private CampaignConstants() {\n  }\n\n  static String urlToOpen(FeedbackNotificationActionItem response, String productKey) {\n    return switch (response) {\n      case LOVE_IT -> switch (productKey) {\n        case \"idea\" -> JETBRAINS_MARKETPLACE;\n        case \"visualstudio\" -> VS_MARKETPLACE;\n        case \"vscode\" -> VSCODE_MARKETPLACE;\n        case \"windsurf\", \"cursor\", \"kiro\" -> OPEN_VSX;\n        default -> null;\n      };\n      case SHARE_FEEDBACK -> switch (productKey) {\n        case \"idea\" -> INTELLIJ_GOOGLE_FORM;\n        case \"visualstudio\" -> VISUAL_STUDIO_GOOGLE_FORM;\n        case \"vscode\", \"windsurf\", \"cursor\", \"kiro\" -> VS_CODE_GOOGLE_FORM;\n        default -> null;\n      };\n      default -> null;\n    };\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/promotion/campaign/CampaignResolvedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.promotion.campaign;\n\npublic record CampaignResolvedEvent(String campaignName, String resolution) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/promotion/campaign/CampaignService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.promotion.campaign;\n\nimport com.google.common.util.concurrent.MoreExecutors;\nimport jakarta.annotation.PreDestroy;\nimport java.nio.file.Path;\nimport java.time.LocalDate;\nimport java.time.OffsetDateTime;\nimport java.time.Period;\nimport java.util.EnumSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.stream.Stream;\nimport javax.annotation.PostConstruct;\nimport org.apache.commons.lang3.EnumUtils;\nimport org.apache.commons.lang3.math.NumberUtils;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.storage.local.FileStorageManager;\nimport org.sonarsource.sonarlint.core.commons.util.FailSafeExecutors;\nimport org.sonarsource.sonarlint.core.promotion.campaign.storage.CampaignsLocalStorage;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.OpenUrlInBrowserParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.MessageActionItem;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.MessageType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowMessageRequestParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowMessageRequestResponse;\nimport org.sonarsource.sonarlint.core.telemetry.InternalDebug;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryService;\nimport org.springframework.beans.factory.annotation.Qualifier;\nimport org.springframework.context.ApplicationEventPublisher;\n\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.sonarsource.sonarlint.core.promotion.campaign.FeedbackNotificationActionItem.LOVE_IT;\nimport static org.sonarsource.sonarlint.core.promotion.campaign.FeedbackNotificationActionItem.MAYBE_LATER;\nimport static org.sonarsource.sonarlint.core.promotion.campaign.FeedbackNotificationActionItem.SHARE_FEEDBACK;\nimport static org.sonarsource.sonarlint.core.promotion.campaign.storage.CampaignsLocalStorage.Campaign;\n\npublic class CampaignService {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final Set<FeedbackNotificationActionItem> RESPONSES_TO_OPEN_URL = EnumSet.of(LOVE_IT, SHARE_FEEDBACK);\n  private static final Map<String, Period> POSTPONE_PERIODS = Map.of(\n    MAYBE_LATER.name(), Period.ofWeeks(1),\n    \"IGNORE\", Period.ofMonths(6)\n  );\n  private static final String SIX_MINUTES_OF_SECONDS = \"360\";\n  private static final int TWO_WEEKS = 14;\n  private static final String CLOSED_BY_USER_ACTION_KEY = \"CLOSED\";\n\n  private final String productKey;\n  private final SonarLintRpcClient client;\n  private final TelemetryService telemetryService;\n  private final FileStorageManager<CampaignsLocalStorage> fileStorageManager;\n  private final ScheduledExecutorService scheduledExecutor;\n  private final ApplicationEventPublisher eventPublisher;\n  private final boolean isEnabled;\n\n  public CampaignService(@Qualifier(\"campaignsPath\") Path campaignsPath, SonarLintRpcClient client, InitializeParams initializeParams, TelemetryService telemetryService,\n    ApplicationEventPublisher eventPublisher) {\n    this.productKey = initializeParams.getTelemetryConstantAttributes().getProductKey();\n    this.client = client;\n    this.telemetryService = telemetryService;\n    this.fileStorageManager = new FileStorageManager<>(campaignsPath, CampaignsLocalStorage::new, CampaignsLocalStorage.class);\n    this.eventPublisher = eventPublisher;\n    this.scheduledExecutor = FailSafeExecutors.newSingleThreadScheduledExecutor(\"SonarLint Telemetry\");\n    this.isEnabled = initializeParams.getBackendCapabilities().contains(BackendCapability.PROMOTIONAL_CAMPAIGNS);\n  }\n\n  @PostConstruct\n  public void checkCampaigns() {\n    if (isEnabled && shouldShowFeedbackNotification()) {\n      var initialDelayProperty = System.getProperty(\"sonarlint.internal.promotion.initialDelay\", SIX_MINUTES_OF_SECONDS);\n      var initialDelay = NumberUtils.toInt(initialDelayProperty, 360);\n      scheduledExecutor.schedule(this::showFeedbackMessage, initialDelay, SECONDS);\n    }\n  }\n\n  private boolean shouldShowFeedbackNotification() {\n    var campaigns = fileStorageManager.getStorage().campaigns();\n    var feedbackCampaign = campaigns.get(CampaignConstants.FEEDBACK_2026_01_CAMPAIGN);\n    if (feedbackCampaign != null) {\n      var lastResponse = feedbackCampaign.lastUserResponse();\n      return isPostponeResponse(lastResponse)\n        && postponeTimePassed(lastResponse, feedbackCampaign);\n    } else {\n      return isInstalledLongEnough();\n    }\n  }\n\n  private static boolean isPostponeResponse(String lastResponse) {\n    return POSTPONE_PERIODS.containsKey(lastResponse);\n  }\n\n  private boolean isInstalledLongEnough() {\n    return OffsetDateTime.now().minusDays(TWO_WEEKS).isAfter(telemetryService.installTime());\n  }\n\n  private static boolean postponeTimePassed(String lastResponse, Campaign feedbackCampaign) {\n    var postpone = POSTPONE_PERIODS.get(lastResponse);\n    var lastShown = feedbackCampaign.lastNotificationShownOn();\n    return lastShown.plus(postpone).isBefore(LocalDate.now());\n  }\n\n  private void showFeedbackMessage() {\n    // Check if notification was already shown today before actually showing it\n    // This prevents duplicate notifications if multiple instances scheduled it\n    var shouldShow = tryMarkAsShownToday();\n    if (shouldShow) {\n      eventPublisher.publishEvent(new CampaignShownEvent(CampaignConstants.FEEDBACK_2026_01_CAMPAIGN));\n      var userChoice = client.showMessageRequest(new ShowMessageRequestParams(\n        MessageType.INFO,\n        \"Enjoying SonarQube for IDE? We'd love to hear what you think.\",\n        getFeedbackNotificationActions()\n      ));\n      userChoice.thenAccept(this::handleFeedbackResponse);\n    }\n  }\n\n  private boolean tryMarkAsShownToday() {\n    var shouldShow = new AtomicBoolean(false);\n\n    fileStorageManager.tryUpdateAtomically(storage -> {\n      var campaigns = storage.campaigns();\n      var existingCampaign = campaigns.get(CampaignConstants.FEEDBACK_2026_01_CAMPAIGN);\n\n      if (existingCampaign == null || !wasShownRecently(existingCampaign.lastNotificationShownOn())) {\n        campaigns.put(CampaignConstants.FEEDBACK_2026_01_CAMPAIGN,\n          new Campaign(CampaignConstants.FEEDBACK_2026_01_CAMPAIGN, LocalDate.now(), \"IGNORE\"));\n        shouldShow.set(true);\n      }\n    });\n\n    return shouldShow.get();\n  }\n\n  private static boolean wasShownRecently(LocalDate lastShown) {\n    var today = LocalDate.now();\n    var yesterday = today.minusDays(1);\n    // Consider \"recently shown\" if shown today or yesterday, this prevents midnight edge case where notification fires just after midnight\n    return lastShown.equals(today) || lastShown.equals(yesterday);\n  }\n\n  private static List<MessageActionItem> getFeedbackNotificationActions() {\n    return Stream.of(FeedbackNotificationActionItem.values())\n      .map(FeedbackNotificationActionItem::toMessageActionItem)\n      .toList();\n  }\n\n  private void handleFeedbackResponse(ShowMessageRequestResponse response) {\n    Optional.of(response)\n      .map(r -> r.isClosedByUser() ? CLOSED_BY_USER_ACTION_KEY : r.getSelectedKey())\n      .ifPresent(this::handleFeedbackResponse);\n  }\n\n  private void handleFeedbackResponse(String responseOption) {\n    fileStorageManager.tryUpdateAtomically(storage -> storage.campaigns().put(\n      CampaignConstants.FEEDBACK_2026_01_CAMPAIGN,\n      new Campaign(CampaignConstants.FEEDBACK_2026_01_CAMPAIGN, LocalDate.now(), responseOption)));\n    eventPublisher.publishEvent(new CampaignResolvedEvent(CampaignConstants.FEEDBACK_2026_01_CAMPAIGN,\n      responseOption));\n\n    var response = EnumUtils.getEnum(FeedbackNotificationActionItem.class, responseOption);\n    if (RESPONSES_TO_OPEN_URL.contains(response)) {\n      var url = CampaignConstants.urlToOpen(response, productKey);\n      if (url != null) {\n        client.openUrlInBrowser(new OpenUrlInBrowserParams(url));\n      } else {\n        redirectToCommunityIfNoLinkFound();\n      }\n    }\n  }\n\n  private void redirectToCommunityIfNoLinkFound() {\n    var showMessageRequestParams = new ShowMessageRequestParams(MessageType.INFO,\n      \"Could not find feedback link for \" + productKey + \". Please consider sharing your feedback directly on our community forum\",\n      List.of(new MessageActionItem(\"OPEN_COMMUNITY\", \"Open Community Forum\", true)));\n\n    client.showMessageRequest(showMessageRequestParams)\n      .thenAccept(response -> {\n        if (response.getSelectedKey() != null && response.getSelectedKey().equals(\"OPEN_COMMUNITY\")) {\n          client.openUrlInBrowser(new OpenUrlInBrowserParams(\"https://community.sonarsource.com/c/sl/11\"));\n        }\n      });\n  }\n\n  @PreDestroy\n  public void close() {\n    if ((!MoreExecutors.shutdownAndAwaitTermination(scheduledExecutor, 1, TimeUnit.SECONDS)) && (InternalDebug.isEnabled())) {\n      LOG.error(\"Failed to stop Campaign Service executor\");\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/promotion/campaign/CampaignShownEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.promotion.campaign;\n\npublic record CampaignShownEvent(String campaignName) {\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/promotion/campaign/FeedbackNotificationActionItem.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.promotion.campaign;\n\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.MessageActionItem;\n\npublic enum FeedbackNotificationActionItem {\n  LOVE_IT(\"Love it!\", true),\n  SHARE_FEEDBACK(\"Share Feedback\", true),\n  MAYBE_LATER(\"Maybe Later\", false);\n\n  private final String message;\n  private final boolean isPrimaryAction;\n\n  FeedbackNotificationActionItem(String message, boolean isPrimaryAction) {\n    this.message = message;\n    this.isPrimaryAction = isPrimaryAction;\n  }\n\n  public MessageActionItem toMessageActionItem() {\n    return new MessageActionItem(name(), message, isPrimaryAction);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/promotion/campaign/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.promotion.campaign;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/promotion/campaign/storage/CampaignsLocalStorage.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.promotion.campaign.storage;\n\nimport java.time.LocalDate;\nimport java.util.HashMap;\nimport java.util.Map;\nimport org.sonarsource.sonarlint.core.commons.storage.local.LocalStorage;\n\npublic record CampaignsLocalStorage(Map<String, Campaign> campaigns) implements LocalStorage {\n\n  public CampaignsLocalStorage() {\n    this(new HashMap<>());\n  }\n\n  public record Campaign(String campaignName, LocalDate lastNotificationShownOn, String lastUserResponse) {\n\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/promotion/campaign/storage/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.promotion.campaign.storage;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/promotion/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.promotion;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/remediation/aicodefix/AiCodeFixFeature.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.remediation.aicodefix;\n\nimport org.sonarsource.sonarlint.core.repository.reporting.RaisedIssue;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityDto;\nimport org.sonarsource.sonarlint.core.serverconnection.aicodefix.AiCodeFixSettings;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerTaintIssue;\nimport org.sonarsource.sonarlint.core.tracking.TrackedIssue;\n\npublic record AiCodeFixFeature(AiCodeFixSettings settings) {\n  public boolean isFixable(TrackedIssue issue) {\n    return settings.supportedRules().contains(issue.getRuleKey()) && issue.getTextRangeWithHash() != null;\n  }\n\n  public boolean isFixable(RaisedIssue issue) {\n    return settings.supportedRules().contains(issue.issueDto().getRuleKey()) && issue.issueDto().getTextRange() != null;\n  }\n\n  public boolean isFixable(ServerTaintIssue serverTaintIssue) {\n    return settings.supportedRules().contains(serverTaintIssue.getRuleKey()) && serverTaintIssue.getTextRange() != null;\n  }\n\n  public boolean isFixable(TaintVulnerabilityDto taintDto) {\n    return settings.supportedRules().contains(taintDto.getRuleKey()) && taintDto.getTextRange() != null;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/remediation/aicodefix/AiCodeFixService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.remediation.aicodefix;\n\nimport com.google.common.collect.Sets;\nimport java.util.Optional;\nimport java.util.UUID;\nimport javax.annotation.Nullable;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseError;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.event.FixSuggestionReceivedEvent;\nimport org.sonarsource.sonarlint.core.fs.ClientFile;\nimport org.sonarsource.sonarlint.core.fs.ClientFileSystemService;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarCloudConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.repository.reporting.PreviouslyRaisedFindingsRepository;\nimport org.sonarsource.sonarlint.core.repository.reporting.RaisedIssue;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.remediation.aicodefix.SuggestFixChangeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.remediation.aicodefix.SuggestFixResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AiSuggestionSource;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.exception.TooManyRequestsException;\nimport org.sonarsource.sonarlint.core.serverapi.fixsuggestions.AiSuggestionRequestBodyDto;\nimport org.sonarsource.sonarlint.core.serverapi.fixsuggestions.AiSuggestionResponseBodyDto;\nimport org.sonarsource.sonarlint.core.serverconnection.aicodefix.AiCodeFix;\nimport org.sonarsource.sonarlint.core.serverconnection.aicodefix.AiCodeFixFeatureEnablement;\nimport org.sonarsource.sonarlint.core.serverconnection.aicodefix.AiCodeFixRepository;\nimport org.sonarsource.sonarlint.core.serverconnection.aicodefix.AiCodeFixSettings;\nimport org.sonarsource.sonarlint.core.tracking.TaintVulnerabilityTrackingService;\nimport org.springframework.context.ApplicationEventPublisher;\n\nimport static java.util.Objects.requireNonNull;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode.CONFIG_SCOPE_NOT_BOUND;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode.CONNECTION_NOT_FOUND;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode.FILE_NOT_FOUND;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode.ISSUE_NOT_FOUND;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode.TOO_MANY_REQUESTS;\n\npublic class AiCodeFixService {\n  private final ConnectionConfigurationRepository connectionRepository;\n  private final ConfigurationRepository configurationRepository;\n  private final SonarQubeClientManager sonarQubeClientManager;\n  private final PreviouslyRaisedFindingsRepository previouslyRaisedFindingsRepository;\n  private final ClientFileSystemService clientFileSystemService;\n  private final ApplicationEventPublisher eventPublisher;\n  private final TaintVulnerabilityTrackingService taintVulnerabilityTrackingService;\n  private final AiCodeFixRepository aiCodeFixRepository;\n\n  public AiCodeFixService(ConnectionConfigurationRepository connectionRepository, ConfigurationRepository configurationRepository, SonarQubeClientManager sonarQubeClientManager,\n    PreviouslyRaisedFindingsRepository previouslyRaisedFindingsRepository, ClientFileSystemService clientFileSystemService,\n    ApplicationEventPublisher eventPublisher, TaintVulnerabilityTrackingService taintVulnerabilityTrackingService, AiCodeFixRepository aiCodeFixRepository) {\n    this.connectionRepository = connectionRepository;\n    this.configurationRepository = configurationRepository;\n    this.sonarQubeClientManager = sonarQubeClientManager;\n    this.previouslyRaisedFindingsRepository = previouslyRaisedFindingsRepository;\n    this.clientFileSystemService = clientFileSystemService;\n    this.eventPublisher = eventPublisher;\n    this.taintVulnerabilityTrackingService = taintVulnerabilityTrackingService;\n    this.aiCodeFixRepository = aiCodeFixRepository;\n  }\n\n  public static AiCodeFixSettings aiCodeFixMapping(AiCodeFix entity) {\n    return new AiCodeFixSettings(\n      Sets.newHashSet(entity.supportedRules()),\n      entity.organizationEligible(),\n      AiCodeFixFeatureEnablement.valueOf(entity.enablement().name()),\n      Sets.newHashSet(entity.enabledProjectKeys()));\n  }\n\n  public Optional<AiCodeFixFeature> getFeature(Binding binding) {\n    return aiCodeFixRepository.get(binding.connectionId())\n      .map(AiCodeFixService::aiCodeFixMapping)\n      .filter(settings -> settings.isFeatureEnabled(binding.sonarProjectKey()))\n      .map(AiCodeFixFeature::new);\n  }\n\n  public SuggestFixResponse suggestFix(String configurationScopeId, UUID issueId, SonarLintCancelMonitor cancelMonitor) {\n    var bindingWithOrg = ensureBound(configurationScopeId);\n    var connection = sonarQubeClientManager.getValidClientOrThrow(bindingWithOrg.binding().connectionId());\n    var responseBodyDto = connection.withClientApiAndReturn(serverApi -> {\n      var issueOptional = previouslyRaisedFindingsRepository.findRaisedIssueById(issueId);\n      if (issueOptional.isPresent()) {\n        return generateResponseBodyForIssue(serverApi, issueOptional.get(), issueId, bindingWithOrg, cancelMonitor);\n      } else {\n        var taintOptional = taintVulnerabilityTrackingService.getTaintVulnerability(configurationScopeId, issueId, cancelMonitor);\n        if (taintOptional.isPresent()) {\n          return generateResponseBodyForTaint(serverApi, taintOptional.get(), configurationScopeId, bindingWithOrg, cancelMonitor);\n        } else {\n          throw new ResponseErrorException(new ResponseError(ISSUE_NOT_FOUND, \"The provided issue or taint does not exist\", issueId));\n        }\n      }\n    });\n    return adapt(responseBodyDto);\n  }\n\n  private AiSuggestionResponseBodyDto generateResponseBodyForIssue(ServerApi serverApi, RaisedIssue raisedIssue, UUID issueId,\n    BindingWithOrg bindingWithOrg, SonarLintCancelMonitor cancelMonitor) {\n    var aiCodeFixFeature = getFeature(bindingWithOrg.binding());\n    if (!aiCodeFixFeature.map(feature -> feature.isFixable(raisedIssue)).orElse(false)) {\n      throw new ResponseErrorException(new ResponseError(ResponseErrorCode.InvalidParams, \"The provided issue cannot be fixed\", issueId));\n    }\n\n    AiSuggestionResponseBodyDto fixResponseDto;\n\n    try {\n      var requestBody = toDto(bindingWithOrg.organizationKey, bindingWithOrg.binding().sonarProjectKey(), raisedIssue);\n      fixResponseDto = serverApi.fixSuggestions().getAiSuggestion(requestBody, cancelMonitor);\n    } catch (TooManyRequestsException e) {\n      throw new ResponseErrorException(new ResponseError(TOO_MANY_REQUESTS, \"AI CodeFix usage has been capped. Too many requests have been made.\", issueId));\n    }\n\n    eventPublisher.publishEvent(new FixSuggestionReceivedEvent(\n      fixResponseDto.id().toString(),\n      serverApi.isSonarCloud() ? AiSuggestionSource.SONARCLOUD : AiSuggestionSource.SONARQUBE,\n      fixResponseDto.changes().size(),\n      // As of today, this is always true since suggestFix is only called by the clients\n      true));\n\n    return fixResponseDto;\n  }\n\n  private AiSuggestionResponseBodyDto generateResponseBodyForTaint(ServerApi serverApi, TaintVulnerabilityDto taint,\n    String configScopeId, BindingWithOrg bindingWithOrg, SonarLintCancelMonitor cancelMonitor) {\n    var aiCodeFixFeature = getFeature(bindingWithOrg.binding());\n    if (!aiCodeFixFeature.map(feature -> feature.isFixable(taint)).orElse(false)) {\n      throw new ResponseErrorException(new ResponseError(ResponseErrorCode.InvalidParams, \"The provided taint cannot be fixed\", taint.getId()));\n    }\n\n    AiSuggestionResponseBodyDto fixResponseDto;\n\n    try {\n      var requestBody = toDto(bindingWithOrg.organizationKey, bindingWithOrg.binding().sonarProjectKey(), taint, configScopeId);\n      fixResponseDto = serverApi.fixSuggestions().getAiSuggestion(requestBody, cancelMonitor);\n    } catch (TooManyRequestsException e) {\n      throw new ResponseErrorException(new ResponseError(TOO_MANY_REQUESTS, \"AI CodeFix usage has been capped. Too many requests have been made.\", taint.getId()));\n    }\n\n    eventPublisher.publishEvent(new FixSuggestionReceivedEvent(\n      fixResponseDto.id().toString(),\n      serverApi.isSonarCloud() ? AiSuggestionSource.SONARCLOUD : AiSuggestionSource.SONARQUBE,\n      fixResponseDto.changes().size(),\n      // As of today, this is always true since suggestFix is only called by the clients\n      true));\n\n    return fixResponseDto;\n  }\n\n  private static SuggestFixResponse adapt(AiSuggestionResponseBodyDto responseBodyDto) {\n    return new SuggestFixResponse(responseBodyDto.id(), responseBodyDto.explanation(),\n      responseBodyDto.changes().stream().map(change -> new SuggestFixChangeDto(change.startLine(), change.endLine(), change.newCode())).toList());\n  }\n\n  private BindingWithOrg ensureBound(String configurationScopeId) {\n    var effectiveBinding = configurationRepository.getEffectiveBinding(configurationScopeId);\n    if (effectiveBinding.isEmpty()) {\n      throw new ResponseErrorException(new ResponseError(CONFIG_SCOPE_NOT_BOUND, \"The provided configuration scope is not bound\", configurationScopeId));\n    }\n    var binding = effectiveBinding.get();\n    var connection = connectionRepository.getConnectionById(binding.connectionId());\n    if (connection == null) {\n      throw new ResponseErrorException(new ResponseError(CONNECTION_NOT_FOUND, \"The provided configuration scope is bound to an unknown connection\", configurationScopeId));\n    }\n    if ((connection instanceof SonarCloudConnectionConfiguration sonarCloudConnection)) {\n      return new BindingWithOrg(sonarCloudConnection.getOrganization(), binding);\n    }\n    return new BindingWithOrg(null, binding);\n  }\n\n  private AiSuggestionRequestBodyDto toDto(@Nullable String organizationKey, String projectKey, RaisedIssue raisedIssue) {\n    // this is not perfect, the file content might have changed since the issue was detected\n    var clientFile = clientFileSystemService.getClientFile(raisedIssue.fileUri());\n    if (clientFile == null) {\n      throw new ResponseErrorException(new ResponseError(FILE_NOT_FOUND, \"The provided issue ID corresponds to an unknown file\", null));\n    }\n    var issue = raisedIssue.issueDto();\n    // the text range presence was checked earlier\n    var textRange = requireNonNull(issue.getTextRange());\n    return new AiSuggestionRequestBodyDto(organizationKey, projectKey,\n      new AiSuggestionRequestBodyDto.Issue(issue.getPrimaryMessage(), textRange.getStartLine(), textRange.getEndLine(), issue.getRuleKey(),\n        clientFile.getContent()));\n  }\n\n  private AiSuggestionRequestBodyDto toDto(@Nullable String organizationKey, String projectKey, TaintVulnerabilityDto taint, String configScopeId) {\n    ClientFile clientFile = null;\n    var baseDir = clientFileSystemService.getBaseDir(configScopeId);\n    if (baseDir != null) {\n      var fileUri = baseDir.resolve(taint.getIdeFilePath()).toUri();\n      clientFile = clientFileSystemService.getClientFile(fileUri);\n    }\n    if (clientFile == null) {\n      throw new ResponseErrorException(new ResponseError(FILE_NOT_FOUND, \"The provided taint ID corresponds to an unknown file\", null));\n    }\n    // the text range presence was checked earlier\n    var textRange = requireNonNull(taint.getTextRange());\n    return new AiSuggestionRequestBodyDto(organizationKey, projectKey,\n      new AiSuggestionRequestBodyDto.Issue(taint.getMessage(), textRange.getStartLine(), textRange.getEndLine(), taint.getRuleKey(), clientFile.getContent()));\n  }\n\n  private record BindingWithOrg(@Nullable String organizationKey, Binding binding) {\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/remediation/aicodefix/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.remediation.aicodefix;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/reporting/FindingReportingService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.reporting;\n\nimport java.net.URI;\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.UnaryOperator;\nimport java.util.stream.Collectors;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.active.rules.ServerActiveRulesChanged;\nimport org.sonarsource.sonarlint.core.active.rules.StandaloneRulesConfigurationChanged;\nimport org.sonarsource.sonarlint.core.analysis.IssuesRaisedEvent;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.BoundScope;\nimport org.sonarsource.sonarlint.core.commons.NewCodeDefinition;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.mode.SeverityModeService;\nimport org.sonarsource.sonarlint.core.newcode.NewCodeService;\nimport org.sonarsource.sonarlint.core.remediation.aicodefix.AiCodeFixFeature;\nimport org.sonarsource.sonarlint.core.remediation.aicodefix.AiCodeFixService;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.reporting.PreviouslyRaisedFindingsRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.RaiseHotspotsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.RaisedHotspotDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaiseIssuesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedFindingDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\nimport org.sonarsource.sonarlint.core.tracking.TrackedIssue;\nimport org.sonarsource.sonarlint.core.tracking.streaming.Alarm;\nimport org.springframework.context.ApplicationEventPublisher;\nimport org.springframework.context.event.EventListener;\n\nimport static java.util.stream.Collectors.groupingBy;\nimport static java.util.stream.Collectors.toMap;\nimport static org.sonarsource.sonarlint.core.DtoMapper.toRaisedHotspotDto;\nimport static org.sonarsource.sonarlint.core.DtoMapper.toRaisedIssueDto;\n\npublic class FindingReportingService {\n  public static final Duration STREAMING_INTERVAL = Duration.ofMillis(300);\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final SonarLintRpcClient client;\n  private final ConfigurationRepository configurationRepository;\n  private final NewCodeService newCodeService;\n  private final SeverityModeService severityModeService;\n  private final PreviouslyRaisedFindingsRepository previouslyRaisedFindingsRepository;\n  private final Map<URI, Collection<TrackedIssue>> issuesPerFileUri = new ConcurrentHashMap<>();\n  private final Map<URI, Collection<TrackedIssue>> securityHotspotsPerFileUri = new ConcurrentHashMap<>();\n  private final Map<String, Alarm> streamingTriggeringAlarmByConfigScopeId = new ConcurrentHashMap<>();\n  private final Map<UUID, Set<URI>> filesPerAnalysis = new ConcurrentHashMap<>();\n  private final ApplicationEventPublisher eventPublisher;\n  private final boolean isStreamingEnabled;\n  private final AiCodeFixService aiCodeFixService;\n\n  public FindingReportingService(SonarLintRpcClient client, ConfigurationRepository configurationRepository, NewCodeService newCodeService, SeverityModeService severityModeService,\n    PreviouslyRaisedFindingsRepository previouslyRaisedFindingsRepository, ApplicationEventPublisher eventPublisher, InitializeParams initializeParams,\n    AiCodeFixService aiCodeFixService) {\n    this.client = client;\n    this.configurationRepository = configurationRepository;\n    this.newCodeService = newCodeService;\n    this.severityModeService = severityModeService;\n    this.previouslyRaisedFindingsRepository = previouslyRaisedFindingsRepository;\n    this.eventPublisher = eventPublisher;\n    this.isStreamingEnabled = initializeParams.getBackendCapabilities().contains(BackendCapability.ISSUE_STREAMING);\n    this.aiCodeFixService = aiCodeFixService;\n  }\n\n  @EventListener\n  public void onStandaloneRulesConfigurationChanged(StandaloneRulesConfigurationChanged event) {\n    if (event.isOnlyDeactivated()) {\n      // if no rules were enabled (only disabled), trigger only a new reporting, removing issues of disabled rules\n      configurationRepository.getConfigScopeIds().stream()\n        .filter(configScopeId -> configurationRepository.getEffectiveBinding(configScopeId).isEmpty())\n        .forEach(configScopeId -> {\n          var deactivatedRules = event.getDeactivatedRules();\n          updateAndReportFindings(configScopeId,\n            hotspot -> raisedFindingUpdater(hotspot, deactivatedRules),\n            issue -> raisedFindingUpdater(issue, deactivatedRules));\n        });\n    }\n  }\n\n  @CheckForNull\n  private static <T extends RaisedFindingDto> T raisedFindingUpdater(T raisedFinding, List<String> deactivatedRules) {\n    if (deactivatedRules.contains(raisedFinding.getRuleKey())) {\n      return null;\n    }\n    return raisedFinding;\n  }\n\n  @EventListener\n  private void onServerActiveRulesChanged(ServerActiveRulesChanged event) {\n    var deactivatedRules = event.deactivatedRules();\n    // if rules were activated, an analysis will be triggered in the AnalysisService, and a new reporting will occur\n    if (event.activatedRules().isEmpty() && !deactivatedRules.isEmpty()) {\n      var changedProjectKeys = event.projectKeys();\n      configurationRepository.getAllBoundScopes().stream()\n        .filter(scope -> event.connectionId().equals(scope.getConnectionId()) && changedProjectKeys.contains(scope.getSonarProjectKey()))\n        .map(BoundScope::getConfigScopeId)\n        .forEach(scopeId -> updateAndReportFindings(scopeId,\n          hotspot -> raisedFindingUpdater(hotspot, deactivatedRules),\n          issue -> raisedFindingUpdater(issue, deactivatedRules)));\n    }\n  }\n\n  public void resetFindingsForFiles(String configurationScopeId, Set<URI> files) {\n    files.forEach(fileUri -> {\n      resetFindingsForFile(issuesPerFileUri, fileUri);\n      resetFindingsForFile(securityHotspotsPerFileUri, fileUri);\n    });\n    previouslyRaisedFindingsRepository.resetFindingsCache(configurationScopeId, files);\n  }\n\n  public void initFilesToAnalyze(UUID analysisId, Set<URI> files) {\n    filesPerAnalysis.computeIfAbsent(analysisId, k -> new HashSet<>()).addAll(files);\n  }\n\n  private static void resetFindingsForFile(Map<URI, Collection<TrackedIssue>> findingsMap, URI fileUri) {\n    findingsMap.computeIfPresent(fileUri, (k, v) -> List.of());\n  }\n\n  public void streamIssue(String configurationScopeId, UUID analysisId, TrackedIssue trackedIssue) {\n    // Cache is cleared on new analysis, but it's possible that 2 analyses almost start at the same time.\n    // In this case, same issues will be reported twice for the same file during the streaming, which will be sent to the client.\n    // A quick workaround is to replace the existing issue with the duplicated one (which should be the most up-to-date).\n    // Ideally, we should be able to cancel the previous analysis if it's not relevant.\n    if (trackedIssue.isSecurityHotspot()) {\n      insertTrackedIssue(securityHotspotsPerFileUri, trackedIssue);\n    } else {\n      insertTrackedIssue(issuesPerFileUri, trackedIssue);\n    }\n    if (isStreamingEnabled) {\n      getStreamingDebounceAlarm(configurationScopeId, analysisId).schedule();\n    }\n  }\n\n  private static void insertTrackedIssue(Map<URI, Collection<TrackedIssue>> map, TrackedIssue trackedIssue) {\n    map.compute(trackedIssue.getFileUri(), (fileUri, fileFindings) -> {\n      // make sure to return an immutable list as it might be iterated over in parallel\n      if (fileFindings == null) {\n        return List.of(trackedIssue);\n      }\n      var newIssues = new ArrayList<>(fileFindings);\n      newIssues.removeIf(i -> i.getId().equals(trackedIssue.getId()));\n      newIssues.add(trackedIssue);\n      return List.copyOf(newIssues);\n    });\n  }\n\n  private void triggerStreaming(String configurationScopeId, UUID analysisId) {\n    var effectiveBinding = configurationRepository.getEffectiveBinding(configurationScopeId);\n    var connectionId = effectiveBinding.map(Binding::connectionId).orElse(null);\n    var newCodeDefinition = newCodeService.getFullNewCodeDefinition(configurationScopeId).orElseGet(NewCodeDefinition::withAlwaysNew);\n    var isMQRMode = severityModeService.isMQRModeForConnection(connectionId);\n    var aiCodeFixFeature = effectiveBinding.flatMap(aiCodeFixService::getFeature);\n    var issuesToRaise = issuesPerFileUri.entrySet().stream()\n      .filter(e -> filesPerAnalysis.get(analysisId).contains(e.getKey()))\n      .map(e -> Map.entry(e.getKey(),\n        e.getValue().stream().map(issue -> toRaisedIssueDto(issue, newCodeDefinition, isMQRMode, aiCodeFixFeature.map(feature -> feature.isFixable(issue)).orElse(false)))\n          .toList()))\n      .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));\n    var hotspotsToRaise = securityHotspotsPerFileUri.entrySet().stream()\n      .filter(e -> filesPerAnalysis.get(analysisId).contains(e.getKey()))\n      .map(e -> Map.entry(e.getKey(), e.getValue().stream().map(issue -> toRaisedHotspotDto(issue, newCodeDefinition, isMQRMode)).toList()))\n      .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));\n    updateRaisedFindingsCacheAndNotifyClient(configurationScopeId, analysisId, issuesToRaise, hotspotsToRaise, true);\n  }\n\n  public void reportTrackedFindings(String configurationScopeId, UUID analysisId, Map<Path, List<TrackedIssue>> issuesToReport, Map<Path, List<TrackedIssue>> hotspotsToReport) {\n    // stop streaming now, we will raise all issues one last time from this method\n    stopStreaming(configurationScopeId);\n    var effectiveBinding = configurationRepository.getEffectiveBinding(configurationScopeId);\n    var connectionId = effectiveBinding.map(Binding::connectionId).orElse(null);\n    var newCodeDefinition = newCodeService.getFullNewCodeDefinition(configurationScopeId).orElseGet(NewCodeDefinition::withAlwaysNew);\n    var isMQRMode = severityModeService.isMQRModeForConnection(connectionId);\n    var aiCodeFixFeature = effectiveBinding.flatMap(aiCodeFixService::getFeature);\n    var issuesToRaise = getIssuesToRaise(issuesToReport, newCodeDefinition, isMQRMode, aiCodeFixFeature);\n    this.eventPublisher.publishEvent(new IssuesRaisedEvent(issuesToRaise.values().stream().flatMap(List::stream).toList()));\n    var hotspotsToRaise = getHotspotsToRaise(hotspotsToReport, newCodeDefinition, isMQRMode);\n    updateRaisedFindingsCacheAndNotifyClient(configurationScopeId, analysisId, issuesToRaise, hotspotsToRaise, false);\n    filesPerAnalysis.remove(analysisId);\n  }\n\n  private synchronized void updateRaisedFindingsCacheAndNotifyClient(String configurationScopeId, @Nullable UUID analysisId, Map<URI, List<RaisedIssueDto>> updatedIssues,\n    Map<URI, List<RaisedHotspotDto>> updatedHotspots, boolean isIntermediatePublication) {\n    var fileIssues = previouslyRaisedFindingsRepository.replaceIssuesForFiles(configurationScopeId, updatedIssues);\n\n    var totalIssues = fileIssues.values().stream().mapToInt(List::size).sum();\n    LOG.debug(\"Reporting {} issues over {} files for configuration scope {}\", totalIssues, fileIssues.size(), configurationScopeId);\n\n    client.raiseIssues(new RaiseIssuesParams(configurationScopeId, fileIssues, isIntermediatePublication, analysisId));\n    var effectiveBindingOpt = configurationRepository.getEffectiveBinding(configurationScopeId);\n    if (effectiveBindingOpt.isPresent()) {\n      // security hotspots are only supported in connected mode\n      var hotspotsToRaise = previouslyRaisedFindingsRepository.replaceHotspotsForFiles(configurationScopeId, updatedHotspots);\n      client.raiseHotspots(new RaiseHotspotsParams(configurationScopeId, hotspotsToRaise, isIntermediatePublication, analysisId));\n    }\n  }\n\n  private void stopStreaming(String configurationScopeId) {\n    var alarm = removeStreamingDebounceAlarmIfExists(configurationScopeId);\n    if (alarm != null) {\n      alarm.shutdownNow();\n    }\n  }\n\n  private Alarm getStreamingDebounceAlarm(String configurationScopeId, UUID analysisId) {\n    return streamingTriggeringAlarmByConfigScopeId.computeIfAbsent(configurationScopeId,\n      id -> new Alarm(\"sonarlint-finding-streamer\", STREAMING_INTERVAL, () -> triggerStreaming(configurationScopeId, analysisId)));\n  }\n\n  private Alarm removeStreamingDebounceAlarmIfExists(String configurationScopeId) {\n    return streamingTriggeringAlarmByConfigScopeId.remove(configurationScopeId);\n  }\n\n  private static Map<URI, List<RaisedIssueDto>> getIssuesToRaise(Map<Path, List<TrackedIssue>> updatedIssues, NewCodeDefinition newCodeDefinition, boolean isMQRMode,\n    Optional<AiCodeFixFeature> aiCodeFixFeature) {\n    LOG.debug(\"AiCodeFix optional is present: {}\", aiCodeFixFeature.isPresent());\n    return updatedIssues.values().stream().flatMap(Collection::stream)\n      .collect(groupingBy(TrackedIssue::getFileUri,\n        Collectors.mapping(issue -> toRaisedIssueDto(issue, newCodeDefinition, isMQRMode, aiCodeFixFeature.map(feature -> {\n          LOG.debug(\"AiCodeFix is fixable: {}\", aiCodeFixFeature.get().isFixable(issue));\n          LOG.debug(\"Supported rules: {}\", aiCodeFixFeature.get().settings().supportedRules());\n          LOG.debug(\"Issue ruleKey {} and text range {}\", issue.getRuleKey(), issue.getTextRangeWithHash());\n          return feature.isFixable(issue);\n        }).orElse(false)),\n          Collectors.toList())));\n  }\n\n  private static Map<URI, List<RaisedHotspotDto>> getHotspotsToRaise(Map<Path, List<TrackedIssue>> hotspots, NewCodeDefinition newCodeDefinition, boolean isMQRMode) {\n    return hotspots.values().stream().flatMap(Collection::stream)\n      .collect(groupingBy(TrackedIssue::getFileUri, Collectors.mapping(hotspot -> toRaisedHotspotDto(hotspot, newCodeDefinition, isMQRMode), Collectors.toList())));\n  }\n\n  public void updateAndReportIssues(String configurationScopeId, UnaryOperator<RaisedIssueDto> issueUpdater) {\n    updateAndReportFindings(configurationScopeId, UnaryOperator.identity(), issueUpdater);\n  }\n\n  public void updateAndReportHotspots(String configurationScopeId, UnaryOperator<RaisedHotspotDto> hotspotUpdater) {\n    updateAndReportFindings(configurationScopeId, hotspotUpdater, UnaryOperator.identity());\n  }\n\n  public void updateAndReportFindings(String configurationScopeId, UnaryOperator<RaisedHotspotDto> hotspotUpdater, UnaryOperator<RaisedIssueDto> issueUpdater) {\n    var updatedHotspots = updateFindings(hotspotUpdater, previouslyRaisedFindingsRepository.getRaisedHotspotsForScope(configurationScopeId));\n    var updatedIssues = updateFindings(issueUpdater, previouslyRaisedFindingsRepository.getRaisedIssuesForScope(configurationScopeId));\n    updateRaisedFindingsCacheAndNotifyClient(configurationScopeId, null, updatedIssues, updatedHotspots, false);\n  }\n\n  private static <F extends RaisedFindingDto> Map<URI, List<F>> updateFindings(UnaryOperator<F> findingUpdater, Map<URI, List<F>> previouslyRaisedFindings) {\n    Map<URI, List<F>> updatedFindings = new HashMap<>();\n    previouslyRaisedFindings.forEach((uri, finding) -> {\n      var updatedFindingsForFile = finding.stream()\n        .map(findingUpdater)\n        .filter(Objects::nonNull)\n        .toList();\n      updatedFindings.put(uri, updatedFindingsForFile);\n    });\n    return updatedFindings;\n  }\n\n  @CheckForNull\n  public RaisedIssueDto findReportedIssue(UUID issueId, NewCodeDefinition newCodeDefinition, boolean isMQRMode, Optional<AiCodeFixFeature> aiCodeFixFeature) {\n    for (var findingsForFile : issuesPerFileUri.values()) {\n      var optFinding = findingsForFile.stream().filter(issue -> issue.getId().equals(issueId)).findFirst();\n      if (optFinding.isPresent()) {\n        return toRaisedIssueDto(optFinding.get(), newCodeDefinition, isMQRMode, aiCodeFixFeature.map(feature -> feature.isFixable(optFinding.get())).orElse(false));\n      }\n    }\n    return null;\n  }\n\n  @CheckForNull\n  public RaisedHotspotDto findReportedHotspot(UUID hotspotId, NewCodeDefinition newCodeDefinition, boolean isMQRMode) {\n    for (var findingsForFile : securityHotspotsPerFileUri.values()) {\n      var optFinding = findingsForFile.stream().filter(hotspot -> hotspot.getId().equals(hotspotId)).findFirst();\n      if (optFinding.isPresent()) {\n        return toRaisedHotspotDto(optFinding.get(), newCodeDefinition, isMQRMode);\n      }\n    }\n    return null;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/reporting/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.reporting;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/repository/config/BindingConfiguration.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.repository.config;\n\nimport java.util.Optional;\nimport java.util.function.BiFunction;\nimport javax.annotation.Nullable;\n\npublic record BindingConfiguration(@Nullable String connectionId, @Nullable String sonarProjectKey, boolean bindingSuggestionDisabled) {\n\n  public static BindingConfiguration noBinding() {\n    return noBinding(false);\n  }\n\n  public static BindingConfiguration noBinding(boolean bindingSuggestionDisabled) {\n    return new BindingConfiguration(null, null, bindingSuggestionDisabled);\n  }\n\n  public boolean isBound() {\n    return connectionId != null && sonarProjectKey != null;\n  }\n\n  public <G> Optional<G> ifBound(BiFunction<String, String, G> calledIfBound) {\n    if (isBound()) {\n      return Optional.of(calledIfBound.apply(connectionId, sonarProjectKey));\n    }\n    return Optional.empty();\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/repository/config/ConfigurationRepository.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.repository.config;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.stream.Collectors;\nimport javax.annotation.CheckForNull;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseError;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.BoundScope;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode;\n\nimport static java.util.Objects.requireNonNull;\nimport static java.util.stream.Collectors.groupingBy;\nimport static java.util.stream.Collectors.toSet;\n\npublic class ConfigurationRepository {\n\n  private final Map<String, ConfigurationScope> configScopeById = new ConcurrentHashMap<>();\n  private final Map<String, BindingConfiguration> bindingByConfigScopeId = new ConcurrentHashMap<>();\n\n  public ConfigurationScope addOrReplace(ConfigurationScope configScope, BindingConfiguration bindingConfig) {\n    var id = configScope.id();\n    var previous = configScopeById.put(id, configScope);\n    bindingByConfigScopeId.put(id, bindingConfig);\n    return previous;\n  }\n\n  @CheckForNull\n  public ConfigurationScopeWithBinding remove(String idToRemove) {\n    var removedScope = configScopeById.remove(idToRemove);\n    var removeBindingConfiguration = bindingByConfigScopeId.remove(idToRemove);\n    return removedScope == null ? null : new ConfigurationScopeWithBinding(removedScope, removeBindingConfiguration);\n  }\n\n  public Map<String, BindingConfiguration> removeBindingForConnection(String connectionId) {\n    var removedBindingByConfigScope = new HashMap<String, BindingConfiguration>();\n    var configScopeIdsToUnbind = bindingByConfigScopeId.entrySet().stream().filter(e -> connectionId.equals(e.getValue().connectionId())).map(Map.Entry::getKey).collect(toSet());\n    configScopeIdsToUnbind.forEach(configScopeId -> {\n      var removedBindingConfiguration = bindingByConfigScopeId.get(configScopeId);\n      if (removedBindingConfiguration != null) {\n        var noBinding = BindingConfiguration.noBinding(removedBindingConfiguration.bindingSuggestionDisabled());\n        updateBinding(configScopeId, noBinding);\n        removedBindingByConfigScope.put(configScopeId, removedBindingConfiguration);\n      }\n    });\n    return removedBindingByConfigScope;\n  }\n\n  public void updateBinding(String configScopeId, BindingConfiguration bindingConfig) {\n    bindingByConfigScopeId.put(configScopeId, bindingConfig);\n  }\n\n  public Set<String> getConfigScopeIds() {\n    return Set.copyOf(configScopeById.keySet());\n  }\n\n  @CheckForNull\n  public BindingConfiguration getBindingConfiguration(String configScopeId) {\n    return bindingByConfigScopeId.get(configScopeId);\n  }\n\n  public Optional<Binding> getEffectiveBinding(String configScopeId) {\n    var configScopeIdToSearchIn = requireNonNull(configScopeId, \"Configuration Scope ID is mandatory\");\n    while (true) {\n      var binding = getConfiguredBinding(configScopeIdToSearchIn);\n      if (binding.isPresent()) {\n        return binding;\n      }\n      var parentId = getParentId(configScopeIdToSearchIn);\n      if (parentId.isEmpty()) {\n        return Optional.empty();\n      }\n      configScopeIdToSearchIn = parentId.get();\n    }\n  }\n\n  public Binding getEffectiveBindingOrThrow(String configScopeId) {\n    return getEffectiveBinding(configScopeId).orElseThrow(() -> {\n      var error = new ResponseError(SonarLintRpcErrorCode.CONFIG_SCOPE_NOT_BOUND, \"No binding for config scope '\" + configScopeId + \"'\", configScopeId);\n      return new ResponseErrorException(error);\n    });\n  }\n\n  public Optional<Binding> getConfiguredBinding(String configScopeId) {\n    var bindingConfiguration = bindingByConfigScopeId.get(configScopeId);\n    if (bindingConfiguration != null && bindingConfiguration.isBound()) {\n      return Optional.of(new Binding(requireNonNull(bindingConfiguration.connectionId()),\n        requireNonNull(bindingConfiguration.sonarProjectKey())));\n    }\n    return Optional.empty();\n  }\n\n  private Optional<String> getParentId(String configScopeId) {\n    var configurationScope = configScopeById.get(configScopeId);\n    if (configurationScope != null) {\n      return Optional.ofNullable(configurationScope.parentId());\n    }\n    return Optional.empty();\n  }\n\n  public Set<String> getLeafConfigScopeIds() {\n    var leafConfigScopeIds = new HashSet<>(configScopeById.keySet());\n    configScopeById.values().forEach(scope -> {\n      var parentId = scope.parentId();\n      if (parentId != null) {\n        leafConfigScopeIds.remove(parentId);\n      }\n    });\n    return leafConfigScopeIds;\n  }\n\n  public boolean isLeafConfigScope(String configScopeId) {\n    return getLeafConfigScopeIds().contains(configScopeId);\n  }\n\n  @CheckForNull\n  public ConfigurationScope getConfigurationScope(String configScopeId) {\n    return configScopeById.get(configScopeId);\n  }\n\n  public Collection<BoundScope> getAllBoundScopes() {\n    return configScopeById.keySet()\n      .stream()\n      .map(scopeId -> {\n        var effectiveBinding = getEffectiveBinding(scopeId);\n        return effectiveBinding.map(binding -> new BoundScope(scopeId, binding)).orElse(null);\n      })\n      .filter(Objects::nonNull)\n      .toList();\n  }\n\n  public Collection<ConfigurationScope> getAllBindableUnboundScopes() {\n    return configScopeById.entrySet()\n      .stream()\n      .filter(e -> e.getValue().bindable())\n      .filter(e -> getEffectiveBinding(e.getKey()).isEmpty())\n      .map(Map.Entry::getValue)\n      .toList();\n  }\n\n  @CheckForNull\n  public BoundScope getBoundScope(String configScopeId) {\n    var effectiveBinding = getEffectiveBinding(configScopeId);\n    return effectiveBinding.map(binding -> new BoundScope(configScopeId, binding)).orElse(null);\n  }\n\n  public Collection<BoundScope> getBoundScopesToConnectionAndSonarProject(String connectionId, String projectKey) {\n    return getBoundScopesToConnection(connectionId)\n      .stream()\n      .filter(b -> projectKey.equals(b.getSonarProjectKey()))\n      .toList();\n  }\n\n  public Collection<BoundScope> getBoundScopesToConnection(String connectionId) {\n    return getAllBoundScopes()\n      .stream()\n      .filter(b -> connectionId.equals(b.getConnectionId()))\n      .toList();\n  }\n\n  public boolean hasScopesBoundToConnection(String connectionId) {\n    return !getBoundScopesToConnection(connectionId).isEmpty();\n  }\n\n  /**\n   * Return the set of Sonar Project keys used in at least one binding for the given connection.\n   */\n  public Set<String> getSonarProjectsUsedForConnection(String connectionId) {\n    return getAllBoundScopes()\n      .stream()\n      .filter(b -> connectionId.equals(b.getConnectionId()))\n      .map(BoundScope::getSonarProjectKey)\n      .collect(toSet());\n  }\n\n  public Map<String, Map<String, Collection<BoundScope>>> getBoundScopeByConnectionAndSonarProject() {\n    return getAllBoundScopes()\n      .stream()\n      .collect(groupingBy(BoundScope::getConnectionId, groupingBy(BoundScope::getSonarProjectKey, Collectors.toCollection(ArrayList::new))));\n  }\n\n  public List<String> getChildrenWithInheritedBinding(String parentId) {\n    return configScopeById.values().stream()\n      .filter(scope -> parentId.equals(scope.parentId()) && (!bindingByConfigScopeId.containsKey(scope.id()) || !bindingByConfigScopeId.get(scope.id()).isBound()))\n      .map(ConfigurationScope::id)\n      .toList();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/repository/config/ConfigurationScope.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.repository.config;\n\nimport javax.annotation.Nullable;\n\n/**\n * @param name The name of this configuration scope. Used for auto-binding.\n */\npublic record ConfigurationScope(String id, @Nullable String parentId, boolean bindable, String name) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/repository/config/ConfigurationScopeWithBinding.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.repository.config;\n\npublic record ConfigurationScopeWithBinding(ConfigurationScope scope, BindingConfiguration bindingConfiguration) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/repository/config/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.repository.config;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/repository/connection/AbstractConnectionConfiguration.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.repository.connection;\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.Objects;\nimport org.apache.commons.lang3.Strings;\nimport org.sonarsource.sonarlint.core.commons.ConnectionKind;\nimport org.sonarsource.sonarlint.core.serverapi.EndpointParams;\n\npublic abstract class AbstractConnectionConfiguration {\n\n  /**\n   * The id of the connection on the client side\n   */\n  private final String connectionId;\n  private final boolean disableNotifications;\n  private final ConnectionKind kind;\n  private final String url;\n\n  protected AbstractConnectionConfiguration(String connectionId, ConnectionKind kind, boolean disableNotifications, String url) {\n    Objects.requireNonNull(connectionId, \"Connection id is mandatory\");\n    this.connectionId = connectionId;\n    this.kind = kind;\n    this.disableNotifications = disableNotifications;\n    this.url = Strings.CS.removeEnd(url, \"/\");\n  }\n\n  public String getConnectionId() {\n    return connectionId;\n  }\n\n  public ConnectionKind getKind() {\n    return kind;\n  }\n\n  public boolean isDisableNotifications() {\n    return disableNotifications;\n  }\n\n  public String getUrl() {\n    return url;\n  }\n\n  public abstract EndpointParams getEndpointParams();\n\n  public boolean isSameServerUrl(String otherUrl) {\n    URI myUri;\n    URI otherUri;\n    try {\n      myUri = new URI(Strings.CS.removeEnd(url, \"/\"));\n      otherUri = new URI(Strings.CS.removeEnd(otherUrl, \"/\"));\n    } catch (URISyntaxException e) {\n      return false;\n    }\n    return Objects.equals(myUri, otherUri);\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    var that = (AbstractConnectionConfiguration) o;\n    return Objects.equals(connectionId, that.connectionId)\n      && Objects.equals(disableNotifications, that.disableNotifications)\n            && Objects.equals(url, that.url);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(connectionId, url);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/repository/connection/ConnectionConfigurationRepository.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.repository.connection;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.ConcurrentHashMap;\nimport javax.annotation.CheckForNull;\nimport org.sonarsource.sonarlint.core.commons.ConnectionKind;\nimport org.sonarsource.sonarlint.core.serverapi.EndpointParams;\n\npublic class ConnectionConfigurationRepository {\n\n  private final Map<String, AbstractConnectionConfiguration> connectionsById = new ConcurrentHashMap<>();\n\n  /**\n   * Add or replace connection configuration.\n   * @return the previous configuration with the same id, if any\n   */\n  @CheckForNull\n  public AbstractConnectionConfiguration addOrReplace(AbstractConnectionConfiguration connectionConfiguration) {\n    return connectionsById.put(connectionConfiguration.getConnectionId(), connectionConfiguration);\n  }\n\n  /**\n   * Remove a connection configuration.\n   * @return the removed configuration, if any\n   */\n  @CheckForNull\n  public AbstractConnectionConfiguration remove(String idToRemove) {\n    return connectionsById.remove(idToRemove);\n  }\n\n  public Map<String, AbstractConnectionConfiguration> getConnectionsById() {\n    return Map.copyOf(connectionsById);\n  }\n\n  @CheckForNull\n  public AbstractConnectionConfiguration getConnectionById(String id) {\n    return connectionsById.get(id);\n  }\n\n  public Optional<EndpointParams> getEndpointParams(String connectionId) {\n    return Optional.ofNullable(getConnectionById(connectionId)).map(AbstractConnectionConfiguration::getEndpointParams);\n  }\n\n  public boolean hasConnectionWithOrigin(String serverOrigin) {\n    // The Origin header has the following format: <scheme>://<host>(:<port>)\n    // Since servers can have an optional \"context path\" after this, we consider a valid match when the server's configured URL begins with the\n    // passed Origin\n    // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin\n    return connectionsById.values().stream()\n      .anyMatch(connection -> haveSameOrigin(connection.getUrl(), serverOrigin));\n  }\n\n  public static boolean haveSameOrigin(String knownServerUrl, String incomingOrigin) {\n    return ensureTrailingSlash(knownServerUrl).startsWith(ensureTrailingSlash(incomingOrigin));\n  }\n\n  private static String ensureTrailingSlash(String s) {\n    return !s.endsWith(\"/\") ? (s + \"/\") : s;\n  }\n\n  public List<AbstractConnectionConfiguration> findByUrl(String serverUrl) {\n    return connectionsById.values().stream()\n      .filter(connection -> connection.isSameServerUrl(serverUrl))\n      .toList();\n  }\n\n  public List<AbstractConnectionConfiguration> findByOrganization(String organization) {\n    return connectionsById.values().stream()\n      .filter(connection -> connection.getKind() == ConnectionKind.SONARCLOUD)\n      .filter(scConnection -> ((SonarCloudConnectionConfiguration) scConnection).getOrganization().equals(organization))\n      .toList();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/repository/connection/SonarCloudConnectionConfiguration.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.repository.connection;\n\nimport java.net.URI;\nimport java.util.Objects;\nimport javax.annotation.Nullable;\nimport org.apache.commons.lang3.Strings;\nimport org.sonarsource.sonarlint.core.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.commons.ConnectionKind;\nimport org.sonarsource.sonarlint.core.serverapi.EndpointParams;\n\npublic class SonarCloudConnectionConfiguration extends AbstractConnectionConfiguration {\n\n  private final URI apiUri;\n  private final String organization;\n  private final SonarCloudRegion region;\n\n  public SonarCloudConnectionConfiguration(URI uri, URI apiUri, String connectionId, String organization, SonarCloudRegion region, boolean disableNotifications) {\n    super(connectionId, ConnectionKind.SONARCLOUD, disableNotifications, uri.toString());\n    this.apiUri = apiUri;\n    this.organization = organization;\n    this.region = region;\n  }\n\n  public String getOrganization() {\n    return organization;\n  }\n\n  @Override\n  public EndpointParams getEndpointParams() {\n    return new EndpointParams(getUrl(), Strings.CS.removeEnd(apiUri.toString(), \"/\"), true, organization);\n  }\n\n  public SonarCloudRegion getRegion() {\n    return region;\n  }\n\n  @Override\n  public boolean equals(@Nullable Object o) {\n    if (this == o) {\n      return true;\n    }\n    if (o == null || getClass() != o.getClass()) {\n      return false;\n    }\n    if (!super.equals(o)) {\n      return false;\n    }\n    var that = (SonarCloudConnectionConfiguration) o;\n    return Objects.equals(organization, that.organization);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(super.hashCode(), organization);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/repository/connection/SonarQubeConnectionConfiguration.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.repository.connection;\n\nimport org.sonarsource.sonarlint.core.commons.ConnectionKind;\nimport org.sonarsource.sonarlint.core.serverapi.EndpointParams;\n\npublic class SonarQubeConnectionConfiguration extends AbstractConnectionConfiguration {\n\n  public SonarQubeConnectionConfiguration(String connectionId, String serverUrl, boolean disableNotifications) {\n    super(connectionId, ConnectionKind.SONARQUBE, disableNotifications, serverUrl);\n  }\n\n  @Override\n  public EndpointParams getEndpointParams() {\n    return new EndpointParams(getUrl(), null, false, null);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/repository/connection/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.repository.connection;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/repository/reporting/PreviouslyRaisedFindingsRepository.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.repository.reporting;\n\nimport java.net.URI;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.RaisedHotspotDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedFindingDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\n\npublic class PreviouslyRaisedFindingsRepository {\n  private final Map<String, Map<URI, List<RaisedIssueDto>>> previouslyRaisedIssuesByScopeId = new ConcurrentHashMap<>();\n  private final Map<String, Map<URI, List<RaisedHotspotDto>>> previouslyRaisedHotspotsByScopeId = new ConcurrentHashMap<>();\n\n  public Map<URI, List<RaisedIssueDto>> replaceIssuesForFiles(String scopeId, Map<URI, List<RaisedIssueDto>> raisedIssues) {\n    return addOrReplaceFindings(scopeId, raisedIssues, previouslyRaisedIssuesByScopeId);\n  }\n\n  public Map<URI, List<RaisedHotspotDto>> replaceHotspotsForFiles(String scopeId, Map<URI, List<RaisedHotspotDto>> raisedHotpots) {\n    return addOrReplaceFindings(scopeId, raisedHotpots, previouslyRaisedHotspotsByScopeId);\n  }\n\n  private static <F extends RaisedFindingDto> Map<URI, List<F>> addOrReplaceFindings(String scopeId, Map<URI, List<F>> raisedFindings,\n    Map<String, Map<URI, List<F>>> previouslyRaisedFindingsByScopeId) {\n    var findingsPerFile = previouslyRaisedFindingsByScopeId.computeIfAbsent(scopeId, k -> new ConcurrentHashMap<>());\n    findingsPerFile.putAll(raisedFindings);\n    return findingsPerFile;\n  }\n\n  public Map<URI, List<RaisedIssueDto>> getRaisedIssuesForScope(String scopeId) {\n    return previouslyRaisedIssuesByScopeId.getOrDefault(scopeId, Map.of());\n  }\n\n  public Map<URI, List<RaisedHotspotDto>> getRaisedHotspotsForScope(String scopeId) {\n    return previouslyRaisedHotspotsByScopeId.getOrDefault(scopeId, Map.of());\n  }\n\n  public void resetFindingsCache(String scopeId, Set<URI> files) {\n    resetCacheForFindings(scopeId, files, previouslyRaisedIssuesByScopeId);\n    resetCacheForFindings(scopeId, files, previouslyRaisedHotspotsByScopeId);\n  }\n\n  private static <F extends RaisedFindingDto> void resetCacheForFindings(String scopeId, Set<URI> files, Map<String, Map<URI, List<F>>> cache) {\n    Map<URI, List<F>> blankCache = files.stream().collect(Collectors.toMap(Function.identity(), e -> new ArrayList<>()));\n    cache.compute(scopeId, (file, issues) -> blankCache);\n  }\n\n  public Optional<RaisedIssue> findRaisedIssueById(UUID issueId) {\n    return previouslyRaisedIssuesByScopeId.values().stream()\n      .flatMap(issuesByUri -> issuesByUri.entrySet().stream()\n        .flatMap(entry -> entry.getValue().stream().filter(issue -> issue.getId().equals(issueId)).findFirst().map(issue -> new RaisedIssue(entry.getKey(), issue)).stream()))\n      .findFirst();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/repository/reporting/RaisedIssue.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.repository.reporting;\n\nimport java.net.URI;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\n\npublic record RaisedIssue(URI fileUri, RaisedIssueDto issueDto) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/repository/reporting/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.repository.reporting;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/repository/rules/RulesRepository.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.repository.rules;\n\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.rule.extractor.SonarLintRuleDefinition;\nimport org.sonarsource.sonarlint.core.rules.RulesExtractionHelper;\nimport org.sonarsource.sonarlint.core.serverconnection.ServerSettings;\nimport org.sonarsource.sonarlint.core.serverconnection.StoredServerInfo;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\n\npublic class RulesRepository {\n\n  private final SonarLintLogger logger = SonarLintLogger.get();\n\n  private final RulesExtractionHelper extractionHelper;\n  private Map<String, SonarLintRuleDefinition> embeddedRulesByKey;\n  private final Map<String, Map<String, SonarLintRuleDefinition>> rulesByKeyByConnectionId = new HashMap<>();\n  private final Map<String, Map<String, String>> ruleKeyReplacementsByConnectionId = new HashMap<>();\n  private final StorageService storageService;\n\n  public RulesRepository(RulesExtractionHelper extractionHelper, StorageService storageService) {\n    this.extractionHelper = extractionHelper;\n    this.storageService = storageService;\n  }\n\n  public synchronized Collection<SonarLintRuleDefinition> getEmbeddedRules() {\n    lazyInit();\n    return embeddedRulesByKey.values();\n  }\n\n  public synchronized Optional<SonarLintRuleDefinition> getEmbeddedRule(String ruleKey) {\n    lazyInit();\n    return Optional.ofNullable(embeddedRulesByKey.get(ruleKey));\n  }\n\n  private synchronized void lazyInit() {\n    if (embeddedRulesByKey == null) {\n      this.embeddedRulesByKey = byKey(extractionHelper.extractEmbeddedRules());\n    }\n  }\n\n  public synchronized Optional<SonarLintRuleDefinition> getRule(String connectionId, String ruleKey) {\n    lazyInit(connectionId);\n    var connectionRules = rulesByKeyByConnectionId.get(connectionId);\n    return Optional.ofNullable(connectionRules.get(ruleKey))\n      .or(() -> Optional.ofNullable(connectionRules.get(ruleKeyReplacementsByConnectionId.get(connectionId).get(ruleKey))));\n  }\n\n  private synchronized void lazyInit(String connectionId) {\n    var rulesByKey = rulesByKeyByConnectionId.get(connectionId);\n    if (rulesByKey == null) {\n      var serverSettings = storageService.connection(connectionId).serverInfo().read().map(StoredServerInfo::globalSettings);\n      setRules(connectionId, extractionHelper.extractRulesForConnection(connectionId, serverSettings.map(ServerSettings::globalSettings).orElseGet(Map::of)));\n    }\n  }\n\n  private void setRules(String connectionId, Collection<SonarLintRuleDefinition> rules) {\n    var rulesByKey = byKey(rules);\n    var ruleKeyReplacements = new HashMap<String, String>();\n    rules.forEach(rule -> rule.getDeprecatedKeys().forEach(deprecatedKey -> ruleKeyReplacements.put(deprecatedKey, rule.getKey())));\n    rulesByKeyByConnectionId.put(connectionId, rulesByKey);\n    ruleKeyReplacementsByConnectionId.put(connectionId, ruleKeyReplacements);\n  }\n\n  private static Map<String, SonarLintRuleDefinition> byKey(Collection<SonarLintRuleDefinition> rules) {\n    return rules.stream()\n      .collect(Collectors.toMap(SonarLintRuleDefinition::getKey, r -> r));\n  }\n\n  public synchronized void evictFor(String connectionId) {\n    logger.debug(\"Evict cached rules definitions for connection '{}'\", connectionId);\n    rulesByKeyByConnectionId.remove(connectionId);\n    ruleKeyReplacementsByConnectionId.remove(connectionId);\n  }\n\n  public synchronized void evictEmbedded() {\n    logger.debug(\"Evict cached embedded rules definitions\");\n    embeddedRulesByKey = null;\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/repository/rules/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.repository.rules;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/rules/CleanCodePrinciples.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rules;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport javax.annotation.CheckForNull;\nimport org.apache.commons.io.IOUtils;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\nclass CleanCodePrinciples {\n\n  @CheckForNull\n  public static String getContent(String key) {\n    var fileStream = CleanCodePrinciples.class.getResourceAsStream(\"/clean-code-principles/\" + key + \".html\");\n    if (fileStream == null) {\n      SonarLintLogger.get().info(\"Unsupported clean code principle key: \" + key);\n      return null;\n    }\n    try {\n      return IOUtils.toString(fileStream, StandardCharsets.UTF_8).trim().replaceAll(\"\\\\r\\\\n?\", \"\\n\");\n    } catch (IOException e) {\n      SonarLintLogger.get().error(\"Could not read content for clean code principle key: \" + key, e);\n      return null;\n    }\n  }\n\n  private CleanCodePrinciples() {\n    // utility class\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/rules/OthersSectionHtmlContent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rules;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport org.apache.commons.io.IOUtils;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic class OthersSectionHtmlContent {\n\n  private static final String FOLDER_NAME = \"/context-rule-description/\";\n  private static final String FILE_EXTENSION = \".html\";\n  private static final String UNSUPPORTED_RULE_DESCRIPTION_FOR_CONTEXT_KEY = \"Unsupported rule description for context key: \";\n  private static final String ERROR_READING_FILE_CONTENT = \"Could not read the content for rule description for context key: \";\n\n  private static final String OTHERS_SECTION_HTML_CONTENT_KEY = \"others_section_html_content\";\n  private OthersSectionHtmlContent() {}\n\n  public static String getHtmlContent() {\n    try (var htmlContentFile = OthersSectionHtmlContent.class.getResourceAsStream(FOLDER_NAME +\n      OTHERS_SECTION_HTML_CONTENT_KEY + FILE_EXTENSION)) {\n      if (htmlContentFile == null) {\n        SonarLintLogger.get().info(UNSUPPORTED_RULE_DESCRIPTION_FOR_CONTEXT_KEY + OTHERS_SECTION_HTML_CONTENT_KEY);\n        return \"\";\n      }\n\n      return IOUtils.toString(htmlContentFile, StandardCharsets.UTF_8).trim().replaceAll(\"\\\\r\\\\n?\", \"\\n\");\n    } catch (IOException ioException) {\n      SonarLintLogger.get().error(ERROR_READING_FILE_CONTENT + OTHERS_SECTION_HTML_CONTENT_KEY, ioException);\n      return \"\";\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/rules/RuleDetails.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rules;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.EnumMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.jetbrains.annotations.NotNull;\nimport org.sonarsource.sonarlint.core.commons.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.StandaloneRuleConfigDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedFindingDto;\nimport org.sonarsource.sonarlint.core.rule.extractor.SonarLintRuleDefinition;\nimport org.sonarsource.sonarlint.core.rule.extractor.SonarLintRuleParamDefinition;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.common.ImpactPayload;\nimport org.sonarsource.sonarlint.core.serverapi.rules.ServerActiveRule;\nimport org.sonarsource.sonarlint.core.serverapi.rules.ServerRule;\n\npublic class RuleDetails {\n\n  public static final String DEFAULT_SECTION = \"default\";\n\n  private final String key;\n  private final SonarLanguage language;\n  private final String name;\n  private final String htmlDescription;\n  private final Map<String, List<DescriptionSection>> descriptionSectionsByKey;\n  private final IssueSeverity defaultSeverity;\n  private final RuleType type;\n  private final CleanCodeAttribute cleanCodeAttribute;\n  private final Map<SoftwareQuality, ImpactSeverity> impacts;\n  private final Collection<EffectiveRuleParam> params;\n  private final String extendedDescription;\n  private final Set<String> educationPrincipleKeys;\n  private final VulnerabilityProbability vulnerabilityProbability;\n\n  public RuleDetails(String key, SonarLanguage language, String name, String htmlDescription, Map<String, List<DescriptionSection>> descriptionSectionsByKey,\n    Map<SoftwareQuality, ImpactSeverity> impacts, @Nullable IssueSeverity defaultSeverity, @Nullable RuleType type, @Nullable CleanCodeAttribute cleanCodeAttribute,\n    @Nullable String extendedDescription, Collection<EffectiveRuleParam> params, Set<String> educationPrincipleKeys,\n    @Nullable VulnerabilityProbability vulnerabilityProbability) {\n    this.key = key;\n    this.language = language;\n    this.name = name;\n    this.htmlDescription = htmlDescription;\n    this.descriptionSectionsByKey = descriptionSectionsByKey;\n    this.defaultSeverity = defaultSeverity;\n    this.type = type;\n    this.cleanCodeAttribute = cleanCodeAttribute;\n    this.impacts = impacts;\n    this.params = params;\n    this.extendedDescription = extendedDescription;\n    this.educationPrincipleKeys = educationPrincipleKeys;\n    this.vulnerabilityProbability = vulnerabilityProbability;\n  }\n\n  public static RuleDetails from(SonarLintRuleDefinition ruleDefinition, @Nullable StandaloneRuleConfigDto ruleConfig) {\n    return new RuleDetails(\n      ruleDefinition.getKey(),\n      ruleDefinition.getLanguage(),\n      ruleDefinition.getName(),\n      ruleDefinition.getHtmlDescription(),\n      ruleDefinition.getDescriptionSections().stream()\n        .map(s -> new DescriptionSection(s.getKey(), s.getHtmlContent(), s.getContext().map(c -> new DescriptionSection.Context(c.getKey(), c.getDisplayName()))))\n        .collect(Collectors.groupingBy(DescriptionSection::getKey)),\n      ruleDefinition.getDefaultImpacts(),\n      ruleDefinition.getDefaultSeverity(),\n      ruleDefinition.getType(),\n      ruleDefinition.getCleanCodeAttribute().orElse(CleanCodeAttribute.defaultCleanCodeAttribute()),\n      null,\n      transformParams(ruleDefinition.getParams(), ruleConfig != null ? ruleConfig.getParamValueByKey() : Map.of()),\n      ruleDefinition.getEducationPrincipleKeys(), ruleDefinition.getVulnerabilityProbability().orElse(null));\n  }\n\n  @NotNull\n  private static List<EffectiveRuleParam> transformParams(Map<String, SonarLintRuleParamDefinition> ruleDefinitionParams, Map<String, String> ruleConfigParams) {\n    return ruleDefinitionParams.values()\n      .stream()\n      .map(p -> new EffectiveRuleParam(p.name(), p.description(), ruleConfigParams.getOrDefault(p.key(), p.defaultValue()), p.defaultValue()))\n      .toList();\n  }\n\n  public static RuleDetails merging(ServerActiveRule activeRuleFromStorage, ServerRule serverRule) {\n    return new RuleDetails(activeRuleFromStorage.getRuleKey(), serverRule.getLanguage(), serverRule.getName(), serverRule.getHtmlDesc(),\n      serverRule.getDescriptionSections().stream()\n        .map(s -> new DescriptionSection(s.getKey(), s.getHtmlContent(), s.getContext().map(c -> new DescriptionSection.Context(c.getKey(), c.getDisplayName()))))\n        .collect(Collectors.groupingBy(DescriptionSection::getKey)),\n      serverRule.getImpacts(),\n      Optional.ofNullable(activeRuleFromStorage.getSeverity()).orElse(serverRule.getSeverity()),\n      serverRule.getType(),\n      serverRule.getCleanCodeAttribute(),\n      serverRule.getHtmlNote(), Collections.emptyList(),\n      serverRule.getEducationPrincipleKeys(),\n      // TODO get vulnerability probability from storage?\n      null);\n  }\n\n  public static RuleDetails merging(ServerRule activeRuleFromServer, SonarLintRuleDefinition ruleDefFromPlugin, boolean skipCleanCodeTaxonomy) {\n    var cleanCodeAttribute = skipCleanCodeTaxonomy ? null : ruleDefFromPlugin.getCleanCodeAttribute().orElse(CleanCodeAttribute.defaultCleanCodeAttribute());\n    var defaultImpacts = skipCleanCodeTaxonomy ? Map.<SoftwareQuality, ImpactSeverity>of() : ruleDefFromPlugin.getDefaultImpacts();\n    return new RuleDetails(ruleDefFromPlugin.getKey(), ruleDefFromPlugin.getLanguage(), ruleDefFromPlugin.getName(), ruleDefFromPlugin.getHtmlDescription(),\n      ruleDefFromPlugin.getDescriptionSections().stream()\n        .map(s -> new DescriptionSection(s.getKey(), s.getHtmlContent(), s.getContext().map(c -> new DescriptionSection.Context(c.getKey(), c.getDisplayName()))))\n        .collect(Collectors.groupingBy(DescriptionSection::getKey)),\n      defaultImpacts,\n      Optional.ofNullable(activeRuleFromServer.getSeverity()).orElse(ruleDefFromPlugin.getDefaultSeverity()), ruleDefFromPlugin.getType(),\n      cleanCodeAttribute,\n      activeRuleFromServer.getHtmlNote(), Collections.emptyList(), ruleDefFromPlugin.getEducationPrincipleKeys(), ruleDefFromPlugin.getVulnerabilityProbability().orElse(null));\n  }\n\n  public static RuleDetails merging(ServerActiveRule activeRuleFromStorage, ServerRule serverRule, SonarLintRuleDefinition templateRuleDefFromPlugin,\n    boolean skipCleanCodeTaxonomy) {\n    var cleanCodeAttribute = skipCleanCodeTaxonomy ? null : templateRuleDefFromPlugin.getCleanCodeAttribute().orElse(CleanCodeAttribute.defaultCleanCodeAttribute());\n    var defaultImpacts = skipCleanCodeTaxonomy ? Map.<SoftwareQuality, ImpactSeverity>of() : templateRuleDefFromPlugin.getDefaultImpacts();\n    return new RuleDetails(\n      activeRuleFromStorage.getRuleKey(),\n      templateRuleDefFromPlugin.getLanguage(),\n      serverRule.getName(),\n      serverRule.getHtmlDesc(),\n      serverRule.getDescriptionSections().stream()\n        .map(s -> new DescriptionSection(s.getKey(), s.getHtmlContent(), s.getContext().map(c -> new DescriptionSection.Context(c.getKey(), c.getDisplayName()))))\n        .collect(Collectors.groupingBy(DescriptionSection::getKey)),\n      mergeImpacts(defaultImpacts, activeRuleFromStorage.getOverriddenImpacts()),\n      serverRule.getSeverity(),\n      serverRule.getType(),\n      cleanCodeAttribute,\n      serverRule.getHtmlNote(),\n      Collections.emptyList(), templateRuleDefFromPlugin.getEducationPrincipleKeys(), templateRuleDefFromPlugin.getVulnerabilityProbability().orElse(null));\n  }\n\n  public static Map<SoftwareQuality, ImpactSeverity> mergeImpacts(Map<SoftwareQuality, ImpactSeverity> defaultImpacts,\n    List<ImpactPayload> overriddenImpacts) {\n    var mergedImpacts = new EnumMap<SoftwareQuality, ImpactSeverity>(SoftwareQuality.class);\n    if (!defaultImpacts.isEmpty()) {\n      mergedImpacts = new EnumMap<>(defaultImpacts);\n    }\n\n    for (var impact : overriddenImpacts) {\n      var quality = SoftwareQuality.valueOf(impact.getSoftwareQuality());\n      var severity = ImpactSeverity.mapSeverity(impact.getSeverity());\n      mergedImpacts.put(quality, severity);\n    }\n\n    return Collections.unmodifiableMap(mergedImpacts);\n  }\n\n  public static RuleDetails merging(RuleDetails serverActiveRuleDetails, RaisedFindingDto raisedFindingDto) {\n    var isMQRMode = raisedFindingDto.getSeverityMode().isRight();\n    var softwareImpacts = new EnumMap<SoftwareQuality, ImpactSeverity>(SoftwareQuality.class);\n    if (isMQRMode) {\n      raisedFindingDto.getSeverityMode().getRight().getImpacts().forEach(\n        i -> softwareImpacts.put(SoftwareQuality.valueOf(i.getSoftwareQuality().name()),\n          ImpactSeverity.valueOf(i.getImpactSeverity().name()))\n      );\n    }\n    return new RuleDetails(serverActiveRuleDetails.getKey(),\n      serverActiveRuleDetails.getLanguage(),\n      serverActiveRuleDetails.getName(),\n      serverActiveRuleDetails.getHtmlDescription(),\n      serverActiveRuleDetails.getDescriptionSectionsByKey(),\n      softwareImpacts,\n      isMQRMode ? null : IssueSeverity.valueOf(raisedFindingDto.getSeverityMode().getLeft().getSeverity().toString()),\n      isMQRMode ? null : RuleType.valueOf(raisedFindingDto.getSeverityMode().getLeft().getType().toString()),\n      isMQRMode ? CleanCodeAttribute.valueOf(raisedFindingDto.getSeverityMode().getRight().getCleanCodeAttribute().name()) : null,\n      serverActiveRuleDetails.getExtendedDescription(),\n      serverActiveRuleDetails.getParams(),\n      serverActiveRuleDetails.educationPrincipleKeys,\n      serverActiveRuleDetails.getVulnerabilityProbability());\n  }\n\n  public static RuleDetails merging(RuleDetails serverActiveRuleDetails, TaintVulnerabilityDto taintVulnerabilityDto) {\n    var isMQRMode = taintVulnerabilityDto.getSeverityMode().isRight();\n    EnumMap<SoftwareQuality, ImpactSeverity> softwareImpacts = new EnumMap<>(SoftwareQuality.class);\n    if (isMQRMode) {\n      taintVulnerabilityDto.getSeverityMode().getRight().getImpacts().forEach(\n        i -> softwareImpacts.put(SoftwareQuality.valueOf(i.getSoftwareQuality().name()),\n          ImpactSeverity.valueOf(i.getImpactSeverity().name()))\n      );\n    }\n    return new RuleDetails(serverActiveRuleDetails.getKey(),\n      serverActiveRuleDetails.getLanguage(),\n      serverActiveRuleDetails.getName(),\n      serverActiveRuleDetails.getHtmlDescription(),\n      serverActiveRuleDetails.getDescriptionSectionsByKey(),\n      softwareImpacts,\n      isMQRMode ? null : IssueSeverity.valueOf(taintVulnerabilityDto.getSeverityMode().getLeft().getSeverity().toString()),\n      isMQRMode ? null : RuleType.valueOf(taintVulnerabilityDto.getSeverityMode().getLeft().getType().toString()),\n      isMQRMode ? CleanCodeAttribute.valueOf(taintVulnerabilityDto.getSeverityMode().getRight().getCleanCodeAttribute().name()) : null,\n      serverActiveRuleDetails.getExtendedDescription(),\n      serverActiveRuleDetails.getParams(),\n      serverActiveRuleDetails.educationPrincipleKeys,\n      serverActiveRuleDetails.getVulnerabilityProbability());\n  }\n\n  public String getKey() {\n    return key;\n  }\n\n  public SonarLanguage getLanguage() {\n    return language;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public String getHtmlDescription() {\n    return htmlDescription;\n  }\n\n  public boolean hasMonolithicDescription() {\n    return descriptionSectionsByKey.isEmpty() || hasOnlyDefaultSection();\n  }\n\n  private boolean hasOnlyDefaultSection() {\n    return descriptionSectionsByKey.size() == 1 && descriptionSectionsByKey.containsKey(DEFAULT_SECTION);\n  }\n\n  public Map<String, List<DescriptionSection>> getDescriptionSectionsByKey() {\n    return descriptionSectionsByKey;\n  }\n\n  @CheckForNull\n  public IssueSeverity getDefaultSeverity() {\n    return defaultSeverity;\n  }\n\n  @CheckForNull\n  public RuleType getType() {\n    return type;\n  }\n\n  public Optional<CleanCodeAttribute> getCleanCodeAttribute() {\n    return Optional.ofNullable(cleanCodeAttribute);\n  }\n\n  public Map<SoftwareQuality, ImpactSeverity> getImpacts() {\n    return impacts;\n  }\n\n  public Collection<EffectiveRuleParam> getParams() {\n    return params;\n  }\n\n  public Set<String> getCleanCodePrincipleKeys() {\n    return educationPrincipleKeys;\n  }\n\n  @CheckForNull\n  public String getExtendedDescription() {\n    return extendedDescription;\n  }\n\n  public VulnerabilityProbability getVulnerabilityProbability() {\n    return vulnerabilityProbability;\n  }\n\n  public static class EffectiveRuleParam {\n    private final String name;\n    private final String description;\n    @Nullable\n    private final String value;\n    @Nullable\n    private final String defaultValue;\n\n    public EffectiveRuleParam(String name, String description, @Nullable String value, @Nullable String defaultValue) {\n      this.name = name;\n      this.description = description;\n      this.value = value;\n      this.defaultValue = defaultValue;\n    }\n\n    public String getName() {\n      return name;\n    }\n\n    public String getDescription() {\n      return description;\n    }\n\n    @CheckForNull\n    public String getValue() {\n      return value;\n    }\n\n    @CheckForNull\n    public String getDefaultValue() {\n      return defaultValue;\n    }\n  }\n\n  public static class DescriptionSection {\n    private final String key;\n    private final String htmlContent;\n    private final Optional<Context> context;\n\n    public DescriptionSection(String key, String htmlContent, Optional<Context> context) {\n      this.key = key;\n      this.htmlContent = htmlContent;\n      this.context = context;\n    }\n\n    public String getKey() {\n      return key;\n    }\n\n    public String getHtmlContent() {\n      return htmlContent;\n    }\n\n    public Optional<Context> getContext() {\n      return context;\n    }\n\n    public static class Context {\n      private final String key;\n      private final String displayName;\n\n      public Context(String key, String displayName) {\n        this.key = key;\n        this.displayName = displayName;\n      }\n\n      public String getKey() {\n        return key;\n      }\n\n      public String getDisplayName() {\n        return displayName;\n      }\n    }\n\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/rules/RuleDetailsAdapter.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rules;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.apache.commons.lang3.StringUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFileEdit;\nimport org.sonarsource.sonarlint.core.analysis.api.Flow;\nimport org.sonarsource.sonarlint.core.analysis.api.QuickFix;\nimport org.sonarsource.sonarlint.core.analysis.api.TextEdit;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.EffectiveRuleDetailsDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.EffectiveRuleParamDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.ImpactDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleContextualSectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleContextualSectionWithDefaultContextKeyDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleDescriptionTabDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleMonolithicDescriptionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleNonContextualSectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleSplitDescriptionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.FileEditDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.IssueFlowDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.IssueLocationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.QuickFixDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.TextEditDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttributeCategory;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.MQRModeDetails;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.StandardModeDetails;\n\nimport static org.sonarsource.sonarlint.core.tracking.TextRangeUtils.toTextRangeDto;\n\npublic class RuleDetailsAdapter {\n  public static final String INTRODUCTION_SECTION_KEY = \"introduction\";\n  public static final String ROOT_CAUSE_SECTION_KEY = \"root_cause\";\n  public static final String ASSESS_THE_PROBLEM_SECTION_KEY = \"assess_the_problem\";\n  public static final String HOW_TO_FIX_SECTION_KEY = \"how_to_fix\";\n  public static final String RESOURCES_SECTION_KEY = \"resources\";\n  private static final String DEFAULT_CONTEXT_KEY = \"others\";\n  private static final String DEFAULT_CONTEXT_DISPLAY_NAME = \"Others\";\n  private static final List<String> SECTION_KEYS_ORDERED = List.of(ROOT_CAUSE_SECTION_KEY, ASSESS_THE_PROBLEM_SECTION_KEY,\n    HOW_TO_FIX_SECTION_KEY, RESOURCES_SECTION_KEY);\n\n  private RuleDetailsAdapter() {\n    // utility class\n  }\n\n  public static EffectiveRuleDetailsDto transform(RuleDetails ruleDetails, @Nullable String contextKey) {\n    var cleanCodeAttribute = ruleDetails.getCleanCodeAttribute().map(RuleDetailsAdapter::adapt).orElse(null);\n    Either<StandardModeDetails, MQRModeDetails> severityDetails = cleanCodeAttribute != null && !ruleDetails.getImpacts().isEmpty() ?\n      Either.forRight(new MQRModeDetails(cleanCodeAttribute, toDto(ruleDetails.getImpacts()))) :\n      Either.forLeft(new StandardModeDetails(adapt(Objects.requireNonNull(ruleDetails.getDefaultSeverity())),\n        adapt(Objects.requireNonNull(ruleDetails.getType()))));\n    return new EffectiveRuleDetailsDto(\n      ruleDetails.getKey(),\n      ruleDetails.getName(),\n      severityDetails,\n      transformDescriptions(ruleDetails, contextKey),\n      transform(ruleDetails.getParams()),\n      adapt(ruleDetails.getLanguage()),\n      adapt(ruleDetails.getVulnerabilityProbability()));\n  }\n\n  public static Either<RuleMonolithicDescriptionDto, RuleSplitDescriptionDto> transformDescriptions(RuleDetails ruleDetails, @Nullable String contextKey) {\n    if (ruleDetails.hasMonolithicDescription()) {\n      return Either.forLeft(transformMonolithicDescription(ruleDetails));\n    }\n    return Either.forRight(transformSplitDescription(ruleDetails, contextKey));\n  }\n\n  private static RuleMonolithicDescriptionDto transformMonolithicDescription(RuleDetails ruleDetails) {\n    var htmlSnippets = new ArrayList<String>();\n    if (!ruleDetails.getDescriptionSectionsByKey().isEmpty()) {\n      // The rule has only `default` section\n      htmlSnippets.addAll(ruleDetails.getDescriptionSectionsByKey().get(\"default\").stream().map(RuleDetails.DescriptionSection::getHtmlContent).toList());\n    } else {\n      htmlSnippets.add(ruleDetails.getHtmlDescription());\n    }\n    htmlSnippets.add(ruleDetails.getExtendedDescription());\n    htmlSnippets.add(getCleanCodePrinciplesContent(ruleDetails.getCleanCodePrincipleKeys()));\n    return new RuleMonolithicDescriptionDto(concat(htmlSnippets));\n  }\n\n  private static String getCleanCodePrinciplesContent(Set<String> cleanCodePrincipleKeys) {\n    var principles = cleanCodePrincipleKeys.stream().sorted(Comparator.naturalOrder()).map(CleanCodePrinciples::getContent).toList();\n    return (principles.stream().anyMatch(StringUtils::isNotBlank) ? \"<h3>Clean Code Principles</h3>\\n\" : \"\") + concat(principles);\n  }\n\n  private static RuleSplitDescriptionDto transformSplitDescription(RuleDetails ruleDetails, @Nullable String contextKey) {\n    var sectionsByKey = ruleDetails.getDescriptionSectionsByKey();\n\n    var tabbedSections = new ArrayList<>(transformSectionsButIntroductionToTabs(ruleDetails, contextKey));\n    addMoreInfoTabIfNeeded(ruleDetails, tabbedSections);\n    return new RuleSplitDescriptionDto(extractIntroductionFromSections(sectionsByKey), tabbedSections);\n  }\n\n  @Nullable\n  private static String extractIntroductionFromSections(Map<String, List<RuleDetails.DescriptionSection>> sectionsByKey) {\n    var introductionSections = sectionsByKey.get(INTRODUCTION_SECTION_KEY);\n    String introductionHtmlContent = null;\n    if (introductionSections != null && !introductionSections.isEmpty()) {\n      // assume there is only one introduction section\n      introductionHtmlContent = introductionSections.get(0).getHtmlContent();\n    }\n    return introductionHtmlContent;\n  }\n\n  private static void addMoreInfoTabIfNeeded(RuleDetails ruleDetails, ArrayList<RuleDescriptionTabDto> tabbedSections) {\n    if (!ruleDetails.getDescriptionSectionsByKey().containsKey(RESOURCES_SECTION_KEY)) {\n      var htmlSnippets = new ArrayList<String>();\n      htmlSnippets.add(ruleDetails.getExtendedDescription());\n      htmlSnippets.add(getCleanCodePrinciplesContent(ruleDetails.getCleanCodePrincipleKeys()));\n      var content = concat(htmlSnippets);\n      if (StringUtils.isNotBlank(content)) {\n        tabbedSections\n          .add(new RuleDescriptionTabDto(getTabTitle(ruleDetails, RESOURCES_SECTION_KEY), Either.forLeft(new RuleNonContextualSectionDto(content))));\n      }\n    }\n  }\n\n  private static Collection<RuleDescriptionTabDto> transformSectionsButIntroductionToTabs(RuleDetails ruleDetails, @Nullable String contextKey) {\n    var tabbedSections = new ArrayList<RuleDescriptionTabDto>();\n    var sectionsByKey = ruleDetails.getDescriptionSectionsByKey();\n    SECTION_KEYS_ORDERED.forEach(sectionKey -> {\n      if (sectionsByKey.containsKey(sectionKey)) {\n        var tabContents = sectionsByKey.get(sectionKey);\n        Either<RuleNonContextualSectionDto, RuleContextualSectionWithDefaultContextKeyDto> content;\n        var foundMatchingContext = tabContents.stream().anyMatch(c -> c.getContext().isPresent() && c.getContext().get().getKey().equals(contextKey));\n        if (tabContents.size() == 1 && tabContents.get(0).getContext().isEmpty()) {\n          content = buildNonContextualSectionDto(ruleDetails, tabContents.get(0));\n        } else {\n          // if there is more than one section, they should all have a context (verified in sonar-plugin-api)\n          var contextualSectionContents = tabContents.stream().map(s -> {\n            var context = s.getContext().get();\n            return new RuleContextualSectionDto(getTabContent(s, ruleDetails.getExtendedDescription(), ruleDetails.getCleanCodePrincipleKeys()), context.getKey(),\n              context.getDisplayName());\n          })\n            .sorted(Comparator.comparing(RuleContextualSectionDto::getDisplayName))\n            .collect(Collectors.toCollection(ArrayList::new));\n          contextualSectionContents.add(\n            new RuleContextualSectionDto(OthersSectionHtmlContent.getHtmlContent(),\n              DEFAULT_CONTEXT_KEY, DEFAULT_CONTEXT_DISPLAY_NAME));\n          content = Either.forRight(new RuleContextualSectionWithDefaultContextKeyDto(foundMatchingContext ? contextKey : DEFAULT_CONTEXT_KEY, contextualSectionContents));\n        }\n        tabbedSections.add(new RuleDescriptionTabDto(getTabTitle(ruleDetails, sectionKey), content));\n      }\n    });\n    return tabbedSections;\n  }\n\n  private static String getTabTitle(RuleDetails ruleDetails, String sectionKey) {\n    switch (sectionKey) {\n      case ROOT_CAUSE_SECTION_KEY:\n        return RuleType.SECURITY_HOTSPOT.equals(ruleDetails.getType()) ? \"What's the risk?\" : \"Why is this an issue?\";\n      case ASSESS_THE_PROBLEM_SECTION_KEY:\n        return \"Assess the risk\";\n      case HOW_TO_FIX_SECTION_KEY:\n        return \"How can I fix it?\";\n      default:\n        return \"More Info\";\n    }\n  }\n\n  private static String concat(Collection<String> htmlSnippets) {\n    return htmlSnippets.stream()\n      .filter(StringUtils::isNotBlank)\n      .collect(Collectors.joining());\n  }\n\n  private static String getTabContent(RuleDetails.DescriptionSection section, @Nullable String extendedDescription, Set<String> educationPrincipleKeys) {\n    var htmlSnippets = new ArrayList<String>();\n    htmlSnippets.add(section.getHtmlContent());\n    if (RESOURCES_SECTION_KEY.equals(section.getKey())) {\n      htmlSnippets.add(extendedDescription);\n      htmlSnippets.add(getCleanCodePrinciplesContent(educationPrincipleKeys));\n    }\n    return concat(htmlSnippets);\n  }\n\n  private static Collection<EffectiveRuleParamDto> transform(Collection<RuleDetails.EffectiveRuleParam> params) {\n    var builder = new ArrayList<EffectiveRuleParamDto>();\n    for (var param : params) {\n      builder.add(new EffectiveRuleParamDto(\n        param.getName(),\n        param.getDescription(),\n        param.getValue(),\n        param.getDefaultValue()));\n    }\n    return builder;\n  }\n\n  @NotNull\n  private static Either<RuleNonContextualSectionDto, RuleContextualSectionWithDefaultContextKeyDto> buildNonContextualSectionDto(RuleDetails ruleDetails,\n    RuleDetails.DescriptionSection matchingContext) {\n    return Either.forLeft(new RuleNonContextualSectionDto(getTabContent(matchingContext, ruleDetails.getExtendedDescription(), ruleDetails.getCleanCodePrincipleKeys())));\n  }\n\n  public static List<ImpactDto> toDto(Map<org.sonarsource.sonarlint.core.commons.SoftwareQuality, org.sonarsource.sonarlint.core.commons.ImpactSeverity> defaultImpacts) {\n    return defaultImpacts.entrySet().stream()\n      .map(e -> new ImpactDto(adapt(e.getKey()), adapt(e.getValue())))\n      .toList();\n  }\n\n  public static CleanCodeAttribute adapt(org.sonarsource.sonarlint.core.commons.CleanCodeAttribute cca) {\n    return CleanCodeAttribute.valueOf(cca.name());\n  }\n\n  public static CleanCodeAttributeCategory adapt(org.sonarsource.sonarlint.core.commons.CleanCodeAttributeCategory ccac) {\n    return CleanCodeAttributeCategory.valueOf(ccac.name());\n  }\n\n  public static IssueSeverity adapt(org.sonarsource.sonarlint.core.commons.IssueSeverity s) {\n    return IssueSeverity.valueOf(s.name());\n  }\n\n  public static org.sonarsource.sonarlint.core.rpc.protocol.common.RuleType adapt(RuleType t) {\n    return org.sonarsource.sonarlint.core.rpc.protocol.common.RuleType.valueOf(t.name());\n  }\n\n  public static Language adapt(SonarLanguage l) {\n    return Language.valueOf(l.name());\n  }\n\n  public static SoftwareQuality adapt(org.sonarsource.sonarlint.core.commons.SoftwareQuality sq) {\n    return SoftwareQuality.valueOf(sq.name());\n  }\n\n  @CheckForNull\n  public static VulnerabilityProbability adapt(@Nullable org.sonarsource.sonarlint.core.commons.VulnerabilityProbability v) {\n    return v != null ? VulnerabilityProbability.valueOf(v.name()) : null;\n  }\n\n  public static ImpactSeverity adapt(org.sonarsource.sonarlint.core.commons.ImpactSeverity is) {\n    return ImpactSeverity.valueOf(is.name());\n  }\n\n  public static IssueFlowDto adapt(Flow f) {\n    return new IssueFlowDto(f.locations().stream().map(RuleDetailsAdapter::adapt).toList());\n  }\n\n  public static IssueLocationDto adapt(org.sonarsource.sonarlint.core.analysis.api.IssueLocation l) {\n    var inputFile = l.getInputFile();\n    var fileUri = inputFile != null ? inputFile.uri() : null;\n    return new IssueLocationDto(toTextRangeDto(l.getTextRange()), l.getMessage(), fileUri);\n  }\n\n  public static QuickFixDto adapt(QuickFix qf) {\n    List<FileEditDto> fileEditDto = qf.inputFileEdits().stream().map(RuleDetailsAdapter::adapt).toList();\n    return new QuickFixDto(fileEditDto, qf.message());\n  }\n\n  private static FileEditDto adapt(ClientInputFileEdit edit) {\n    return new FileEditDto(edit.target().uri(), edit.textEdits().stream().map(RuleDetailsAdapter::adapt).toList());\n  }\n\n  private static TextEditDto adapt(TextEdit textEdit) {\n    return new TextEditDto(Objects.requireNonNull(toTextRangeDto(textEdit.range())), textEdit.newText());\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/rules/RuleNotFoundException.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rules;\n\npublic class RuleNotFoundException extends Exception {\n  private final String ruleKey;\n\n  public RuleNotFoundException(String message, String ruleKey) {\n    super(message);\n    this.ruleKey = ruleKey;\n  }\n\n  public String getRuleKey() {\n    return ruleKey;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/rules/RulesExtractionHelper.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rules;\n\nimport java.util.List;\nimport java.util.Map;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.languages.LanguageSupportRepository;\nimport org.sonarsource.sonarlint.core.plugin.PluginsService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rule.extractor.RuleSettings;\nimport org.sonarsource.sonarlint.core.rule.extractor.RulesDefinitionExtractor;\nimport org.sonarsource.sonarlint.core.rule.extractor.SonarLintRuleDefinition;\n\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SECURITY_HOTSPOTS;\n\npublic class RulesExtractionHelper {\n\n  private final SonarLintLogger logger = SonarLintLogger.get();\n\n  private final PluginsService pluginsService;\n  private final LanguageSupportRepository languageSupportRepository;\n  private final RulesDefinitionExtractor ruleExtractor = new RulesDefinitionExtractor();\n  private final boolean enableSecurityHotspots;\n\n  public RulesExtractionHelper(PluginsService pluginsService, LanguageSupportRepository languageSupportRepository, InitializeParams params) {\n    this.pluginsService = pluginsService;\n    this.languageSupportRepository = languageSupportRepository;\n    this.enableSecurityHotspots = params.getBackendCapabilities().contains(SECURITY_HOTSPOTS);\n  }\n\n  public List<SonarLintRuleDefinition> extractEmbeddedRules() {\n    logger.debug(\"Extracting standalone rules metadata\");\n    return ruleExtractor.extractRules(pluginsService.getEmbeddedPlugins().plugins().getAllPluginInstancesByKeys(),\n      languageSupportRepository.getEnabledLanguagesInStandaloneMode(), false, false, new RuleSettings(Map.of()));\n  }\n\n  public List<SonarLintRuleDefinition> extractRulesForConnection(String connectionId, Map<String, String> globalSettings) {\n    logger.debug(\"Extracting rules metadata for connection '{}'\", connectionId);\n    var settings = new RuleSettings(globalSettings);\n    return ruleExtractor.extractRules(pluginsService.getPlugins(connectionId).plugins().getAllPluginInstancesByKeys(),\n      languageSupportRepository.getEnabledLanguagesInConnectedMode(), true, enableSecurityHotspots, settings);\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/rules/RulesService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rules;\n\nimport jakarta.inject.Named;\nimport jakarta.inject.Singleton;\nimport java.util.Map;\nimport java.util.stream.Collectors;\nimport org.jetbrains.annotations.NotNull;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.repository.rules.RulesRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleDefinitionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleParamDefinitionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleParamType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.rule.extractor.SonarLintRuleDefinition;\nimport org.sonarsource.sonarlint.core.rule.extractor.SonarLintRuleParamDefinition;\nimport org.sonarsource.sonarlint.core.rule.extractor.SonarLintRuleParamType;\n\nimport static org.sonarsource.sonarlint.core.rules.RuleDetailsAdapter.adapt;\nimport static org.sonarsource.sonarlint.core.rules.RuleDetailsAdapter.toDto;\n\n@Named\n@Singleton\npublic class RulesService {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  public static final String IN_EMBEDDED_RULES = \"' in embedded rules\";\n  private final RulesRepository rulesRepository;\n  public static final String COULD_NOT_FIND_RULE = \"Could not find rule '\";\n\n  public RulesService(RulesRepository rulesRepository) {\n    this.rulesRepository = rulesRepository;\n  }\n\n  public Map<String, RuleDefinitionDto> listAllStandaloneRulesDefinitions() {\n    return rulesRepository.getEmbeddedRules()\n      .stream()\n      .map(RulesService::convert)\n      .collect(Collectors.toMap(RuleDefinitionDto::getKey, r -> r));\n  }\n\n  @NotNull\n  public static RuleDefinitionDto convert(SonarLintRuleDefinition r) {\n    var cleanCodeAttribute = r.getCleanCodeAttribute().map(RuleDetailsAdapter::adapt).orElse(CleanCodeAttribute.CONVENTIONAL);\n    return new RuleDefinitionDto(r.getKey(), r.getName(), cleanCodeAttribute, toDto(r.getDefaultImpacts()),\n      convert(r.getParams()), r.isActiveByDefault(), adapt(r.getLanguage()));\n  }\n\n  private static Map<String, RuleParamDefinitionDto> convert(Map<String, SonarLintRuleParamDefinition> params) {\n    return params.values().stream().map(RulesService::convert).collect(Collectors.toMap(RuleParamDefinitionDto::getKey, r -> r));\n  }\n\n  private static RuleParamDefinitionDto convert(SonarLintRuleParamDefinition paramDef) {\n    return new RuleParamDefinitionDto(paramDef.key(), paramDef.name(), paramDef.description(), paramDef.defaultValue(), convert(paramDef.type()), paramDef.multiple(),\n      paramDef.possibleValues());\n  }\n\n  private static RuleParamType convert(SonarLintRuleParamType type) {\n    try {\n      return RuleParamType.valueOf(type.name());\n    } catch (IllegalArgumentException unknownType) {\n      LOG.warn(\"Unknown parameter type: {}\", type.name());\n      return RuleParamType.STRING;\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/rules/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rules;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/sca/DependencyRiskService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.sca;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.UUID;\nimport javax.annotation.CheckForNull;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseError;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.branch.SonarProjectBranchTrackingService;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.event.DependencyRisksSynchronizedEvent;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.CheckDependencyRiskSupportedResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.DependencyRiskTransition;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.DependencyRiskDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.OpenUrlInBrowserParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.sca.DidChangeDependencyRisksParams;\nimport org.sonarsource.sonarlint.core.serverapi.EndpointParams;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\nimport org.sonarsource.sonarlint.core.serverapi.UrlUtils;\nimport org.sonarsource.sonarlint.core.serverapi.features.Feature;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerDependencyRisk;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.sonarsource.sonarlint.core.sync.ScaSynchronizationService;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryService;\nimport org.springframework.context.event.EventListener;\n\npublic class DependencyRiskService {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final Version SCA_MIN_SQ_VERSION = Version.create(\"2025.4\");\n\n  private final ConfigurationRepository configurationRepository;\n  private final ConnectionConfigurationRepository connectionRepository;\n  private final StorageService storageService;\n  private final SonarQubeClientManager sonarQubeClientManager;\n  private final SonarProjectBranchTrackingService branchTrackingService;\n  private final ScaSynchronizationService scaSynchronizationService;\n  private final SonarLintRpcClient client;\n  private final TelemetryService telemetryService;\n\n  public DependencyRiskService(ConfigurationRepository configurationRepository, ConnectionConfigurationRepository connectionRepository, StorageService storageService,\n    SonarQubeClientManager sonarQubeClientManager, SonarProjectBranchTrackingService branchTrackingService, ScaSynchronizationService scaSynchronizationService,\n    SonarLintRpcClient client, TelemetryService telemetryService) {\n    this.configurationRepository = configurationRepository;\n    this.connectionRepository = connectionRepository;\n    this.storageService = storageService;\n    this.sonarQubeClientManager = sonarQubeClientManager;\n    this.branchTrackingService = branchTrackingService;\n    this.scaSynchronizationService = scaSynchronizationService;\n    this.client = client;\n    this.telemetryService = telemetryService;\n  }\n\n  public List<DependencyRiskDto> listAll(String configurationScopeId, boolean shouldRefresh, SonarLintCancelMonitor cancelMonitor) {\n    return configurationRepository.getEffectiveBinding(configurationScopeId)\n      .map(binding -> loadDependencyRisks(configurationScopeId, binding, shouldRefresh, cancelMonitor))\n      .orElseGet(Collections::emptyList);\n  }\n\n  @EventListener\n  public void onDependencyRisksSynchronized(DependencyRisksSynchronizedEvent event) {\n    var summary = event.summary();\n    var connectionId = event.connectionId();\n    var sonarProjectKey = event.sonarProjectKey();\n    configurationRepository.getBoundScopesToConnectionAndSonarProject(connectionId, sonarProjectKey)\n      .forEach(boundScope -> client.didChangeDependencyRisks(new DidChangeDependencyRisksParams(boundScope.getConfigScopeId(), summary.deletedItemIds(),\n        summary.addedItems().stream()\n          .map(DependencyRiskService::toDto)\n          .toList(),\n        summary.updatedItems().stream()\n          .map(DependencyRiskService::toDto)\n          .toList())));\n  }\n\n  public CheckDependencyRiskSupportedResponse checkSupported(String configurationScopeId) {\n    var configScope = configurationRepository.getConfigurationScope(configurationScopeId);\n    if (configScope == null) {\n      var error = new ResponseError(SonarLintRpcErrorCode.CONFIG_SCOPE_NOT_FOUND, \"The provided configuration scope does not exist: \" + configurationScopeId, configurationScopeId);\n      throw new ResponseErrorException(error);\n    }\n    var effectiveBinding = configurationRepository.getEffectiveBinding(configurationScopeId);\n    if (effectiveBinding.isEmpty()) {\n      return new CheckDependencyRiskSupportedResponse(false, \"The project is not bound, please bind it to SonarQube Server Enterprise 2025.4 or higher\");\n    }\n    var connectionId = effectiveBinding.get().connectionId();\n    var connection = connectionRepository.getConnectionById(connectionId);\n    if (connection == null) {\n      var error = new ResponseError(SonarLintRpcErrorCode.CONNECTION_NOT_FOUND, \"The provided configuration scope is bound to an unknown connection: \" + connectionId,\n        connectionId);\n      throw new ResponseErrorException(error);\n    }\n    var optServerInfo = storageService.connection(connectionId).serverInfo().read();\n    if (optServerInfo.isEmpty()) {\n      var error = new ResponseError(SonarLintRpcErrorCode.CONNECTION_NOT_FOUND, \"Could not retrieve server information for connection\",\n        connectionId);\n      throw new ResponseErrorException(error);\n    }\n    var serverInfo = optServerInfo.get();\n    if (!connection.getEndpointParams().isSonarCloud() && !serverInfo.version().satisfiesMinRequirement(SCA_MIN_SQ_VERSION)) {\n      return new CheckDependencyRiskSupportedResponse(false, \"The connected SonarQube Server version is lower than the minimum supported version 2025.4\");\n    }\n    if (!serverInfo.hasFeature(Feature.SCA)) {\n      return new CheckDependencyRiskSupportedResponse(false, \"The connected SonarQube Server does not have Advanced Security enabled (requires Enterprise edition or higher)\");\n    }\n    return new CheckDependencyRiskSupportedResponse(true, null);\n  }\n\n  private List<DependencyRiskDto> loadDependencyRisks(String configurationScopeId, Binding binding, boolean shouldRefresh, SonarLintCancelMonitor cancelMonitor) {\n    return branchTrackingService.awaitEffectiveSonarProjectBranch(configurationScopeId)\n      .map(matchedBranch -> {\n        if (shouldRefresh) {\n          sonarQubeClientManager.withActiveClient(binding.connectionId(),\n            serverApi -> scaSynchronizationService.synchronize(serverApi, binding.connectionId(), binding.sonarProjectKey(), matchedBranch, cancelMonitor));\n        }\n        var projectStorage = storageService.binding(binding);\n        return projectStorage.findings().loadDependencyRisks(matchedBranch)\n          .stream().map(DependencyRiskService::toDto)\n          .toList();\n      }).orElseGet(Collections::emptyList);\n  }\n\n  private static DependencyRiskDto toDto(ServerDependencyRisk serverDependencyRisk) {\n    return new DependencyRiskDto(\n      serverDependencyRisk.key(),\n      DependencyRiskDto.Type.valueOf(serverDependencyRisk.type().name()),\n      DependencyRiskDto.Severity.valueOf(serverDependencyRisk.severity().name()),\n      DependencyRiskDto.SoftwareQuality.valueOf(serverDependencyRisk.quality().name()),\n      DependencyRiskDto.Status.valueOf(serverDependencyRisk.status().name()),\n      serverDependencyRisk.packageName(),\n      serverDependencyRisk.packageVersion(),\n      serverDependencyRisk.vulnerabilityId(),\n      serverDependencyRisk.cvssScore(),\n      serverDependencyRisk.transitions().stream()\n        .map(transition -> DependencyRiskDto.Transition.valueOf(transition.name()))\n        .toList());\n  }\n\n  public void changeStatus(String configurationScopeId, UUID dependencyRiskKey, DependencyRiskTransition transition, @CheckForNull String comment,\n    SonarLintCancelMonitor cancelMonitor) {\n    var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId);\n    var serverConnection = sonarQubeClientManager.getValidClientOrThrow(binding.connectionId());\n    var projectServerIssueStore = storageService.binding(binding).findings();\n    var branchName = branchTrackingService.awaitEffectiveSonarProjectBranch(configurationScopeId);\n\n    if (branchName.isEmpty()) {\n      throw new IllegalArgumentException(\"Could not determine matched branch for configuration scope \" + configurationScopeId);\n    }\n\n    var dependencyRisks = projectServerIssueStore.loadDependencyRisks(branchName.get());\n    var dependencyRiskOpt = dependencyRisks.stream().filter(risk -> risk.key().equals(dependencyRiskKey)).findFirst();\n\n    if (dependencyRiskOpt.isEmpty()) {\n      throw new DependencyRiskNotFoundException(\"Dependency Risk with key \" + dependencyRiskKey + \" was not found\", dependencyRiskKey.toString());\n    }\n\n    var dependencyRisk = dependencyRiskOpt.get();\n\n    if (!dependencyRisk.transitions().contains(adaptTransition(transition))) {\n      throw new IllegalArgumentException(\"Transition \" + transition + \" is not allowed for this dependency risk\");\n    }\n\n    if ((transition == DependencyRiskTransition.ACCEPT || transition == DependencyRiskTransition.SAFE)\n      && (comment == null || comment.isBlank())) {\n      throw new IllegalArgumentException(\"Comment is required for ACCEPT and SAFE transitions\");\n    }\n\n    LOG.info(\"Changing status for dependency risk {} to {} with comment: {}\", dependencyRiskKey, transition, comment);\n\n    var newStatus = switch (transition) {\n      case ACCEPT -> ServerDependencyRisk.Status.ACCEPT;\n      case SAFE -> ServerDependencyRisk.Status.SAFE;\n      case REOPEN -> ServerDependencyRisk.Status.OPEN;\n      case CONFIRM -> ServerDependencyRisk.Status.CONFIRM;\n    };\n    var updatedDependencyRisk = dependencyRisk.withStatus(newStatus);\n\n    serverConnection.withClientApi(serverApi -> {\n      serverApi.sca().changeStatus(dependencyRiskKey, transition.name(), comment, cancelMonitor);\n      projectServerIssueStore.updateDependencyRiskStatus(dependencyRiskKey, newStatus, updatedDependencyRisk.transitions());\n      client.didChangeDependencyRisks(new DidChangeDependencyRisksParams(configurationScopeId, Set.of(), List.of(), List.of(toDto(updatedDependencyRisk))));\n    });\n  }\n\n  private static ServerDependencyRisk.Transition adaptTransition(DependencyRiskTransition transition) {\n    return switch (transition) {\n      case REOPEN -> ServerDependencyRisk.Transition.REOPEN;\n      case CONFIRM -> ServerDependencyRisk.Transition.CONFIRM;\n      case ACCEPT -> ServerDependencyRisk.Transition.ACCEPT;\n      case SAFE -> ServerDependencyRisk.Transition.SAFE;\n    };\n  }\n\n  public void openDependencyRiskInBrowser(String configurationScopeId, UUID dependencyKey) {\n    var effectiveBinding = configurationRepository.getEffectiveBinding(configurationScopeId);\n    var endpointParams = effectiveBinding.flatMap(binding -> connectionRepository.getEndpointParams(binding.connectionId()));\n    if (effectiveBinding.isEmpty() || endpointParams.isEmpty()) {\n      throw new IllegalArgumentException(String.format(\"Configuration scope '%s' is not bound properly, unable to open dependency risk\", configurationScopeId));\n    }\n    var branchName = branchTrackingService.awaitEffectiveSonarProjectBranch(configurationScopeId);\n    if (branchName.isEmpty()) {\n      throw new IllegalArgumentException(String.format(\"Configuration scope %s has no matching branch, unable to open dependency risk\", configurationScopeId));\n    }\n\n    var url = buildDependencyRiskBrowseUrl(effectiveBinding.get().sonarProjectKey(), branchName.get(), dependencyKey, endpointParams.get());\n\n    client.openUrlInBrowser(new OpenUrlInBrowserParams(url));\n\n    telemetryService.dependencyRiskInvestigatedRemotely();\n  }\n\n  static String buildDependencyRiskBrowseUrl(String projectKey, String branch, UUID dependencyKey, EndpointParams endpointParams) {\n    var relativePath = new StringBuilder(\"/dependency-risks/\")\n      .append(UrlUtils.urlEncode(dependencyKey.toString()))\n      .append(\"/what?id=\")\n      .append(UrlUtils.urlEncode(projectKey))\n      .append(\"&branch=\")\n      .append(UrlUtils.urlEncode(branch))\n      .toString();\n\n    return ServerApiHelper.concat(endpointParams.getBaseUrl(), relativePath);\n  }\n\n  public static class DependencyRiskNotFoundException extends RuntimeException {\n    private final String key;\n\n    public DependencyRiskNotFoundException(String message, String key) {\n      super(message);\n      this.key = key;\n    }\n\n    public String getKey() {\n      return key;\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/sca/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.sca;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/server/event/ServerEventsService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.server.event;\n\nimport com.google.common.util.concurrent.MoreExecutors;\nimport jakarta.annotation.PreDestroy;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.ConnectionKind;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.util.FailSafeExecutors;\nimport org.sonarsource.sonarlint.core.event.BindingConfigChangedEvent;\nimport org.sonarsource.sonarlint.core.event.ConfigurationScopeRemovedEvent;\nimport org.sonarsource.sonarlint.core.event.ConfigurationScopesAddedWithBindingEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationAddedEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationRemovedEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationUpdatedEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionCredentialsChangedEvent;\nimport org.sonarsource.sonarlint.core.event.SonarServerEventReceivedEvent;\nimport org.sonarsource.sonarlint.core.languages.LanguageSupportRepository;\nimport org.sonarsource.sonarlint.core.repository.config.BindingConfiguration;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.springframework.context.ApplicationEventPublisher;\nimport org.springframework.context.event.EventListener;\n\nimport static java.util.Objects.requireNonNull;\nimport static java.util.stream.Collectors.mapping;\nimport static java.util.stream.Collectors.toSet;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SERVER_SENT_EVENTS;\n\npublic class ServerEventsService {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final ConfigurationRepository configurationRepository;\n  private final ConnectionConfigurationRepository connectionConfigurationRepository;\n  private final SonarQubeClientManager sonarQubeClientManager;\n  private final LanguageSupportRepository languageSupportRepository;\n  private final boolean shouldManageServerSentEvents;\n  private final ApplicationEventPublisher eventPublisher;\n  private final Map<String, SonarQubeEventStream> streamsPerConnectionId = new ConcurrentHashMap<>();\n  private final ExecutorService executorService = FailSafeExecutors.newSingleThreadExecutor(\"sonarlint-server-sent-events-subscriber\");\n\n  public ServerEventsService(ConfigurationRepository configurationRepository, ConnectionConfigurationRepository connectionConfigurationRepository,\n    SonarQubeClientManager sonarQubeClientManager, LanguageSupportRepository languageSupportRepository, InitializeParams initializeParams,\n    ApplicationEventPublisher eventPublisher) {\n    this.configurationRepository = configurationRepository;\n    this.connectionConfigurationRepository = connectionConfigurationRepository;\n    this.sonarQubeClientManager = sonarQubeClientManager;\n    this.languageSupportRepository = languageSupportRepository;\n    this.shouldManageServerSentEvents = initializeParams.getBackendCapabilities().contains(SERVER_SENT_EVENTS);\n    this.eventPublisher = eventPublisher;\n  }\n\n  @EventListener\n  public void handle(ConfigurationScopesAddedWithBindingEvent event) {\n    if (!shouldManageServerSentEvents) {\n      return;\n    }\n    executorService.execute(() -> subscribeAll(event.getConfigScopeIds()));\n  }\n\n  @EventListener\n  public void handle(ConfigurationScopeRemovedEvent event) {\n    if (!shouldManageServerSentEvents) {\n      return;\n    }\n    var removedScope = event.removedConfigurationScope();\n    var removedBindingConfiguration = event.removedBindingConfiguration();\n    var bindingConfigurationFromRepository = configurationRepository.getBindingConfiguration(removedScope.id());\n    if (bindingConfigurationFromRepository == null\n      || isBindingDifferent(removedBindingConfiguration, bindingConfigurationFromRepository)) {\n      // it has not been re-added in the meantime, or re-added with different binding\n      executorService.execute(() -> unsubscribe(removedBindingConfiguration));\n    }\n  }\n\n  @EventListener\n  public void handle(BindingConfigChangedEvent event) {\n    if (!shouldManageServerSentEvents) {\n      return;\n    }\n    var previousBinding = event.previousConfig();\n    if (isBindingDifferent(previousBinding, event.newConfig())) {\n      executorService.execute(() -> {\n        unsubscribe(previousBinding);\n        subscribe(event.configScopeId());\n      });\n    }\n  }\n\n  @EventListener\n  public void handle(ConnectionConfigurationAddedEvent event) {\n    if (!shouldManageServerSentEvents) {\n      return;\n    }\n    // This is only to handle the case where binding was invalid (connection did not exist) and became valid (matching connection was created)\n    var connectionId = event.addedConnectionId();\n    executorService.execute(() -> subscribe(connectionId, configurationRepository.getSonarProjectsUsedForConnection(connectionId)));\n  }\n\n  @EventListener\n  public void handle(ConnectionConfigurationRemovedEvent event) {\n    if (!shouldManageServerSentEvents) {\n      return;\n    }\n    executorService.execute(() -> {\n      var stream = streamsPerConnectionId.remove(event.removedConnectionId());\n      if (stream != null) {\n        stream.stop();\n      }\n    });\n  }\n\n  @EventListener\n  public void handle(ConnectionConfigurationUpdatedEvent event) {\n    if (!shouldManageServerSentEvents) {\n      return;\n    }\n    // URL might have changed, in doubt resubscribe\n    executorService.execute(() -> resubscribe(event.updatedConnectionId()));\n  }\n\n  @EventListener\n  public void handle(ConnectionCredentialsChangedEvent event) {\n    if (!shouldManageServerSentEvents) {\n      return;\n    }\n    executorService.execute(() -> resubscribe(event.getConnectionId()));\n  }\n\n  private static boolean isBindingDifferent(BindingConfiguration previousConfig, BindingConfiguration newConfig) {\n    return !Objects.equals(previousConfig.sonarProjectKey(), newConfig.sonarProjectKey())\n      || !Objects.equals(previousConfig.connectionId(), newConfig.connectionId());\n  }\n\n  private void subscribeAll(Set<String> configurationScopeIds) {\n    configurationScopeIds.stream()\n      .map(configurationRepository::getConfiguredBinding)\n      .flatMap(Optional::stream)\n      .collect(Collectors.groupingBy(Binding::connectionId, mapping(Binding::sonarProjectKey, toSet())))\n      .forEach(this::subscribe);\n  }\n\n  private void subscribe(String scopeId) {\n    configurationRepository.getConfiguredBinding(scopeId)\n      .ifPresent(binding -> subscribe(binding.connectionId(), Set.of(binding.sonarProjectKey())));\n  }\n\n  private void subscribe(String connectionId, Set<String> possiblyNewProjectKeys) {\n    if (supportsServerSentEvents(connectionId)) {\n      var stream = streamsPerConnectionId.computeIfAbsent(connectionId, k -> openStream(connectionId));\n      stream.subscribeNew(possiblyNewProjectKeys);\n    }\n  }\n\n  private SonarQubeEventStream openStream(String connectionId) {\n    return new SonarQubeEventStream(languageSupportRepository.getEnabledLanguagesInConnectedMode(), connectionId, sonarQubeClientManager,\n      e -> eventPublisher.publishEvent(new SonarServerEventReceivedEvent(connectionId, e)));\n  }\n\n  private boolean supportsServerSentEvents(String connectionId) {\n    var connection = connectionConfigurationRepository.getConnectionById(connectionId);\n    return connection != null && connection.getKind() == ConnectionKind.SONARQUBE;\n  }\n\n  private void unsubscribe(BindingConfiguration previousBindingConfiguration) {\n    if (previousBindingConfiguration.isBound()) {\n      var connectionId = requireNonNull(previousBindingConfiguration.connectionId());\n      var projectKey = requireNonNull(previousBindingConfiguration.sonarProjectKey());\n      if (supportsServerSentEvents(connectionId) && streamsPerConnectionId.containsKey(connectionId)\n        && configurationRepository.getSonarProjectsUsedForConnection(connectionId).stream().noneMatch(usedProjectKey -> usedProjectKey.equals(projectKey))) {\n        streamsPerConnectionId.get(connectionId).unsubscribe(projectKey);\n      }\n    }\n  }\n\n  private void resubscribe(String connectionId) {\n    if (supportsServerSentEvents(connectionId) && streamsPerConnectionId.containsKey(connectionId)) {\n      streamsPerConnectionId.get(connectionId).resubscribe();\n    }\n  }\n\n  @PreDestroy\n  public void shutdown() {\n    if (!MoreExecutors.shutdownAndAwaitTermination(executorService, 1, TimeUnit.SECONDS)) {\n      LOG.warn(\"Unable to stop server-sent events subscriber service in a timely manner\");\n    }\n    streamsPerConnectionId.values().forEach(SonarQubeEventStream::stop);\n    streamsPerConnectionId.clear();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/server/event/SonarQubeEventStream.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.server.event;\n\nimport java.util.LinkedHashSet;\nimport java.util.Set;\nimport java.util.function.Consumer;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.serverapi.push.SonarServerEvent;\nimport org.sonarsource.sonarlint.core.serverapi.stream.EventStream;\n\npublic class SonarQubeEventStream {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private EventStream eventStream;\n  private final Set<String> subscribedProjectKeys = new LinkedHashSet<>();\n  private final Set<SonarLanguage> enabledLanguages;\n  private final String connectionId;\n  private final SonarQubeClientManager sonarQubeClientManager;\n  private final Consumer<SonarServerEvent> eventConsumer;\n\n  public SonarQubeEventStream(Set<SonarLanguage> enabledLanguages, String connectionId, SonarQubeClientManager sonarQubeClientManager, Consumer<SonarServerEvent> eventConsumer) {\n    this.enabledLanguages = enabledLanguages;\n    this.connectionId = connectionId;\n    this.sonarQubeClientManager = sonarQubeClientManager;\n    this.eventConsumer = eventConsumer;\n  }\n\n  public synchronized void subscribeNew(Set<String> possiblyNewProjectKeys) {\n    if (!possiblyNewProjectKeys.isEmpty() && !subscribedProjectKeys.containsAll(possiblyNewProjectKeys)) {\n      cancelSubscription();\n      subscribedProjectKeys.addAll(possiblyNewProjectKeys);\n      attemptSubscription(subscribedProjectKeys);\n    }\n  }\n\n  public synchronized void resubscribe() {\n    cancelSubscription();\n    if (!subscribedProjectKeys.isEmpty()) {\n      attemptSubscription(subscribedProjectKeys);\n    }\n  }\n\n  public synchronized void unsubscribe(String projectKey) {\n    cancelSubscription();\n    subscribedProjectKeys.remove(projectKey);\n    if (!subscribedProjectKeys.isEmpty()) {\n      attemptSubscription(subscribedProjectKeys);\n    }\n  }\n\n  private void attemptSubscription(Set<String> projectKeys) {\n    if (!enabledLanguages.isEmpty()) {\n      try {\n        sonarQubeClientManager.withActiveClient(connectionId,\n          serverApi -> eventStream = serverApi.push().subscribe(projectKeys, enabledLanguages, e -> notifyHandlers(e, eventConsumer)));\n      } catch (Exception e) {\n        LOG.debug(\"Error while subscribing to event-stream\", e);\n      }\n    }\n  }\n\n  private static void notifyHandlers(SonarServerEvent sonarServerEvent, Consumer<SonarServerEvent> clientEventConsumer) {\n    clientEventConsumer.accept(sonarServerEvent);\n  }\n\n  private void cancelSubscription() {\n    if (eventStream != null) {\n      eventStream.close();\n      eventStream = null;\n    }\n  }\n\n  public synchronized void stop() {\n    subscribedProjectKeys.clear();\n    cancelSubscription();\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/server/event/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.server.event;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/smartnotifications/LastEventPolling.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.smartnotifications;\n\nimport java.time.Instant;\nimport java.time.ZoneId;\nimport java.time.ZonedDateTime;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\n\npublic class LastEventPolling {\n\n  private final StorageService storage;\n\n  public LastEventPolling(StorageService storage) {\n    this.storage = storage;\n  }\n\n  public ZonedDateTime getLastEventPolling(String connectionId, String projectKey) {\n    var lastEventPollingEpoch = storage.connection(connectionId).project(projectKey).smartNotifications().readLastEventPolling();\n    return lastEventPollingEpoch.map(aLong -> ZonedDateTime.ofInstant(Instant.ofEpochMilli(aLong), ZoneId.systemDefault()))\n      .orElseGet(ZonedDateTime::now);\n  }\n\n  public void setLastEventPolling(ZonedDateTime dateTime, String connectionId, String projectKey) {\n    var smartNotificationsStorage = storage.connection(connectionId).project(projectKey).smartNotifications();\n    var lastEventPolling = smartNotificationsStorage.readLastEventPolling();\n    var dateTimeEpoch = dateTime.toInstant().toEpochMilli();\n    if (lastEventPolling.isPresent() && dateTimeEpoch <= lastEventPolling.get()) {\n      // this can happen if the settings changed between the read and write\n      return;\n    }\n    smartNotificationsStorage.store(dateTimeEpoch);\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/smartnotifications/SmartNotifications.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.smartnotifications;\n\nimport com.google.common.util.concurrent.MoreExecutors;\nimport jakarta.annotation.PostConstruct;\nimport jakarta.annotation.PreDestroy;\nimport java.time.ZonedDateTime;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.commons.BoundScope;\nimport org.sonarsource.sonarlint.core.commons.ConnectionKind;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.ExecutorServiceShutdownWatchable;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.commons.util.FailSafeExecutors;\nimport org.sonarsource.sonarlint.core.event.SonarServerEventReceivedEvent;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.AbstractConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarCloudConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.smartnotification.ShowSmartNotificationParams;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.developers.SearchEventsResponseDto;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryService;\nimport org.sonarsource.sonarlint.core.websocket.WebSocketService;\nimport org.sonarsource.sonarlint.core.websocket.events.SmartNotificationEvent;\nimport org.springframework.context.event.EventListener;\n\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SMART_NOTIFICATIONS;\n\npublic class SmartNotifications {\n\n  private final SonarLintLogger logger = SonarLintLogger.get();\n\n  private final ConfigurationRepository configurationRepository;\n  private final ConnectionConfigurationRepository connectionRepository;\n  private final SonarQubeClientManager sonarQubeClientManager;\n  private final SonarLintRpcClient client;\n  private final TelemetryService telemetryService;\n  private final WebSocketService webSocketService;\n  private final InitializeParams params;\n  private final LastEventPolling lastEventPollingService;\n  private ExecutorServiceShutdownWatchable<ScheduledExecutorService> smartNotificationsPolling;\n\n  public SmartNotifications(ConfigurationRepository configurationRepository, ConnectionConfigurationRepository connectionRepository, SonarQubeClientManager sonarQubeClientManager,\n    SonarLintRpcClient client, StorageService storageService, TelemetryService telemetryService, WebSocketService webSocketService, InitializeParams params) {\n    this.configurationRepository = configurationRepository;\n    this.connectionRepository = connectionRepository;\n    this.sonarQubeClientManager = sonarQubeClientManager;\n    this.client = client;\n    this.telemetryService = telemetryService;\n    this.webSocketService = webSocketService;\n    this.params = params;\n    lastEventPollingService = new LastEventPolling(storageService);\n  }\n\n  @PostConstruct\n  public void initialize() {\n    if (!params.getBackendCapabilities().contains(SMART_NOTIFICATIONS)) {\n      return;\n    }\n    smartNotificationsPolling = new ExecutorServiceShutdownWatchable<>(FailSafeExecutors.newSingleThreadScheduledExecutor(\"Smart Notifications Polling\"));\n    var cancelMonitor = new SonarLintCancelMonitor();\n    cancelMonitor.watchForShutdown(smartNotificationsPolling);\n    smartNotificationsPolling.getWrapped().scheduleAtFixedRate(() -> this.poll(cancelMonitor), 1, 60, TimeUnit.SECONDS);\n  }\n\n  private void poll(SonarLintCancelMonitor cancelMonitor) {\n    var boundScopeByConnectionAndSonarProject = configurationRepository.getBoundScopeByConnectionAndSonarProject();\n    boundScopeByConnectionAndSonarProject.forEach((connectionId, boundScopesByProject) -> {\n      var connection = connectionRepository.getConnectionById(connectionId);\n      if (connection != null && !connection.isDisableNotifications() && !shouldSkipPolling(connection)) {\n        sonarQubeClientManager.withActiveClient(connectionId,\n          serverApi -> manageNotificationsForConnection(serverApi, boundScopesByProject, connection, cancelMonitor));\n      }\n    });\n  }\n\n  private void manageNotificationsForConnection(ServerApi serverApi, Map<String, Collection<BoundScope>> boundScopesPerProjectKey,\n    AbstractConnectionConfiguration connection, SonarLintCancelMonitor cancelMonitor) {\n    var developersApi = serverApi.developers();\n    var connectionId = connection.getConnectionId();\n    var projectKeysByLastEventPolling = boundScopesPerProjectKey.keySet().stream()\n      .collect(Collectors.toMap(Function.identity(),\n        p -> getLastNotificationTime(lastEventPollingService.getLastEventPolling(connectionId, p))));\n\n    List<SearchEventsResponseDto.Event> notifications;\n    try {\n      notifications = developersApi.searchEvents(projectKeysByLastEventPolling, cancelMonitor).events();\n    } catch (Exception e) {\n      logger.error(\"Failed to get notifications\", e);\n      notifications = List.of();\n    }\n\n    for (var n : notifications) {\n      var scopeIds = boundScopesPerProjectKey.get(n.project()).stream().map(BoundScope::getConfigScopeId).collect(Collectors.toSet());\n      var smartNotification = new ShowSmartNotificationParams(n.message(), n.link(), scopeIds,\n        n.category(), connectionId);\n      client.showSmartNotification(smartNotification);\n      telemetryService.smartNotificationsReceived(n.category());\n    }\n\n    projectKeysByLastEventPolling.keySet()\n      .forEach(projectKey -> lastEventPollingService.setLastEventPolling(ZonedDateTime.now(), connectionId, projectKey));\n  }\n\n  private boolean shouldSkipPolling(AbstractConnectionConfiguration connection) {\n    if (connection.getKind() == ConnectionKind.SONARCLOUD) {\n      var region = ((SonarCloudConnectionConfiguration) connection).getRegion();\n      return webSocketService.hasOpenConnection(region);\n    }\n    return false;\n  }\n\n  @PreDestroy\n  public void shutdown() {\n    if (smartNotificationsPolling != null && !MoreExecutors.shutdownAndAwaitTermination(smartNotificationsPolling, 5, TimeUnit.SECONDS)) {\n      logger.warn(\"Unable to stop smart notifications executor service in a timely manner\");\n    }\n  }\n\n  private static ZonedDateTime getLastNotificationTime(ZonedDateTime lastTime) {\n    var oneDayAgo = ZonedDateTime.now().minusDays(1);\n    return lastTime.isAfter(oneDayAgo) ? lastTime : oneDayAgo;\n  }\n\n  @EventListener\n  public void onServerEventReceived(SonarServerEventReceivedEvent eventReceived) {\n    var serverEvent = eventReceived.getEvent();\n    if (serverEvent instanceof SmartNotificationEvent smartNotificationEvent) {\n      notifyClient(eventReceived.getConnectionId(), smartNotificationEvent);\n    }\n  }\n\n  private void notifyClient(String connectionId, SmartNotificationEvent event) {\n    var projectKey = event.project();\n    var boundScopes = configurationRepository.getBoundScopesToConnectionAndSonarProject(connectionId, projectKey);\n    client.showSmartNotification(new ShowSmartNotificationParams(event.message(), event.link(),\n      boundScopes.stream().map(BoundScope::getConfigScopeId).collect(Collectors.toSet()), event.category(), connectionId));\n    telemetryService.smartNotificationsReceived(event.category());\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/smartnotifications/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.smartnotifications;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/spring/SonarLintSpringAppConfig.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.spring;\n\nimport java.net.ProxySelector;\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.util.concurrent.ExecutorService;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.apache.hc.client5.http.auth.CredentialsProvider;\nimport org.apache.hc.core5.util.Timeout;\nimport org.jooq.DSLContext;\nimport org.sonarsource.sonarlint.core.BindingCandidatesFinder;\nimport org.sonarsource.sonarlint.core.BindingClueProvider;\nimport org.sonarsource.sonarlint.core.BindingSuggestionProvider;\nimport org.sonarsource.sonarlint.core.ConfigurationService;\nimport org.sonarsource.sonarlint.core.ConnectionService;\nimport org.sonarsource.sonarlint.core.ConnectionSuggestionProvider;\nimport org.sonarsource.sonarlint.core.MCPServerConfigurationProvider;\nimport org.sonarsource.sonarlint.core.OrganizationsCache;\nimport org.sonarsource.sonarlint.core.SharedConnectedModeSettingsProvider;\nimport org.sonarsource.sonarlint.core.SonarCloudActiveEnvironment;\nimport org.sonarsource.sonarlint.core.SonarCodeContextService;\nimport org.sonarsource.sonarlint.core.SonarProjectsCache;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.TokenGeneratorHelper;\nimport org.sonarsource.sonarlint.core.UserPaths;\nimport org.sonarsource.sonarlint.core.VersionSoonUnsupportedHelper;\nimport org.sonarsource.sonarlint.core.active.rules.ActiveRulesService;\nimport org.sonarsource.sonarlint.core.ai.ide.AiAgentService;\nimport org.sonarsource.sonarlint.core.ai.ide.AiHookService;\nimport org.sonarsource.sonarlint.core.analysis.AnalysisSchedulerCache;\nimport org.sonarsource.sonarlint.core.analysis.AnalysisService;\nimport org.sonarsource.sonarlint.core.analysis.NodeJsService;\nimport org.sonarsource.sonarlint.core.analysis.UserAnalysisPropertiesRepository;\nimport org.sonarsource.sonarlint.core.branch.SonarProjectBranchTrackingService;\nimport org.sonarsource.sonarlint.core.commons.dogfood.DogfoodEnvironmentDetectionService;\nimport org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabase;\nimport org.sonarsource.sonarlint.core.commons.util.FailSafeExecutors;\nimport org.sonarsource.sonarlint.core.embedded.server.AnalyzeFileListRequestHandler;\nimport org.sonarsource.sonarlint.core.embedded.server.AwaitingUserTokenFutureRepository;\nimport org.sonarsource.sonarlint.core.embedded.server.EmbeddedServer;\nimport org.sonarsource.sonarlint.core.embedded.server.RequestHandlerBindingAssistant;\nimport org.sonarsource.sonarlint.core.embedded.server.ToggleAutomaticAnalysisRequestHandler;\nimport org.sonarsource.sonarlint.core.embedded.server.handler.GeneratedUserTokenHandler;\nimport org.sonarsource.sonarlint.core.embedded.server.handler.ShowFixSuggestionRequestHandler;\nimport org.sonarsource.sonarlint.core.embedded.server.handler.ShowHotspotRequestHandler;\nimport org.sonarsource.sonarlint.core.embedded.server.handler.ShowIssueRequestHandler;\nimport org.sonarsource.sonarlint.core.embedded.server.handler.StatusRequestHandler;\nimport org.sonarsource.sonarlint.core.file.PathTranslationService;\nimport org.sonarsource.sonarlint.core.file.ServerFilePathsProvider;\nimport org.sonarsource.sonarlint.core.fs.ClientFileSystemService;\nimport org.sonarsource.sonarlint.core.fs.FileExclusionService;\nimport org.sonarsource.sonarlint.core.fs.OpenFilesRepository;\nimport org.sonarsource.sonarlint.core.hotspot.HotspotService;\nimport org.sonarsource.sonarlint.core.http.AskClientCertificatePredicate;\nimport org.sonarsource.sonarlint.core.http.ClientProxyCredentialsProvider;\nimport org.sonarsource.sonarlint.core.http.ClientProxySelector;\nimport org.sonarsource.sonarlint.core.http.HttpClientProvider;\nimport org.sonarsource.sonarlint.core.http.HttpConfig;\nimport org.sonarsource.sonarlint.core.http.ThreadFactories;\nimport org.sonarsource.sonarlint.core.http.ssl.CertificateStore;\nimport org.sonarsource.sonarlint.core.http.ssl.SslConfig;\nimport org.sonarsource.sonarlint.core.issue.IssueService;\nimport org.sonarsource.sonarlint.core.languages.LanguageSupportRepository;\nimport org.sonarsource.sonarlint.core.local.only.XodusLocalOnlyIssueStorageService;\nimport org.sonarsource.sonarlint.core.log.LogService;\nimport org.sonarsource.sonarlint.core.mode.SeverityModeService;\nimport org.sonarsource.sonarlint.core.monitoring.MonitoringInitializationParams;\nimport org.sonarsource.sonarlint.core.monitoring.MonitoringService;\nimport org.sonarsource.sonarlint.core.monitoring.MonitoringUserIdStore;\nimport org.sonarsource.sonarlint.core.newcode.NewCodeService;\nimport org.sonarsource.sonarlint.core.plugin.PluginLifecycleService;\nimport org.sonarsource.sonarlint.core.plugin.PluginStatusNotifierService;\nimport org.sonarsource.sonarlint.core.plugin.PluginsRepository;\nimport org.sonarsource.sonarlint.core.plugin.PluginsService;\nimport org.sonarsource.sonarlint.core.plugin.loading.strategy.ConnectedArtifactsLoadingStrategyFactory;\nimport org.sonarsource.sonarlint.core.plugin.loading.strategy.StandaloneArtifactsLoadingStrategy;\nimport org.sonarsource.sonarlint.core.plugin.skipped.SkippedPluginsNotifierService;\nimport org.sonarsource.sonarlint.core.plugin.skipped.SkippedPluginsRepository;\nimport org.sonarsource.sonarlint.core.plugin.source.binaries.BinariesArtifactSource;\nimport org.sonarsource.sonarlint.core.plugin.source.binaries.BinariesLocalCacheManager;\nimport org.sonarsource.sonarlint.core.plugin.source.binaries.BinariesSignatureVerifier;\nimport org.sonarsource.sonarlint.core.plugin.source.server.ServerPluginDownloader;\nimport org.sonarsource.sonarlint.core.plugin.source.server.ServerPluginsCache;\nimport org.sonarsource.sonarlint.core.progress.ClientAwareTaskManager;\nimport org.sonarsource.sonarlint.core.remediation.aicodefix.AiCodeFixService;\nimport org.sonarsource.sonarlint.core.reporting.FindingReportingService;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.reporting.PreviouslyRaisedFindingsRepository;\nimport org.sonarsource.sonarlint.core.repository.rules.RulesRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.HttpConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.SslConfigurationDto;\nimport org.sonarsource.sonarlint.core.rules.RulesExtractionHelper;\nimport org.sonarsource.sonarlint.core.rules.RulesService;\nimport org.sonarsource.sonarlint.core.sca.DependencyRiskService;\nimport org.sonarsource.sonarlint.core.server.event.ServerEventsService;\nimport org.sonarsource.sonarlint.core.serverconnection.aicodefix.AiCodeFixRepository;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.KnownFindingsRepository;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.LocalOnlyIssuesRepository;\nimport org.sonarsource.sonarlint.core.smartnotifications.SmartNotifications;\nimport org.sonarsource.sonarlint.core.storage.SonarLintDatabaseService;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.sonarsource.sonarlint.core.sync.FindingsSynchronizationService;\nimport org.sonarsource.sonarlint.core.sync.HotspotSynchronizationService;\nimport org.sonarsource.sonarlint.core.sync.IssueSynchronizationService;\nimport org.sonarsource.sonarlint.core.sync.ScaSynchronizationService;\nimport org.sonarsource.sonarlint.core.sync.SonarProjectBranchesSynchronizationService;\nimport org.sonarsource.sonarlint.core.sync.SynchronizationService;\nimport org.sonarsource.sonarlint.core.sync.TaintSynchronizationService;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryLocalStorageManager;\nimport org.sonarsource.sonarlint.core.tracking.LocalOnlyIssueRepository;\nimport org.sonarsource.sonarlint.core.tracking.TaintVulnerabilityTrackingService;\nimport org.sonarsource.sonarlint.core.tracking.TrackingService;\nimport org.sonarsource.sonarlint.core.tracking.XodusKnownFindingsStorageService;\nimport org.sonarsource.sonarlint.core.websocket.WebSocketService;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Import;\nimport org.springframework.context.event.ApplicationEventMulticaster;\nimport org.springframework.context.event.SimpleApplicationEventMulticaster;\nimport org.springframework.scheduling.support.TaskUtils;\n\nimport static org.sonarsource.sonarlint.core.http.ssl.CertificateStore.DEFAULT_PASSWORD;\nimport static org.sonarsource.sonarlint.core.http.ssl.CertificateStore.DEFAULT_STORE_TYPE;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.MONITORING;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.TELEMETRY;\n\n@Configuration\n// Can't use classpath scanning in OSGi, so waiting to move out of process, we have to declare our beans manually\n// @ComponentScan(basePackages = \"org.sonarsource.sonarlint.core\")\n@Import({\n  AskClientCertificatePredicate.class,\n  ClientProxySelector.class,\n  ClientProxyCredentialsProvider.class,\n  ConfigurationService.class,\n  ConfigurationRepository.class,\n  RulesService.class,\n  SonarQubeClientManager.class,\n  ConnectionConfigurationRepository.class,\n  RulesRepository.class,\n  RulesExtractionHelper.class,\n  PluginsService.class,\n  SkippedPluginsNotifierService.class,\n  PluginStatusNotifierService.class,\n  PluginsRepository.class,\n  PluginLifecycleService.class,\n  SkippedPluginsRepository.class,\n  LanguageSupportRepository.class,\n  ConnectionService.class,\n  TokenGeneratorHelper.class,\n  EmbeddedServer.class,\n  StatusRequestHandler.class,\n  GeneratedUserTokenHandler.class,\n  AwaitingUserTokenFutureRepository.class,\n  ShowHotspotRequestHandler.class,\n  ShowIssueRequestHandler.class,\n  ShowFixSuggestionRequestHandler.class,\n  BindingSuggestionProvider.class,\n  ConnectionSuggestionProvider.class,\n  BindingClueProvider.class,\n  SonarProjectsCache.class,\n  SonarProjectBranchTrackingService.class,\n  SynchronizationService.class,\n  HotspotService.class,\n  IssueService.class,\n  AnalysisService.class,\n  SmartNotifications.class,\n  LocalOnlyIssueRepository.class,\n  WebSocketService.class,\n  ServerEventsService.class,\n  VersionSoonUnsupportedHelper.class,\n  XodusLocalOnlyIssueStorageService.class,\n  StorageService.class,\n  SeverityModeService.class,\n  NewCodeService.class,\n  RequestHandlerBindingAssistant.class,\n  TaintVulnerabilityTrackingService.class,\n  SonarProjectBranchesSynchronizationService.class,\n  TaintSynchronizationService.class,\n  IssueSynchronizationService.class,\n  HotspotSynchronizationService.class,\n  ClientFileSystemService.class,\n  SonarCodeContextService.class,\n  PathTranslationService.class,\n  ServerFilePathsProvider.class,\n  FileExclusionService.class,\n  NodeJsService.class,\n  OrganizationsCache.class,\n  BindingCandidatesFinder.class,\n  SharedConnectedModeSettingsProvider.class,\n  MCPServerConfigurationProvider.class,\n  AnalysisSchedulerCache.class,\n  XodusKnownFindingsStorageService.class,\n  TrackingService.class,\n  FindingsSynchronizationService.class,\n  FindingReportingService.class,\n  PreviouslyRaisedFindingsRepository.class,\n  UserAnalysisPropertiesRepository.class,\n  OpenFilesRepository.class,\n  DogfoodEnvironmentDetectionService.class,\n  MonitoringService.class,\n  MonitoringUserIdStore.class,\n  AiCodeFixService.class,\n  ClientAwareTaskManager.class,\n  ScaSynchronizationService.class,\n  DependencyRiskService.class,\n  ToggleAutomaticAnalysisRequestHandler.class,\n  AnalyzeFileListRequestHandler.class,\n  AiAgentService.class,\n  AiHookService.class,\n  LogService.class,\n  ActiveRulesService.class,\n  AiCodeFixRepository.class,\n  SonarLintDatabaseService.class,\n  LocalOnlyIssuesRepository.class,\n  ServerPluginsCache.class,\n  KnownFindingsRepository.class,\n  StandaloneArtifactsLoadingStrategy.class,\n  ConnectedArtifactsLoadingStrategyFactory.class,\n  BinariesArtifactSource.class,\n  BinariesLocalCacheManager.class,\n  BinariesSignatureVerifier.class,\n  ServerPluginDownloader.class\n})\npublic class SonarLintSpringAppConfig {\n\n  @Bean(name = \"applicationEventMulticaster\")\n  public ApplicationEventMulticaster simpleApplicationEventMulticaster() {\n    var eventMulticaster = new SimpleApplicationEventMulticaster();\n    eventMulticaster.setErrorHandler(TaskUtils.LOG_AND_SUPPRESS_ERROR_HANDLER);\n    return eventMulticaster;\n  }\n\n  @Bean\n  UserPaths provideClientPaths(InitializeParams initializeParams) {\n    return UserPaths.from(initializeParams);\n  }\n\n  @Bean\n  SonarCloudActiveEnvironment provideSonarCloudActiveEnvironment(InitializeParams params) {\n    var alternativeSonarCloudEnv = params.getAlternativeSonarCloudEnvironment();\n    return alternativeSonarCloudEnv == null ? SonarCloudActiveEnvironment.prod()\n      : new SonarCloudActiveEnvironment(alternativeSonarCloudEnv.getAlternateRegionUris());\n  }\n\n  @Bean\n  HttpClientProvider provideHttpClientProvider(InitializeParams params, UserPaths userPaths, AskClientCertificatePredicate askClientCertificatePredicate,\n    ProxySelector proxySelector, CredentialsProvider proxyCredentialsProvider) {\n    return new HttpClientProvider(params.getClientConstantInfo().getUserAgent(), adapt(params.getHttpConfiguration(), userPaths.getUserHome()), askClientCertificatePredicate,\n      proxySelector, proxyCredentialsProvider);\n  }\n\n  @Bean\n  MonitoringInitializationParams provideMonitoringInitParams(InitializeParams params, TelemetryLocalStorageManager telemetryService) {\n    return new MonitoringInitializationParams(\n      params.getBackendCapabilities().contains(MONITORING),\n      params.getBackendCapabilities().contains(TELEMETRY) && telemetryService.isEnabled(),\n      params.getTelemetryConstantAttributes().getProductKey(),\n      params.getTelemetryConstantAttributes().getProductVersion(),\n      params.getTelemetryConstantAttributes().getIdeVersion());\n  }\n\n  // disable automatic destroy call, shutdown is handled by SonarLintDatabaseService\n  // MonitoringService dependency ensures Sentry is initialized before database migrations run\n  @Bean(destroyMethod = \"\")\n  SonarLintDatabase provideDatabase(UserPaths userPaths, MonitoringService monitoringService) {\n    return new SonarLintDatabase(userPaths.getStorageRoot());\n  }\n\n  @Bean\n  DSLContext provideDSLContext(SonarLintDatabase database) {\n    return database.dsl();\n  }\n\n  private static HttpConfig adapt(HttpConfigurationDto dto, @Nullable Path sonarlintUserHome) {\n    return new HttpConfig(adapt(dto.getSslConfiguration(), sonarlintUserHome), toTimeout(dto.getConnectTimeout()), toTimeout(dto.getSocketTimeout()),\n      toTimeout(dto.getConnectionRequestTimeout()), toTimeout(dto.getResponseTimeout()));\n  }\n\n  private static SslConfig adapt(SslConfigurationDto dto, @Nullable Path sonarlintUserHome) {\n    return new SslConfig(\n      adaptStore(dto.getKeyStorePath(), dto.getKeyStorePassword(), dto.getKeyStoreType(), sonarlintUserHome, \"keystore\"),\n      adaptStore(dto.getTrustStorePath(), dto.getTrustStorePassword(), dto.getTrustStoreType(), sonarlintUserHome, \"truststore\"));\n  }\n\n  private static CertificateStore adaptStore(@Nullable Path storePathConfig, @Nullable String storePasswordConfig, @Nullable String storeTypeConfig,\n    @Nullable Path sonarlintUserHome,\n    String defaultStoreName) {\n    var storePath = storePathConfig;\n    if (storePath == null && sonarlintUserHome != null) {\n      storePath = sonarlintUserHome.resolve(\"ssl/\" + defaultStoreName + \".p12\");\n    }\n    if (storePath != null) {\n      var keyStorePassword = storePasswordConfig == null ? DEFAULT_PASSWORD : storePasswordConfig;\n      var keyStoreType = storeTypeConfig == null ? DEFAULT_STORE_TYPE : storeTypeConfig;\n      return new CertificateStore(storePath, keyStorePassword, keyStoreType);\n    }\n    return null;\n  }\n\n  @CheckForNull\n  private static Timeout toTimeout(@Nullable Duration duration) {\n    return duration == null ? null : Timeout.of(duration);\n  }\n\n  @Bean(name = \"pluginDownloadExecutor\", destroyMethod = \"shutdown\")\n  public ExecutorService pluginDownloadExecutor() {\n    return FailSafeExecutors.newCachedThreadPool(ThreadFactories.threadWithNamePrefix(\"sonarlint-plugin-download-\"));\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/spring/SpringApplicationContextInitializer.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.spring;\n\nimport org.sonarsource.sonarlint.core.labs.IdeLabsSpringConfig;\nimport org.sonarsource.sonarlint.core.promotion.PromotionSpringConfig;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetrySpringConfig;\nimport org.sonarsource.sonarlint.core.telemetry.gessie.GessieSpringConfig;\nimport org.springframework.context.ConfigurableApplicationContext;\nimport org.springframework.context.annotation.AnnotationConfigApplicationContext;\n\nimport static java.util.Objects.requireNonNull;\n\npublic class SpringApplicationContextInitializer implements AutoCloseable {\n\n  private final AnnotationConfigApplicationContext applicationContext;\n\n  public SpringApplicationContextInitializer(SonarLintRpcClient client, InitializeParams params) {\n    applicationContext = new AnnotationConfigApplicationContext();\n    applicationContext.register(SonarLintSpringAppConfig.class);\n    applicationContext.register(TelemetrySpringConfig.class);\n    applicationContext.register(GessieSpringConfig.class);\n    applicationContext.register(IdeLabsSpringConfig.class);\n    applicationContext.register(PromotionSpringConfig.class);\n    applicationContext.registerBean(\"sonarlintClient\", SonarLintRpcClient.class, () -> requireNonNull(client));\n    applicationContext.registerBean(\"initializeParams\", InitializeParams.class, () -> params);\n    applicationContext.refresh();\n  }\n\n  public ConfigurableApplicationContext getInitializedApplicationContext() {\n    return applicationContext;\n  }\n\n  @Override\n  public void close() throws Exception {\n    applicationContext.close();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/spring/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.spring;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/storage/SonarLintDatabaseService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.storage;\n\nimport jakarta.annotation.PostConstruct;\nimport jakarta.annotation.PreDestroy;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabase;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarCloudConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarQubeConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.serverconnection.aicodefix.AiCodeFixRepository;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.LocalOnlyIssuesRepository;\nimport org.springframework.context.annotation.Lazy;\nimport org.springframework.stereotype.Component;\n\nimport static org.sonarsource.sonarlint.core.commons.storage.model.Tables.SERVER_BRANCHES;\nimport static org.sonarsource.sonarlint.core.commons.storage.model.Tables.SERVER_DEPENDENCY_RISKS;\nimport static org.sonarsource.sonarlint.core.commons.storage.model.Tables.SERVER_FINDINGS;\n\n@Component\n@Lazy(false)\npublic class SonarLintDatabaseService {\n\n  private final SonarLintDatabase database;\n  private final LocalOnlyIssuesRepository localOnlyIssuesRepository;\n  private final AiCodeFixRepository aiCodeFixRepository;\n  private final Set<String> initialConnectionIds;\n\n  public SonarLintDatabaseService(SonarLintDatabase database, LocalOnlyIssuesRepository localOnlyIssuesRepository, AiCodeFixRepository aiCodeFixRepository,\n    InitializeParams params) {\n    this.database = database;\n    this.localOnlyIssuesRepository = localOnlyIssuesRepository;\n    this.aiCodeFixRepository = aiCodeFixRepository;\n    this.initialConnectionIds = Stream.concat(\n      params.getSonarQubeConnections().stream().map(SonarQubeConnectionConfigurationDto::getConnectionId),\n      params.getSonarCloudConnections().stream().map(SonarCloudConnectionConfigurationDto::getConnectionId))\n      .collect(Collectors.toSet());\n  }\n\n  public SonarLintDatabase getDatabase() {\n    return database;\n  }\n\n  @PostConstruct\n  public void postConstruct() {\n    cleanupNonExistingConnections();\n    localOnlyIssuesRepository.purgeIssuesOlderThan(Instant.now().minus(7, ChronoUnit.DAYS));\n  }\n\n  private void cleanupNonExistingConnections() {\n    aiCodeFixRepository.deleteUnknownConnections(initialConnectionIds);\n    // this should be moved to ServerFindingRepository but the current design does not allow it\n    database.dsl().deleteFrom(SERVER_FINDINGS)\n      .where(SERVER_FINDINGS.CONNECTION_ID.notIn(initialConnectionIds))\n      .execute();\n    database.dsl().deleteFrom(SERVER_DEPENDENCY_RISKS)\n      .where(SERVER_DEPENDENCY_RISKS.CONNECTION_ID.notIn(initialConnectionIds))\n      .execute();\n    database.dsl().deleteFrom(SERVER_BRANCHES)\n      .where(SERVER_BRANCHES.CONNECTION_ID.notIn(initialConnectionIds))\n      .execute();\n  }\n\n  @PreDestroy\n  public void preDestroy() {\n    database.shutdown();\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/storage/StorageService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.storage;\n\nimport java.nio.file.Path;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport org.sonarsource.sonarlint.core.UserPaths;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationRemovedEvent;\nimport org.sonarsource.sonarlint.core.serverconnection.ConnectionStorage;\nimport org.sonarsource.sonarlint.core.serverconnection.SonarProjectStorage;\nimport org.springframework.context.event.EventListener;\n\npublic class StorageService {\n  private final Path globalStorageRoot;\n  private final Map<String, ConnectionStorage> connectionStorageById = new ConcurrentHashMap<>();\n  private final SonarLintDatabaseService databaseService;\n\n  public StorageService(UserPaths userPaths, SonarLintDatabaseService databaseService) {\n    this.globalStorageRoot = userPaths.getStorageRoot();\n    this.databaseService = databaseService;\n  }\n\n  public ConnectionStorage connection(String connectionId) {\n    return connectionStorageById.computeIfAbsent(connectionId, k -> new ConnectionStorage(globalStorageRoot, connectionId, databaseService.getDatabase()));\n  }\n\n  public SonarProjectStorage binding(Binding binding) {\n    return connection(binding.connectionId()).project(binding.sonarProjectKey());\n  }\n\n  @EventListener\n  public void handleEvent(ConnectionConfigurationRemovedEvent connectionConfigurationRemovedEvent) {\n    var removedConnectionId = connectionConfigurationRemovedEvent.removedConnectionId();\n    var connectionStorage = connection(removedConnectionId);\n    connectionStorage.delete();\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/storage/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.storage;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/AnalyzerConfigurationSynchronized.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.sync;\n\nimport java.util.Set;\nimport org.sonarsource.sonarlint.core.commons.Binding;\n\npublic record AnalyzerConfigurationSynchronized(Binding binding, Set<String> configScopeIds) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/BranchBinding.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.sync;\n\nimport java.util.Objects;\nimport org.sonarsource.sonarlint.core.commons.Binding;\n\npublic class BranchBinding {\n  private final Binding binding;\n  private final String branchName;\n\n  public BranchBinding(Binding binding, String branchName) {\n    this.binding = binding;\n    this.branchName = branchName;\n  }\n\n  public Binding getBinding() {\n    return binding;\n  }\n\n  public String getBranchName() {\n    return branchName;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    BranchBinding that = (BranchBinding) o;\n    return Objects.equals(binding, that.binding) && Objects.equals(branchName, that.branchName);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(binding, branchName);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/ConfigurationScopesSynchronizedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.sync;\n\nimport java.util.Set;\n\npublic class ConfigurationScopesSynchronizedEvent {\n  private final Set<String> configScopeIds;\n\n  public ConfigurationScopesSynchronizedEvent(Set<String> configScopeIds) {\n    this.configScopeIds = configScopeIds;\n  }\n\n  public Set<String> getConfigScopeIds() {\n    return configScopeIds;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/FindingsSynchronizationService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.sync;\n\nimport java.nio.file.Path;\nimport java.util.LinkedList;\nimport java.util.Set;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.branch.SonarProjectBranchTrackingService;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.commons.util.FailSafeExecutors;\nimport org.sonarsource.sonarlint.core.file.FilePathTranslation;\nimport org.sonarsource.sonarlint.core.file.PathTranslationService;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\n\npublic class FindingsSynchronizationService {\n  private static final int FETCH_ALL_ISSUES_THRESHOLD = 10;\n  private final ConfigurationRepository configurationRepository;\n  private final SonarProjectBranchTrackingService branchTrackingService;\n  private final PathTranslationService pathTranslationService;\n  private final IssueSynchronizationService issueSynchronizationService;\n  private final HotspotSynchronizationService hotspotSynchronizationService;\n  private final ExecutorService issueUpdaterExecutorService;\n  private final boolean shouldRefreshHotspots;\n\n  public FindingsSynchronizationService(ConfigurationRepository configurationRepository, SonarProjectBranchTrackingService branchTrackingService,\n    PathTranslationService pathTranslationService, IssueSynchronizationService issueSynchronizationService, HotspotSynchronizationService hotspotSynchronizationService,\n    InitializeParams initializeParams) {\n    this.configurationRepository = configurationRepository;\n    this.branchTrackingService = branchTrackingService;\n    this.pathTranslationService = pathTranslationService;\n    this.issueSynchronizationService = issueSynchronizationService;\n    this.hotspotSynchronizationService = hotspotSynchronizationService;\n    this.issueUpdaterExecutorService = FailSafeExecutors.newSingleThreadExecutor(\"sonarlint-server-tracking-issue-updater\");\n    this.shouldRefreshHotspots = initializeParams.getBackendCapabilities().contains(BackendCapability.SECURITY_HOTSPOTS);\n  }\n\n  public void refreshServerFindings(String configurationScopeId, Set<Path> pathsToRefresh) {\n    var effectiveBindingOpt = configurationRepository.getEffectiveBinding(configurationScopeId);\n    var activeBranchOpt = branchTrackingService.awaitEffectiveSonarProjectBranch(configurationScopeId);\n    var translationOpt = pathTranslationService.getOrComputePathTranslation(configurationScopeId);\n    if (effectiveBindingOpt.isPresent() && activeBranchOpt.isPresent() && translationOpt.isPresent()) {\n      var binding = effectiveBindingOpt.get();\n      var activeBranch = activeBranchOpt.get();\n      var translation = translationOpt.get();\n      var cancelMonitor = new SonarLintCancelMonitor();\n      refreshServerIssues(cancelMonitor, binding, activeBranch, pathsToRefresh, translation);\n      if (shouldRefreshHotspots) {\n        refreshServerSecurityHotspots(cancelMonitor, binding, activeBranch, pathsToRefresh, translationOpt.get());\n      }\n    }\n  }\n\n  private void refreshServerIssues(SonarLintCancelMonitor cancelMonitor, Binding binding, String activeBranch,\n    Set<Path> pathsInvolved, FilePathTranslation translation) {\n    var serverFileRelativePaths = pathsInvolved.stream().map(translation::ideToServerPath).collect(Collectors.toSet());\n    var downloadAllIssuesAtOnce = serverFileRelativePaths.size() > FETCH_ALL_ISSUES_THRESHOLD;\n    var fetchTasks = new LinkedList<CompletableFuture<?>>();\n    if (downloadAllIssuesAtOnce) {\n      fetchTasks.add(CompletableFuture.runAsync(() -> issueSynchronizationService.fetchProjectIssues(binding, activeBranch, cancelMonitor), issueUpdaterExecutorService));\n    } else {\n      fetchTasks.addAll(serverFileRelativePaths.stream()\n        .map(serverFileRelativePath -> CompletableFuture.runAsync(() -> issueSynchronizationService\n          .fetchFileIssues(binding, serverFileRelativePath, activeBranch, cancelMonitor), issueUpdaterExecutorService))\n        .toList());\n    }\n    CompletableFuture.allOf(fetchTasks.toArray(new CompletableFuture[0])).join();\n  }\n\n  private void refreshServerSecurityHotspots(SonarLintCancelMonitor cancelMonitor, Binding binding, String activeBranch,\n    Set<Path> pathsInvolved, FilePathTranslation translation) {\n    var serverFileRelativePaths = pathsInvolved.stream().map(translation::ideToServerPath).collect(Collectors.toSet());\n    var downloadAllSecurityHotspotsAtOnce = serverFileRelativePaths.size() > FETCH_ALL_ISSUES_THRESHOLD;\n    var fetchTasks = new LinkedList<CompletableFuture<?>>();\n    if (downloadAllSecurityHotspotsAtOnce) {\n      fetchTasks.add(CompletableFuture.runAsync(() -> hotspotSynchronizationService.fetchProjectHotspots(binding, activeBranch, cancelMonitor), issueUpdaterExecutorService));\n    } else {\n      fetchTasks.addAll(serverFileRelativePaths.stream()\n        .map(serverFileRelativePath -> CompletableFuture\n          .runAsync(() -> hotspotSynchronizationService.fetchFileHotspots(binding, activeBranch, serverFileRelativePath, cancelMonitor), issueUpdaterExecutorService))\n        .toList());\n    }\n    CompletableFuture.allOf(fetchTasks.toArray(new CompletableFuture[0])).join();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/HotspotSynchronizationService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.sync;\n\nimport java.nio.file.Path;\nimport java.util.LinkedHashSet;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.languages.LanguageSupportRepository;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.hotspot.HotspotApi;\nimport org.sonarsource.sonarlint.core.serverconnection.ConnectionStorage;\nimport org.sonarsource.sonarlint.core.serverconnection.HotspotDownloader;\nimport org.sonarsource.sonarlint.core.serverconnection.ServerHotspotUpdater;\nimport org.sonarsource.sonarlint.core.serverconnection.ServerInfoSynchronizer;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\n\npublic class HotspotSynchronizationService {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final StorageService storageService;\n  private final LanguageSupportRepository languageSupportRepository;\n  private final SonarQubeClientManager sonarQubeClientManager;\n\n  public HotspotSynchronizationService(StorageService storageService, LanguageSupportRepository languageSupportRepository, SonarQubeClientManager sonarQubeClientManager) {\n    this.storageService = storageService;\n    this.languageSupportRepository = languageSupportRepository;\n    this.sonarQubeClientManager = sonarQubeClientManager;\n  }\n\n  public void syncServerHotspotsForProject(ServerApi serverApi, String connectionId, String projectKey, String branchName, SonarLintCancelMonitor cancelMonitor) {\n    var storage = storageService.connection(connectionId);\n    var serverVersion = getSonarServerVersion(serverApi, storage, cancelMonitor);\n    var enabledLanguagesToSync = languageSupportRepository.getEnabledLanguagesInConnectedMode().stream().filter(SonarLanguage::shouldSyncInConnectedMode)\n      .collect(Collectors.toCollection(LinkedHashSet::new));\n    var hotspotsUpdater = new ServerHotspotUpdater(storage, new HotspotDownloader(enabledLanguagesToSync));\n    if (HotspotApi.supportHotspotsPull(serverApi.isSonarCloud(), serverVersion)) {\n      LOG.info(\"[SYNC] Synchronizing hotspots for project '{}' on branch '{}'\", projectKey, branchName);\n      hotspotsUpdater.sync(serverApi.hotspot(), projectKey, branchName, enabledLanguagesToSync, cancelMonitor);\n    } else {\n      LOG.debug(\"Incremental hotspot sync is not supported. Skipping.\");\n    }\n  }\n\n  private static Version getSonarServerVersion(ServerApi serverApi, ConnectionStorage storage, SonarLintCancelMonitor cancelMonitor) {\n    var serverInfoSynchronizer = new ServerInfoSynchronizer(storage);\n    return serverInfoSynchronizer.readOrSynchronizeServerInfo(serverApi, cancelMonitor).version();\n  }\n\n  public void fetchProjectHotspots(Binding binding, String activeBranch, SonarLintCancelMonitor cancelMonitor) {\n    sonarQubeClientManager.withActiveClient(binding.connectionId(), serverApi ->\n      downloadAllServerHotspots(binding.connectionId(), serverApi, binding.sonarProjectKey(), activeBranch, cancelMonitor));\n  }\n\n  private void downloadAllServerHotspots(String connectionId, ServerApi serverApi, String projectKey, String branchName, SonarLintCancelMonitor cancelMonitor) {\n    var storage = storageService.connection(connectionId);\n    var enabledLanguagesToSync = languageSupportRepository.getEnabledLanguagesInConnectedMode().stream().filter(SonarLanguage::shouldSyncInConnectedMode)\n      .collect(Collectors.toCollection(LinkedHashSet::new));\n    var hotspotsUpdater = new ServerHotspotUpdater(storage, new HotspotDownloader(enabledLanguagesToSync));\n    hotspotsUpdater.updateAll(serverApi.hotspot(), projectKey, branchName, enabledLanguagesToSync, cancelMonitor);\n  }\n\n  public void fetchFileHotspots(Binding binding, String activeBranch, Path serverFilePath, SonarLintCancelMonitor cancelMonitor) {\n    sonarQubeClientManager.withActiveClient(binding.connectionId(), serverApi ->\n      downloadAllServerHotspotsForFile(binding.connectionId(), serverApi, binding.sonarProjectKey(), serverFilePath, activeBranch, cancelMonitor));\n  }\n\n  private void downloadAllServerHotspotsForFile(String connectionId, ServerApi serverApi, String projectKey, Path serverRelativeFilePath, String branchName,\n    SonarLintCancelMonitor cancelMonitor) {\n    var storage = storageService.connection(connectionId);\n    var serverVersion = getSonarServerVersion(serverApi, storage, cancelMonitor);\n    var enabledLanguagesToSync = languageSupportRepository.getEnabledLanguagesInConnectedMode().stream().filter(SonarLanguage::shouldSyncInConnectedMode)\n      .collect(Collectors.toCollection(LinkedHashSet::new));\n    var hotspotsUpdater = new ServerHotspotUpdater(storage, new HotspotDownloader(enabledLanguagesToSync));\n    hotspotsUpdater.updateForFile(serverApi.hotspot(), projectKey, serverRelativeFilePath, branchName, () -> serverVersion, cancelMonitor);\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/IssueSynchronizationService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.sync;\n\nimport java.nio.file.Path;\nimport java.util.LinkedHashSet;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.languages.LanguageSupportRepository;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverconnection.IssueDownloader;\nimport org.sonarsource.sonarlint.core.serverconnection.ServerIssueUpdater;\nimport org.sonarsource.sonarlint.core.serverconnection.TaintIssueDownloader;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\n\npublic class IssueSynchronizationService {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final StorageService storageService;\n  private final LanguageSupportRepository languageSupportRepository;\n  private final SonarQubeClientManager sonarQubeClientManager;\n\n  public IssueSynchronizationService(StorageService storageService, LanguageSupportRepository languageSupportRepository,\n    SonarQubeClientManager sonarQubeClientManager) {\n    this.storageService = storageService;\n    this.languageSupportRepository = languageSupportRepository;\n    this.sonarQubeClientManager = sonarQubeClientManager;\n  }\n\n  public void syncServerIssuesForProject(ServerApi serverApi, String connectionId, String projectKey, String branchName, SonarLintCancelMonitor cancelMonitor) {\n    var storage = storageService.connection(connectionId);\n    var enabledLanguagesToSync = languageSupportRepository.getEnabledLanguagesInConnectedMode().stream().filter(SonarLanguage::shouldSyncInConnectedMode)\n      .collect(Collectors.toCollection(LinkedHashSet::new));\n    var issuesUpdater = new ServerIssueUpdater(storage, new IssueDownloader(enabledLanguagesToSync), new TaintIssueDownloader(enabledLanguagesToSync));\n    if (serverApi.isSonarCloud()) {\n      LOG.debug(\"Incremental issue sync is not supported by SonarCloud. Skipping.\");\n    } else {\n      LOG.info(\"[SYNC] Synchronizing issues for project '{}' on branch '{}'\", projectKey, branchName);\n      issuesUpdater.sync(serverApi, projectKey, branchName, enabledLanguagesToSync, cancelMonitor);\n    }\n  }\n\n  public void fetchProjectIssues(Binding binding, String activeBranch, SonarLintCancelMonitor cancelMonitor) {\n    sonarQubeClientManager.withActiveClient(binding.connectionId(),\n      serverApi -> downloadServerIssuesForProject(binding.connectionId(), serverApi, binding.sonarProjectKey(), activeBranch, cancelMonitor));\n  }\n\n  private void downloadServerIssuesForProject(String connectionId, ServerApi serverApi, String projectKey, String branchName, SonarLintCancelMonitor cancelMonitor) {\n    var storage = storageService.connection(connectionId);\n    var issuesUpdater = new ServerIssueUpdater(storage, new IssueDownloader(enabledLanguagesToSync()), new TaintIssueDownloader(enabledLanguagesToSync()));\n    issuesUpdater.update(serverApi, projectKey, branchName, enabledLanguagesToSync(), cancelMonitor);\n  }\n\n  public void fetchFileIssues(Binding binding, Path serverFileRelativePath, String activeBranch, SonarLintCancelMonitor cancelMonitor) {\n    sonarQubeClientManager.withActiveClient(binding.connectionId(),\n      serverApi -> downloadServerIssuesForFile(binding.connectionId(), serverApi, binding.sonarProjectKey(), serverFileRelativePath, activeBranch, cancelMonitor));\n  }\n\n  public void downloadServerIssuesForFile(String connectionId, ServerApi serverApi, String projectKey, Path serverFileRelativePath, String branchName,\n    SonarLintCancelMonitor cancelMonitor) {\n    var storage = storageService.connection(connectionId);\n    var enabledLanguagesToSync = languageSupportRepository.getEnabledLanguagesInConnectedMode().stream().filter(SonarLanguage::shouldSyncInConnectedMode)\n      .collect(Collectors.toCollection(LinkedHashSet::new));\n    var issuesUpdater = new ServerIssueUpdater(storage, new IssueDownloader(enabledLanguagesToSync), new TaintIssueDownloader(enabledLanguagesToSync));\n    issuesUpdater.updateFileIssuesIfNeeded(serverApi, projectKey, serverFileRelativePath, branchName, cancelMonitor);\n  }\n\n  private Set<SonarLanguage> enabledLanguagesToSync() {\n    return languageSupportRepository.getEnabledLanguagesInConnectedMode().stream()\n      .filter(SonarLanguage::shouldSyncInConnectedMode)\n      .collect(Collectors.toCollection(LinkedHashSet::new));\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/PluginsSynchronizedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.sync;\n\nimport org.jetbrains.annotations.Nullable;\n\npublic record PluginsSynchronizedEvent(@Nullable String connectionId) {\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/ScaSynchronizationService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.sync;\n\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.event.DependencyRisksSynchronizedEvent;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.features.Feature;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerDependencyRisk;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.UpdateSummary;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.springframework.context.ApplicationEventPublisher;\n\nimport static java.util.stream.Collectors.toSet;\n\npublic class ScaSynchronizationService {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final StorageService storageService;\n  private final ApplicationEventPublisher eventPublisher;\n  private final boolean isScaSynchronizationEnabled;\n\n  public ScaSynchronizationService(StorageService storageService, ApplicationEventPublisher eventPublisher, InitializeParams initializeParams) {\n    this.storageService = storageService;\n    this.eventPublisher = eventPublisher;\n    this.isScaSynchronizationEnabled = initializeParams.getBackendCapabilities().contains(BackendCapability.SCA_SYNCHRONIZATION);\n  }\n\n  public void synchronize(ServerApi serverApi, String connectionId, String sonarProjectKey, String branchName, SonarLintCancelMonitor cancelMonitor) {\n    if (!isScaSynchronizationEnabled) {\n      return;\n    }\n    if (!isScaSupported(connectionId)) {\n      return;\n    }\n    LOG.info(\"[SYNC] Synchronizing dependency risks for project '{}' on branch '{}'\", sonarProjectKey, branchName);\n\n    var summary = updateServerDependencyRisksForProject(serverApi, connectionId, sonarProjectKey, branchName, cancelMonitor);\n    if (summary.hasAnythingChanged()) {\n      eventPublisher.publishEvent(new DependencyRisksSynchronizedEvent(connectionId, sonarProjectKey, branchName, summary));\n    }\n  }\n\n  private UpdateSummary<ServerDependencyRisk> updateServerDependencyRisksForProject(ServerApi serverApi, String connectionId, String sonarProjectKey, String branchName,\n    SonarLintCancelMonitor cancelMonitor) {\n    var issuesReleases = serverApi.sca().getIssuesReleases(sonarProjectKey, branchName, cancelMonitor);\n    var findingsStore = storageService.connection(connectionId).project(sonarProjectKey).findings();\n\n    var previousDependencyRisks = findingsStore.loadDependencyRisks(branchName);\n    var previousDependencyRiskKeys = previousDependencyRisks.stream().map(ServerDependencyRisk::key).collect(toSet());\n\n    var serverDependencyRisks = issuesReleases.issuesReleases().stream()\n      .map(issueRelease -> new ServerDependencyRisk(\n        issueRelease.key(),\n        ServerDependencyRisk.Type.valueOf(issueRelease.type().name()),\n        ServerDependencyRisk.Severity.valueOf(issueRelease.severity().name()),\n        ServerDependencyRisk.SoftwareQuality.valueOf(issueRelease.quality().name()),\n        ServerDependencyRisk.Status.valueOf(issueRelease.status().name()),\n        issueRelease.release().packageName(),\n        issueRelease.release().version(),\n        issueRelease.vulnerabilityId(),\n        issueRelease.cvssScore(),\n        issueRelease.transitions().stream().map(Enum::name).map(ServerDependencyRisk.Transition::valueOf).toList()))\n      .toList();\n\n    findingsStore.replaceAllDependencyRisksOfBranch(branchName, serverDependencyRisks);\n\n    var newDependencyRiskKeys = serverDependencyRisks.stream().map(ServerDependencyRisk::key).collect(toSet());\n    var deletedDependencyRiskIds = previousDependencyRisks.stream()\n      .map(ServerDependencyRisk::key)\n      .filter(key -> !newDependencyRiskKeys.contains(key))\n      .collect(toSet());\n    var addedDependencyRisks = serverDependencyRisks.stream()\n      .filter(issue -> !previousDependencyRiskKeys.contains(issue.key()))\n      .toList();\n    var updatedDependencyRisks = serverDependencyRisks.stream()\n      .filter(issue -> previousDependencyRiskKeys.contains(issue.key()))\n      .toList();\n\n    return new UpdateSummary<>(deletedDependencyRiskIds, addedDependencyRisks, updatedDependencyRisks);\n  }\n\n  private boolean isScaSupported(String connectionId) {\n    var serverInfo = storageService.connection(connectionId).serverInfo().read();\n    return serverInfo.map(info -> info.hasFeature(Feature.SCA)).orElse(false);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/SonarProjectBranchesChangedEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.sync;\n\npublic class SonarProjectBranchesChangedEvent {\n  private final String connectionId;\n  private final String sonarProjectKey;\n\n  public SonarProjectBranchesChangedEvent(String connectionId, String sonarProjectKey) {\n    this.connectionId = connectionId;\n    this.sonarProjectKey = sonarProjectKey;\n  }\n\n  public String getConnectionId() {\n    return connectionId;\n  }\n\n  public String getSonarProjectKey() {\n    return sonarProjectKey;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/SonarProjectBranchesSynchronizationService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.sync;\n\nimport java.util.Optional;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.branches.ServerBranch;\nimport org.sonarsource.sonarlint.core.serverconnection.ProjectBranches;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.springframework.context.ApplicationEventPublisher;\n\nimport static java.util.stream.Collectors.toSet;\n\n/**\n * This service manages the synchronization of the SonarProject branches from the Sonar server in the local storage.\n */\npublic class SonarProjectBranchesSynchronizationService {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final StorageService storageService;\n  private final SonarQubeClientManager sonarQubeClientManager;\n  private final ApplicationEventPublisher eventPublisher;\n\n  public SonarProjectBranchesSynchronizationService(StorageService storageService, SonarQubeClientManager sonarQubeClientManager, ApplicationEventPublisher eventPublisher) {\n    this.storageService = storageService;\n    this.sonarQubeClientManager = sonarQubeClientManager;\n    this.eventPublisher = eventPublisher;\n  }\n\n  public void sync(String connectionId, String sonarProjectKey, SonarLintCancelMonitor cancelMonitor) {\n    sonarQubeClientManager.withActiveClient(connectionId, serverApi -> {\n      var branchesStorage = storageService.connection(connectionId).project(sonarProjectKey).branches();\n      Optional<ProjectBranches> oldBranches = Optional.empty();\n      if (branchesStorage.exists()) {\n        oldBranches = Optional.of(branchesStorage.read());\n      }\n      var newBranches = getProjectBranches(serverApi, sonarProjectKey, cancelMonitor);\n      branchesStorage.store(newBranches);\n      if (oldBranches.isEmpty() || !oldBranches.get().equals(newBranches)) {\n        LOG.debug(\"Project branches changed for project '{}'\", sonarProjectKey);\n        eventPublisher.publishEvent(new SonarProjectBranchesChangedEvent(connectionId, sonarProjectKey));\n      }\n    });\n  }\n\n  public ProjectBranches getProjectBranches(ServerApi serverApi, String projectKey, SonarLintCancelMonitor cancelMonitor) {\n    LOG.info(\"Synchronizing project branches for project '{}'\", projectKey);\n    var allBranches = serverApi.branches().getAllBranches(projectKey, cancelMonitor);\n    var mainBranch = allBranches.stream().filter(ServerBranch::isMain).findFirst().map(ServerBranch::getName)\n      .orElseThrow(() -> new IllegalStateException(\"No main branch for project '\" + projectKey + \"'\"));\n    return new ProjectBranches(allBranches.stream().map(ServerBranch::getName).collect(toSet()), mainBranch);\n  }\n\n  public String findMainBranch(String connectionId, String projectKey, SonarLintCancelMonitor cancelMonitor) {\n    var branchesStorage = storageService.binding(new Binding(connectionId, projectKey)).branches();\n    if (branchesStorage.exists()) {\n      var storedBranches = branchesStorage.read();\n      return storedBranches.getMainBranchName();\n    } else {\n      return sonarQubeClientManager.withActiveClientAndReturn(connectionId,\n          serverApi -> getProjectBranches(serverApi, projectKey, cancelMonitor))\n        .map(ProjectBranches::getMainBranchName).orElseThrow();\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/SynchronizationService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.sync;\n\nimport com.google.common.util.concurrent.MoreExecutors;\nimport jakarta.annotation.PostConstruct;\nimport jakarta.annotation.PreDestroy;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashSet;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.branch.MatchedSonarProjectBranchChangedEvent;\nimport org.sonarsource.sonarlint.core.branch.SonarProjectBranchTrackingService;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.BoundScope;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.ExecutorServiceShutdownWatchable;\nimport org.sonarsource.sonarlint.core.commons.progress.ProgressIndicator;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.commons.progress.TaskManager;\nimport org.sonarsource.sonarlint.core.commons.util.FailSafeExecutors;\nimport org.sonarsource.sonarlint.core.event.BindingConfigChangedEvent;\nimport org.sonarsource.sonarlint.core.event.ConfigurationScopeRemovedEvent;\nimport org.sonarsource.sonarlint.core.event.ConfigurationScopesAddedWithBindingEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionCredentialsChangedEvent;\nimport org.sonarsource.sonarlint.core.languages.LanguageSupportRepository;\nimport org.sonarsource.sonarlint.core.plugin.PluginsService;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.sync.DidSynchronizeConfigurationScopeParams;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.exception.ForbiddenException;\nimport org.sonarsource.sonarlint.core.serverapi.exception.UnauthorizedException;\nimport org.sonarsource.sonarlint.core.serverconnection.LocalStorageSynchronizer;\nimport org.sonarsource.sonarlint.core.serverconnection.OrganizationSynchronizer;\nimport org.sonarsource.sonarlint.core.serverconnection.ServerInfoSynchronizer;\nimport org.sonarsource.sonarlint.core.serverconnection.SonarServerSettingsChangedEvent;\nimport org.sonarsource.sonarlint.core.serverconnection.UserSynchronizer;\nimport org.sonarsource.sonarlint.core.serverconnection.aicodefix.AiCodeFixRepository;\nimport org.sonarsource.sonarlint.core.serverconnection.aicodefix.AiCodeFixSettingsSynchronizer;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.springframework.context.ApplicationEventPublisher;\nimport org.springframework.context.event.EventListener;\n\nimport static java.util.Objects.requireNonNull;\nimport static java.util.stream.Collectors.groupingBy;\nimport static java.util.stream.Collectors.mapping;\nimport static java.util.stream.Collectors.toCollection;\nimport static java.util.stream.Collectors.toSet;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.PROJECT_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SECURITY_HOTSPOTS;\n\npublic class SynchronizationService {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final SonarLintRpcClient client;\n  private final ConfigurationRepository configurationRepository;\n  private final LanguageSupportRepository languageSupportRepository;\n  private final SonarQubeClientManager sonarQubeClientManager;\n  private final TaskManager taskManager;\n  private final StorageService storageService;\n  private final boolean branchSpecificSynchronizationEnabled;\n  private final boolean fullSynchronizationEnabled;\n  private final SynchronizationTimestampRepository<String> scopeSynchronizationTimestampRepository = new SynchronizationTimestampRepository<>();\n  private final SynchronizationTimestampRepository<Binding> bindingSynchronizationTimestampRepository = new SynchronizationTimestampRepository<>();\n  private final SynchronizationTimestampRepository<BranchBinding> branchSynchronizationTimestampRepository = new SynchronizationTimestampRepository<>();\n  private final TaintSynchronizationService taintSynchronizationService;\n  private final ScaSynchronizationService scaSynchronizationService;\n  private final IssueSynchronizationService issueSynchronizationService;\n  private final HotspotSynchronizationService hotspotSynchronizationService;\n  private final SonarProjectBranchesSynchronizationService sonarProjectBranchesSynchronizationService;\n  private final SonarProjectBranchTrackingService sonarProjectBranchTrackingService;\n  private final ApplicationEventPublisher applicationEventPublisher;\n  private final ExecutorServiceShutdownWatchable<ScheduledExecutorService> scheduledSynchronizer = new ExecutorServiceShutdownWatchable<>(\n    FailSafeExecutors.newSingleThreadScheduledExecutor(\"SonarLint Local Storage Synchronizer\"));\n  private final Set<String> ignoreBranchEventForScopes = ConcurrentHashMap.newKeySet();\n  private final boolean shouldSynchronizeHotspots;\n  private final AiCodeFixRepository aiCodeFixRepository;\n  private final PluginsService pluginsService;\n\n  public SynchronizationService(SonarLintRpcClient client, ConfigurationRepository configurationRepository, LanguageSupportRepository languageSupportRepository,\n    SonarQubeClientManager sonarQubeClientManager, TaskManager taskManager, StorageService storageService, InitializeParams params,\n    TaintSynchronizationService taintSynchronizationService, ScaSynchronizationService scaSynchronizationService, IssueSynchronizationService issueSynchronizationService,\n    HotspotSynchronizationService hotspotSynchronizationService, SonarProjectBranchesSynchronizationService sonarProjectBranchesSynchronizationService,\n    SonarProjectBranchTrackingService sonarProjectBranchTrackingService, ApplicationEventPublisher applicationEventPublisher, AiCodeFixRepository aiCodeFixRepository,\n    PluginsService pluginsService) {\n    this.client = client;\n    this.configurationRepository = configurationRepository;\n    this.languageSupportRepository = languageSupportRepository;\n    this.sonarQubeClientManager = sonarQubeClientManager;\n    this.taskManager = taskManager;\n    this.storageService = storageService;\n    this.branchSpecificSynchronizationEnabled = params.getBackendCapabilities().contains(PROJECT_SYNCHRONIZATION);\n    this.shouldSynchronizeHotspots = params.getBackendCapabilities().contains(SECURITY_HOTSPOTS);\n    this.fullSynchronizationEnabled = params.getBackendCapabilities().contains(FULL_SYNCHRONIZATION);\n    this.taintSynchronizationService = taintSynchronizationService;\n    this.scaSynchronizationService = scaSynchronizationService;\n    this.issueSynchronizationService = issueSynchronizationService;\n    this.hotspotSynchronizationService = hotspotSynchronizationService;\n    this.sonarProjectBranchesSynchronizationService = sonarProjectBranchesSynchronizationService;\n    this.sonarProjectBranchTrackingService = sonarProjectBranchTrackingService;\n    this.applicationEventPublisher = applicationEventPublisher;\n    this.aiCodeFixRepository = aiCodeFixRepository;\n    this.pluginsService = pluginsService;\n  }\n\n  @PostConstruct\n  public void startScheduledSync() {\n    if (!branchSpecificSynchronizationEnabled) {\n      return;\n    }\n    var initialDelay = Long.parseLong(System.getProperty(\"sonarlint.internal.synchronization.initialDelay\", \"3600\"));\n    var syncPeriod = Long.parseLong(System.getProperty(\"sonarlint.internal.synchronization.period\", \"3600\"));\n    var cancelMonitor = new SonarLintCancelMonitor();\n    cancelMonitor.watchForShutdown(scheduledSynchronizer);\n    scheduledSynchronizer.getWrapped().scheduleAtFixedRate(() -> safeSyncAllConfigScopes(cancelMonitor), initialDelay, syncPeriod, TimeUnit.SECONDS);\n  }\n\n  // we must catch errors for the scheduling to not stop\n  private void safeSyncAllConfigScopes(SonarLintCancelMonitor cancelMonitor) {\n    try {\n      synchronizeProjectsSync(configurationRepository.getBoundScopeByConnectionAndSonarProject(), cancelMonitor);\n    } catch (Exception e) {\n      LOG.error(\"Error during the auto-sync\", e);\n    }\n  }\n\n  private void synchronizeProjectsAsync(Map<String, Map<String, Collection<BoundScope>>> boundScopeByConnectionAndSonarProject) {\n    var cancelMonitor = new SonarLintCancelMonitor();\n    cancelMonitor.watchForShutdown(scheduledSynchronizer);\n    scheduledSynchronizer.execute(() -> synchronizeProjectsSync(boundScopeByConnectionAndSonarProject, cancelMonitor));\n  }\n\n  private void synchronizeProjectsSync(Map<String, Map<String, Collection<BoundScope>>> boundScopeByConnectionAndSonarProject, SonarLintCancelMonitor cancelMonitor) {\n    if (boundScopeByConnectionAndSonarProject.isEmpty()) {\n      return;\n    }\n    taskManager.createAndRunTask(null, UUID.randomUUID(), \"Synchronizing projects...\", null, false, false, progressIndicator -> {\n      var connectionsCount = boundScopeByConnectionAndSonarProject.size();\n      var progressGap = 100f / connectionsCount;\n      var progress = 0f;\n      var synchronizedConfScopeIds = new HashSet<String>();\n      for (var entry : boundScopeByConnectionAndSonarProject.entrySet()) {\n        var connectionId = entry.getKey();\n        progressIndicator.notifyProgress(\"Synchronizing with '\" + connectionId + \"'...\", Math.round(progress));\n        synchronizeProjectsOfTheSameConnection(connectionId, entry.getValue(), progressIndicator, synchronizedConfScopeIds, progress, progressGap, cancelMonitor);\n        progress += progressGap;\n      }\n      if (!synchronizedConfScopeIds.isEmpty()) {\n        applicationEventPublisher.publishEvent(new ConfigurationScopesSynchronizedEvent(synchronizedConfScopeIds));\n        client.didSynchronizeConfigurationScopes(new DidSynchronizeConfigurationScopeParams(synchronizedConfScopeIds));\n      }\n    }, cancelMonitor);\n  }\n\n  private void synchronizeProjectsOfTheSameConnection(String connectionId, Map<String, Collection<BoundScope>> boundScopeBySonarProject, ProgressIndicator progressIndicator,\n    Set<String> synchronizedConfScopeIds, float progress, float progressGap, SonarLintCancelMonitor cancelMonitor) {\n    if (boundScopeBySonarProject.isEmpty()) {\n      return;\n    }\n    sonarQubeClientManager.withActiveClient(connectionId, serverApi -> {\n      var subProgressGap = progressGap / boundScopeBySonarProject.size();\n      var subProgress = progress;\n      for (var entry : boundScopeBySonarProject.entrySet()) {\n        synchronizeProjectWithProgress(serverApi, connectionId, entry.getKey(), entry.getValue(), progressIndicator, cancelMonitor, synchronizedConfScopeIds, subProgress);\n        subProgress += subProgressGap;\n      }\n    });\n  }\n\n  private void synchronizeProjectWithProgress(ServerApi serverApi, String connectionId, String sonarProjectKey, Collection<BoundScope> boundScopes,\n    ProgressIndicator progressIndicator, SonarLintCancelMonitor cancelMonitor, Set<String> synchronizedConfigScopeIds, float subProgress) {\n    var allScopes = configurationRepository.getBoundScopesToConnectionAndSonarProject(connectionId, sonarProjectKey);\n    var allScopesByOptBranch = allScopes.stream()\n      .collect(groupingBy(b -> sonarProjectBranchTrackingService.awaitEffectiveSonarProjectBranch(b.getConfigScopeId())));\n    allScopesByOptBranch\n      .forEach((branchNameOpt, scopes) -> branchNameOpt.ifPresent(branchName -> {\n        var branchBinding = new BranchBinding(new Binding(connectionId, sonarProjectKey), branchName);\n        if (shouldSynchronizeBranch(branchBinding)) {\n          branchSynchronizationTimestampRepository.setLastSynchronizationTimestampToNow(branchBinding);\n          progressIndicator.notifyProgress(\"Synchronizing project '\" + sonarProjectKey + \"'...\", (int) subProgress);\n          issueSynchronizationService.syncServerIssuesForProject(serverApi, connectionId, sonarProjectKey, branchName, cancelMonitor);\n          taintSynchronizationService.synchronizeTaintVulnerabilities(serverApi, connectionId, sonarProjectKey, branchName, cancelMonitor);\n          scaSynchronizationService.synchronize(serverApi, connectionId, sonarProjectKey, branchName, cancelMonitor);\n          if (shouldSynchronizeHotspots) {\n            hotspotSynchronizationService.syncServerHotspotsForProject(serverApi, connectionId, sonarProjectKey, branchName, cancelMonitor);\n          }\n          synchronizedConfigScopeIds.addAll(boundScopes.stream().map(BoundScope::getConfigScopeId).collect(toSet()));\n        }\n      }));\n  }\n\n  public Version readOrSynchronizeServerVersion(String connectionId, ServerApi serverApi, SonarLintCancelMonitor cancelMonitor) {\n    var serverInfoSynchronizer = new ServerInfoSynchronizer(storageService.connection(connectionId));\n    return serverInfoSynchronizer.readOrSynchronizeServerInfo(serverApi, cancelMonitor).version();\n  }\n\n  @EventListener\n  public void onConfigurationsScopeAdded(ConfigurationScopesAddedWithBindingEvent event) {\n    if (!fullSynchronizationEnabled) {\n      return;\n    }\n    LOG.debug(\"Synchronizing new configuration scopes: {}\", event.getConfigScopeIds());\n    var scopesToSynchronize = event.getConfigScopeIds()\n      .stream().map(configurationRepository::getBoundScope)\n      .filter(Objects::nonNull)\n      .collect(groupingBy(BoundScope::getConnectionId));\n    scopesToSynchronize.forEach(this::synchronizeConnectionAndProjectsIfNeededAsync);\n  }\n\n  @EventListener\n  public void onConfigurationScopeRemoved(ConfigurationScopeRemovedEvent event) {\n    var scopeId = event.getRemovedConfigurationScopeId();\n    LOG.debug(\"Config scope {} removed, managing caches\", scopeId);\n    scopeSynchronizationTimestampRepository.clearLastSynchronizationTimestamp(scopeId);\n    var previousBinding = event.removedBindingConfiguration();\n    if (previousBinding.isBound()) {\n      var connectionId = requireNonNull(previousBinding.connectionId());\n      var projectKey = requireNonNull(previousBinding.sonarProjectKey());\n      var scopes = configurationRepository.getBoundScopesToConnectionAndSonarProject(connectionId, projectKey);\n      if (scopes.isEmpty()) {\n        // no remaining scope bound to this connection and project, clear the cache\n        LOG.debug(\"Clearing the synchronization cache for {}, binding={}\", scopeId, previousBinding);\n        var binding = new Binding(connectionId, projectKey);\n        bindingSynchronizationTimestampRepository.clearLastSynchronizationTimestamp(binding);\n        branchSynchronizationTimestampRepository.clearLastSynchronizationTimestampIf(branchBinding -> branchBinding.getBinding().equals(binding));\n      } else {\n        LOG.debug(\"Other config scopes are still bound to {}, see {}, keeping the cache\", previousBinding, scopes);\n      }\n    } else {\n      LOG.debug(\"Removed config scope was not bound, {}, keeping the cache\", previousBinding);\n    }\n  }\n\n  @EventListener\n  public void onBindingChanged(BindingConfigChangedEvent event) {\n    if (!fullSynchronizationEnabled) {\n      return;\n    }\n    var configScopeId = event.configScopeId();\n    scopeSynchronizationTimestampRepository.clearLastSynchronizationTimestamp(configScopeId);\n    if (event.previousConfig().isBound()) {\n      // when unbinding, we want to let future rebinds trigger a sync\n      var previousBinding = new Binding(requireNonNull(event.previousConfig().connectionId()), requireNonNull(event.previousConfig().sonarProjectKey()));\n      bindingSynchronizationTimestampRepository.clearLastSynchronizationTimestamp(previousBinding);\n      branchSynchronizationTimestampRepository.clearLastSynchronizationTimestampIf(branchBinding -> branchBinding.getBinding().equals(previousBinding));\n    }\n    var newConnectionId = event.newConfig().connectionId();\n    if (newConnectionId != null) {\n      synchronizeConnectionAndProjectsIfNeededAsync(\n        newConnectionId,\n        List.of(new BoundScope(configScopeId, newConnectionId, requireNonNull(event.newConfig().sonarProjectKey()))));\n    }\n  }\n\n  @EventListener\n  public void onConnectionCredentialsChanged(ConnectionCredentialsChangedEvent event) {\n    if (!fullSynchronizationEnabled) {\n      return;\n    }\n    var connectionId = event.getConnectionId();\n    LOG.debug(\"Synchronizing connection '{}' after credentials changed\", connectionId);\n    var bindingsForUpdatedConnection = configurationRepository.getBoundScopesToConnection(connectionId);\n    // Clear the synchronization timestamp for all the scopes so that sync is not skipped\n    bindingsForUpdatedConnection.forEach(boundScope -> {\n      scopeSynchronizationTimestampRepository.clearLastSynchronizationTimestamp(boundScope.getConfigScopeId());\n      var binding = new Binding(connectionId, boundScope.getSonarProjectKey());\n      bindingSynchronizationTimestampRepository.clearLastSynchronizationTimestamp(binding);\n      branchSynchronizationTimestampRepository.clearLastSynchronizationTimestampIf(branchBinding -> branchBinding.getBinding().equals(binding));\n    });\n    synchronizeConnectionAndProjectsIfNeededAsync(connectionId, bindingsForUpdatedConnection);\n  }\n\n  private void synchronizeConnectionAndProjectsIfNeededAsync(String connectionId, Collection<BoundScope> boundScopes) {\n    var cancelMonitor = new SonarLintCancelMonitor();\n    cancelMonitor.watchForShutdown(scheduledSynchronizer);\n    scheduledSynchronizer.execute(\n      () -> sonarQubeClientManager.withActiveClient(connectionId, serverApi -> synchronizeConnectionAndProjectsIfNeededSync(connectionId, serverApi, boundScopes, cancelMonitor)));\n  }\n\n  private void synchronizeConnectionAndProjectsIfNeededSync(String connectionId, ServerApi serverApi, Collection<BoundScope> boundScopes, SonarLintCancelMonitor cancelMonitor) {\n    var scopesToSync = boundScopes.stream().filter(this::shouldSynchronizeScope).toList();\n    if (scopesToSync.isEmpty()) {\n      return;\n    }\n    scopesToSync.forEach(scope -> scopeSynchronizationTimestampRepository.setLastSynchronizationTimestampToNow(scope.getConfigScopeId()));\n    // We will already trigger a sync of the project storage so we can temporarily ignore branch changed event for these config scopes\n    ignoreBranchEventForScopes.addAll(scopesToSync.stream().map(BoundScope::getConfigScopeId).collect(toSet()));\n    var enabledLanguagesToSync = languageSupportRepository.getEnabledLanguagesInConnectedMode().stream()\n      .filter(SonarLanguage::shouldSyncInConnectedMode).collect(Collectors.toCollection(LinkedHashSet::new));\n    var storage = storageService.connection(connectionId);\n    var serverInfoSynchronizer = new ServerInfoSynchronizer(storage);\n    var storageSynchronizer = new LocalStorageSynchronizer(enabledLanguagesToSync, serverInfoSynchronizer, storage);\n    synchronizePlugins(connectionId);\n    var aiCodeFixSynchronizer = new AiCodeFixSettingsSynchronizer(storage, new OrganizationSynchronizer(storage), aiCodeFixRepository);\n    var userSynchronizer = new UserSynchronizer(storage);\n    try {\n      LOG.debug(\"Synchronizing storage of connection '{}'\", connectionId);\n      userSynchronizer.synchronize(serverApi, cancelMonitor);\n      var summary = storageSynchronizer.synchronizeServerInfosAndPlugins(serverApi, cancelMonitor);\n      scopesToSync = scopesToSync.stream()\n        .filter(boundScope -> shouldSynchronizeBinding(new Binding(connectionId, boundScope.getSonarProjectKey()))).toList();\n      var scopesPerProjectKey = scopesToSync.stream()\n        .collect(groupingBy(BoundScope::getSonarProjectKey, mapping(BoundScope::getConfigScopeId, toSet())));\n      aiCodeFixSynchronizer.synchronize(serverApi, summary.version(), scopesPerProjectKey.keySet(), cancelMonitor);\n      scopesPerProjectKey.forEach((projectKey, configScopeIds) -> {\n        var binding = new Binding(connectionId, projectKey);\n        bindingSynchronizationTimestampRepository.setLastSynchronizationTimestampToNow(binding);\n        LOG.debug(\"Synchronizing storage of Sonar project '{}' for connection '{}'\", projectKey, connectionId);\n        var analyzerConfigUpdateSummary = storageSynchronizer.synchronizeAnalyzerConfig(serverApi, projectKey, cancelMonitor);\n        // XXX we might want to group those 2 events under one\n        if (!analyzerConfigUpdateSummary.getUpdatedSettingsValueByKey().isEmpty()) {\n          applicationEventPublisher.publishEvent(\n            new SonarServerSettingsChangedEvent(connectionId, configScopeIds, analyzerConfigUpdateSummary.getUpdatedSettingsValueByKey()));\n        }\n        applicationEventPublisher.publishEvent(new AnalyzerConfigurationSynchronized(binding, configScopeIds));\n        sonarProjectBranchesSynchronizationService.sync(connectionId, projectKey, cancelMonitor);\n      });\n      synchronizeProjectsSync(\n        Map.of(connectionId, scopesToSync.stream().map(scope -> new BoundScope(scope.getConfigScopeId(), connectionId, scope.getSonarProjectKey()))\n          .collect(groupingBy(BoundScope::getSonarProjectKey, toCollection(ArrayList::new)))),\n        cancelMonitor);\n    } catch (Exception e) {\n      LOG.error(\"Error during synchronization\", e);\n      if (e instanceof UnauthorizedException || e instanceof ForbiddenException) {\n        throw e;\n      }\n    } finally {\n      ignoreBranchEventForScopes.removeAll(scopesToSync.stream().map(BoundScope::getConfigScopeId).collect(toSet()));\n    }\n  }\n\n  private void synchronizePlugins(String connectionId) {\n    var plugins = pluginsService.getPlugins(connectionId);\n    // synchronization is synchronous, wait for downloads to happen\n    plugins.artifactsResult().getAllDownloadsFuture()\n      .ifPresent(future -> {\n        try {\n          future.get(5, TimeUnit.MINUTES);\n        } catch (InterruptedException e) {\n          Thread.currentThread().interrupt();\n          throw new RuntimeException(e);\n        } catch (ExecutionException | TimeoutException e) {\n          throw new RuntimeException(e);\n        }\n      });\n  }\n\n  private boolean shouldSynchronizeBinding(Binding binding) {\n    boolean result = bindingSynchronizationTimestampRepository.getLastSynchronizationDate(binding)\n      .map(lastSync -> lastSync.isBefore(Instant.now().minus(getSyncPeriod(), ChronoUnit.SECONDS)))\n      .orElse(true);\n    if (!result) {\n      LOG.debug(\"Skipping synchronization of binding '{}' because it was synchronized recently\", binding);\n    }\n    return result;\n  }\n\n  private boolean shouldSynchronizeScope(BoundScope configScope) {\n    boolean result = scopeSynchronizationTimestampRepository.getLastSynchronizationDate(configScope.getConfigScopeId())\n      .map(lastSync -> lastSync.isBefore(Instant.now().minus(getSyncPeriod(), ChronoUnit.SECONDS)))\n      .orElse(true);\n    if (!result) {\n      LOG.debug(\"Skipping synchronization of configuration scope '{}' because it was synchronized recently\", configScope.getConfigScopeId());\n    }\n    return result;\n  }\n\n  private boolean shouldSynchronizeBranch(BranchBinding branchBinding) {\n    boolean result = branchSynchronizationTimestampRepository.getLastSynchronizationDate(branchBinding)\n      .map(lastSync -> lastSync.isBefore(Instant.now().minus(getSyncPeriod(), ChronoUnit.SECONDS)))\n      .orElse(true);\n    if (!result) {\n      LOG.debug(\"Skipping synchronization of branch '{}' because it was synchronized recently\", branchBinding.getBranchName());\n    }\n    return result;\n  }\n\n  private static long getSyncPeriod() {\n    return Long.parseLong(System.getProperty(\"sonarlint.internal.synchronization.scope.period\", \"300\"));\n  }\n\n  @EventListener\n  public void onSonarProjectBranchChanged(MatchedSonarProjectBranchChangedEvent changedEvent) {\n    if (!branchSpecificSynchronizationEnabled) {\n      return;\n    }\n    var configurationScopeId = changedEvent.getConfigurationScopeId();\n    if (ignoreBranchEventForScopes.contains(configurationScopeId)) {\n      return;\n    }\n    configurationRepository.getEffectiveBinding(configurationScopeId).ifPresent(binding -> synchronizeProjectsAsync(Map.of(requireNonNull(binding.connectionId()),\n      Map.of(binding.sonarProjectKey(), List.of(new BoundScope(configurationScopeId, binding))))));\n  }\n\n  @PreDestroy\n  public void shutdown() {\n    if (!MoreExecutors.shutdownAndAwaitTermination(scheduledSynchronizer, 5, TimeUnit.SECONDS)) {\n      LOG.warn(\"Unable to stop synchronizer executor service in a timely manner\");\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/SynchronizationTimestampRepository.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.sync;\n\nimport java.time.Instant;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.Predicate;\n\npublic class SynchronizationTimestampRepository<T> {\n  private final Map<T, Instant> lastSynchronizationTimestampPerSource = new ConcurrentHashMap<>();\n\n  public Optional<Instant> getLastSynchronizationDate(T source) {\n    return Optional.ofNullable(lastSynchronizationTimestampPerSource.get(source));\n  }\n\n  public void setLastSynchronizationTimestampToNow(T source) {\n    lastSynchronizationTimestampPerSource.put(source, Instant.now());\n  }\n\n  public void clearLastSynchronizationTimestamp(T source) {\n    lastSynchronizationTimestampPerSource.remove(source);\n  }\n\n  public void clearLastSynchronizationTimestampIf(Predicate<T> predicate) {\n    lastSynchronizationTimestampPerSource.keySet().removeIf(predicate);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/TaintSynchronizationService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.sync;\n\nimport java.util.LinkedHashSet;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.branch.SonarProjectBranchTrackingService;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.event.TaintVulnerabilitiesSynchronizedEvent;\nimport org.sonarsource.sonarlint.core.languages.LanguageSupportRepository;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverconnection.IssueDownloader;\nimport org.sonarsource.sonarlint.core.serverconnection.ServerIssueUpdater;\nimport org.sonarsource.sonarlint.core.serverconnection.TaintIssueDownloader;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerTaintIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.UpdateSummary;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.springframework.context.ApplicationEventPublisher;\n\nimport static java.util.stream.Collectors.groupingBy;\n\npublic class TaintSynchronizationService {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final ConfigurationRepository configurationRepository;\n  private final SonarProjectBranchTrackingService branchTrackingService;\n  private final StorageService storageService;\n  private final LanguageSupportRepository languageSupportRepository;\n  private final SonarQubeClientManager sonarQubeClientManager;\n  private final ApplicationEventPublisher eventPublisher;\n\n  public TaintSynchronizationService(ConfigurationRepository configurationRepository, SonarProjectBranchTrackingService branchTrackingService,\n                                     StorageService storageService, LanguageSupportRepository languageSupportRepository,\n                                     SonarQubeClientManager sonarQubeClientManager, ApplicationEventPublisher eventPublisher) {\n    this.configurationRepository = configurationRepository;\n    this.branchTrackingService = branchTrackingService;\n    this.storageService = storageService;\n    this.languageSupportRepository = languageSupportRepository;\n    this.sonarQubeClientManager = sonarQubeClientManager;\n    this.eventPublisher = eventPublisher;\n  }\n\n  public void synchronizeTaintVulnerabilities(String connectionId, String projectKey, SonarLintCancelMonitor cancelMonitor) {\n    sonarQubeClientManager.withActiveClient(connectionId, serverApi -> {\n      var allScopes = configurationRepository.getBoundScopesToConnectionAndSonarProject(connectionId, projectKey);\n      var allScopesByOptBranch = allScopes.stream()\n        .collect(groupingBy(b -> branchTrackingService.awaitEffectiveSonarProjectBranch(b.getConfigScopeId())));\n      allScopesByOptBranch\n        .forEach((branchNameOpt, scopes) -> branchNameOpt.ifPresent(branchName -> synchronizeTaintVulnerabilities(serverApi, connectionId, projectKey, branchName, cancelMonitor)));\n    });\n  }\n\n  public void synchronizeTaintVulnerabilities(ServerApi serverApi, String connectionId, String projectKey, String branch, SonarLintCancelMonitor cancelMonitor) {\n    if (languageSupportRepository.areTaintVulnerabilitiesSupported()) {\n      var summary = updateServerTaintIssuesForProject(connectionId, serverApi, projectKey, branch, cancelMonitor);\n      if (summary.hasAnythingChanged()) {\n        eventPublisher.publishEvent(new TaintVulnerabilitiesSynchronizedEvent(connectionId, projectKey, branch, summary));\n      }\n    }\n  }\n\n  private UpdateSummary<ServerTaintIssue> updateServerTaintIssuesForProject(String connectionId, ServerApi serverApi, String projectKey,\n    String branchName, SonarLintCancelMonitor cancelMonitor) {\n    var storage = storageService.connection(connectionId);\n    var enabledLanguagesToSync = languageSupportRepository.getEnabledLanguagesInConnectedMode().stream().filter(SonarLanguage::shouldSyncInConnectedMode)\n      .collect(Collectors.toCollection(LinkedHashSet::new));\n    var issuesUpdater = new ServerIssueUpdater(storage, new IssueDownloader(enabledLanguagesToSync), new TaintIssueDownloader(enabledLanguagesToSync));\n    if (serverApi.isSonarCloud()) {\n      return issuesUpdater.downloadProjectTaints(serverApi, projectKey, branchName, enabledLanguagesToSync, cancelMonitor);\n    } else {\n      LOG.info(\"[SYNC] Synchronizing taint issues for project '{}' on branch '{}'\", projectKey, branchName);\n      return issuesUpdater.syncTaints(serverApi, projectKey, branchName, enabledLanguagesToSync, cancelMonitor);\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.sync;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/telemetry/TelemetryServerAttributesProvider.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Objects;\nimport java.util.function.Predicate;\nimport javax.annotation.CheckForNull;\nimport org.sonarsource.sonarlint.core.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.active.rules.ActiveRulesService;\nimport org.sonarsource.sonarlint.core.analysis.NodeJsService;\nimport org.sonarsource.sonarlint.core.commons.BoundScope;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarCloudConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarQubeConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.repository.rules.RulesRepository;\nimport org.sonarsource.sonarlint.core.serverconnection.Organization;\nimport org.sonarsource.sonarlint.core.serverconnection.StoredServerInfo;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\n\npublic class TelemetryServerAttributesProvider {\n\n  private final ConfigurationRepository configurationRepository;\n  private final ConnectionConfigurationRepository connectionConfigurationRepository;\n  private final ActiveRulesService activeRulesService;\n  private final RulesRepository rulesRepository;\n  private final NodeJsService nodeJsService;\n  private final StorageService storageService;\n\n  public TelemetryServerAttributesProvider(ConfigurationRepository configurationRepository,\n    ConnectionConfigurationRepository connectionConfigurationRepository, ActiveRulesService activeRulesService, RulesRepository rulesRepository,\n    NodeJsService nodeJsService, StorageService storageService) {\n    this.configurationRepository = configurationRepository;\n    this.connectionConfigurationRepository = connectionConfigurationRepository;\n    this.activeRulesService = activeRulesService;\n    this.rulesRepository = rulesRepository;\n    this.nodeJsService = nodeJsService;\n    this.storageService = storageService;\n  }\n\n  public TelemetryServerAttributes getTelemetryServerLiveAttributes() {\n    var allBindings = configurationRepository.getAllBoundScopes();\n\n    var usesConnectedMode = !allBindings.isEmpty();\n    var usesSonarCloud = allBindings.stream().anyMatch(isSonarCloudConnectionConfiguration());\n\n    var childBindingCount = countChildBindings();\n    var sonarQubeServerBindingCount = countSonarQubeServerBindings(allBindings);\n    var sonarQubeCloudEUBindingCount = countSonarQubeCloudBindings(allBindings, SonarCloudRegion.EU);\n    var sonarQubeCloudUSBindingCount = countSonarQubeCloudBindings(allBindings, SonarCloudRegion.US);\n\n    var devNotificationsDisabled = allBindings.stream().anyMatch(this::hasDisableNotifications);\n\n    var nonDefaultEnabledRules = new ArrayList<String>();\n    var defaultDisabledRules = new ArrayList<String>();\n\n    activeRulesService.getStandaloneRuleConfig().forEach((ruleKey, standaloneRuleConfigDto) -> {\n      var optionalEmbeddedRule = rulesRepository.getEmbeddedRule(ruleKey);\n      if (optionalEmbeddedRule.isEmpty()) {\n        return;\n      }\n      var activeByDefault = optionalEmbeddedRule.get().isActiveByDefault();\n      var isActive = standaloneRuleConfigDto.isActive();\n      if (activeByDefault && !isActive) {\n        defaultDisabledRules.add(ruleKey);\n      } else if (!activeByDefault && isActive) {\n        nonDefaultEnabledRules.add(ruleKey);\n      }\n    });\n\n    var nodeJsVersion = getNodeJsVersion();\n\n    var connectionsAttributes = connectionConfigurationRepository.getConnectionsById().keySet().stream()\n      .map(storageService::connection)\n      .map(c -> {\n        var userId = c.user().read().orElse(null);\n        var serverId = c.serverInfo().read().map(StoredServerInfo::serverId).orElse(null);\n        var orgId = c.organization().read().map(Organization::id).orElse(null);\n\n        if (userId == null && serverId == null && orgId == null) {\n          return null;\n        }\n\n        return new TelemetryConnectionAttributes(userId, serverId, orgId);\n      })\n      .filter(Objects::nonNull)\n      .toList();\n\n    return new TelemetryServerAttributes(usesConnectedMode, usesSonarCloud, childBindingCount, sonarQubeServerBindingCount,\n      sonarQubeCloudEUBindingCount, sonarQubeCloudUSBindingCount, devNotificationsDisabled, nonDefaultEnabledRules,\n      defaultDisabledRules, nodeJsVersion, connectionsAttributes);\n  }\n\n  private int countSonarQubeCloudBindings(Collection<BoundScope> allBindings, SonarCloudRegion region) {\n    return (int) allBindings.stream()\n      .filter(binding -> {\n        if (connectionConfigurationRepository.getConnectionById(binding.getConnectionId()) instanceof SonarCloudConnectionConfiguration scBinding) {\n          return region.equals(scBinding.getRegion());\n        }\n        return false;\n      }).count();\n  }\n\n  private int countSonarQubeServerBindings(Collection<BoundScope> allBindings) {\n    return (int) allBindings.stream()\n      .filter(binding -> connectionConfigurationRepository.getConnectionById(binding.getConnectionId()) instanceof SonarQubeConnectionConfiguration)\n      .count();\n  }\n\n  // We are looking for leaf config scope IDs that are bound to a different project key than their parents\n  private int countChildBindings() {\n    return (int) configurationRepository.getLeafConfigScopeIds().stream()\n      .filter(scopeId -> {\n        var configScope = configurationRepository.getConfigurationScope(scopeId);\n        if (configScope != null && configScope.parentId() != null) {\n          var parentBindingConfig = configurationRepository.getBindingConfiguration(configScope.parentId());\n          var leafBindingConfig = configurationRepository.getBindingConfiguration(scopeId);\n          if (parentBindingConfig != null && leafBindingConfig != null) {\n            var parentProjectKey = parentBindingConfig.sonarProjectKey();\n            var leafProjectKey = leafBindingConfig.sonarProjectKey();\n            return parentProjectKey != null && leafProjectKey != null && !parentProjectKey.equals(leafProjectKey);\n          }\n        }\n        return false;\n      })\n      .count();\n  }\n\n  @CheckForNull\n  private String getNodeJsVersion() {\n    return nodeJsService.getActiveNodeJsVersion().map(Objects::toString).orElse(null);\n  }\n\n  private boolean hasDisableNotifications(BoundScope binding) {\n    return Objects.requireNonNull(connectionConfigurationRepository.getConnectionById(binding.getConnectionId())).isDisableNotifications();\n  }\n\n  private Predicate<BoundScope> isSonarCloudConnectionConfiguration() {\n    return binding -> connectionConfigurationRepository.getConnectionById(binding.getConnectionId()) instanceof SonarCloudConnectionConfiguration;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/telemetry/TelemetryService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\nimport com.google.common.util.concurrent.MoreExecutors;\nimport jakarta.annotation.PreDestroy;\nimport java.time.OffsetDateTime;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.analysis.AnalysisFinishedEvent;\nimport org.sonarsource.sonarlint.core.analysis.AutomaticAnalysisSettingChangedEvent;\nimport org.sonarsource.sonarlint.core.analysis.IssuesRaisedEvent;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.util.FailSafeExecutors;\nimport org.sonarsource.sonarlint.core.event.FixSuggestionReceivedEvent;\nimport org.sonarsource.sonarlint.core.event.LocalOnlyIssueStatusChangedEvent;\nimport org.sonarsource.sonarlint.core.event.MatchingSessionEndedEvent;\nimport org.sonarsource.sonarlint.core.event.ServerIssueStatusChangedEvent;\nimport org.sonarsource.sonarlint.core.event.TelemetryUpdatedEvent;\nimport org.sonarsource.sonarlint.core.promotion.campaign.CampaignResolvedEvent;\nimport org.sonarsource.sonarlint.core.promotion.campaign.CampaignShownEvent;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.AiAgent;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.GetStatusResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedFindingDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AnalysisReportingTriggeredParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.FixSuggestionResolvedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.HelpAndFeedbackClickedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.McpTransportMode;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.ToolCalledParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.springframework.context.ApplicationEventPublisher;\nimport org.springframework.context.event.EventListener;\n\nimport static java.util.Optional.ofNullable;\nimport static java.util.concurrent.TimeUnit.MINUTES;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.TELEMETRY;\n\npublic class TelemetryService {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final long TELEMETRY_UPLOAD_DELAY = TimeUnit.HOURS.toMinutes(TelemetryManager.MIN_HOURS_BETWEEN_UPLOAD + 1L);\n\n  private final ScheduledExecutorService scheduledExecutor;\n  private final TelemetryManager telemetryManager;\n  private final TelemetryServerAttributesProvider telemetryServerAttributesProvider;\n  private final SonarLintRpcClient client;\n  private final boolean isTelemetryFeatureEnabled;\n  private final ApplicationEventPublisher applicationEventPublisher;\n\n  public TelemetryService(InitializeParams initializeParams, SonarLintRpcClient sonarlintClient,\n    TelemetryServerAttributesProvider telemetryServerAttributesProvider, TelemetryManager telemetryManager, ApplicationEventPublisher applicationEventPublisher) {\n    this.isTelemetryFeatureEnabled = initializeParams.getBackendCapabilities().contains(TELEMETRY);\n    this.client = sonarlintClient;\n    this.telemetryServerAttributesProvider = telemetryServerAttributesProvider;\n    this.telemetryManager = telemetryManager;\n    this.applicationEventPublisher = applicationEventPublisher;\n    this.scheduledExecutor = FailSafeExecutors.newSingleThreadScheduledExecutor(\"SonarLint Telemetry\");\n\n    initTelemetryAndScheduleUpload(initializeParams);\n  }\n\n  private void initTelemetryAndScheduleUpload(InitializeParams initializeParams) {\n    if (!isTelemetryFeatureEnabled) {\n      LOG.info(\"Telemetry disabled on server startup\");\n      return;\n    }\n    updateTelemetry(localStorage -> {\n      localStorage.setInitialNewCodeFocus(initializeParams.isFocusOnNewCode());\n      localStorage.setInitialAutomaticAnalysisEnablement(initializeParams.isAutomaticAnalysisEnabled());\n    });\n    var initialDelay = Integer.parseInt(System.getProperty(\"sonarlint.internal.telemetry.initialDelay\", \"1\"));\n    scheduledExecutor.scheduleWithFixedDelay(this::upload, initialDelay, TELEMETRY_UPLOAD_DELAY, MINUTES);\n  }\n\n  private void upload() {\n    var telemetryLiveAttributes = getTelemetryLiveAttributes();\n    if (Objects.nonNull(telemetryLiveAttributes)) {\n      telemetryManager.uploadAndClearTelemetry(telemetryLiveAttributes);\n    }\n  }\n\n  public GetStatusResponse getStatus() {\n    return new GetStatusResponse(isEnabled());\n  }\n\n  public void enableTelemetry() {\n    if (!isTelemetryFeatureEnabled) {\n      LOG.warn(\"Telemetry was disabled on server startup. Ignoring client request.\");\n      return;\n    }\n    var telemetryLiveAttributes = getTelemetryLiveAttributes();\n    if (Objects.nonNull(telemetryLiveAttributes)) {\n      telemetryManager.enable(telemetryLiveAttributes);\n      applicationEventPublisher.publishEvent(new TelemetryUpdatedEvent(true));\n    }\n  }\n\n  public void disableTelemetry() {\n    var telemetryLiveAttributes = getTelemetryLiveAttributes();\n    if (Objects.nonNull(telemetryLiveAttributes)) {\n      telemetryManager.disable(telemetryLiveAttributes);\n      applicationEventPublisher.publishEvent(new TelemetryUpdatedEvent(false));\n    }\n  }\n\n  @Nullable\n  private TelemetryLiveAttributes getTelemetryLiveAttributes() {\n    try {\n      var serverLiveAttributes = telemetryServerAttributesProvider.getTelemetryServerLiveAttributes();\n      var clientLiveAttributes = client.getTelemetryLiveAttributes().get(10, TimeUnit.SECONDS);\n      return new TelemetryLiveAttributes(serverLiveAttributes, clientLiveAttributes);\n    } catch (InterruptedException e) {\n      Thread.currentThread().interrupt();\n      if (InternalDebug.isEnabled()) {\n        LOG.error(\"Failed to fetch telemetry payload\", e);\n      }\n    } catch (Exception e) {\n      if (InternalDebug.isEnabled()) {\n        LOG.error(\"Failed to fetch telemetry payload\", e);\n      }\n    }\n    return null;\n  }\n\n  public boolean isEnabled() {\n    return isTelemetryFeatureEnabled && telemetryManager.isTelemetryEnabledByUser();\n  }\n\n  public OffsetDateTime installTime() {\n    return telemetryManager.installTime();\n  }\n\n  private void updateTelemetry(Consumer<TelemetryLocalStorage> updater) {\n    if (isEnabled()) {\n      telemetryManager.updateTelemetry(updater);\n    }\n  }\n\n  public void hotspotOpenedInBrowser() {\n    updateTelemetry(TelemetryLocalStorage::incrementOpenHotspotInBrowserCount);\n  }\n\n  public void showHotspotRequestReceived() {\n    updateTelemetry(TelemetryLocalStorage::incrementShowHotspotRequestCount);\n  }\n\n  public void showIssueRequestReceived() {\n    updateTelemetry(TelemetryLocalStorage::incrementShowIssueRequestCount);\n  }\n\n  public void taintVulnerabilitiesInvestigatedLocally() {\n    updateTelemetry(TelemetryLocalStorage::incrementTaintVulnerabilitiesInvestigatedLocallyCount);\n  }\n\n  public void taintVulnerabilitiesInvestigatedRemotely() {\n    updateTelemetry(TelemetryLocalStorage::incrementTaintVulnerabilitiesInvestigatedRemotelyCount);\n  }\n\n  public void helpAndFeedbackLinkClicked(HelpAndFeedbackClickedParams params) {\n    updateTelemetry(localStorage -> localStorage.helpAndFeedbackLinkClicked(params.getItemId()));\n  }\n\n  public void analysisReportingTriggered(AnalysisReportingTriggeredParams params) {\n    updateTelemetry(localStorage -> localStorage.analysisReportingTriggered(params.getAnalysisType()));\n  }\n\n  public void fixSuggestionResolved(FixSuggestionResolvedParams params) {\n    updateTelemetry(localStorage -> localStorage.fixSuggestionResolved(params.getSuggestionId(), params.getStatus(), params.getSnippetIndex()));\n  }\n\n  public void smartNotificationsReceived(String eventType) {\n    updateTelemetry(localStorage -> localStorage.incrementDevNotificationsCount(eventType));\n  }\n\n  public void analysisDoneOnSingleLanguage(@Nullable Language language, int analysisTimeMs) {\n    updateTelemetry(localStorage -> {\n      var languageName = ofNullable(language)\n        .map(Enum::name)\n        .map(SonarLanguage::valueOf)\n        .map(SonarLanguage::getSonarLanguageKey)\n        .orElse(\"others\");\n      localStorage.setUsedAnalysis(languageName, analysisTimeMs);\n    });\n  }\n\n  public void analysisDoneOnMultipleFiles() {\n    updateTelemetry(TelemetryLocalStorage::setUsedAnalysis);\n  }\n\n  public void smartNotificationsClicked(String eventType) {\n    updateTelemetry(localStorage -> localStorage.incrementDevNotificationsClicked(eventType));\n  }\n\n  public void addQuickFixAppliedForRule(String ruleKey) {\n    updateTelemetry(localStorage -> localStorage.addQuickFixAppliedForRule(ruleKey));\n  }\n\n  public void addReportedRules(Set<String> ruleKeys) {\n    updateTelemetry(s -> s.addReportedRules(ruleKeys));\n  }\n\n  public void hotspotStatusChanged() {\n    updateTelemetry(TelemetryLocalStorage::incrementHotspotStatusChangedCount);\n  }\n\n  public void newCodeFocusChanged() {\n    updateTelemetry(TelemetryLocalStorage::incrementNewCodeFocusChange);\n  }\n\n  private void issueStatusChanged(String ruleKey) {\n    updateTelemetry(telemetryLocalStorage -> telemetryLocalStorage.addIssueStatusChanged(ruleKey));\n  }\n\n  public void addedManualBindings() {\n    updateTelemetry(TelemetryLocalStorage::incrementManualAddedBindingsCount);\n  }\n\n  public void addedImportedBindings() {\n    updateTelemetry(TelemetryLocalStorage::incrementImportedAddedBindingsCount);\n  }\n\n  public void addedAutomaticBindings() {\n    updateTelemetry(TelemetryLocalStorage::incrementAutoAddedBindingsCount);\n  }\n\n  public void acceptedBindingSuggestion(BindingSuggestionOrigin bindingSuggestionOrigin) {\n    if (bindingSuggestionOrigin.equals(BindingSuggestionOrigin.REMOTE_URL)) {\n      updateTelemetry(TelemetryLocalStorage::incrementNewBindingsRemoteUrlCount);\n    }\n\n    if (bindingSuggestionOrigin.equals(BindingSuggestionOrigin.PROJECT_NAME)) {\n      updateTelemetry(TelemetryLocalStorage::incrementNewBindingsProjectNameCount);\n    }\n\n    if (bindingSuggestionOrigin.equals(BindingSuggestionOrigin.SHARED_CONFIGURATION)) {\n      updateTelemetry(TelemetryLocalStorage::incrementNewBindingsSharedConfigurationCount);\n    }\n\n    if (bindingSuggestionOrigin.equals(BindingSuggestionOrigin.PROPERTIES_FILE)) {\n      updateTelemetry(TelemetryLocalStorage::incrementNewBindingsPropertiesFileCount);\n    }\n  }\n\n  public void exportedConnectedMode() {\n    updateTelemetry(TelemetryLocalStorage::incrementExportedConnectedModeCount);\n  }\n\n  public void suggestedRemoteBinding() {\n    updateTelemetry(TelemetryLocalStorage::incrementSuggestedRemoteBindingsCount);\n  }\n\n  public void mcpIntegrationEnabled() {\n    updateTelemetry(storage -> storage.setMcpIntegrationEnabled(true));\n  }\n\n  public void mcpTransportModeUsed(McpTransportMode transportMode) {\n    updateTelemetry(storage -> storage.setMcpTransportModeUsed(transportMode));\n  }\n\n  public void toolCalled(ToolCalledParams params) {\n    updateTelemetry(storage -> storage.incrementToolCalledCount(params.getToolName(), params.isSucceeded()));\n  }\n\n  public void taintInvestigatedLocally() {\n    updateTelemetry(TelemetryLocalStorage::incrementTaintInvestigatedLocallyCount);\n  }\n\n  public void taintInvestigatedRemotely() {\n    updateTelemetry(TelemetryLocalStorage::incrementTaintInvestigatedRemotelyCount);\n  }\n\n  public void hotspotInvestigatedLocally() {\n    updateTelemetry(TelemetryLocalStorage::incrementHotspotInvestigatedLocallyCount);\n  }\n\n  public void hotspotInvestigatedRemotely() {\n    updateTelemetry(TelemetryLocalStorage::incrementHotspotInvestigatedRemotelyCount);\n  }\n\n  public void issueInvestigatedLocally() {\n    updateTelemetry(TelemetryLocalStorage::incrementIssueInvestigatedLocallyCount);\n  }\n\n  public void dependencyRiskInvestigatedRemotely() {\n    updateTelemetry(TelemetryLocalStorage::incrementDependencyRiskInvestigatedRemotelyCount);\n  }\n\n  public void dependencyRiskInvestigatedLocally() {\n    updateTelemetry(TelemetryLocalStorage::incrementDependencyRiskInvestigatedLocallyCount);\n  }\n\n  public void findingsFiltered(String filterType) {\n    updateTelemetry(localStorage -> localStorage.findingsFiltered(filterType));\n  }\n\n  public void automaticAnalysisSettingToggled() {\n    updateTelemetry(TelemetryLocalStorage::incrementAutomaticAnalysisToggledCount);\n  }\n\n  public void mcpServerConfigurationRequested() {\n    updateTelemetry(TelemetryLocalStorage::incrementMcpServerConfigurationRequestedCount);\n  }\n\n  public void mcpRuleFileRequested() {\n    updateTelemetry(TelemetryLocalStorage::incrementMcpRuleFileRequestedCount);\n  }\n\n  public void ideLabsLinkClicked(String linkId) {\n    updateTelemetry(storage -> storage.ideLabsLinkClicked(linkId));\n  }\n\n  public void ideLabsFeedbackLinkClicked(String featureId) {\n    updateTelemetry(storage -> storage.ideLabsFeedbackLinkClicked(featureId));\n  }\n\n  public void aiHookInstalled(AiAgent aiAgent) {\n    updateTelemetry(storage -> storage.aiHookInstalled(aiAgent));\n  }\n\n  @EventListener\n  public void onMatchingSessionEnded(MatchingSessionEndedEvent event) {\n    updateTelemetry(telemetryLocalStorage -> {\n      telemetryLocalStorage.addNewlyFoundIssues(event.newIssuesFound());\n      telemetryLocalStorage.addFixedIssues(event.issuesFixed());\n    });\n  }\n\n  @EventListener\n  public void onAutomaticAnalysisSettingChanged(AutomaticAnalysisSettingChangedEvent event) {\n    automaticAnalysisSettingToggled();\n  }\n\n  @EventListener\n  public void onServerIssueStatusChanged(ServerIssueStatusChangedEvent event) {\n    issueStatusChanged(event.getFinding().getRuleKey());\n  }\n\n  @EventListener\n  public void onLocalOnlyIssueStatusChanged(LocalOnlyIssueStatusChangedEvent event) {\n    issueStatusChanged(event.getIssue().getRuleKey());\n  }\n\n  @EventListener\n  public void onAnalysisFinished(AnalysisFinishedEvent event) {\n    var languagePerFile = event.getLanguagePerFile();\n    if (languagePerFile.size() == 1 && event.succeededForAllFiles()) {\n      var fileLanguage = languagePerFile.entrySet().iterator().next().getValue();\n      analysisDoneOnSingleLanguage(fileLanguage == null ? null : Language.valueOf(fileLanguage.name()), (int) event.getAnalysisDuration().toMillis());\n    } else {\n      analysisDoneOnMultipleFiles();\n    }\n    addReportedRules(event.getReportedRuleKeys());\n  }\n\n  @EventListener\n  public void onFixSuggestionReceived(FixSuggestionReceivedEvent event) {\n    updateTelemetry(localStorage -> localStorage.fixSuggestionReceived(\n      event.fixSuggestionId(),\n      event.source(),\n      event.snippetsCount(),\n      event.wasGeneratedFromIde()));\n  }\n\n  @EventListener\n  public void onIssuesRaised(IssuesRaisedEvent event) {\n    var issuesToReport = event.issues().stream()\n      .filter(RaisedIssueDto::isAiCodeFixable)\n      .map(RaisedFindingDto::getId)\n      .collect(Collectors.toSet());\n    updateTelemetry(localStorage -> localStorage.addIssuesWithPossibleAiFixFromIde(issuesToReport));\n  }\n\n  @EventListener\n  public void onCampaignShown(CampaignShownEvent event) {\n    updateTelemetry(localStorage -> localStorage.campaignShown(event.campaignName()));\n  }\n\n  @EventListener\n  public void onCampaignResolved(CampaignResolvedEvent event) {\n    updateTelemetry(localStorage -> localStorage.campaignResolved(event.campaignName(), event.resolution()));\n  }\n\n  @PreDestroy\n  public void close() {\n    if ((!MoreExecutors.shutdownAndAwaitTermination(scheduledExecutor, 1, TimeUnit.SECONDS)) && (InternalDebug.isEnabled())) {\n      LOG.error(\"Failed to stop telemetry executor\");\n    }\n  }\n\n  public void updateListFilesPerformance(int size, long timeMs) {\n    if (!isTelemetryFeatureEnabled) {\n      LOG.info(\"Telemetry disabled on server startup\");\n      return;\n    }\n    updateTelemetry(localStorage -> localStorage.updateListFilesPerformance(size, timeMs));\n  }\n\n  public void supportedLanguagesPanelOpened() {\n    updateTelemetry(TelemetryLocalStorage::incrementSupportedLanguagesPanelOpenedCount);\n  }\n\n  public void supportedLanguagesPanelCtaClicked() {\n    updateTelemetry(TelemetryLocalStorage::incrementSupportedLanguagesPanelCtaClickedCount);\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/telemetry/TelemetrySpringConfig.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\nimport java.nio.file.Path;\nimport org.sonarsource.sonarlint.core.UserPaths;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Import;\n\n@Configuration\n@Import({\n  TelemetryService.class,\n  TelemetryManager.class,\n  TelemetryLocalStorageManager.class,\n  TelemetryHttpClient.class,\n  TelemetryServerAttributesProvider.class\n})\npublic class TelemetrySpringConfig {\n\n  public static final String PROPERTY_TELEMETRY_ENDPOINT = \"sonarlint.internal.telemetry.endpoint\";\n  private static final String TELEMETRY_ENDPOINT = \"https://telemetry.sonarsource.com/sonarlint\";\n\n  @Bean(name = \"telemetryPath\")\n  Path provideTelemetryPath(UserPaths userPaths) {\n    return userPaths.getHomeIdeSpecificDir(\"telemetry\").resolve(\"usage\");\n  }\n\n  @Bean(name = \"telemetryEndpoint\")\n  String provideTelemetryEndpoint() {\n    return System.getProperty(PROPERTY_TELEMETRY_ENDPOINT, TELEMETRY_ENDPOINT);\n  }\n\n  @Bean(name = \"initializeParams\")\n  InitializeParams provideInitializeParams(InitializeParams params) {\n    return params;\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/telemetry/gessie/GessieSpringConfig.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.gessie;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Import;\n\n@Configuration\n@Import({\n  GessieService.class,\n  GessieHttpClient.class\n})\npublic class GessieSpringConfig {\n\n  public static final String PROPERTY_GESSIE_ENDPOINT = \"sonarlint.internal.telemetry.gessie.endpoint\";\n  public static final String PROPERTY_GESSIE_API_KEY = \"sonarlint.internal.telemetry.gessie.api.key\";\n  private static final String GESSIE_ENDPOINT = \"https://events.sonardata.io\";\n  private static final String IDE_SOURCE = \"CiiwpdWnR21rWEOkgJ8tr3EYSXb7dzaQ5ezbipLb\";\n\n  @Bean\n  String gessieEndpoint() {\n    return System.getProperty(PROPERTY_GESSIE_ENDPOINT, GESSIE_ENDPOINT);\n  }\n\n  @Bean\n  String gessieApiKey() {\n    return System.getProperty(PROPERTY_GESSIE_API_KEY, IDE_SOURCE);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/telemetry/gessie/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.telemetry.gessie;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/telemetry/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.telemetry;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/IntroductionDateProvider.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.Collection;\n\npublic interface IntroductionDateProvider {\n  Instant determineIntroductionDate(Path filePath, Collection<Integer> lineNumber);\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/IssueMapper.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking;\n\nimport java.time.Instant;\nimport java.util.EnumMap;\nimport java.util.Map;\nimport java.util.UUID;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.analysis.RawIssue;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ResolutionStatus;\n\nimport static org.sonarsource.sonarlint.core.tracking.TextRangeUtils.getLineWithHash;\nimport static org.sonarsource.sonarlint.core.tracking.TextRangeUtils.getTextRangeWithHash;\n\npublic class IssueMapper {\n\n  private static final Map<IssueStatus, ResolutionStatus> STATUS_MAPPING = statusMapping();\n\n  private IssueMapper() {\n    // utils\n  }\n\n  public static TrackedIssue toTrackedIssue(RawIssue issue, Instant introductionDate) {\n    return new TrackedIssue(UUID.randomUUID(), issue.getMessage(), introductionDate, false, issue.getSeverity(),\n      issue.getRuleType(), issue.getRuleKey(), getTextRangeWithHash(issue.getTextRange(),\n      issue.getClientInputFile()), getLineWithHash(issue.getTextRange(),\n      issue.getClientInputFile()), null, issue.getImpacts(), issue.getFlows(), issue.getQuickFixes(),\n      issue.getVulnerabilityProbability(), null, null, issue.getRuleDescriptionContextKey(), issue.getCleanCodeAttribute(), issue.getFileUri());\n  }\n\n  public static ResolutionStatus mapStatus(@Nullable IssueStatus status) {\n    return STATUS_MAPPING.get(status);\n  }\n\n  private static EnumMap<IssueStatus, ResolutionStatus> statusMapping() {\n    return new EnumMap<>(Map.of(\n      IssueStatus.ACCEPT, ResolutionStatus.ACCEPT,\n      IssueStatus.FALSE_POSITIVE, ResolutionStatus.FALSE_POSITIVE,\n      IssueStatus.WONT_FIX, ResolutionStatus.WONT_FIX\n    ));\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/IssueStatusBinding.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking;\n\nimport java.io.ByteArrayInputStream;\nimport jetbrains.exodus.bindings.BindingUtils;\nimport jetbrains.exodus.bindings.ComparableBinding;\nimport jetbrains.exodus.util.LightOutputStream;\nimport org.jetbrains.annotations.NotNull;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\n\npublic class IssueStatusBinding extends ComparableBinding {\n\n  @Override\n  public Comparable readObject(@NotNull ByteArrayInputStream stream) {\n    return IssueStatus.values()[BindingUtils.readInt(stream)];\n  }\n\n  @Override\n  public void writeObject(@NotNull LightOutputStream output, @NotNull Comparable object) {\n    final var cPair = (IssueStatus) object;\n    output.writeUnsignedInt(cPair.ordinal() ^ 0x80_000_000);\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/KnownFindings.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking;\n\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport org.sonarsource.sonarlint.core.commons.KnownFinding;\n\npublic class KnownFindings {\n  private final Map<Path, List<KnownFinding>> issuesPerFile;\n  private final Map<Path, List<KnownFinding>> securityHotspotsPerFile;\n\n  public KnownFindings(Map<Path, List<KnownFinding>> issuesPerFile, Map<Path, List<KnownFinding>> securityHotspotsPerFile) {\n    this.issuesPerFile = issuesPerFile;\n    this.securityHotspotsPerFile = securityHotspotsPerFile;\n  }\n\n  public Map<Path, List<KnownFinding>> getIssuesPerFile() {\n    return issuesPerFile;\n  }\n\n  public Map<Path, List<KnownFinding>> getSecurityHotspotsPerFile() {\n    return securityHotspotsPerFile;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/LocalOnlyIssueRepository.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking;\n\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.UUID;\nimport java.util.concurrent.ConcurrentHashMap;\nimport org.sonarsource.sonarlint.core.commons.LocalOnlyIssue;\n\npublic class LocalOnlyIssueRepository {\n  private final Map<Path, List<LocalOnlyIssue>> localOnlyIssuesByRelativePath = new ConcurrentHashMap<>();\n\n  public void save(Path serverRelativePath, List<LocalOnlyIssue> localOnlyIssues) {\n    localOnlyIssuesByRelativePath.put(serverRelativePath, localOnlyIssues);\n  }\n\n  public Optional<LocalOnlyIssue> findByKey(UUID localOnlyIssueKey) {\n    return localOnlyIssuesByRelativePath.values().stream().flatMap(List::stream).filter(issue -> issue.getId().equals(localOnlyIssueKey)).findFirst();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/LocalOnlySecurityHotspot.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking;\n\nimport java.util.UUID;\n\npublic class LocalOnlySecurityHotspot {\n  private final UUID id;\n\n  public LocalOnlySecurityHotspot(UUID id) {\n    this.id = id;\n  }\n\n  public UUID getId() {\n    return id;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/TaintVulnerabilityTrackingService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking;\n\nimport java.time.Instant;\nimport java.util.Collections;\nimport java.util.EnumMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.function.Predicate;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.branch.SonarProjectBranchTrackingService;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.event.ServerIssueStatusChangedEvent;\nimport org.sonarsource.sonarlint.core.event.SonarServerEventReceivedEvent;\nimport org.sonarsource.sonarlint.core.event.TaintVulnerabilitiesSynchronizedEvent;\nimport org.sonarsource.sonarlint.core.file.FilePathTranslation;\nimport org.sonarsource.sonarlint.core.file.PathTranslationService;\nimport org.sonarsource.sonarlint.core.mode.SeverityModeService;\nimport org.sonarsource.sonarlint.core.remediation.aicodefix.AiCodeFixFeature;\nimport org.sonarsource.sonarlint.core.remediation.aicodefix.AiCodeFixService;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.ImpactDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.taint.vulnerability.DidChangeTaintVulnerabilitiesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.MQRModeDetails;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.RuleType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.StandardModeDetails;\nimport org.sonarsource.sonarlint.core.serverapi.push.IssueChangedEvent;\nimport org.sonarsource.sonarlint.core.serverapi.push.TaintVulnerabilityClosedEvent;\nimport org.sonarsource.sonarlint.core.serverapi.push.TaintVulnerabilityRaisedEvent;\nimport org.sonarsource.sonarlint.core.serverconnection.aicodefix.AiCodeFixRepository;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerTaintIssue;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.sonarsource.sonarlint.core.sync.TaintSynchronizationService;\nimport org.springframework.context.event.EventListener;\n\nimport static java.util.Collections.emptyList;\nimport static java.util.Collections.emptySet;\n\npublic class TaintVulnerabilityTrackingService {\n  private final SonarLintRpcClient client;\n  private final ConfigurationRepository configurationRepository;\n  private final SonarProjectBranchTrackingService branchTrackingService;\n  private final TaintSynchronizationService taintSynchronizationService;\n  private final StorageService storageService;\n  private final PathTranslationService pathTranslationService;\n  private final SeverityModeService severityModeService;\n  private final AiCodeFixRepository aiCodeFixRepository;\n\n  public TaintVulnerabilityTrackingService(SonarLintRpcClient client, ConfigurationRepository configurationRepository, SonarProjectBranchTrackingService branchTrackingService,\n    TaintSynchronizationService taintSynchronizationService, StorageService storageService, PathTranslationService pathTranslationService, SeverityModeService severityModeService,\n    AiCodeFixRepository aiCodeFixRepository) {\n    this.client = client;\n    this.configurationRepository = configurationRepository;\n    this.branchTrackingService = branchTrackingService;\n    this.taintSynchronizationService = taintSynchronizationService;\n    this.storageService = storageService;\n    this.pathTranslationService = pathTranslationService;\n    this.severityModeService = severityModeService;\n    this.aiCodeFixRepository = aiCodeFixRepository;\n  }\n\n  public List<TaintVulnerabilityDto> listAll(String configurationScopeId, boolean shouldRefresh, SonarLintCancelMonitor cancelMonitor) {\n    return configurationRepository.getEffectiveBinding(configurationScopeId)\n      .map(binding -> loadTaintVulnerabilities(configurationScopeId, binding, shouldRefresh, cancelMonitor))\n      .orElseGet(Collections::emptyList);\n  }\n\n  @EventListener\n  public void onServerEventReceived(SonarServerEventReceivedEvent eventReceived) {\n    var connectionId = eventReceived.getConnectionId();\n    var serverEvent = eventReceived.getEvent();\n    if (serverEvent instanceof TaintVulnerabilityRaisedEvent raisedEvent) {\n      insertIntoStorageAndNotifyClient(connectionId, raisedEvent);\n    } else if (serverEvent instanceof TaintVulnerabilityClosedEvent closedEvent) {\n      removeFromStorageAndNotifyClient(connectionId, closedEvent);\n    } else if ((serverEvent instanceof IssueChangedEvent changedEvent)) {\n      updateStorageAndNotifyClient(connectionId, changedEvent);\n    }\n  }\n\n  @EventListener\n  public void onServerIssueStatusChanged(ServerIssueStatusChangedEvent event) {\n    var finding = event.getFinding();\n    if (finding instanceof ServerTaintIssue taintVulnerability) {\n      var connectionId = event.getConnectionId();\n      var projectKey = event.getProjectKey();\n      var isMQRMode = severityModeService.isMQRModeForConnection(event.getConnectionId());\n      var isAiCodeFixable = isAiCodeFixable(taintVulnerability, new Binding(connectionId, projectKey));\n      configurationRepository.getBoundScopesToConnectionAndSonarProject(connectionId, projectKey)\n        .forEach(boundScope -> {\n          var newCodeDefinition = storageService.connection(connectionId).project(projectKey).newCodeDefinition().read()\n            .<Predicate<Instant>>map(definition -> definition::isOnNewCode).orElse(date -> true);\n          var pathTranslation = pathTranslationService.getOrComputePathTranslation(boundScope.getConfigScopeId());\n          pathTranslation.ifPresent(translation -> client.didChangeTaintVulnerabilities(\n            new DidChangeTaintVulnerabilitiesParams(boundScope.getConfigScopeId(), emptySet(), emptyList(),\n              List.of(toDto(taintVulnerability, newCodeDefinition, translation, isMQRMode, isAiCodeFixable)))));\n        });\n    }\n  }\n\n  @EventListener\n  public void onTaintVulnerabilitiesSynchronized(TaintVulnerabilitiesSynchronizedEvent event) {\n    var summary = event.getSummary();\n    var connectionId = event.getConnectionId();\n    var sonarProjectKey = event.getSonarProjectKey();\n    var newCodeDefinition = storageService.connection(connectionId).project(sonarProjectKey).newCodeDefinition().read()\n      .<Predicate<Instant>>map(definition -> definition::isOnNewCode).orElse(date -> true);\n    var isMQRMode = severityModeService.isMQRModeForConnection(event.getConnectionId());\n    configurationRepository.getBoundScopesToConnectionAndSonarProject(connectionId, sonarProjectKey).forEach(boundScope -> {\n      var pathTranslation = pathTranslationService.getOrComputePathTranslation(boundScope.getConfigScopeId());\n      pathTranslation\n        .ifPresent(\n          translation -> client.didChangeTaintVulnerabilities(new DidChangeTaintVulnerabilitiesParams(boundScope.getConfigScopeId(), summary.deletedItemIds(),\n            summary.addedItems().stream()\n              .map(taint -> {\n                var isAiCodeFixable = isAiCodeFixable(taint, new Binding(connectionId, sonarProjectKey));\n                return toDto(taint, newCodeDefinition, translation, isMQRMode, isAiCodeFixable);\n              })\n              .toList(),\n            summary.updatedItems().stream()\n              .map(taint -> {\n                var isAiCodeFixable = isAiCodeFixable(taint, new Binding(connectionId, sonarProjectKey));\n                return toDto(taint, newCodeDefinition, translation, isMQRMode, isAiCodeFixable);\n              })\n              .toList())));\n    });\n  }\n\n  private void insertIntoStorageAndNotifyClient(String connectionId, TaintVulnerabilityRaisedEvent event) {\n    var newTaintVulnerability = new ServerTaintIssue(\n      UUID.randomUUID(),\n      event.getKey(),\n      false,\n      null,\n      event.getRuleKey(),\n      event.getMainLocation().getMessage(),\n      event.getMainLocation().getFilePath(),\n      event.getCreationDate(),\n      event.getSeverity(),\n      event.getType(),\n      adapt(event.getMainLocation().getTextRange()),\n      event.getRuleDescriptionContextKey(),\n      event.getCleanCodeAttribute().orElse(null),\n      event.getImpacts(),\n      adapt(event.getFlows()));\n    var projectKey = event.getProjectKey();\n    var binding = new Binding(connectionId, projectKey);\n    storageService.binding(binding).findings().insert(event.getBranchName(), newTaintVulnerability);\n    var isMQRMode = severityModeService.isMQRModeForConnection(connectionId);\n    var isAiCodeFixable = isAiCodeFixable(newTaintVulnerability, binding);\n    configurationRepository.getBoundScopesToConnectionAndSonarProject(connectionId, projectKey).forEach(boundScope -> {\n      var pathTranslation = pathTranslationService.getOrComputePathTranslation(boundScope.getConfigScopeId());\n      pathTranslation.ifPresent(translation -> client.didChangeTaintVulnerabilities(\n        new DidChangeTaintVulnerabilitiesParams(boundScope.getConfigScopeId(), emptySet(),\n          List.of(toDto(newTaintVulnerability, date -> true, translation, isMQRMode, isAiCodeFixable)),\n          emptyList())));\n    });\n  }\n\n  private void removeFromStorageAndNotifyClient(String connectionId, TaintVulnerabilityClosedEvent event) {\n    var projectKey = event.getProjectKey();\n    storageService.connection(connectionId)\n      .project(projectKey)\n      .findings()\n      .deleteTaintIssueBySonarServerKey(event.getTaintIssueKey())\n      .ifPresent(deletedId -> configurationRepository.getBoundScopesToConnectionAndSonarProject(connectionId, projectKey).forEach(boundScope -> client\n        .didChangeTaintVulnerabilities(new DidChangeTaintVulnerabilitiesParams(boundScope.getConfigScopeId(), Set.of(deletedId), emptyList(), emptyList()))));\n  }\n\n  private void updateStorageAndNotifyClient(String connectionId, IssueChangedEvent event) {\n    var projectKey = event.getProjectKey();\n    var updatedTaintVulnerabilities = updateTaintIssues(connectionId, projectKey, event.getUserSeverity(), event.getUserType(), event.getResolved(), event.getImpactedIssues());\n    if (!updatedTaintVulnerabilities.isEmpty()) {\n      configurationRepository.getBoundScopesToConnectionAndSonarProject(connectionId, projectKey).forEach(boundScope -> {\n        var newCodeDefinition = storageService.connection(connectionId).project(projectKey).newCodeDefinition().read()\n          .<Predicate<Instant>>map(definition -> definition::isOnNewCode).orElse(date -> true);\n        var isMQRMode = severityModeService.isMQRModeForConnection(connectionId);\n        pathTranslationService.getOrComputePathTranslation(boundScope.getConfigScopeId())\n          .ifPresent(translation -> client.didChangeTaintVulnerabilities(new DidChangeTaintVulnerabilitiesParams(boundScope.getConfigScopeId(), emptySet(),\n            emptyList(),\n            updatedTaintVulnerabilities.stream()\n              .map(taintIssue -> {\n                var isAiCodeFixable = isAiCodeFixable(taintIssue, boundScope.getBinding());\n                return toDto(taintIssue, newCodeDefinition, translation, isMQRMode, isAiCodeFixable);\n              })\n              .toList())));\n      });\n    }\n  }\n\n  private List<ServerTaintIssue> updateTaintIssues(String connectionId, String projectKey, @Nullable org.sonarsource.sonarlint.core.commons.IssueSeverity userSeverity,\n    @Nullable org.sonarsource.sonarlint.core.commons.RuleType userType, @Nullable Boolean resolved, List<IssueChangedEvent.Issue> issues) {\n    var findingsStorage = storageService.connection(connectionId).project(projectKey).findings();\n    return issues.stream().map(issueEvent -> findingsStorage.updateTaintIssueBySonarServerKey(issueEvent.getIssueKey(), issue -> {\n      if (userSeverity != null) {\n        issue.setSeverity(userSeverity);\n      }\n      if (userType != null) {\n        issue.setType(userType);\n      }\n      if (resolved != null) {\n        issue.setResolved(resolved);\n      }\n      var impacts = issueEvent.getImpacts();\n      if (!impacts.isEmpty()) {\n        issue.setImpacts(mergeImpacts(issue.getImpacts(), impacts));\n      }\n    })).flatMap(Optional::stream).toList();\n  }\n\n  private static Map<org.sonarsource.sonarlint.core.commons.SoftwareQuality, ImpactSeverity> mergeImpacts(\n    Map<org.sonarsource.sonarlint.core.commons.SoftwareQuality, ImpactSeverity> defaultImpacts,\n    Map<org.sonarsource.sonarlint.core.commons.SoftwareQuality, ImpactSeverity> overriddenImpacts) {\n    var mergedImpacts = new EnumMap<org.sonarsource.sonarlint.core.commons.SoftwareQuality, ImpactSeverity>(org.sonarsource.sonarlint.core.commons.SoftwareQuality.class);\n    if (!defaultImpacts.isEmpty()) {\n      mergedImpacts = new EnumMap<>(defaultImpacts);\n    }\n\n    for (var entry : overriddenImpacts.entrySet()) {\n      var quality = org.sonarsource.sonarlint.core.commons.SoftwareQuality.valueOf(entry.getKey().name());\n      var severity = ImpactSeverity.mapSeverity(entry.getValue().name());\n      mergedImpacts.put(quality, severity);\n    }\n\n    return Collections.unmodifiableMap(mergedImpacts);\n  }\n\n  private List<TaintVulnerabilityDto> loadTaintVulnerabilities(String configurationScopeId, Binding binding, boolean shouldRefresh, SonarLintCancelMonitor cancelMonitor) {\n    var matchedBranchOpt = branchTrackingService.awaitEffectiveSonarProjectBranch(configurationScopeId);\n    var pathTranslationOpt = pathTranslationService.getOrComputePathTranslation(configurationScopeId);\n    if (matchedBranchOpt.isPresent() && pathTranslationOpt.isPresent()) {\n      if (shouldRefresh) {\n        taintSynchronizationService.synchronizeTaintVulnerabilities(binding.connectionId(), binding.sonarProjectKey(), cancelMonitor);\n      }\n      var projectStorage = storageService.binding(binding);\n      var newCodeDefinition = projectStorage.newCodeDefinition().read().<Predicate<Instant>>map(definition -> definition::isOnNewCode).orElse(date -> true);\n      var isMQRMode = severityModeService.isMQRModeForConnection(binding.connectionId());\n      var translation = pathTranslationOpt.get();\n      String matchedBranch = matchedBranchOpt.get();\n      return projectStorage.findings().loadTaint(matchedBranch)\n        .stream().map(serverTaintIssue -> {\n          var isAiCodeFixable = isAiCodeFixable(serverTaintIssue, binding);\n          return toDto(serverTaintIssue, newCodeDefinition, translation, isMQRMode, isAiCodeFixable);\n        })\n        .toList();\n    } else {\n      return Collections.emptyList();\n    }\n  }\n\n  public Optional<TaintVulnerabilityDto> getTaintVulnerability(String configurationScopeId, UUID issueId, SonarLintCancelMonitor cancelMonitor) {\n    var maybeBinding = configurationRepository.getEffectiveBinding(configurationScopeId);\n    return maybeBinding.flatMap(binding -> loadTaintVulnerabilities(configurationScopeId, binding, false, cancelMonitor)\n      .stream()\n      .filter(taintVulnerabilityDto -> taintVulnerabilityDto.getId().equals(issueId))\n      .findFirst());\n  }\n\n  private static TaintVulnerabilityDto toDto(ServerTaintIssue serverTaintIssue, Predicate<Instant> isOnNewCode, FilePathTranslation translation, boolean isMQRMode,\n    boolean isAiCodeFixable) {\n    var cleanCodeAttribute = serverTaintIssue.getCleanCodeAttribute().map(attribute -> CleanCodeAttribute.valueOf(attribute.name())).orElse(null);\n    var impacts = serverTaintIssue.getImpacts().entrySet().stream()\n      .map(e -> new ImpactDto(SoftwareQuality.valueOf(e.getKey().name()),\n        org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity.valueOf(e.getValue().name())))\n      .toList();\n    var resolutionStatus = IssueMapper.mapStatus(serverTaintIssue.getResolutionStatus());\n    return new TaintVulnerabilityDto(\n      serverTaintIssue.getId(),\n      serverTaintIssue.getSonarServerKey(),\n      serverTaintIssue.isResolved(),\n      resolutionStatus,\n      serverTaintIssue.getRuleKey(),\n      serverTaintIssue.getMessage(),\n      translation.serverToIdePath(serverTaintIssue.getFilePath()),\n      serverTaintIssue.getCreationDate(),\n      // In practice, it could happen that the Clean Code Attribute is set but the impacts are empty.\n      isMQRMode && !impacts.isEmpty() && cleanCodeAttribute != null\n        ? Either.forRight(new MQRModeDetails(cleanCodeAttribute, impacts))\n        : Either.forLeft(new StandardModeDetails(IssueSeverity.valueOf(serverTaintIssue.getSeverity().name()),\n          RuleType.valueOf(serverTaintIssue.getType().name()))),\n      toDto(serverTaintIssue.getFlows(), translation),\n      TextRangeUtils.adapt(serverTaintIssue.getTextRange()),\n      serverTaintIssue.getRuleDescriptionContextKey(),\n      isOnNewCode.test(serverTaintIssue.getCreationDate()),\n      isAiCodeFixable);\n  }\n\n  private boolean isAiCodeFixable(ServerTaintIssue serverTaintIssue, Binding binding) {\n    return aiCodeFixRepository.get(binding.connectionId())\n      .map(AiCodeFixService::aiCodeFixMapping)\n      .filter(feature -> feature.isFeatureEnabled(binding.sonarProjectKey()))\n      .map(AiCodeFixFeature::new)\n      .map(feature -> feature.isFixable(serverTaintIssue))\n      .orElse(false);\n  }\n\n  public static List<TaintVulnerabilityDto.FlowDto> toDto(List<ServerTaintIssue.Flow> flows, FilePathTranslation translation) {\n    return flows.stream().map(flow -> new TaintVulnerabilityDto.FlowDto(\n      flow.locations().stream()\n        .map(location -> {\n          var filePath = location.filePath();\n          return new TaintVulnerabilityDto.FlowDto.LocationDto(TextRangeUtils.adapt(location.textRange()), location.message(),\n            filePath == null ? null : translation.serverToIdePath(filePath));\n        })\n        .toList()))\n      .toList();\n  }\n\n  private static List<ServerTaintIssue.Flow> adapt(List<TaintVulnerabilityRaisedEvent.Flow> flows) {\n    return flows.stream().map(TaintVulnerabilityTrackingService::adapt).toList();\n  }\n\n  private static ServerTaintIssue.Flow adapt(TaintVulnerabilityRaisedEvent.Flow flow) {\n    return new ServerTaintIssue.Flow(flow.getLocations().stream().map(TaintVulnerabilityTrackingService::adapt).toList());\n  }\n\n  private static ServerTaintIssue.ServerIssueLocation adapt(TaintVulnerabilityRaisedEvent.Location location) {\n    return new ServerTaintIssue.ServerIssueLocation(\n      location.getFilePath(),\n      adapt(location.getTextRange()),\n      location.getMessage());\n  }\n\n  public static TextRangeWithHash adapt(TaintVulnerabilityRaisedEvent.Location.TextRange range) {\n    return new TextRangeWithHash(range.getStartLine(), range.getStartLineOffset(), range.getEndLine(), range.getEndLineOffset(), range.getHash());\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/TextRangeUtils.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking;\n\nimport java.io.IOException;\nimport java.util.regex.Pattern;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.apache.commons.codec.digest.DigestUtils;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.commons.LineWithHash;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TextRangeWithHashDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;\n\nimport static org.apache.commons.lang3.StringUtils.isEmpty;\n\npublic class TextRangeUtils {\n\n  private static final Pattern MATCH_ALL_WHITESPACES = Pattern.compile(\"\\\\s\");\n\n  private TextRangeUtils() {\n    // utils\n  }\n\n  @CheckForNull\n  public static TextRangeWithHash getTextRangeWithHash(@Nullable TextRange textRange, @Nullable ClientInputFile file) {\n    if (textRange == null) return null;\n    String hash = computeTextRangeHash(textRange, file);\n    return new TextRangeWithHash(textRange.getStartLine(), textRange.getStartLineOffset(), textRange.getEndLine(),\n      textRange.getEndLineOffset(), hash);\n  }\n\n  @CheckForNull\n  public static LineWithHash getLineWithHash(@Nullable TextRange textRange, @Nullable ClientInputFile file) {\n    if (textRange == null) return null;\n    String hash = computeLineHash(textRange, file);\n    return new LineWithHash(textRange.getStartLine(), hash);\n  }\n\n  @CheckForNull\n  public static TextRangeDto toTextRangeDto(@Nullable TextRangeWithHash textRange) {\n    if (textRange == null) return null;\n    return new TextRangeDto(textRange.getStartLine(), textRange.getStartLineOffset(), textRange.getEndLine(), textRange.getEndLineOffset());\n  }\n\n  @CheckForNull\n  public static TextRangeDto toTextRangeDto(@Nullable TextRange textRange) {\n    if (textRange == null) return null;\n    return new TextRangeDto(textRange.getStartLine(), textRange.getStartLineOffset(), textRange.getEndLine(),\n      textRange.getEndLineOffset());\n  }\n\n  static String computeTextRangeHash(TextRange textRange, @Nullable ClientInputFile file) {\n    if (file == null) return \"\";\n    var textRangeContent = getTextRangeContent(file, textRange);\n    return hash(textRangeContent);\n  }\n\n  static String computeLineHash(TextRange textRange, @Nullable ClientInputFile file) {\n    if (file == null) return \"\";\n    var textRangeContent = getLineContent(file, textRange);\n    return hash(textRangeContent);\n  }\n\n  private static String getLineContent(ClientInputFile file, TextRange textRange) {\n    var fileContent = getFileContentOrEmptyString(file);\n    if (isEmpty(fileContent)) return \"\";\n    var lines = fileContent.lines().toList();\n    if (lines.size() < textRange.getStartLine()) return \"\";\n    var line = lines.get(textRange.getStartLine() - 1);\n    return hash(line);\n  }\n\n  static String getFileContentOrEmptyString(ClientInputFile file) {\n    try {\n      return file.contents();\n    } catch (IOException e) {\n      return \"\";\n    }\n  }\n\n  public static String getTextRangeContent(@Nullable ClientInputFile file, @Nullable TextRange textRange) {\n    if (file == null || textRange == null) return \"\";\n    var contentLines = getFileContentOrEmptyString(file).lines().toList();\n    var startLine = textRange.getStartLine() - 1;\n    var endLine = textRange.getEndLine() - 1;\n    if (startLine == endLine) {\n      var startLineContent = contentLines.get(startLine);\n      var endLineOffset = Math.min(textRange.getEndLineOffset(), startLineContent.length());\n      return startLineContent.substring(textRange.getStartLineOffset(), endLineOffset);\n    }\n\n    var contentBuilder = new StringBuilder();\n    contentBuilder.append(contentLines.get(startLine).substring(textRange.getStartLineOffset()))\n      .append(System.lineSeparator());\n    for (int i = startLine + 1; i < endLine; i++) {\n      contentBuilder.append(contentLines.get(i)).append(System.lineSeparator());\n    }\n    var endLineContent = endLine < contentLines.size() ? contentLines.get(endLine) : \"\";\n    var endLineOffset = Math.min(textRange.getEndLineOffset(), endLineContent.length());\n    contentBuilder.append(endLineContent, 0, endLineOffset);\n    return contentBuilder.toString();\n  }\n\n  @CheckForNull\n  public static TextRangeWithHashDto adapt(@Nullable TextRangeWithHash textRange) {\n    return textRange == null ? null\n      : new TextRangeWithHashDto(textRange.getStartLine(), textRange.getStartLineOffset(), textRange.getEndLine(), textRange.getEndLineOffset(), textRange.getHash());\n  }\n\n  static String hash(String codeSnippet) {\n    String codeSnippetWithoutWhitespaces = MATCH_ALL_WHITESPACES.matcher(codeSnippet).replaceAll(\"\");\n    return DigestUtils.md5Hex(codeSnippetWithoutWhitespaces);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/TrackedIssue.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking;\n\nimport java.net.URI;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.analysis.api.Flow;\nimport org.sonarsource.sonarlint.core.analysis.api.QuickFix;\nimport org.sonarsource.sonarlint.core.commons.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.LineWithHash;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.HotspotStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ResolutionStatus;\n\npublic class TrackedIssue {\n  private final UUID id;\n  private final String message;\n  private final String ruleKey;\n  private final TextRangeWithHash textRangeWithHash;\n  private final LineWithHash lineWithHash;\n  private final String serverKey;\n  private final Instant introductionDate;\n  private final boolean resolved;\n  private final IssueSeverity severity;\n  private final RuleType type;\n  private final Map<SoftwareQuality, ImpactSeverity> impacts;\n  private final List<Flow> flows;\n  private final List<QuickFix> quickFixes;\n  private final VulnerabilityProbability vulnerabilityProbability;\n  private final HotspotStatus hotspotStatus;\n  private final ResolutionStatus resolutionStatus;\n  private final String ruleDescriptionContextKey;\n  private final CleanCodeAttribute cleanCodeAttribute;\n  private final URI fileUri;\n\n  public TrackedIssue(UUID id, String message, @Nullable Instant introductionDate, boolean resolved, IssueSeverity overriddenSeverity, RuleType type, String ruleKey,\n    @Nullable TextRangeWithHash textRangeWithHash, @Nullable LineWithHash lineWithHash, @Nullable String serverKey, Map<SoftwareQuality, ImpactSeverity> impacts, List<Flow> flows,\n    List<QuickFix> quickFixes, @Nullable VulnerabilityProbability vulnerabilityProbability, @Nullable HotspotStatus hotspotStatus, @Nullable ResolutionStatus resolutionStatus, \n    @Nullable String ruleDescriptionContextKey, CleanCodeAttribute cleanCodeAttribute, @Nullable URI fileUri) {\n    this.id = id;\n    this.message = message;\n    this.ruleKey = ruleKey;\n    this.textRangeWithHash = textRangeWithHash;\n    this.lineWithHash = lineWithHash;\n    this.serverKey = serverKey;\n    this.introductionDate = introductionDate;\n    this.resolved = resolved;\n    this.severity = overriddenSeverity;\n    this.type = type;\n    this.impacts = impacts;\n    this.flows = flows;\n    this.quickFixes = quickFixes;\n    this.vulnerabilityProbability = vulnerabilityProbability;\n    this.hotspotStatus = hotspotStatus;\n    this.resolutionStatus = resolutionStatus;\n    this.ruleDescriptionContextKey = ruleDescriptionContextKey;\n    this.cleanCodeAttribute = cleanCodeAttribute;\n    this.fileUri = fileUri;\n  }\n\n  public UUID getId() {\n    return id;\n  }\n\n  @CheckForNull\n  public String getServerKey() {\n    return serverKey;\n  }\n\n  public Instant getIntroductionDate() {\n    return introductionDate;\n  }\n\n  public boolean isResolved() {\n    return resolved;\n  }\n\n  public IssueSeverity getSeverity() {\n    return severity;\n  }\n\n  public RuleType getType() {\n    return type;\n  }\n\n  public boolean isSecurityHotspot() {\n    return getType() == RuleType.SECURITY_HOTSPOT;\n  }\n\n  public String getRuleKey() {\n    return ruleKey;\n  }\n\n  @CheckForNull\n  public TextRangeWithHash getTextRangeWithHash() {\n    return textRangeWithHash;\n  }\n\n  public String getMessage() {\n    return message;\n  }\n\n  public Map<SoftwareQuality, ImpactSeverity> getImpacts() {\n    return impacts;\n  }\n\n  public List<Flow> getFlows() {\n    return flows;\n  }\n\n  public List<QuickFix> getQuickFixes() {\n    return quickFixes;\n  }\n\n  @CheckForNull\n  public VulnerabilityProbability getVulnerabilityProbability() {\n    return vulnerabilityProbability;\n  }\n\n  @CheckForNull\n  public String getRuleDescriptionContextKey() {\n    return ruleDescriptionContextKey;\n  }\n\n  public CleanCodeAttribute getCleanCodeAttribute() {\n    return cleanCodeAttribute;\n  }\n\n  @CheckForNull\n  public LineWithHash getLineWithHash() {\n    return lineWithHash;\n  }\n\n  @CheckForNull\n  public URI getFileUri() {\n    return fileUri;\n  }\n\n  @CheckForNull\n  public HotspotStatus getHotspotStatus() {\n    return hotspotStatus;\n  }\n\n  public ResolutionStatus getResolutionStatus() {\n    return resolutionStatus;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/TrackingService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking;\n\nimport jakarta.annotation.PostConstruct;\nimport java.net.URI;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.function.Function;\nimport java.util.function.UnaryOperator;\nimport java.util.stream.Collectors;\nimport javax.annotation.CheckForNull;\nimport org.sonarsource.sonarlint.core.analysis.AnalysisFailedEvent;\nimport org.sonarsource.sonarlint.core.analysis.AnalysisFinishedEvent;\nimport org.sonarsource.sonarlint.core.analysis.AnalysisStartedEvent;\nimport org.sonarsource.sonarlint.core.analysis.RawIssueDetectedEvent;\nimport org.sonarsource.sonarlint.core.branch.SonarProjectBranchTrackingService;\nimport org.sonarsource.sonarlint.core.commons.KnownFinding;\nimport org.sonarsource.sonarlint.core.commons.LocalOnlyIssue;\nimport org.sonarsource.sonarlint.core.commons.MultiFileBlameResult;\nimport org.sonarsource.sonarlint.core.commons.NewCodeDefinition;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.util.git.GitService;\nimport org.sonarsource.sonarlint.core.commons.util.git.exceptions.GitException;\nimport org.sonarsource.sonarlint.core.event.MatchingSessionEndedEvent;\nimport org.sonarsource.sonarlint.core.file.PathTranslationService;\nimport org.sonarsource.sonarlint.core.newcode.NewCodeService;\nimport org.sonarsource.sonarlint.core.reporting.FindingReportingService;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.HotspotStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ResolutionStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fs.GetBaseDirParams;\nimport org.sonarsource.sonarlint.core.serverapi.hotspot.ServerHotspot;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.KnownFindingsRepository;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.LocalOnlyIssuesRepository;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerIssue;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.sonarsource.sonarlint.core.sync.FindingsSynchronizationService;\nimport org.sonarsource.sonarlint.core.tracking.matching.IssueMatcher;\nimport org.sonarsource.sonarlint.core.tracking.matching.LocalOnlyIssueMatchingAttributesMapper;\nimport org.sonarsource.sonarlint.core.tracking.matching.MatchingSession;\nimport org.sonarsource.sonarlint.core.tracking.matching.ServerHotspotMatchingAttributesMapper;\nimport org.sonarsource.sonarlint.core.tracking.matching.ServerIssueMatchingAttributesMapper;\nimport org.sonarsource.sonarlint.core.tracking.matching.TrackedIssueFindingMatchingAttributeMapper;\nimport org.springframework.context.ApplicationEventPublisher;\nimport org.springframework.context.event.EventListener;\n\nimport static java.util.Objects.requireNonNull;\nimport static java.util.stream.Collectors.toMap;\n\npublic class TrackingService {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final SonarLintRpcClient client;\n  private final ConfigurationRepository configurationRepository;\n  private final SonarProjectBranchTrackingService branchTrackingService;\n  private final PathTranslationService pathTranslationService;\n  private final FindingReportingService reportingService;\n  private final Map<UUID, MatchingSession> matchingSessionByAnalysisId = new HashMap<>();\n  private final XodusKnownFindingsStorageService knownFindingsStorageService;\n  private final StorageService storageService;\n  private final LocalOnlyIssueRepository localOnlyIssueRepository;\n  private final FindingsSynchronizationService findingsSynchronizationService;\n  private final NewCodeService newCodeService;\n  private final ApplicationEventPublisher eventPublisher;\n  private final KnownFindingsRepository knownFindingsRepository;\n  private final LocalOnlyIssuesRepository localOnlyIssuesRepository;\n  private final GitService gitService;\n\n  public TrackingService(SonarLintRpcClient client, ConfigurationRepository configurationRepository, SonarProjectBranchTrackingService branchTrackingService,\n    PathTranslationService pathTranslationService, FindingReportingService reportingService, XodusKnownFindingsStorageService knownFindingsStorageService,\n    StorageService storageService, LocalOnlyIssueRepository localOnlyIssueRepository, FindingsSynchronizationService findingsSynchronizationService, NewCodeService newCodeService,\n    ApplicationEventPublisher eventPublisher, KnownFindingsRepository knownFindingsRepository, LocalOnlyIssuesRepository localOnlyIssuesRepository) {\n    this.client = client;\n    this.configurationRepository = configurationRepository;\n    this.branchTrackingService = branchTrackingService;\n    this.pathTranslationService = pathTranslationService;\n    this.reportingService = reportingService;\n    this.knownFindingsStorageService = knownFindingsStorageService;\n    this.storageService = storageService;\n    this.localOnlyIssueRepository = localOnlyIssueRepository;\n    this.findingsSynchronizationService = findingsSynchronizationService;\n    this.newCodeService = newCodeService;\n    this.eventPublisher = eventPublisher;\n    this.knownFindingsRepository = knownFindingsRepository;\n    this.localOnlyIssuesRepository = localOnlyIssuesRepository;\n    this.gitService = GitService.create();\n  }\n\n  @PostConstruct\n  public void migrateData() {\n    if (knownFindingsStorageService.exists()) {\n      try {\n        LOG.info(\"Migrating the Xodus known findings to H2\");\n        var migrationStart = System.currentTimeMillis();\n        var xodusKnownFindingsStore = knownFindingsStorageService.get();\n        var findingsPerConfigScope = xodusKnownFindingsStore.loadAll();\n        knownFindingsRepository.storeFindings(findingsPerConfigScope);\n        LOG.info(\"Migrated Xodus known findings to H2, took {}ms\", System.currentTimeMillis() - migrationStart);\n      } catch (Exception e) {\n        LOG.error(\"Unable to migrate known findings, will use fresh DB\", e);\n      }\n    }\n    // always call to remove lingering temporary files\n    knownFindingsStorageService.delete();\n  }\n\n  @EventListener\n  public void onAnalysisStarted(AnalysisStartedEvent event) {\n    var configurationScopeId = event.getConfigurationScopeId();\n    var matchingSession = startMatchingSession(configurationScopeId, event.getFileRelativePaths(), event.getFileUris(), event.getFileContentProvider());\n    matchingSessionByAnalysisId.put(event.getAnalysisId(), matchingSession);\n    reportingService.resetFindingsForFiles(configurationScopeId, event.getFileUris());\n    reportingService.initFilesToAnalyze(event.getAnalysisId(), event.getFileUris());\n  }\n\n  @EventListener\n  public void onIssueDetected(RawIssueDetectedEvent event) {\n    var analysisId = event.analysisId();\n    var matchingSession = matchingSessionByAnalysisId.get(analysisId);\n    if (matchingSession == null) {\n      // an issue was detected outside any analysis, this normally shouldn't happen\n      return;\n    }\n    var detectedIssue = event.detectedIssue();\n    var isSupported = detectedIssue.isInFile();\n    if (isSupported) {\n      // we don't support global issues for now\n      var trackedIssue = matchingSession.matchWithKnownFinding(requireNonNull(detectedIssue.getIdeRelativePath()), detectedIssue);\n      reportingService.streamIssue(event.configurationScopeId(), analysisId, trackedIssue);\n    }\n  }\n\n  @EventListener\n  public void onAnalysisFailed(AnalysisFailedEvent event) {\n    matchingSessionByAnalysisId.remove(event.analysisId());\n  }\n\n  @EventListener\n  public void onAnalysisFinished(AnalysisFinishedEvent event) {\n    var analysisId = event.getAnalysisId();\n    var matchingSession = matchingSessionByAnalysisId.remove(analysisId);\n    if (matchingSession == null) {\n      // a not-started analysis finished, this normally shouldn't happen\n      return;\n    }\n    var configurationScopeId = event.getConfigurationScopeId();\n    if (event.shouldFetchServerIssues()) {\n      findingsSynchronizationService.refreshServerFindings(configurationScopeId, matchingSession.getRelativePathsInvolved());\n    }\n    var result = matchWithServerFindings(configurationScopeId, matchingSession);\n    reportingService.reportTrackedFindings(configurationScopeId, analysisId, result.issuesToReport, result.hotspotsToReport);\n  }\n\n  private MatchingResult matchWithServerFindings(String configurationScopeId, MatchingSession matchingSession) {\n    var effectiveBindingOpt = configurationRepository.getEffectiveBinding(configurationScopeId);\n    var activeBranchOpt = branchTrackingService.awaitEffectiveSonarProjectBranch(configurationScopeId);\n    var translationOpt = pathTranslationService.getOrComputePathTranslation(configurationScopeId);\n    var issuesToReport = matchingSession.getIssuesPerFile();\n    var hotspotsToReport = matchingSession.getSecurityHotspotsPerFile();\n    if (effectiveBindingOpt.isPresent() && activeBranchOpt.isPresent() && translationOpt.isPresent()) {\n      var binding = effectiveBindingOpt.get();\n      var activeBranch = activeBranchOpt.get();\n      var translation = translationOpt.get();\n      issuesToReport = issuesToReport.entrySet().stream().map(e -> {\n        var ideRelativePath = e.getKey();\n        var serverRelativePath = translation.ideToServerPath(ideRelativePath);\n        var serverIssues = storageService.binding(binding).findings().load(activeBranch, serverRelativePath);\n        var localOnlyIssues = localOnlyIssuesRepository.loadForFile(configurationScopeId, serverRelativePath);\n        var matches = matchWithServerIssues(serverRelativePath, serverIssues, localOnlyIssues, e.getValue());\n        return Map.entry(ideRelativePath, matches);\n      }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\n      hotspotsToReport = hotspotsToReport.entrySet().stream().map(e -> {\n        var ideRelativePath = e.getKey();\n        var serverRelativePath = translation.ideToServerPath(ideRelativePath);\n        var serverHotspots = storageService.binding(binding).findings().loadHotspots(activeBranch, serverRelativePath);\n        var matches = matchWithServerHotspots(serverHotspots, e.getValue());\n        return Map.entry(ideRelativePath, matches);\n      }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\n    }\n    issuesToReport.forEach((clientRelativePath, trackedIssues) -> storeTrackedIssues(configurationScopeId, clientRelativePath, trackedIssues));\n    hotspotsToReport.forEach((clientRelativePath, trackedHotspots) -> storeTrackedSecurityHotspots(configurationScopeId, clientRelativePath, trackedHotspots));\n    eventPublisher.publishEvent(new MatchingSessionEndedEvent(matchingSession.countNewIssues(), matchingSession.countRemainingUnmatchedIssues()));\n    return new MatchingResult(issuesToReport, hotspotsToReport);\n  }\n\n  private void storeTrackedIssues(String configurationScopeId, Path clientRelativePath, Collection<TrackedIssue> newKnownIssues) {\n    knownFindingsRepository.storeKnownIssues(configurationScopeId, clientRelativePath,\n      newKnownIssues.stream().map(i -> new KnownFinding(i.getId(), i.getServerKey(), i.getTextRangeWithHash(), i.getLineWithHash(), i.getRuleKey(), i.getMessage(),\n        i.getIntroductionDate())).toList());\n  }\n\n  private void storeTrackedSecurityHotspots(String configurationScopeId, Path clientRelativePath,\n    Collection<TrackedIssue> newKnownSecurityHotspots) {\n    knownFindingsRepository.storeKnownSecurityHotspots(configurationScopeId, clientRelativePath,\n      newKnownSecurityHotspots.stream().map(i -> new KnownFinding(i.getId(), i.getServerKey(), i.getTextRangeWithHash(), i.getLineWithHash(), i.getRuleKey(), i.getMessage(),\n        i.getIntroductionDate())).toList());\n  }\n\n  private List<TrackedIssue> matchWithServerIssues(Path serverRelativePath, List<ServerIssue<?>> serverIssues,\n    List<LocalOnlyIssue> localOnlyIssues, Collection<TrackedIssue> trackedIssues) {\n    var serverIssueMatcher = new IssueMatcher<TrackedIssue, ServerIssue<?>>(new ServerIssueMatchingAttributesMapper(), serverIssues);\n    var serverMatchingResult = serverIssueMatcher.matchWith(new TrackedIssueFindingMatchingAttributeMapper(), trackedIssues);\n    var localIssueMatcher = new IssueMatcher<TrackedIssue, LocalOnlyIssue>(new LocalOnlyIssueMatchingAttributesMapper(), localOnlyIssues);\n    var localMatchingResult = localIssueMatcher.matchWith(new TrackedIssueFindingMatchingAttributeMapper(), trackedIssues);\n    var matches = trackedIssues.stream().map(trackedIssue -> {\n      var matchToServer = serverMatchingResult.getMatch(trackedIssue);\n      if (matchToServer != null) {\n        return updateTrackedIssueWithServerData(trackedIssue, matchToServer);\n      } else {\n        var matchToLocal = localMatchingResult.getMatch(trackedIssue);\n        if (matchToLocal != null) {\n          return updateTrackedIssueWithLocalOnlyIssueData(trackedIssue, matchToLocal);\n        }\n        return trackedIssue;\n      }\n    }).toList();\n    localOnlyIssueRepository.save(serverRelativePath,\n      matches.stream().filter(issue -> issue.getServerKey() == null).map(issue -> newLocalOnlyIssue(serverRelativePath, issue)).toList());\n    return matches;\n  }\n\n  private static List<TrackedIssue> matchWithServerHotspots(Collection<ServerHotspot> serverHotspots, Collection<TrackedIssue> trackedIssues) {\n    var serverIssueMatcher = new IssueMatcher<TrackedIssue, ServerHotspot>(new ServerHotspotMatchingAttributesMapper(), serverHotspots);\n    var serverMatchingResult = serverIssueMatcher.matchWith(new TrackedIssueFindingMatchingAttributeMapper(), trackedIssues);\n    return trackedIssues.stream().map(trackedIssue -> {\n      var matchToServer = serverMatchingResult.getMatch(trackedIssue);\n      if (matchToServer != null) {\n        return updateRawHotspotWithServerData(trackedIssue, matchToServer);\n      } else {\n        return trackedIssue;\n      }\n    }).toList();\n  }\n\n  private static LocalOnlyIssue newLocalOnlyIssue(Path serverRelativePath, TrackedIssue issue) {\n    return new LocalOnlyIssue(issue.getId(), serverRelativePath, issue.getTextRangeWithHash(), issue.getLineWithHash(),\n      issue.getRuleKey(), issue.getMessage(), null);\n  }\n\n  private static TrackedIssue updateTrackedIssueWithServerData(TrackedIssue trackedIssue, ServerIssue<?> serverIssue) {\n    var serverSeverity = serverIssue.getUserSeverity();\n    var severity = serverSeverity != null ? serverSeverity : trackedIssue.getSeverity();\n    var impacts = serverIssue.getImpacts().isEmpty() ? trackedIssue.getImpacts() : serverIssue.getImpacts();\n    var status = IssueMapper.mapStatus(serverIssue.getResolutionStatus());\n    return new TrackedIssue(trackedIssue.getId(), trackedIssue.getMessage(), serverIssue.getCreationDate(),\n      serverIssue.isResolved(), severity, serverIssue.getType(), serverIssue.getRuleKey(), trackedIssue.getTextRangeWithHash(),\n      trackedIssue.getLineWithHash(), serverIssue.getKey(), impacts, trackedIssue.getFlows(),\n      trackedIssue.getQuickFixes(), trackedIssue.getVulnerabilityProbability(), trackedIssue.getHotspotStatus(), status, trackedIssue.getRuleDescriptionContextKey(),\n      trackedIssue.getCleanCodeAttribute(), trackedIssue.getFileUri());\n  }\n\n  private static TrackedIssue updateRawHotspotWithServerData(TrackedIssue trackedHotspot, ServerHotspot serverHotspot) {\n    return new TrackedIssue(trackedHotspot.getId(), trackedHotspot.getMessage(), serverHotspot.getCreationDate(),\n      serverHotspot.getStatus().isResolved(), trackedHotspot.getSeverity(), RuleType.SECURITY_HOTSPOT, trackedHotspot.getRuleKey(),\n      trackedHotspot.getTextRangeWithHash(), trackedHotspot.getLineWithHash(),\n      serverHotspot.getKey(), trackedHotspot.getImpacts(), trackedHotspot.getFlows(), trackedHotspot.getQuickFixes(),\n      serverHotspot.getVulnerabilityProbability(), HotspotStatus.valueOf(serverHotspot.getStatus().name()), null, trackedHotspot.getRuleDescriptionContextKey(),\n      trackedHotspot.getCleanCodeAttribute(), trackedHotspot.getFileUri());\n  }\n\n  private static TrackedIssue updateTrackedIssueWithLocalOnlyIssueData(TrackedIssue trackedIssue, LocalOnlyIssue localOnlyIssue) {\n    var resolution = localOnlyIssue.getResolution();\n    ResolutionStatus status = null;\n    if (resolution != null) {\n      status = IssueMapper.mapStatus(resolution.getStatus());\n    }\n    return new TrackedIssue(trackedIssue.getId(), trackedIssue.getMessage(), trackedIssue.getIntroductionDate(),\n      resolution != null, trackedIssue.getSeverity(), trackedIssue.getType(), trackedIssue.getRuleKey(), trackedIssue.getTextRangeWithHash(),\n      trackedIssue.getLineWithHash(), trackedIssue.getServerKey(), trackedIssue.getImpacts(), trackedIssue.getFlows(),\n      trackedIssue.getQuickFixes(), trackedIssue.getVulnerabilityProbability(), trackedIssue.getHotspotStatus(), status, trackedIssue.getRuleDescriptionContextKey(),\n      trackedIssue.getCleanCodeAttribute(), trackedIssue.getFileUri());\n  }\n\n  private MatchingSession startMatchingSession(String configurationScopeId, Set<Path> fileRelativePaths, Set<URI> fileUris, UnaryOperator<String> fileContentProvider) {\n    var issuesByRelativePath = fileRelativePaths.stream()\n      .collect(toMap(Function.identity(), relativePath -> knownFindingsRepository.loadIssuesForFile(configurationScopeId, relativePath)));\n    var hotspotsByRelativePath = fileRelativePaths.stream()\n      .collect(toMap(Function.identity(), relativePath -> knownFindingsRepository.loadSecurityHotspotsForFile(configurationScopeId, relativePath)));\n    var introductionDateProvider = getIntroductionDateProvider(configurationScopeId, fileRelativePaths, fileUris, fileContentProvider);\n    var previousFindings = new KnownFindings(issuesByRelativePath, hotspotsByRelativePath);\n    return new MatchingSession(previousFindings, introductionDateProvider);\n  }\n\n  private IntroductionDateProvider getIntroductionDateProvider(String configurationScopeId, Set<Path> fileRelativePaths, Set<URI> fileUris,\n    UnaryOperator<String> fileContentProvider) {\n    var baseDir = getBaseDir(configurationScopeId);\n    if (baseDir != null) {\n      try {\n        var newCodeDefinition = newCodeService.getFullNewCodeDefinition(configurationScopeId);\n        var thresholdDate = newCodeDefinition.map(NewCodeDefinition::getThresholdDate).orElse(NewCodeDefinition.withAlwaysNew().getThresholdDate());\n        var blameResult = gitService.getBlameResult(baseDir, fileRelativePaths, fileUris, fileContentProvider, thresholdDate);\n        return (filePath, lineNumbers) -> determineIntroductionDate(filePath, lineNumbers, blameResult);\n      } catch (GitException e) {\n        LOG.info(\"Could not get git blame data for file {} in {}. \", e.getPath(), configurationScopeId);\n      } catch (Exception e) {\n        LOG.error(\"Cannot access blame info for \" + configurationScopeId, e);\n      }\n    }\n    LOG.debug(\"Git blame is not working. Falling back to detection date as the introduction date\");\n    // we keep the detection date as the introduction date\n    return (filePath, lineNumber) -> Instant.now();\n  }\n\n  @CheckForNull\n  private Path getBaseDir(String configurationScopeId) {\n    try {\n      return client.getBaseDir(new GetBaseDirParams(configurationScopeId)).join().getBaseDir();\n    } catch (Exception e) {\n      LOG.error(\"Error when requesting the base dir\", e);\n      return null;\n    }\n  }\n\n  private static Instant determineIntroductionDate(Path path, Collection<Integer> lineNumbers, MultiFileBlameResult multiFileBlameResult) {\n    return multiFileBlameResult.getLatestChangeDateForLinesInFile(path, lineNumbers).orElse(Instant.now());\n  }\n\n  private record MatchingResult(Map<Path, List<TrackedIssue>> issuesToReport,\n    Map<Path, List<TrackedIssue>> hotspotsToReport) {\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/XodusKnownFindingsStorageService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.concurrent.atomic.AtomicReference;\nimport javax.annotation.PreDestroy;\nimport org.apache.commons.io.FileUtils;\nimport org.sonarsource.sonarlint.core.UserPaths;\n\nimport static org.sonarsource.sonarlint.core.commons.storage.XodusPurgeUtils.deleteInFolderWithPattern;\nimport static org.sonarsource.sonarlint.core.tracking.XodusKnownFindingsStore.BACKUP_TAR_GZ;\nimport static org.sonarsource.sonarlint.core.tracking.XodusKnownFindingsStore.KNOWN_FINDINGS_STORE;\n\npublic class XodusKnownFindingsStorageService {\n\n  private final Path projectsStorageBaseDir;\n  private final Path workDir;\n  private final AtomicReference<XodusKnownFindingsStore> trackedIssuesStore = new AtomicReference<>();\n\n  public XodusKnownFindingsStorageService(UserPaths userPaths) {\n    this.projectsStorageBaseDir = userPaths.getStorageRoot();\n    this.workDir = userPaths.getWorkDir();\n  }\n\n  public boolean exists() {\n    return Files.exists(projectsStorageBaseDir.resolve(XodusKnownFindingsStore.BACKUP_TAR_GZ));\n  }\n\n  public synchronized XodusKnownFindingsStore get() {\n    var store = trackedIssuesStore.get();\n    if (store == null) {\n      try {\n        store = new XodusKnownFindingsStore(projectsStorageBaseDir, workDir);\n        trackedIssuesStore.set(store);\n        return store;\n      } catch (IOException e) {\n        throw new IllegalStateException(\"Unable to create tracked issues database\", e);\n      }\n    }\n    return store;\n  }\n\n  @PreDestroy\n  public void close() {\n    var store = trackedIssuesStore.get();\n    if (store != null) {\n      store.close();\n    }\n  }\n\n  public void delete() {\n    var store = trackedIssuesStore.getAndSet(null);\n    if (store != null) {\n      store.close();\n    }\n    FileUtils.deleteQuietly(projectsStorageBaseDir.resolve(BACKUP_TAR_GZ).toFile());\n    deleteInFolderWithPattern(workDir, KNOWN_FINDINGS_STORE + \"*\");\n    deleteInFolderWithPattern(projectsStorageBaseDir, \"known_findings_backup*\");\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/XodusKnownFindingsStore.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.stream.StreamSupport;\nimport jetbrains.exodus.entitystore.Entity;\nimport jetbrains.exodus.entitystore.PersistentEntityStore;\nimport jetbrains.exodus.entitystore.PersistentEntityStores;\nimport jetbrains.exodus.env.EnvironmentConfig;\nimport jetbrains.exodus.env.Environments;\nimport org.apache.commons.io.FileUtils;\nimport org.sonarsource.sonarlint.core.commons.KnownFinding;\nimport org.sonarsource.sonarlint.core.commons.LineWithHash;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.Findings;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.InstantBinding;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.TarGzUtils;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.UuidBinding;\n\nimport static java.util.Objects.requireNonNull;\nimport static java.util.stream.Collectors.flatMapping;\nimport static java.util.stream.Collectors.groupingBy;\nimport static java.util.stream.Collectors.toMap;\n\npublic class XodusKnownFindingsStore {\n\n  static final String KNOWN_FINDINGS_STORE = \"known-findings-store\";\n  private static final String CONFIGURATION_SCOPE_ID_ENTITY_TYPE = \"Scope\";\n  private static final String CONFIGURATION_SCOPE_ID_TO_FILES_LINK_NAME = \"files\";\n  private static final String PATH_PROPERTY_NAME = \"path\";\n  private static final String NAME_PROPERTY_NAME = \"name\";\n  private static final String FILE_TO_ISSUES_LINK_NAME = \"issues\";\n  private static final String FILE_TO_SECURITY_HOTSPOTS_LINK_NAME = \"hotspots\";\n  private static final String UUID_PROPERTY_NAME = \"uuid\";\n  private static final String SERVER_KEY_PROPERTY_NAME = \"serverKey\";\n  private static final String INTRODUCTION_DATE_PROPERTY_NAME = \"introductionDate\";\n  private static final String RULE_KEY_PROPERTY_NAME = \"ruleKey\";\n  private static final String RANGE_HASH_PROPERTY_NAME = \"rangeHash\";\n  private static final String LINE_HASH_PROPERTY_NAME = \"lineHash\";\n  private static final String START_LINE_PROPERTY_NAME = \"startLine\";\n  private static final String START_LINE_OFFSET_PROPERTY_NAME = \"startLineOffset\";\n  private static final String END_LINE_PROPERTY_NAME = \"endLine\";\n  private static final String END_LINE_OFFSET_PROPERTY_NAME = \"endLineOffset\";\n  private static final String MESSAGE_BLOB_NAME = \"message\";\n  static final String BACKUP_TAR_GZ = \"known_findings_backup.tar.gz\";\n  private final PersistentEntityStore entityStore;\n  private final Path xodusDbDir;\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  public XodusKnownFindingsStore(Path backupDir, Path workDir) throws IOException {\n    xodusDbDir = Files.createTempDirectory(workDir, KNOWN_FINDINGS_STORE);\n    var backupFile = backupDir.resolve(BACKUP_TAR_GZ);\n    if (Files.isRegularFile(backupFile)) {\n      LOG.debug(\"Restoring previous known findings database from {}\", backupFile);\n      try {\n        TarGzUtils.extractTarGz(backupFile, xodusDbDir);\n      } catch (Exception e) {\n        LOG.error(\"Unable to restore known findings backup {}\", backupFile);\n      }\n    }\n    LOG.debug(\"Starting known findings database from {}\", xodusDbDir);\n    this.entityStore = buildEntityStore();\n    entityStore.executeInTransaction(txn -> {\n      entityStore.registerCustomPropertyType(txn, Instant.class, new InstantBinding());\n      entityStore.registerCustomPropertyType(txn, UUID.class, new UuidBinding());\n    });\n  }\n\n  public Map<String, Map<Path, Findings>> loadAll() {\n    return entityStore.computeInReadonlyTransaction(txn -> StreamSupport.stream(txn.getAll(CONFIGURATION_SCOPE_ID_ENTITY_TYPE).spliterator(), false)\n      .collect(groupingBy(\n        e -> (String) requireNonNull(e.getProperty(NAME_PROPERTY_NAME)),\n        flatMapping(e -> StreamSupport.stream(e.getLinks(CONFIGURATION_SCOPE_ID_TO_FILES_LINK_NAME).spliterator(), false),\n          toMap(\n            f -> Paths.get((String) requireNonNull(f.getProperty(PATH_PROPERTY_NAME))),\n            f -> new Findings(\n              StreamSupport.stream(f.getLinks(FILE_TO_ISSUES_LINK_NAME).spliterator(), false)\n                .map(XodusKnownFindingsStore::adapt).toList(),\n              StreamSupport.stream(f.getLinks(FILE_TO_SECURITY_HOTSPOTS_LINK_NAME).spliterator(), false)\n                .map(XodusKnownFindingsStore::adapt).toList()),\n            Findings::mergeWith)))));\n  }\n\n  private static KnownFinding adapt(Entity storedFinding) {\n    var uuid = (UUID) requireNonNull(storedFinding.getProperty(UUID_PROPERTY_NAME));\n    var serverKey = (String) storedFinding.getProperty(SERVER_KEY_PROPERTY_NAME);\n    var introductionDate = (Instant) requireNonNull(storedFinding.getProperty(INTRODUCTION_DATE_PROPERTY_NAME));\n    var ruleKey = (String) requireNonNull(storedFinding.getProperty(RULE_KEY_PROPERTY_NAME));\n    var msg = requireNonNull(storedFinding.getBlobString(MESSAGE_BLOB_NAME));\n    var startLine = (Integer) storedFinding.getProperty(START_LINE_PROPERTY_NAME);\n\n    TextRangeWithHash textRange = null;\n    LineWithHash lineWithHash = null;\n    if (startLine != null) {\n      var rangeHash = (String) storedFinding.getProperty(RANGE_HASH_PROPERTY_NAME);\n      if (rangeHash != null) {\n        var startLineOffset = (Integer) storedFinding.getProperty(START_LINE_OFFSET_PROPERTY_NAME);\n        var endLine = (Integer) storedFinding.getProperty(END_LINE_PROPERTY_NAME);\n        var endLineOffset = (Integer) storedFinding.getProperty(END_LINE_OFFSET_PROPERTY_NAME);\n        textRange = new TextRangeWithHash(startLine, startLineOffset, endLine, endLineOffset, rangeHash);\n      }\n      var lineHash = (String) storedFinding.getProperty(LINE_HASH_PROPERTY_NAME);\n      if (lineHash != null) {\n        lineWithHash = new LineWithHash(startLine, lineHash);\n      }\n    }\n    return new KnownFinding(\n      uuid,\n      serverKey,\n      textRange,\n      lineWithHash,\n      ruleKey,\n      msg,\n      introductionDate);\n  }\n\n  private PersistentEntityStore buildEntityStore() {\n    var environment = Environments.newInstance(xodusDbDir.toAbsolutePath().toFile(), new EnvironmentConfig()\n      .setLogAllowRemote(true)\n      .setLogAllowRemovable(true)\n      .setLogAllowRamDisk(true));\n    var entityStoreImpl = PersistentEntityStores.newInstance(environment);\n    entityStoreImpl.setCloseEnvironment(true);\n    return entityStoreImpl;\n  }\n\n  public void close() {\n    entityStore.close();\n    FileUtils.deleteQuietly(xodusDbDir.toFile());\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/matching/IssueMatcher.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking.matching;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport javax.annotation.Nullable;\n\n/**\n * Match a collection of issues.\n *\n * @param <LEFT>  type of the issues that are in the first collection\n * @param <RIGHT> type of the issues that are in the second collection\n */\npublic class IssueMatcher<LEFT, RIGHT> {\n\n  private static final List<MatchingCriterionFactory> MATCHING_CRITERIA = List.of(\n    // 1. match issues with same server issue key\n    ServerIssueMatchingCriterion::new,\n    // 2. match issues with same rule, same line and same text range hash, but not necessarily with same message\n    LineAndTextRangeHashMatchingCriterion::new,\n    // 3. match issues with same rule, same message and same text range hash\n    TextRangeHashAndMessageMatchingCriterion::new,\n    // 4. match issues with same rule, same line and same message\n    LineAndMessageMatchingCriterion::new,\n    // 5. match issues with same rule and same text range hash but different line and different message.\n    // See SONAR-2812\n    TextRangeHashMatchingCriterion::new,\n    // 6. match issues with same rule, same line and same line hash\n    LineAndLineHashMatchingCriterion::new,\n    // 7. match issues with same rule and same line hash\n    LineHashMatchingCriterion::new);\n\n  private final Map<MatchingCriterionFactory, Map<MatchingCriterion, List<RIGHT>>> rightIssuesByCriterion = new HashMap<>();\n  private final MatchingAttributesMapper<RIGHT> rightMapper;\n  private final Collection<RIGHT> rightIssues;\n\n  public IssueMatcher(MatchingAttributesMapper<RIGHT> rightMapper, Collection<RIGHT> rightIssues) {\n    this.rightMapper = rightMapper;\n    this.rightIssues = new ArrayList<>(rightIssues);\n    for (var matchingCriterion : MATCHING_CRITERIA) {\n      var issuesByCriterion = new HashMap<MatchingCriterion, List<RIGHT>>();\n      for (RIGHT right : rightIssues) {\n        var criterionAppliedToIssue = matchingCriterion.build(right, rightMapper);\n        issuesByCriterion.computeIfAbsent(criterionAppliedToIssue, k -> new ArrayList<>()).add(right);\n      }\n\n      rightIssuesByCriterion.put(matchingCriterion, issuesByCriterion);\n    }\n  }\n\n  public MatchingResult<LEFT, RIGHT> matchWith(MatchingAttributesMapper<LEFT> leftMapper, Collection<LEFT> leftIssues) {\n    var result = new MatchingResult<LEFT, RIGHT>(leftIssues);\n\n    for (var matchingCriterion : MATCHING_CRITERIA) {\n      if (result.isComplete()) {\n        break;\n      }\n      matchWithCriterion(result, leftMapper, matchingCriterion);\n    }\n\n    return result;\n  }\n\n  private void matchWithCriterion(MatchingResult<LEFT, RIGHT> result, MatchingAttributesMapper<LEFT> leftMapper, MatchingCriterionFactory criterionFactory) {\n    for (LEFT left : result.getUnmatchedLefts()) {\n      var leftKey = criterionFactory.build(left, leftMapper);\n      var rightCandidates = rightIssuesByCriterion.get(criterionFactory).get(leftKey);\n      if (rightCandidates != null && !rightCandidates.isEmpty()) {\n        // TODO taking the first one. Could be improved if there are more than 2 issues on the same line.\n        // Message could be checked to take the best one.\n        var match = rightCandidates.iterator().next();\n        result.recordMatch(left, match);\n        removeRight(match);\n      }\n    }\n  }\n\n  private void removeRight(RIGHT right) {\n    rightIssues.remove(right);\n    MATCHING_CRITERIA.forEach(criterion -> {\n      var rights = rightIssuesByCriterion.get(criterion).get(criterion.build(right, rightMapper));\n      if (rights != null) {\n        rights.remove(right);\n      }\n    });\n  }\n\n  public int getUnmatchedIssuesCount() {\n    return rightIssues.size();\n  }\n\n  private interface MatchingCriterion {\n  }\n\n  private interface MatchingCriterionFactory {\n    <G> MatchingCriterion build(G issue, MatchingAttributesMapper<G> mapper);\n  }\n\n  private static class LineAndTextRangeHashMatchingCriterion implements MatchingCriterion {\n    private final String ruleKey;\n    @Nullable\n    private final String textRangeHash;\n    @Nullable\n    private final Integer line;\n\n    <G> LineAndTextRangeHashMatchingCriterion(G issue, MatchingAttributesMapper<G> mapper) {\n      this.ruleKey = mapper.getRuleKey(issue);\n      this.line = mapper.getLine(issue).orElse(null);\n      this.textRangeHash = mapper.getTextRangeHash(issue).orElse(null);\n    }\n\n    // note: the design of the enclosing caller ensures that 'o' is of the correct class and not null\n    @Override\n    public boolean equals(Object o) {\n      var that = (LineAndTextRangeHashMatchingCriterion) o;\n      // start with most discriminant field\n      return Objects.equals(line, that.line)\n        && Objects.equals(textRangeHash, that.textRangeHash)\n        && ruleKey.equals(that.ruleKey);\n    }\n\n    @Override\n    public int hashCode() {\n      var result = ruleKey.hashCode();\n      result = 31 * result + (textRangeHash != null ? textRangeHash.hashCode() : 0);\n      result = 31 * result + (line != null ? line.hashCode() : 0);\n      return result;\n    }\n  }\n\n  private static class LineAndLineHashMatchingCriterion implements MatchingCriterion {\n    private final String ruleKey;\n    @Nullable\n    private final Integer line;\n    private final String lineHash;\n\n    <G> LineAndLineHashMatchingCriterion(G issue, MatchingAttributesMapper<G> mapper) {\n      this.ruleKey = mapper.getRuleKey(issue);\n      this.line = mapper.getLine(issue).orElse(null);\n      this.lineHash = mapper.getLineHash(issue).orElse(\"\");\n    }\n\n    // note: the design of the enclosing caller ensures that 'o' is of the correct class and not null\n    @Override\n    public boolean equals(Object o) {\n      var that = (LineAndLineHashMatchingCriterion) o;\n      // start with most discriminant field\n      return Objects.equals(line, that.line)\n        && Objects.equals(lineHash, that.lineHash)\n        && ruleKey.equals(that.ruleKey);\n    }\n\n    @Override\n    public int hashCode() {\n      var result = ruleKey.hashCode();\n      result = 31 * result + (lineHash != null ? lineHash.hashCode() : 0);\n      result = 31 * result + (line != null ? line.hashCode() : 0);\n      return result;\n    }\n  }\n\n  private static class LineHashMatchingCriterion implements MatchingCriterion {\n    private final String ruleKey;\n    private final String lineHash;\n\n    <G> LineHashMatchingCriterion(G issue, MatchingAttributesMapper<G> mapper) {\n      this.ruleKey = mapper.getRuleKey(issue);\n      this.lineHash = mapper.getLineHash(issue).orElse(\"\");\n    }\n\n    // note: the design of the enclosing caller ensures that 'o' is of the correct class and not null\n    @Override\n    public boolean equals(Object o) {\n      var that = (LineHashMatchingCriterion) o;\n      // start with most discriminant field\n      return Objects.equals(lineHash, that.lineHash)\n        && ruleKey.equals(that.ruleKey);\n    }\n\n    @Override\n    public int hashCode() {\n      var result = ruleKey.hashCode();\n      result = 31 * result + (lineHash != null ? lineHash.hashCode() : 0);\n      return result;\n    }\n  }\n\n  private static class TextRangeHashAndMessageMatchingCriterion implements MatchingCriterion {\n    private final String ruleKey;\n    private final String message;\n    private final String textRangeHash;\n\n    <G> TextRangeHashAndMessageMatchingCriterion(G issue, MatchingAttributesMapper<G> mapper) {\n      this.ruleKey = mapper.getRuleKey(issue);\n      this.message = mapper.getMessage(issue);\n      this.textRangeHash = mapper.getTextRangeHash(issue).orElse(null);\n    }\n\n    // note: the design of the enclosing caller ensures that 'o' is of the correct class and not null\n    @Override\n    public boolean equals(Object o) {\n      var that = (TextRangeHashAndMessageMatchingCriterion) o;\n      // start with most discriminant field\n      return Objects.equals(textRangeHash, that.textRangeHash)\n        && message.equals(that.message)\n        && ruleKey.equals(that.ruleKey);\n    }\n\n    @Override\n    public int hashCode() {\n      var result = ruleKey.hashCode();\n      result = 31 * result + message.hashCode();\n      result = 31 * result + (textRangeHash != null ? textRangeHash.hashCode() : 0);\n      return result;\n    }\n  }\n\n  private static class LineAndMessageMatchingCriterion implements MatchingCriterion {\n    private final String ruleKey;\n    private final String message;\n    @Nullable\n    private final Integer line;\n\n    <G> LineAndMessageMatchingCriterion(G issue, MatchingAttributesMapper<G> mapper) {\n      this.ruleKey = mapper.getRuleKey(issue);\n      this.message = mapper.getMessage(issue);\n      this.line = mapper.getLine(issue).orElse(null);\n    }\n\n    // note: the design of the enclosing caller ensures that 'o' is of the correct class and not null\n    @Override\n    public boolean equals(Object o) {\n      var that = (LineAndMessageMatchingCriterion) o;\n      // start with most discriminant field\n      return Objects.equals(line, that.line)\n        && message.equals(that.message)\n        && ruleKey.equals(that.ruleKey);\n    }\n\n    @Override\n    public int hashCode() {\n      var result = ruleKey.hashCode();\n      result = 31 * result + message.hashCode();\n      result = 31 * result + (line != null ? line.hashCode() : 0);\n      return result;\n    }\n  }\n\n  private static class TextRangeHashMatchingCriterion implements MatchingCriterion {\n    private final String ruleKey;\n    private final String textRangeHash;\n\n    <G> TextRangeHashMatchingCriterion(G issue, MatchingAttributesMapper<G> mapper) {\n      this.ruleKey = mapper.getRuleKey(issue);\n      this.textRangeHash = mapper.getTextRangeHash(issue).orElse(\"\");\n    }\n\n    // note: the design of the enclosing caller ensures that 'o' is of the correct class and not null\n    @Override\n    public boolean equals(Object o) {\n      var that = (TextRangeHashMatchingCriterion) o;\n      // start with most discriminant field\n      return Objects.equals(textRangeHash, that.textRangeHash)\n        && ruleKey.equals(that.ruleKey);\n    }\n\n    @Override\n    public int hashCode() {\n      var result = ruleKey.hashCode();\n      result = 31 * result + (textRangeHash != null ? textRangeHash.hashCode() : 0);\n      return result;\n    }\n  }\n\n  private static class ServerIssueMatchingCriterion implements MatchingCriterion {\n    @Nullable\n    private final String serverIssueKey;\n\n    <G> ServerIssueMatchingCriterion(G issue, MatchingAttributesMapper<G> mapper) {\n      serverIssueKey = mapper.getServerIssueKey(issue).orElse(null);\n    }\n\n    // note: the design of the enclosing caller ensures that 'o' is of the correct class and not null\n    @Override\n    public boolean equals(Object o) {\n      var that = (ServerIssueMatchingCriterion) o;\n      return that != null && !isBlank(serverIssueKey) && !isBlank(that.serverIssueKey) && serverIssueKey.equals(that.serverIssueKey);\n    }\n\n    private static boolean isBlank(@Nullable String s) {\n      return s == null || s.isEmpty();\n    }\n\n    @Override\n    public int hashCode() {\n      return serverIssueKey != null ? serverIssueKey.hashCode() : 0;\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/matching/KnownIssueMatchingAttributesMapper.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking.matching;\n\nimport java.util.Optional;\nimport org.sonarsource.sonarlint.core.commons.KnownFinding;\nimport org.sonarsource.sonarlint.core.commons.LineWithHash;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\n\npublic class KnownIssueMatchingAttributesMapper implements MatchingAttributesMapper<KnownFinding> {\n\n  @Override\n  public String getRuleKey(KnownFinding issue) {\n    return issue.getRuleKey();\n  }\n\n  @Override\n  public Optional<Integer> getLine(KnownFinding issue) {\n    return Optional.ofNullable(issue.getLineWithHash()).map(LineWithHash::getNumber);\n  }\n\n  @Override\n  public Optional<String> getTextRangeHash(KnownFinding issue) {\n    return Optional.ofNullable(issue.getTextRangeWithHash()).map(TextRangeWithHash::getHash);\n  }\n\n  @Override\n  public Optional<String> getLineHash(KnownFinding issue) {\n    return Optional.ofNullable(issue.getLineWithHash()).map(LineWithHash::getHash);\n  }\n\n  @Override\n  public String getMessage(KnownFinding issue) {\n    return issue.getMessage();\n  }\n\n  @Override\n  public Optional<String> getServerIssueKey(KnownFinding issue) {\n    return Optional.empty();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/matching/LocalOnlyIssueMatchingAttributesMapper.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking.matching;\n\nimport java.util.Optional;\nimport org.sonarsource.sonarlint.core.commons.LineWithHash;\nimport org.sonarsource.sonarlint.core.commons.LocalOnlyIssue;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\n\npublic class LocalOnlyIssueMatchingAttributesMapper implements MatchingAttributesMapper<LocalOnlyIssue> {\n\n  @Override\n  public String getRuleKey(LocalOnlyIssue issue) {\n    return issue.getRuleKey();\n  }\n\n  @Override\n  public Optional<Integer> getLine(LocalOnlyIssue issue) {\n    return Optional.ofNullable(issue.getLineWithHash()).map(LineWithHash::getNumber);\n  }\n\n  @Override\n  public Optional<String> getTextRangeHash(LocalOnlyIssue issue) {\n    return Optional.ofNullable(issue.getTextRangeWithHash()).map(TextRangeWithHash::getHash);\n  }\n\n  @Override\n  public Optional<String> getLineHash(LocalOnlyIssue issue) {\n    return Optional.ofNullable(issue.getLineWithHash()).map(LineWithHash::getHash);\n  }\n\n  @Override\n  public String getMessage(LocalOnlyIssue issue) {\n    return issue.getMessage();\n  }\n\n  @Override\n  public Optional<String> getServerIssueKey(LocalOnlyIssue issue) {\n    return Optional.empty();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/matching/MatchingAttributesMapper.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking.matching;\n\nimport java.util.Optional;\n\npublic interface MatchingAttributesMapper<G> {\n\n\n  String getRuleKey(G issue);\n\n  Optional<Integer> getLine(G issue);\n\n  Optional<String> getTextRangeHash(G issue);\n\n  Optional<String> getLineHash(G issue);\n\n  String getMessage(G issue);\n\n  Optional<String> getServerIssueKey(G issue);\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/matching/MatchingResult.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking.matching;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.IdentityHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport javax.annotation.CheckForNull;\n\n/**\n * Store the result of matching of issues.\n *\n * @param <LEFT>  type of the issues that are in the first collection\n * @param <RIGHT> type of the issues that are in the second collection\n */\npublic class MatchingResult<LEFT, RIGHT> {\n\n  /**\n   * Matched issues -> a left issue is associated to a right issue\n   */\n  private final IdentityHashMap<LEFT, RIGHT> leftToRight = new IdentityHashMap<>();\n\n  private final Collection<LEFT> lefts;\n\n  public MatchingResult(Collection<LEFT> leftIssues) {\n    this.lefts = leftIssues;\n  }\n\n  /**\n   * Returns an Iterable to be traversed when matching issues. That means\n   * that the traversal does not fail if method {@link #recordMatch(LEFT, RIGHT)}\n   * is called.\n   */\n  public Iterable<LEFT> getUnmatchedLefts() {\n    List<LEFT> result = new ArrayList<>();\n    for (LEFT left : lefts) {\n      if (!leftToRight.containsKey(left)) {\n        result.add(left);\n      }\n    }\n    return result;\n  }\n\n  public Map<LEFT, RIGHT> getMatchedLefts() {\n    return leftToRight;\n  }\n\n  void recordMatch(LEFT left, RIGHT right) {\n    leftToRight.put(left, right);\n  }\n\n  boolean isComplete() {\n    return leftToRight.size() == lefts.size();\n  }\n\n  @CheckForNull\n  public RIGHT getMatch(LEFT left) {\n    return leftToRight.get(left);\n  }\n\n  public Optional<RIGHT> getMatchOpt(LEFT left) {\n    return Optional.ofNullable(getMatch(left));\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/matching/MatchingSession.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking.matching;\n\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.analysis.RawIssue;\nimport org.sonarsource.sonarlint.core.commons.KnownFinding;\nimport org.sonarsource.sonarlint.core.tracking.IntroductionDateProvider;\nimport org.sonarsource.sonarlint.core.tracking.IssueMapper;\nimport org.sonarsource.sonarlint.core.tracking.KnownFindings;\nimport org.sonarsource.sonarlint.core.tracking.TextRangeUtils;\nimport org.sonarsource.sonarlint.core.tracking.TrackedIssue;\n\npublic class MatchingSession {\n  private final Map<Path, IssueMatcher<RawIssue, KnownFinding>> issueMatchersByFile = new HashMap<>();\n  private final Map<Path, IssueMatcher<RawIssue, KnownFinding>> hotspotMatchersByFile = new HashMap<>();\n\n  private final IntroductionDateProvider introductionDateProvider;\n  private final ConcurrentHashMap<Path, List<TrackedIssue>> issuesPerFile = new ConcurrentHashMap<>();\n  private final ConcurrentHashMap<Path, List<TrackedIssue>> securityHotspotsPerFile = new ConcurrentHashMap<>();\n  private final Set<Path> relativePathsInvolved = new HashSet<>();\n  private long newIssuesFound = 0;\n\n  public MatchingSession(KnownFindings previousFindings, IntroductionDateProvider introductionDateProvider) {\n    var knownIssuesPerFile = previousFindings.getIssuesPerFile().entrySet().stream().map(entry -> Map.entry(entry.getKey(), new ArrayList<>(entry.getValue())))\n      .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\n    var knownSecurityHotspotsPerFile = previousFindings.getSecurityHotspotsPerFile().entrySet().stream()\n      .map(entry -> Map.entry(entry.getKey(), new ArrayList<>(entry.getValue()))).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\n    knownIssuesPerFile.forEach((path, issues) -> issueMatchersByFile.put(path, new IssueMatcher<>(new KnownIssueMatchingAttributesMapper(), issues)));\n    knownSecurityHotspotsPerFile.forEach((path, hotspots) -> hotspotMatchersByFile.put(path, new IssueMatcher<>(new KnownIssueMatchingAttributesMapper(), hotspots)));\n    this.introductionDateProvider = introductionDateProvider;\n  }\n\n  public TrackedIssue matchWithKnownFinding(Path relativePath, RawIssue rawIssue) {\n    if (rawIssue.isSecurityHotspot()) {\n      return matchWithKnownSecurityHotspot(relativePath, rawIssue);\n    } else {\n      return matchWithKnownIssue(relativePath, rawIssue);\n    }\n  }\n\n  public TrackedIssue matchWithKnownSecurityHotspot(Path relativePath, RawIssue newSecurityHotspot) {\n    var hotspotMatcher = hotspotMatchersByFile.get(relativePath);\n    if (hotspotMatcher == null) {\n      throw new IllegalStateException(\"No hotspot matcher found for \" + relativePath);\n    }\n    var trackedSecurityHotspot = matchWithKnownFinding(relativePath, hotspotMatcher, newSecurityHotspot);\n    securityHotspotsPerFile.computeIfAbsent(relativePath, f -> new ArrayList<>()).add(trackedSecurityHotspot);\n    relativePathsInvolved.add(relativePath);\n    return trackedSecurityHotspot;\n  }\n\n  private TrackedIssue matchWithKnownIssue(Path relativePath, RawIssue rawIssue) {\n    var issueMatcher = issueMatchersByFile.get(relativePath);\n    if (issueMatcher == null) {\n      throw new IllegalStateException(\"No issue matcher found for \" + relativePath);\n    }\n\n    var trackedIssue = matchWithKnownFinding(relativePath, issueMatcher, rawIssue);\n    issuesPerFile.computeIfAbsent(relativePath, f -> new ArrayList<>()).add(trackedIssue);\n    relativePathsInvolved.add(relativePath);\n    return trackedIssue;\n  }\n\n  private TrackedIssue matchWithKnownFinding(Path relativePath, IssueMatcher<RawIssue, KnownFinding> issueMatcher, RawIssue newFinding) {\n    var localMatchingResult = issueMatcher.matchWith(new RawIssueFindingMatchingAttributeMapper(), List.of(newFinding));\n    return localMatchingResult.getMatchOpt(newFinding)\n      .map(knownFinding -> updateKnownFindingWithRawIssueData(knownFinding, newFinding))\n      .orElseGet(() -> newlyKnownIssue(relativePath, newFinding));\n  }\n\n  public static TrackedIssue updateKnownFindingWithRawIssueData(KnownFinding knownIssue, RawIssue rawIssue) {\n    return new TrackedIssue(knownIssue.getId(), rawIssue.getMessage(), knownIssue.getIntroductionDate(),\n      false, rawIssue.getSeverity(), rawIssue.getRuleType(), rawIssue.getRuleKey(),\n      TextRangeUtils.getTextRangeWithHash(rawIssue.getTextRange(), rawIssue.getClientInputFile()),\n      TextRangeUtils.getLineWithHash(rawIssue.getTextRange(), rawIssue.getClientInputFile()), knownIssue.getServerKey(), rawIssue.getImpacts(), rawIssue.getFlows(),\n      rawIssue.getQuickFixes(), rawIssue.getVulnerabilityProbability(), null, null, rawIssue.getRuleDescriptionContextKey(),\n      rawIssue.getCleanCodeAttribute(), rawIssue.getFileUri());\n  }\n\n  private TrackedIssue newlyKnownIssue(Path relativePath, RawIssue rawFinding) {\n    newIssuesFound++;\n    var introductionDate = introductionDateProvider.determineIntroductionDate(relativePath, rawFinding.getLineNumbers());\n    return IssueMapper.toTrackedIssue(rawFinding, introductionDate);\n  }\n\n  public Map<Path, List<TrackedIssue>> getIssuesPerFile() {\n    return issuesPerFile;\n  }\n\n  public Map<Path, List<TrackedIssue>> getSecurityHotspotsPerFile() {\n    return securityHotspotsPerFile;\n  }\n\n  public Set<Path> getRelativePathsInvolved() {\n    return relativePathsInvolved;\n  }\n\n  public long countNewIssues() {\n    return newIssuesFound;\n  }\n\n  public long countRemainingUnmatchedIssues() {\n    return issueMatchersByFile.values().stream().mapToLong(IssueMatcher::getUnmatchedIssuesCount).sum();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/matching/RawIssueFindingMatchingAttributeMapper.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking.matching;\n\nimport java.util.Optional;\nimport org.sonarsource.sonarlint.core.analysis.RawIssue;\n\npublic class RawIssueFindingMatchingAttributeMapper implements MatchingAttributesMapper<RawIssue> {\n\n  @Override\n  public String getRuleKey(RawIssue issue) {\n    return issue.getRuleKey();\n  }\n\n  @Override\n  public Optional<Integer> getLine(RawIssue issue) {\n    return issue.getLine();\n  }\n\n  @Override\n  public Optional<String> getTextRangeHash(RawIssue issue) {\n    return issue.getTextRangeHash();\n  }\n\n  @Override\n  public Optional<String> getLineHash(RawIssue issue) {\n    return issue.getLineHash();\n  }\n\n  @Override\n  public String getMessage(RawIssue issue) {\n    return issue.getMessage();\n  }\n\n  @Override\n  public Optional<String> getServerIssueKey(RawIssue issue) {\n    return Optional.empty();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/matching/ServerHotspotMatchingAttributesMapper.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking.matching;\n\nimport java.util.Optional;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.serverapi.hotspot.ServerHotspot;\n\npublic class ServerHotspotMatchingAttributesMapper implements MatchingAttributesMapper<ServerHotspot> {\n\n  @Override\n  public String getRuleKey(ServerHotspot issue) {\n    return issue.getRuleKey();\n  }\n\n  @Override\n  public Optional<Integer> getLine(ServerHotspot issue) {\n    return Optional.of(issue.getTextRange().getStartLine());\n  }\n\n  @Override\n  public Optional<String> getTextRangeHash(ServerHotspot issue) {\n    var textRange = issue.getTextRange();\n    if (textRange instanceof TextRangeWithHash textRangeWithHash) {\n      return Optional.of(textRangeWithHash.getHash());\n    }\n    return Optional.empty();\n  }\n\n  @Override\n  public Optional<String> getLineHash(ServerHotspot issue) {\n    // no line hash for hotspots\n    return Optional.empty();\n  }\n\n  @Override\n  public String getMessage(ServerHotspot issue) {\n    return issue.getMessage();\n  }\n\n  @Override\n  public Optional<String> getServerIssueKey(ServerHotspot issue) {\n    return Optional.of(issue.getKey());\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/matching/ServerIssueMatchingAttributesMapper.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking.matching;\n\nimport java.util.Optional;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.LineLevelServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.RangeLevelServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerIssue;\n\npublic class ServerIssueMatchingAttributesMapper implements MatchingAttributesMapper<ServerIssue<?>> {\n\n  @Override\n  public String getRuleKey(ServerIssue<?> issue) {\n    return issue.getRuleKey();\n  }\n\n  @Override\n  public Optional<Integer> getLine(ServerIssue<?> issue) {\n    if (issue instanceof LineLevelServerIssue lineLevelServerIssue) {\n      return Optional.of(lineLevelServerIssue.getLine());\n    }\n    if (issue instanceof RangeLevelServerIssue rangeLevelServerIssue) {\n      return Optional.of(rangeLevelServerIssue.getTextRange().getStartLine());\n    }\n    return Optional.empty();\n  }\n\n  @Override\n  public Optional<String> getTextRangeHash(ServerIssue<?> issue) {\n    if (issue instanceof RangeLevelServerIssue rangeLevelServerIssue) {\n      return Optional.of(rangeLevelServerIssue.getTextRange().getHash());\n    }\n    return Optional.empty();\n  }\n\n  @Override\n  public Optional<String> getLineHash(ServerIssue<?> issue) {\n    if (issue instanceof LineLevelServerIssue lineLevelServerIssue) {\n      return Optional.of(lineLevelServerIssue.getLineHash());\n    }\n    return Optional.empty();\n  }\n\n  @Override\n  public String getMessage(ServerIssue<?> issue) {\n    return issue.getMessage();\n  }\n\n  @Override\n  public Optional<String> getServerIssueKey(ServerIssue<?> issue) {\n    return Optional.of(issue.getKey());\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/matching/TrackedIssueFindingMatchingAttributeMapper.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking.matching;\n\nimport java.util.Optional;\nimport org.sonarsource.sonarlint.core.tracking.TrackedIssue;\n\npublic class TrackedIssueFindingMatchingAttributeMapper implements MatchingAttributesMapper<TrackedIssue> {\n\n  @Override\n  public String getRuleKey(TrackedIssue issue) {\n    return issue.getRuleKey();\n  }\n\n  @Override\n  public Optional<Integer> getLine(TrackedIssue issue) {\n    var textRange = issue.getTextRangeWithHash();\n    if (textRange == null) {\n      return Optional.empty();\n    }\n    return Optional.of(textRange.getStartLine());\n  }\n\n  @Override\n  public Optional<String> getTextRangeHash(TrackedIssue issue) {\n    var textRange = issue.getTextRangeWithHash();\n    if (textRange == null) {\n      return Optional.empty();\n    }\n    return Optional.of(textRange.getHash());\n  }\n\n  @Override\n  public Optional<String> getLineHash(TrackedIssue issue) {\n    var lineWithHash = issue.getLineWithHash();\n    if (lineWithHash != null) {\n      return Optional.of(lineWithHash.getHash());\n    }\n    return Optional.empty();\n  }\n\n  @Override\n  public String getMessage(TrackedIssue issue) {\n    return issue.getMessage();\n  }\n\n  @Override\n  public Optional<String> getServerIssueKey(TrackedIssue issue) {\n    return Optional.ofNullable(issue.getServerKey());\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/matching/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.tracking.matching;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.tracking;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/streaming/Alarm.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking.streaming;\n\nimport java.time.Duration;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.ScheduledFuture;\nimport java.util.concurrent.TimeUnit;\nimport org.sonarsource.sonarlint.core.commons.util.FailSafeExecutors;\n\npublic class Alarm {\n  private final Duration duration;\n  private final Runnable endRunnable;\n  private final ScheduledExecutorService executorService;\n  private ScheduledFuture<?> scheduledFuture;\n\n  public Alarm(String name, Duration duration, Runnable endRunnable) {\n    this.duration = duration;\n    this.endRunnable = endRunnable;\n    this.executorService = FailSafeExecutors.newSingleThreadScheduledExecutor(name);\n  }\n\n  public void schedule() {\n    // if already scheduled, don't re-schedule\n    if (scheduledFuture == null) {\n      scheduledFuture = executorService.schedule(this::notifyEnd, duration.toMillis(), TimeUnit.MILLISECONDS);\n    }\n  }\n\n  public void reset() {\n    cancelRunning();\n    schedule();\n  }\n\n  private void notifyEnd() {\n    if (!executorService.isShutdown()) {\n      scheduledFuture = null;\n      endRunnable.run();\n    }\n  }\n\n  public void shutdownNow() {\n    cancelRunning();\n    executorService.shutdownNow();\n  }\n\n  private void cancelRunning() {\n    if (scheduledFuture != null) {\n      scheduledFuture.cancel(false);\n    }\n    scheduledFuture = null;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/streaming/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.tracking.streaming;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/websocket/History.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.websocket;\n\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\npublic class History {\n  private final Map<String, Instant> receivedMessages = new ConcurrentHashMap<>();\n  public void recordMessage(String message) {\n    receivedMessages.put(message, Instant.now());\n  }\n\n  public boolean exists(String message) {\n    return receivedMessages.containsKey(message);\n  }\n\n  public void forgetOlderThan(Duration expiryDuration) {\n    var now = Instant.now();\n    receivedMessages.values().removeIf(messageDate -> messageDate.isBefore(now.minus(expiryDuration)));\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/websocket/SonarCloudWebSocket.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.websocket;\n\nimport com.google.common.util.concurrent.MoreExecutors;\nimport com.google.gson.Gson;\nimport com.google.gson.JsonObject;\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.http.WebSocket;\nimport java.nio.channels.UnresolvedAddressException;\nimport java.time.Duration;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.util.FailSafeExecutors;\nimport org.sonarsource.sonarlint.core.http.WebSocketClient;\nimport org.sonarsource.sonarlint.core.serverapi.push.SonarServerEvent;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.EventParser;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.IssueChangedEventParser;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.SecurityHotspotChangedEventParser;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.SecurityHotspotClosedEventParser;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.SecurityHotspotRaisedEventParser;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.TaintVulnerabilityClosedEventParser;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.TaintVulnerabilityRaisedEventParser;\nimport org.sonarsource.sonarlint.core.websocket.parsing.SmartNotificationEventParser;\n\npublic class SonarCloudWebSocket {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private static final Map<String, EventParser<?>> parsersByTypeForProjectFilter = Map.of(\n    \"QualityGateChanged\", new SmartNotificationEventParser(\"QUALITY_GATE\"),\n    \"IssueChanged\", new IssueChangedEventParser(),\n    \"SecurityHotspotClosed\", new SecurityHotspotClosedEventParser(),\n    \"SecurityHotspotRaised\", new SecurityHotspotRaisedEventParser(),\n    \"SecurityHotspotChanged\", new SecurityHotspotChangedEventParser(),\n    \"TaintVulnerabilityClosed\", new TaintVulnerabilityClosedEventParser(),\n    \"TaintVulnerabilityRaised\", new TaintVulnerabilityRaisedEventParser());\n  private static final Map<String, EventParser<?>> parsersByTypeForProjectUserFilter = Map.of(\n    \"MyNewIssues\", new SmartNotificationEventParser(\"NEW_ISSUES\"));\n  private static final Map<String, EventParser<?>> parsersByType = Stream.of(parsersByTypeForProjectFilter, parsersByTypeForProjectUserFilter)\n    .flatMap(map -> map.entrySet().stream())\n    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\n  private static final String PROJECT_FILTER_TYPE = \"PROJECT\";\n  private static final String PROJECT_USER_FILTER_TYPE = \"PROJECT_USER\";\n  private static final Gson gson = new Gson();\n  private CompletableFuture<WebSocket> wsFuture;\n  private final History history = new History();\n  private final ScheduledExecutorService sonarCloudWebSocketScheduler = FailSafeExecutors.newSingleThreadScheduledExecutor(\"sonarcloud-websocket-scheduled-jobs\");\n\n  private final AtomicBoolean closingInitiated = new AtomicBoolean(false);\n  private final CompletableFuture<?> webSocketInputClosed = new CompletableFuture<>();\n\n  public static SonarCloudWebSocket create(URI webSocketsEndpointUri, WebSocketClient webSocketClient, Consumer<SonarServerEvent> serverEventConsumer,\n    Runnable connectionEndedRunnable) {\n    var webSocket = new SonarCloudWebSocket();\n    var currentThreadOutput = SonarLintLogger.get().getTargetForCopy();\n    LOG.info(\"Creating WebSocket connection to \" + webSocketsEndpointUri);\n    webSocket.wsFuture = webSocketClient.createWebSocketConnection(webSocketsEndpointUri, rawEvent -> webSocket.handleRawMessage(rawEvent, serverEventConsumer), () -> {\n      webSocket.webSocketInputClosed.complete(null);\n      // Don't call the callback if the client has triggered the closing\n      if (!webSocket.closingInitiated.get()) {\n        connectionEndedRunnable.run();\n      }\n    });\n    webSocket.wsFuture.thenAccept(ws -> {\n      SonarLintLogger.get().setTarget(currentThreadOutput);\n      webSocket.sonarCloudWebSocketScheduler.scheduleAtFixedRate(webSocket::cleanUpMessageHistory, 0, 5, TimeUnit.MINUTES);\n      webSocket.sonarCloudWebSocketScheduler.schedule(connectionEndedRunnable, 119, TimeUnit.MINUTES);\n      webSocket.sonarCloudWebSocketScheduler.scheduleAtFixedRate(() -> keepAlive(ws), 9, 9, TimeUnit.MINUTES);\n    });\n    webSocket.wsFuture.exceptionally(t -> {\n      SonarLintLogger.get().setTarget(currentThreadOutput);\n      LOG.error(\"Error while trying to create WebSocket connection for \" + webSocketsEndpointUri, t);\n      return null;\n    });\n    return webSocket;\n  }\n\n  private static void keepAlive(WebSocket ws) {\n    ws.sendText(\"{\\\"action\\\": \\\"keep_alive\\\",\\\"statusCode\\\":200}\", true);\n  }\n\n  private void cleanUpMessageHistory() {\n    history.forgetOlderThan(Duration.ofMinutes(1));\n  }\n\n  private SonarCloudWebSocket() {\n  }\n\n  public void subscribe(String projectKey) {\n    send(\"subscribe\", projectKey, parsersByTypeForProjectFilter, PROJECT_FILTER_TYPE);\n    send(\"subscribe\", projectKey, parsersByTypeForProjectUserFilter, PROJECT_USER_FILTER_TYPE);\n  }\n\n  public void unsubscribe(String projectKey) {\n    send(\"unsubscribe\", projectKey, parsersByTypeForProjectFilter, PROJECT_FILTER_TYPE);\n    send(\"unsubscribe\", projectKey, parsersByTypeForProjectUserFilter, PROJECT_USER_FILTER_TYPE);\n  }\n\n  private void send(String messageType, String projectKey, Map<String, EventParser<?>> parsersByType, String filter) {\n    var eventsKey = parsersByType.keySet().toArray(new String[0]);\n    Arrays.sort(eventsKey);\n    var payload = new WebSocketEventSubscribePayload(messageType, eventsKey, filter, projectKey);\n\n    var jsonString = gson.toJson(payload);\n    try {\n      // wait for the message to be sent to not fail the next one, see SLCORE-787\n      this.wsFuture.thenCompose(ws -> {\n        LOG.debug(\"sent '\" + messageType + \"' for project '\" + projectKey + \"'\");\n        return ws.sendText(jsonString, true);\n      }).join();\n    } catch (Exception e) {\n      LOG.error(\"Error when sending a message in the WebSocket channel\", e);\n    }\n  }\n\n  private void handleRawMessage(String message, Consumer<SonarServerEvent> serverEventConsumer) {\n    if (history.exists(message)) {\n      // SC implements at least 1 time delivery, so we need to de-duplicate the messages\n      return;\n    }\n    history.recordMessage(message);\n    try {\n      var wsEvent = gson.fromJson(message, WebSocketEvent.class);\n      parse(wsEvent).ifPresent(serverEventConsumer);\n      LOG.debug(\"Server event received: \" + message, LogOutput.Level.DEBUG);\n    } catch (Exception e) {\n      LOG.error(\"Malformed event received: \" + message, e);\n    }\n  }\n\n  private static Optional<? extends SonarServerEvent> parse(WebSocketEvent event) {\n    var eventType = event.event;\n    if (eventType == null) {\n      return Optional.empty();\n    }\n\n    if (parsersByType.containsKey(eventType)) {\n      return tryParsing(parsersByType.get(eventType), event);\n    } else {\n      LOG.error(\"Unknown '{}' event type \", eventType);\n      return Optional.empty();\n    }\n  }\n\n  private static Optional<? extends SonarServerEvent> tryParsing(EventParser<? extends SonarServerEvent> eventParser, WebSocketEvent event) {\n    try {\n      return eventParser.parse(event.data.toString());\n    } catch (Exception e) {\n      LOG.error(\"Cannot parse '{}' received event\", event.event, e);\n      return Optional.empty();\n    }\n  }\n\n  public void close(String reason) {\n    LOG.debug(\"Closing SonarCloud WebSocket connection, reason={}...\", reason);\n    this.closingInitiated.set(true);\n    if (this.wsFuture != null) {\n      // output could already be closed if an error occurred\n      try {\n        // Check if the future completed exceptionally before trying to get the result\n        if (this.wsFuture.isCompletedExceptionally()) {\n          LOG.debug(\"WebSocket connection was already closed, skipping close operation\");\n        } else if (this.wsFuture.isDone()) {\n          this.wsFuture.thenAccept(ws -> close(ws, this.webSocketInputClosed)).get();\n        } else {\n          // Future is still pending, cancel it\n          this.wsFuture.cancel(true);\n          LOG.debug(\"WebSocket connection was still pending, cancelled\");\n        }\n      } catch (InterruptedException e) {\n        Thread.currentThread().interrupt();\n      } catch (ExecutionException e) {\n        LOG.error(\"Cannot close the WebSocket output\", e);\n      }\n      this.wsFuture = null;\n    }\n    if (!MoreExecutors.shutdownAndAwaitTermination(sonarCloudWebSocketScheduler, 1, TimeUnit.SECONDS)) {\n      LOG.warn(\"Unable to stop SonarCloud WebSocket job scheduler in a timely manner\");\n    }\n  }\n\n  private static void close(WebSocket ws, CompletableFuture<?> webSocketInputClosed) {\n    if (!ws.isOutputClosed()) {\n      try {\n        // close output\n        ws.sendClose(WebSocket.NORMAL_CLOSURE, \"\").get();\n        LOG.debug(\"Waiting for SonarCloud WebSocket input to be closed...\");\n        webSocketInputClosed.get(10, TimeUnit.SECONDS);\n        LOG.debug(\"SonarCloud WebSocket closed\");\n      } catch (InterruptedException e) {\n        Thread.currentThread().interrupt();\n      } catch (ExecutionException e) {\n        handleExecutionException(e);\n      } catch (TimeoutException e) {\n        handleTimeoutException(ws, e);\n      }\n    }\n\n  }\n\n  private static void handleExecutionException(ExecutionException e) {\n    // This might fail with an \"IOException: Output closed\" in case the WebSocket was closed by the server or reached EOL which\n    // is fine and should not throw an error message to the user misleading them that something is not working correctly.\n    var cause = e.getCause();\n    if (cause instanceof UnresolvedAddressException || (cause instanceof IOException\n      && (cause.getMessage().contains(\"Output closed\") || cause.getMessage().contains(\"closed output\")))) {\n      LOG.debug(\"WebSocket could not be closed gracefully\", e);\n    } else {\n      LOG.error(\"Cannot close the WebSocket output\", e);\n    }\n  }\n\n  private static void handleTimeoutException(WebSocket ws, TimeoutException e) {\n    LOG.error(\"The WebSocket input did not close in a timely manner\", e);\n    if (!ws.isInputClosed()) {\n      // close input\n      ws.abort();\n    }\n  }\n\n  public boolean isOpen() {\n    if (wsFuture == null || !wsFuture.isDone() || wsFuture.isCompletedExceptionally() || wsFuture.isCancelled()) {\n      return false;\n    }\n    var ws = wsFuture.getNow(null);\n    return ws != null && !ws.isInputClosed() && !ws.isOutputClosed();\n  }\n\n  private static class WebSocketEvent {\n    private String event;\n    private JsonObject data;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/websocket/WebSocketEventSubscribePayload.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.websocket;\n\npublic class WebSocketEventSubscribePayload {\n  private final String action;\n  private final String[] events;\n  private final String filterType;\n  private final String project;\n\n  public WebSocketEventSubscribePayload(String action, String[] events, String filterType, String project) {\n    this.action = action;\n    this.events = events;\n    this.filterType = filterType;\n    this.project = project;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/websocket/WebSocketManager.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.websocket;\n\nimport java.net.URI;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.ExecutorService;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.util.FailSafeExecutors;\nimport org.sonarsource.sonarlint.core.event.SonarServerEventReceivedEvent;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.serverapi.push.SonarServerEvent;\nimport org.springframework.context.ApplicationEventPublisher;\n\npublic class WebSocketManager {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private SonarCloudWebSocket sonarCloudWebSocket;\n  private final Set<String> connectionIdsInterestedInNotifications = new HashSet<>();\n  private String connectionIdUsedToCreateConnection;\n  private final Map<String, String> subscribedProjectKeysByConfigScopes = new HashMap<>();\n  private final ExecutorService executorService = FailSafeExecutors.newSingleThreadExecutor(\"sonarlint-websocket-subscriber\");\n  private final ApplicationEventPublisher eventPublisher;\n  private final SonarQubeClientManager sonarQubeClientManager;\n  private final ConfigurationRepository configurationRepository;\n  private final URI websocketEndpointUri;\n\n  public WebSocketManager(ApplicationEventPublisher eventPublisher, SonarQubeClientManager sonarQubeClientManager, ConfigurationRepository configurationRepository,\n    URI websocketEndpointUri) {\n    this.eventPublisher = eventPublisher;\n    this.sonarQubeClientManager = sonarQubeClientManager;\n    this.configurationRepository = configurationRepository;\n    this.websocketEndpointUri = websocketEndpointUri;\n  }\n\n  private void handleSonarServerEvent(SonarServerEvent event) {\n    connectionIdsInterestedInNotifications.forEach(id -> eventPublisher.publishEvent(new SonarServerEventReceivedEvent(id, event)));\n  }\n\n  public void forgetConnection(String connectionId, String reason) {\n    var previouslyInterestedInNotifications = connectionIdsInterestedInNotifications.remove(connectionId);\n    if (!previouslyInterestedInNotifications) {\n      return;\n    }\n    if (connectionIdsInterestedInNotifications.isEmpty()) {\n      closeSocket(reason);\n      subscribedProjectKeysByConfigScopes.clear();\n    } else if (this.connectionIdUsedToCreateConnection.equals(connectionId)) {\n      // stop using the credentials, switch to another connection\n      var otherConnectionId = connectionIdsInterestedInNotifications.stream().findAny().orElseThrow();\n      removeProjectsFromSubscriptionListForConnection(connectionId);\n      this.reopenConnection(otherConnectionId, reason + \", reopening for other SC connection\");\n    } else {\n      configurationRepository.getBoundScopesToConnection(connectionId)\n        .forEach(configScope -> forget(configScope.getConfigScopeId()));\n    }\n  }\n\n  private void removeProjectsFromSubscriptionListForConnection(String updatedConnectionId) {\n    var configurationScopesToUnsubscribe = configurationRepository.getBoundScopesToConnection(updatedConnectionId);\n    for (var configScope : configurationScopesToUnsubscribe) {\n      subscribedProjectKeysByConfigScopes.remove(configScope.getConfigScopeId());\n    }\n  }\n\n  /**\n   * @return the connection if it was or has been opened, else empty\n   */\n  public Optional<SonarCloudWebSocket> createConnectionIfNeeded(String connectionId) {\n    connectionIdsInterestedInNotifications.add(connectionId);\n    if (hasOpenConnection()) {\n      return Optional.of(sonarCloudWebSocket);\n    }\n    try {\n      return sonarQubeClientManager.getValidWebSocketClient(connectionId)\n        .map(webSocketClient -> {\n          this.sonarCloudWebSocket = SonarCloudWebSocket.create(this.websocketEndpointUri, webSocketClient, this::handleSonarServerEvent, this::reopenConnectionOnClose);\n          this.connectionIdUsedToCreateConnection = connectionId;\n          return sonarCloudWebSocket;\n        });\n    } catch (Exception e) {\n      LOG.error(\"Error while creating WebSocket connection\", e);\n      return Optional.empty();\n    }\n  }\n\n  public void reopenConnection(String connectionId, String reason) {\n    closeSocket(reason);\n    createConnectionIfNeeded(connectionId)\n      .ifPresent(connection -> resubscribeAll());\n  }\n\n  protected void reopenConnectionOnClose() {\n    executorService.execute(() -> {\n      var connectionId = connectionIdsInterestedInNotifications.stream().findFirst().orElse(null);\n      if (this.sonarCloudWebSocket != null && connectionId != null) {\n        // If connection already exists, close it and create new one before it expires on its own\n        this.reopenConnection(connectionId, \"WebSocket was closed by server or reached EOL\");\n      }\n    });\n  }\n\n  public void closeSocketIfNoMoreNeeded() {\n    if (subscribedProjectKeysByConfigScopes.isEmpty()) {\n      closeSocket(\"No more bound project\");\n    }\n  }\n\n  public void subscribe(String configScopeId, Binding binding) {\n    createConnectionIfNeeded(binding.connectionId())\n      .ifPresent(connection -> {\n        var projectKey = binding.sonarProjectKey();\n        if (subscribedProjectKeysByConfigScopes.containsKey(configScopeId) && !subscribedProjectKeysByConfigScopes.get(configScopeId).equals(projectKey)) {\n          this.forget(configScopeId);\n        }\n        if (!subscribedProjectKeysByConfigScopes.containsValue(projectKey)) {\n          connection.subscribe(projectKey);\n        }\n        subscribedProjectKeysByConfigScopes.put(configScopeId, projectKey);\n      });\n  }\n\n  private void resubscribeAll() {\n    var uniqueProjectKeys = new HashSet<>(subscribedProjectKeysByConfigScopes.values());\n    uniqueProjectKeys.forEach(projectKey -> sonarCloudWebSocket.subscribe(projectKey));\n  }\n\n  public void closeSocket(String reason) {\n    if (this.sonarCloudWebSocket != null) {\n      var socket = this.sonarCloudWebSocket;\n      this.sonarCloudWebSocket = null;\n      this.connectionIdUsedToCreateConnection = null;\n      socket.close(reason);\n    }\n  }\n\n  public boolean hasOpenConnection() {\n    return sonarCloudWebSocket != null && sonarCloudWebSocket.isOpen();\n  }\n\n  public void forget(String configScopeId) {\n    var projectKey = subscribedProjectKeysByConfigScopes.remove(configScopeId);\n    if (projectKey != null && !subscribedProjectKeysByConfigScopes.containsValue(projectKey) && hasOpenConnection()) {\n      sonarCloudWebSocket.unsubscribe(projectKey);\n    }\n  }\n\n  public Map<String, String> getSubscribedProjectKeysByConfigScopes() {\n    return subscribedProjectKeysByConfigScopes;\n  }\n\n  public boolean isInterestedInNotifications(String connectionId) {\n    return connectionIdsInterestedInNotifications.contains(connectionId);\n  }\n\n  public Set<String> getConnectionIdsInterestedInNotifications() {\n    return connectionIdsInterestedInNotifications;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/websocket/WebSocketService.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.websocket;\n\nimport com.google.common.util.concurrent.MoreExecutors;\nimport jakarta.annotation.PreDestroy;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\nimport javax.annotation.CheckForNull;\nimport org.sonarsource.sonarlint.core.SonarCloudActiveEnvironment;\nimport org.sonarsource.sonarlint.core.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.BoundScope;\nimport org.sonarsource.sonarlint.core.commons.ConnectionKind;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.util.FailSafeExecutors;\nimport org.sonarsource.sonarlint.core.event.BindingConfigChangedEvent;\nimport org.sonarsource.sonarlint.core.event.ConfigurationScopeRemovedEvent;\nimport org.sonarsource.sonarlint.core.event.ConfigurationScopesAddedWithBindingEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationAddedEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationRemovedEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationUpdatedEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionCredentialsChangedEvent;\nimport org.sonarsource.sonarlint.core.repository.config.BindingConfiguration;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarCloudConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.springframework.context.ApplicationEventPublisher;\nimport org.springframework.context.event.EventListener;\n\nimport static java.util.Objects.requireNonNull;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SERVER_SENT_EVENTS;\n\npublic class WebSocketService {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final boolean shouldEnableWebSockets;\n  private final ConnectionConfigurationRepository connectionConfigurationRepository;\n  private final ConfigurationRepository configurationRepository;\n  private final Map<SonarCloudRegion, WebSocketManager> webSocketsByRegion;\n  private final ExecutorService executorService = FailSafeExecutors.newSingleThreadExecutor(\"sonarlint-websocket-subscriber\");\n\n  public WebSocketService(ConnectionConfigurationRepository connectionConfigurationRepository, ConfigurationRepository configurationRepository,\n    SonarQubeClientManager sonarQubeClientManager, InitializeParams params, SonarCloudActiveEnvironment sonarCloudActiveEnvironment,\n    ApplicationEventPublisher eventPublisher) {\n    this.connectionConfigurationRepository = connectionConfigurationRepository;\n    this.configurationRepository = configurationRepository;\n    this.shouldEnableWebSockets = params.getBackendCapabilities().contains(SERVER_SENT_EVENTS);\n    this.webSocketsByRegion = Map.of(\n      SonarCloudRegion.US,\n      new WebSocketManager(eventPublisher, sonarQubeClientManager, configurationRepository, sonarCloudActiveEnvironment.getWebSocketsEndpointUri(SonarCloudRegion.US)),\n      SonarCloudRegion.EU,\n      new WebSocketManager(eventPublisher, sonarQubeClientManager, configurationRepository, sonarCloudActiveEnvironment.getWebSocketsEndpointUri(SonarCloudRegion.EU)));\n  }\n\n  @EventListener\n  public void handleEvent(BindingConfigChangedEvent bindingConfigChangedEvent) {\n    if (!shouldEnableWebSockets) {\n      return;\n    }\n    executorService.execute(() -> {\n      considerScope(bindingConfigChangedEvent.configScopeId());\n      // possible change of region for the binding; need to unsubscribe from the old region (subscription to the new one will be done in considerScope)\n      if (didChangeRegion(bindingConfigChangedEvent.previousConfig(), bindingConfigChangedEvent.newConfig())) {\n        // will only enter this block if previous connection (and connectionId) existed\n        var previousRegion = ((SonarCloudConnectionConfiguration) connectionConfigurationRepository\n          .getConnectionById(bindingConfigChangedEvent.previousConfig().connectionId())).getRegion();\n        webSocketsByRegion.get(previousRegion).forget(bindingConfigChangedEvent.configScopeId());\n        webSocketsByRegion.get(previousRegion).closeSocketIfNoMoreNeeded();\n      }\n    });\n  }\n\n  @EventListener\n  public void handleEvent(ConfigurationScopesAddedWithBindingEvent configurationScopesAddedEvent) {\n    if (!shouldEnableWebSockets) {\n      return;\n    }\n    executorService.execute(() -> considerAllBoundConfigurationScopes(configurationScopesAddedEvent.getConfigScopeIds()));\n  }\n\n  @EventListener\n  public void handleEvent(ConfigurationScopeRemovedEvent configurationScopeRemovedEvent) {\n    if (!shouldEnableWebSockets) {\n      return;\n    }\n    var removedConfigurationScopeId = configurationScopeRemovedEvent.getRemovedConfigurationScopeId();\n    executorService.execute(() ->\n      webSocketsByRegion.forEach((region, webSocketManager) -> {\n        webSocketManager.forget(removedConfigurationScopeId);\n        webSocketManager.closeSocketIfNoMoreNeeded();\n      })\n    );\n  }\n\n  @EventListener\n  public void handleEvent(ConnectionConfigurationAddedEvent connectionConfigurationAddedEvent) {\n    if (!shouldEnableWebSockets) {\n      return;\n    }\n    // This is only to handle the case where binding was invalid (connection did not exist) and became valid (matching connection was created)\n    executorService.execute(() -> considerConnection(connectionConfigurationAddedEvent.addedConnectionId()));\n  }\n\n  @EventListener\n  public void handleEvent(ConnectionConfigurationUpdatedEvent connectionConfigurationUpdatedEvent) {\n    if (!shouldEnableWebSockets) {\n      return;\n    }\n    var updatedConnectionId = connectionConfigurationUpdatedEvent.updatedConnectionId();\n    executorService.execute(() -> {\n      if (didDisableNotifications(updatedConnectionId)) {\n        webSocketsByRegion.forEach((region, webSocketManager) ->\n          webSocketManager.forgetConnection(updatedConnectionId, \"Notifications were disabled\")\n        );\n      } else if (didEnableNotifications(updatedConnectionId)) {\n        considerConnection(updatedConnectionId);\n      }\n    });\n  }\n\n  @EventListener\n  public void handleEvent(ConnectionConfigurationRemovedEvent connectionConfigurationRemovedEvent) {\n    if (!shouldEnableWebSockets) {\n      return;\n    }\n    String removedConnectionId = connectionConfigurationRemovedEvent.removedConnectionId();\n    executorService.execute(() ->\n      webSocketsByRegion.forEach((region, webSocketManager) ->\n        webSocketManager.forgetConnection(removedConnectionId, \"Connection was removed\")\n      )\n    );\n  }\n\n  @EventListener\n  public void handleEvent(ConnectionCredentialsChangedEvent connectionCredentialsChangedEvent) {\n    if (!shouldEnableWebSockets) {\n      return;\n    }\n    var connectionId = connectionCredentialsChangedEvent.getConnectionId();\n    executorService.execute(() -> {\n      if (isEligibleConnection(connectionId) && isInterestedInNotifications(connectionId)) {\n        var region = ((SonarCloudConnectionConfiguration) connectionConfigurationRepository.getConnectionById(connectionId)).getRegion();\n        webSocketsByRegion.get(region).reopenConnection(connectionId, \"Credentials have changed\");\n      }\n    });\n  }\n\n  private void considerConnection(String connectionId) {\n    var configScopeIds = configurationRepository.getBoundScopesToConnection(connectionId)\n      .stream().map(BoundScope::getConfigScopeId)\n      .collect(Collectors.toSet());\n    considerAllBoundConfigurationScopes(configScopeIds);\n  }\n\n  private void considerAllBoundConfigurationScopes(Set<String> configScopeIds) {\n    for (String scopeId : configScopeIds) {\n      considerScope(scopeId);\n    }\n  }\n\n  private void considerScope(String scopeId) {\n    var binding = getCurrentBinding(scopeId);\n    if (binding != null && isEligibleConnection(binding.connectionId())) {\n      var connection = requireNonNull(connectionConfigurationRepository.getConnectionById(binding.connectionId()));\n      var region = ((SonarCloudConnectionConfiguration) connection).getRegion();\n      webSocketsByRegion.get(region).subscribe(scopeId, binding);\n    } else if (isSubscribedToAProject(scopeId)) {\n      // no binding or binding is not eligible, unsubscribe from all regions if it was subscribed to a project\n      webSocketsByRegion.forEach((region, webSocketManager) -> {\n        webSocketManager.forget(scopeId);\n        webSocketManager.closeSocketIfNoMoreNeeded();\n      });\n    }\n  }\n\n  private boolean isInterestedInNotifications(String connectionId) {\n    return webSocketsByRegion.values().stream().anyMatch(webSocketManager -> webSocketManager.isInterestedInNotifications(connectionId));\n  }\n\n  private boolean isEligibleConnection(String connectionId) {\n    var connection = connectionConfigurationRepository.getConnectionById(connectionId);\n    return connection != null && connection.getKind().equals(ConnectionKind.SONARCLOUD) && !connection.isDisableNotifications();\n  }\n\n  private boolean didChangeRegion(BindingConfiguration previousBindingConfiguration, BindingConfiguration newBindingConfiguration) {\n    var previousConnectionId = previousBindingConfiguration.connectionId();\n    var previousConnection = previousConnectionId != null ? connectionConfigurationRepository.getConnectionById(previousConnectionId) : null;\n    var newConnectionId = newBindingConfiguration.connectionId();\n    var newConnection = newConnectionId != null ? connectionConfigurationRepository.getConnectionById(newConnectionId) : null;\n    if (newConnection == null || previousConnection == null) {\n      // nothing to do\n      return false;\n    } else if (previousConnection instanceof SonarCloudConnectionConfiguration previousConn &&\n      newConnection instanceof SonarCloudConnectionConfiguration newConn) {\n      // was SonarCloud connection and still is - check if region changed\n      return previousConn.getRegion() != newConn.getRegion();\n    }\n    return false;\n  }\n\n  @CheckForNull\n  private Binding getCurrentBinding(String configScopeId) {\n    var bindingConfiguration = configurationRepository.getBindingConfiguration(configScopeId);\n    if (bindingConfiguration != null && bindingConfiguration.isBound()) {\n      return new Binding(requireNonNull(bindingConfiguration.connectionId()), requireNonNull(bindingConfiguration.sonarProjectKey()));\n    }\n    return null;\n  }\n\n  private boolean didDisableNotifications(String connectionId) {\n    if (isInterestedInNotifications(connectionId)) {\n      var connection = connectionConfigurationRepository.getConnectionById(connectionId);\n      return connection != null && connection.getKind().equals(ConnectionKind.SONARCLOUD) && connection.isDisableNotifications();\n    }\n    return false;\n  }\n\n  private boolean didEnableNotifications(String connectionId) {\n    return isEligibleConnection(connectionId) && !isInterestedInNotifications(connectionId);\n  }\n\n  private boolean isSubscribedToAProject(String configScopeId) {\n    for (var webSocketManager : webSocketsByRegion.values()) {\n      var subscribedProjectKey = webSocketManager.getSubscribedProjectKeysByConfigScopes().get(configScopeId);\n      if (subscribedProjectKey != null) {\n        // we are interested if it was subscribed to a project in any region\n        return true;\n      }\n    }\n    return false;\n  }\n\n  public boolean hasOpenConnection(SonarCloudRegion region) {\n    return webSocketsByRegion.get(region).hasOpenConnection();\n  }\n\n  @PreDestroy\n  public void shutdown() {\n    if (!MoreExecutors.shutdownAndAwaitTermination(executorService, 1, TimeUnit.SECONDS)) {\n      LOG.warn(\"Unable to stop websockets subscriber service in a timely manner\");\n    }\n    webSocketsByRegion.forEach((region, webSocketManager) -> {\n      webSocketManager.closeSocket(\"Backend is shutting down\");\n      webSocketManager.getSubscribedProjectKeysByConfigScopes().clear();\n      webSocketManager.getConnectionIdsInterestedInNotifications().clear();\n    });\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/websocket/events/SmartNotificationEvent.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.websocket.events;\n\nimport org.sonarsource.sonarlint.core.serverapi.push.SonarServerEvent;\n\nimport static org.apache.commons.text.StringEscapeUtils.escapeHtml4;\n\npublic record SmartNotificationEvent(String message, String link, String project, String date,\n                                     String category) implements SonarServerEvent {\n\n  public SmartNotificationEvent(String message, String link, String project, String date, String category) {\n    this.message = escapeHtml4(message);\n    this.link = link;\n    this.project = project;\n    this.date = date;\n    this.category = category;\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/websocket/events/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.websocket.events;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/websocket/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.websocket;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/websocket/parsing/SmartNotificationEventParser.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.websocket.parsing;\n\nimport com.google.gson.Gson;\nimport java.util.Optional;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.EventParser;\nimport org.sonarsource.sonarlint.core.websocket.events.SmartNotificationEvent;\n\nimport static org.sonarsource.sonarlint.core.serverapi.util.ServerApiUtils.isBlank;\n\npublic class SmartNotificationEventParser implements EventParser<SmartNotificationEvent> {\n\n  private final Gson gson = new Gson();\n  private final String category;\n\n  public SmartNotificationEventParser(String category) {\n    this.category = category;\n  }\n\n  @Override\n  public Optional<SmartNotificationEvent> parse(String jsonData) {\n    var payload = gson.fromJson(jsonData, SmartNotificationEventPayload.class);\n    if (payload.isInvalid()) {\n      SonarLintLogger.get().error(\"Invalid payload for 'SmartNotification' event of category '\" + category + \"': {}\", jsonData);\n      return Optional.empty();\n    }\n    return Optional.of(new SmartNotificationEvent(\n      payload.message,\n      payload.link,\n      payload.project,\n      payload.date,\n      category));\n  }\n\n  private static class SmartNotificationEventPayload {\n    private String message;\n    private String link;\n    private String project;\n    private String date;\n\n    private boolean isInvalid() {\n      return isBlank(message) || isBlank(link) || isBlank(project) || date == null;\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/main/java/org/sonarsource/sonarlint/core/websocket/parsing/package-info.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.websocket.parsing;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/core/src/main/resources/ai/hooks/sonarqube_analysis_hook.js",
    "content": "#!/usr/bin/env node\n// SonarQube for IDE {{AGENT}} Hook - sonarqube_analysis_hook\n// Auto-generated script for Node.js\n// Connects AI Agents to SonarQube for IDE backend\n\nconst http = require('node:http');\n\nconst STARTING_PORT = 64120;\nconst ENDING_PORT = 64130;\nconst EXPECTED_IDE_NAME = '{{AGENT}}';\nconst PORT_SCAN_TIMEOUT = 100;\n\nasync function findBackendPort() {\n  const portPromises = [];\n  for (let port = STARTING_PORT; port <= ENDING_PORT; port++) {\n    portPromises.push(checkPort(port));\n  }\n  const results = await Promise.allSettled(portPromises);\n  for (const result of results) {\n    if (result.status === 'fulfilled' && result.value !== null) {\n      return result.value;\n    }\n  }\n  return null;\n}\n\nfunction checkPort(port) {\n  return new Promise((resolve) => {\n    const req = http.get({\n      hostname: 'localhost',\n      port,\n      path: '/sonarlint/api/status',\n      timeout: PORT_SCAN_TIMEOUT,\n      headers: {\n        'Origin': 'ai-agent://{{AGENT}}'\n      }\n    }, (res) => {\n      let data = '';\n      res.on('data', chunk => data += chunk);\n      res.on('end', () => {\n        try {\n          const status = JSON.parse(data);\n          if (status.ideName === EXPECTED_IDE_NAME) {\n            resolve(port);\n          } else {\n            resolve(null);\n          }\n        } catch (e) {\n          resolve(null);\n        }\n      });\n    });\n    \n    req.on('error', () => {\n      resolve(null);\n    });\n    req.on('timeout', () => {\n      req.destroy();\n      resolve(null);\n    });\n  });\n}\n\nfunction analyzeFile(port, filePath) {\n  console.log(`Analyzing: ${filePath} (port ${port})`);\n  const requestBody = JSON.stringify({ fileAbsolutePaths: [filePath] });\n  const options = {\n    hostname: 'localhost',\n    port,\n    path: '/sonarlint/api/analysis/files',\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/json',\n      'Content-Length': Buffer.byteLength(requestBody),\n      'Origin': 'ai-agent://{{AGENT}}'\n    },\n    agent: new http.Agent({ keepAlive: false }),\n    timeout: 1000\n  };\n  const req = http.request(options);\n  req.on('socket', (socket) => {\n    socket.unref();\n  });\n  req.on('error', (err) => {\n    console.log(`Error: ${err.message}`);\n  });\n  req.write(requestBody);\n  req.end();\n  setImmediate(() => {\n    process.exit(0);\n  });\n}\n\n// Read the event JSON from stdin\nlet eventJson = '';\nprocess.stdin.setEncoding('utf8');\n\nprocess.stdin.on('data', (chunk) => {\n  eventJson += chunk;\n});\n\nprocess.stdin.on('end', async () => {\n  try {\n    const event = JSON.parse(eventJson);\n    const filePath = event.tool_info?.file_path;\n    if (!filePath) {\n      console.log('No file path in event');\n      return;\n    }\n    const port = await findBackendPort();\n    if (!port) {\n      console.log('Backend not found');\n      process.exit(1);\n    }\n    analyzeFile(port, filePath);\n  } catch (e) {\n    console.log(`Error: ${e.message}`);\n    process.exit(1);\n  }\n});\n\n"
  },
  {
    "path": "backend/core/src/main/resources/ai/hooks/sonarqube_analysis_hook.py",
    "content": "#!/usr/bin/env python3\n# SonarQube for IDE {{AGENT}} Hook - sonarqube_analysis_hook\n# Auto-generated script for Python\n# Connects AI Agents to SonarQube for IDE backend\n\nimport sys\nimport json\nimport urllib.request\nimport urllib.error\n\nSTARTING_PORT = 64120\nENDING_PORT = 64130\nEXPECTED_IDE_NAME = '{{AGENT}}'\nPORT_SCAN_TIMEOUT = 0.1\n\ndef find_backend_port():\n    \"\"\"Fast port discovery: find the correct SonarQube for IDE backend\"\"\"\n    for port in range(STARTING_PORT, ENDING_PORT + 1):\n        if check_port(port):\n            return port\n    return None\n\ndef check_port(port):\n    \"\"\"Check if a port has a valid SonarQube for IDE backend\"\"\"\n    try:\n        url = f'http://localhost:{port}/sonarlint/api/status'\n        req = urllib.request.Request(url, headers={'Origin': 'ai-agent://{{AGENT}}'})\n        with urllib.request.urlopen(req, timeout=PORT_SCAN_TIMEOUT) as response:\n            if response.status == 200:\n                data = json.loads(response.read().decode('utf-8'))\n                ide_name = data.get('ideName')\n                if ide_name == EXPECTED_IDE_NAME:\n                    return True\n    except Exception:\n        pass\n    return False\n\ndef analyze_file(port, file_path):\n    \"\"\"Call the analysis endpoint (fire-and-forget, non-blocking)\"\"\"\n    print(f'Analyzing: {file_path} (port {port})')\n    request_body = json.dumps({'fileAbsolutePaths': [file_path]})\n    url = f'http://localhost:{port}/sonarlint/api/analysis/files'\n    req = urllib.request.Request(\n        url,\n        data=request_body.encode('utf-8'),\n        headers={\n            'Content-Type': 'application/json',\n            'Origin': 'ai-agent://{{AGENT}}'\n        }\n    )\n\n    try:\n        response = urllib.request.urlopen(req, timeout=1)\n        response.close()\n    except Exception as e:\n        print(f'Error: {e}')\n\n    sys.exit(0)\n\ndef main():\n    try:\n        event_json = sys.stdin.read()\n        event = json.loads(event_json)\n\n        tool_info = event.get('tool_info', {})\n        file_path = tool_info.get('file_path')\n        \n        if not file_path:\n            print('No file path in event')\n            return\n\n        port = find_backend_port()\n        if not port:\n            print('Backend not found')\n            sys.exit(1)\n\n        analyze_file(port, file_path)\n    \n    except json.JSONDecodeError as e:\n        print(f'JSON error: {e}')\n        sys.exit(1)\n    except Exception as e:\n        print(f'Error: {e}')\n        sys.exit(1)\n\nif __name__ == '__main__':\n    main()\n\n"
  },
  {
    "path": "backend/core/src/main/resources/ai/hooks/sonarqube_analysis_hook.sh",
    "content": "#!/bin/bash\n# SonarQube for IDE {{AGENT}} Hook - sonarqube_analysis_hook\n# Auto-generated script for Bash\n# Connects AI Agents to SonarQube for IDE backend\n\nset -e\n\nSTARTING_PORT=64120\nENDING_PORT=64130\nEXPECTED_IDE_NAME=\"{{AGENT}}\"\nPORT_SCAN_TIMEOUT=0.1\n\nfind_backend_port() {\n  for port in $(seq $STARTING_PORT $ENDING_PORT); do\n    if check_port \"$port\"; then\n      echo \"$port\"\n      return 0\n    fi\n  done\n  return 1\n}\n\ncheck_port() {\n  local port=$1\n  local status_json=$(curl -s --max-time $PORT_SCAN_TIMEOUT -H \"Origin: ai-agent://{{AGENT}}\" \"http://localhost:${port}/sonarlint/api/status\" 2>/dev/null)\n  \n  if [ $? -ne 0 ]; then\n    return 1\n  fi\n  \n  local ide_name=$(echo \"$status_json\" | jq -r '.ideName // empty' 2>/dev/null)\n  \n  if [ \"$ide_name\" = \"$EXPECTED_IDE_NAME\" ]; then\n    return 0\n  fi\n  \n  return 1\n}\n\nanalyze_file() {\n  local port=$1\n  local file_path=$2\n  echo \"Analyzing: $file_path (port $port)\"\n  local request_body=$(jq -n --arg file \"$file_path\" '{fileAbsolutePaths: [$file]}')\n\n  curl -s --max-time 5 -X POST \\\n    -H \"Content-Type: application/json\" \\\n    -H \"Origin: ai-agent://{{AGENT}}\" \\\n    -H \"Connection: close\" \\\n    -d \"$request_body\" \\\n    \"http://localhost:${port}/sonarlint/api/analysis/files\" \\\n    > /dev/null 2>&1 &\n\n  return 0\n}\n\nEVENT_JSON=$(cat)\n\nif ! command -v jq &> /dev/null; then\n  echo \"jq not found\"\n  exit 1\nfi\n\nFILE_PATH=$(echo \"$EVENT_JSON\" | jq -r '.tool_info.file_path // empty' 2>/dev/null || echo \"\")\n\nif [ -z \"$FILE_PATH\" ]; then\n  echo \"No file path in event\"\n  exit 0\nfi\n\nBACKEND_PORT=$(find_backend_port)\nif [ $? -ne 0 ]; then\n  echo \"Backend not found\"\n  exit 1\nfi\n\nanalyze_file \"$BACKEND_PORT\" \"$FILE_PATH\"\n\n"
  },
  {
    "path": "backend/core/src/main/resources/clean-code-principles/defense_in_depth.html",
    "content": "<h4>Defense-In-Depth</h4>\n<p>\n    Applications and infrastructure benefit greatly from relying on multiple security mechanisms\n    layered on top of each other. If one security mechanism fails, there is a high probability\n    that the subsequent layers of security will successfully defend against the attack.\n</p>\n<p>A non-exhaustive list of these code protection ramparts includes the following:</p>\n<ul>\n    <li>Minimizing the attack surface of the code</li>\n    <li>Application of the principle of least privilege</li>\n    <li>Validation and sanitization of data</li>\n    <li>Encrypting incoming, outgoing, or stored data with secure cryptography</li>\n    <li>Ensuring that internal errors cannot disrupt the overall runtime</li>\n    <li>Separation of tasks and access to information</li>\n</ul>\n\n<p>\n    Note that these layers must be simple enough to use in an everyday workflow. Security\n    measures should not break usability.\n</p>\n"
  },
  {
    "path": "backend/core/src/main/resources/clean-code-principles/never_trust_user_input.html",
    "content": "<h4>Never Trust User Input</h4>\n<p>\n    Applications must treat all user input and, more generally, all third-party data as\n    attacker-controlled data.\n</p>\n<p>\n    The application must determine where the third-party data comes from and treat that data\n    source as an attack vector. Two rules apply:\n</p>\n\n<p>\n    First, before using it in the application&apos;s business logic, the application must\n    validate the attacker-controlled data against predefined formats, such as:\n</p>\n<ul>\n    <li>Character sets</li>\n    <li>Sizes</li>\n    <li>Types</li>\n    <li>Or any strict schema</li>\n</ul>\n\n<p>\n    Second, the application must sanitize string data before inserting it into interpreted\n    contexts (client-side code, file paths, SQL queries). Unsanitized code can corrupt the\n    application&apos;s logic.\n</p>\n"
  },
  {
    "path": "backend/core/src/main/resources/context-rule-description/others_section_html_content.html",
    "content": "<h4>How can I fix it in another component or framework?</h4>\n<p>Although the main framework or component you use in your project is not listed, you may find helpful content in the instructions we provide.</p>\n<p>Caution: The libraries mentioned in these instructions may not be appropriate for your code.</p>\n<p></p>\n<ul class=\"other-context-list\">\n    <li class=\"do\">Do use libraries that are compatible with the frameworks you are using.</li>\n    <li class=\"dont\">Don't blindly copy and paste the fix-ups into your code.</li>\n</ul>\n<h4>Help us improve</h4>\n<p>Let us know if the instructions we provide do not work for you.\n    Tell us which framework you use and why our solution does not work by submitting an idea on the SonarLint product-board.</p>\n<a href=\"https://portal.productboard.com/sonarsource/4-sonarlint/submit-idea\">Submit an idea</a>\n<p>We will do our best to provide you with more relevant instructions in the future.</p>\n"
  },
  {
    "path": "backend/core/src/main/resources/ondemand/plugins.properties",
    "content": "cfamily.version=${cfamily.version}\ncs.version=${csharp.version}\nomnisharp.version=${omnisharp.version}"
  },
  {
    "path": "backend/core/src/main/resources/ondemand/sonarsource-public.key",
    "content": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: Hockeypuck 2.2\nComment: Hostname:\n\nxsFNBGCGrYsBEAC/Ws37TXMujQ4z2ioXlh5SlrWaCzdN5RSBAQEKaiuuQeuwdWku\nbsnhI2f7YgxfJh2if6hCsGeWx3Wd2paLT9IqJbnIltOzHQkYXajIJrJVDep31wQD\nFsjQS8DWdRGkrldc2ClWZs1PAGC4Snp9bNYrnlE8Z1uHVnmN2R0aQ3v7PGw2qpQ9\nXxsQl9m30hMDb4IZBOKy92PC+xNpb6dgee3HJ8uJ2t/nTUCuP1FsMPGP3crbK9po\nUOUigIWMKNnYTyHbx+p22EQIn3iKQU4DQTeZm1/rUnfuULp2Zhl+fTs6U/czCrdr\n7DN4MCzthK7DMhDHH7/uVk53+e0oe0FJZSxYE1ppjvLz4Ox7xMHrlOMFIqb9JOgn\nexUDV34KcPByHqY4ff7IL94Tx7YAwEplnJYBEfb0sYfmjai4PCFj74gjjCmhQUm8\n5Cbm23JvDGck9W75wc6qj7wcFpZrFtfpOsz10YsprM5TcmK9rEIV+o+bRqoNs5hS\n+heZmdz7LoWJgarJnlkPjDDOXW54bA5kS8ARlkxllzZ+f0BwaN/HBNbVv3gkBHUX\nYOxphjESdv/WByNQMgzoIBiUt02RqAJg9PECLJSjSfFzd2F9g7Lmc0TUdA/kLEZm\nDqgrDjPkfkwnSqCglI38Z/gcVoSDN2iYhEIfuGoZXbjG4IDVuFYyGZjimQARAQAB\nzShTb25hclNvdXJjZSBTLkEuIDxpbmZyYUBzb25hcnNvdXJjZS5jb20+wsGUBBMB\nCgA+FiEEZ58e6SsZYJ3oFv3oHbGY+TUl7BoFAmCGrYsCGwEFCQlmAYAFCwkIBwIG\nFQoJCAsCBBYCAwECHgECF4AACgkQHbGY+TUl7Bpn+w/8DZjbw5SqguIMnIN1lmZC\nDCNSKk7CJNpkO7ZjXYZo9ZzGlULse4wlqoW5cVH3NiOATV4BnQQotSoeBr8RFdh0\nTI+Zbt2wKv3j4+LxIlalfnYrj77SRh43qqmAKxVS5HAdEXfHNfBtNV88CJTTByX/\nPAw7vIbI+6YwwIP/ps33GrESjDZNefdLuTvq3FwrTNicoWnXrIFbs01lNfy6NTfk\n5ZrVHjmTQxHrh0VY4vNZNQYnTzET3fMmhudlIxXPuuNSPl2X1UaTVFNHSwK/IsOr\nm8oWZfG++HgbVmR9YG1Ci7tYTBc+gbp8xel5FjzKcBLQfZwqsnz/Gn3PlPCwKXNI\nuq0Gp925P86scOlCz73Wfy8vde3rc6j+hzlgKuwgunJvl+cyWAyTdvTkcpCN6QJk\nR6ZuXrNkqCzbxT0NNoWEHSDJmJ8ECqJRfza6ag7lReWaT/dGZ/R9a19pbGmGXuqq\nqcwE9hRognxejhAn7mfVpLEsGJwrQEeVQCKQVFIZkFpUr3oYOIPppGxguM97ZNvY\nuZnHq9UwufRMR83h0XWWdTqurYoAcHkjeXH0DKXkM9kQg86FSf/KSWj9cI8/q3en\nVM+HboxrzY8Cc91IwXLOgV1ipowwy8fcnyU8GD+P3bvh1J/nVgzm+NTJ4RIfbDDq\n4Q6vWIDIAfqnRK3aTr2atSTOwU0EaSxelAEQAMjFbyhLN7gPxyvuKGcBP5L7b4UK\nkDSPPqBXHmv1KiEspKSF2cy84F6xdcbfuk3/V0wieu7/1S4Ro7CawBAS8U0VcXEw\nX0MdbBaB0nlVV1QwAsF2e9bDbVpFZb9kOiBWRwXLANJu0NnH0CW3IB4ba23JS+72\n0P6d3Jf3ylvF/I/7HvNGyjxRQ4B7wGX+IzK9rVf/UjQ9ce8oDVIv6gki1z28fCGm\nnv05fIvWTcU1+yUWP4cnDtfOGjHh/ISps6cfPM2xruhCVGQ5UQD5Jabswx5ZXCsa\nHLRRBinhlElfPFBlc5e123RWRjgfrOGQtnkom7agCHmUlbWL2QEFrw3Jab/xspXW\n8oGhMyfKHFlpC/Tni5b17AL22r2v5XFe//uSJGh70zRLIk+xjg3YmW6/jfxbESTF\ndHdi8f5l08ALveuJ4I8sIMct42+HMkqiZVuIeg1IlyVzQv8FAZAuiGSMUlomNsLj\nHO9yk9Y6We7dVG9Fben0oAh7R4b+ZqyWy0rbh8SJeu3v+CT/hLmO/Ag0xo3zBv+X\nwgB5RYRhr7fVCEUMOkUO7yYxvlt/r+HRzMgV9lTIlVF9UZCQIpluvVWWiE9DXpbR\nlYVRy+FTaQmDKJBO3+QBR5jEzI5EKUuRFeBdzT0SBudBdE3r3AZl5uOwzTcSqLAp\nmryAdW+/vZAlS2tJABEBAAHCw7IEGAEKACYWIQRnnx7pKxlgnegW/egdsZj5NSXs\nGgUCaSxelAIbAgUJAeEzgAJACRAdsZj5NSXsGsF0IAQZAQoAHRYhBNFDbA26zqSH\nAq+Xw2Px3XdTuLMVBQJpLF6UAAoJEGPx3XdTuLMVX2gP/jxQKk8JHjosAhpd1eyv\n/99x255Fx46KNpSTus2VKg0ffB+kDKqN5plbPExa6MEC/oxnGBio14ennnYLOIap\necx3WBJYh59wsjlDUwgOLsA+tfzyo6nBW/UIgYVZEATNkhbCuw3lQFZHW5e+be+K\nxuGBZqDH5IKJGL3XodyVNesND1phusLhMco34zVVc5LvRyJ5CzZgjXTIOzx1qbX0\nuJalpYsd4LnChQNuzpRv31zO1d8FG5kE39osU6VNJh7fnDCnbXuj3FvZnPos26rT\nHNafdg+e+/8dOOvwt4UJ2tFAlLhiF/wQxFixhz1b4zXEOYRKuRn1XKm6XDaAT1Kp\n/zlmTQ2F0ObftNjaO9l6mTlunGey5CHUAZ2tAsdDwYIqewIrjjF0o+geMjlKmflI\nh5gkkcxNDXEagjkJXMzL8pzs8+g1B1cg1NHL9hsjp5VqhLD10J48n6C3nhwP1noB\neM3GtKtcK6gK6IYxn3Fou7ABCbRhQXQVAci7iPJ4nW2ySQeQmeLl4lGxWPWZct9I\nLtH8ly+m2+7h7srMwDjRPeA3Owc7JgdLXdWdG7F4zppW4JqWvXIJ3wAJ+KFof6p6\nWrEPWfHVM1qieTdchenPufH3CA474vYH/+l4ie+URXT9v2gukJ4LMVfrTxqJhUPw\nS4k6EOF/2sAi3EcXKDF4o+iEkqUQALGe/o8fng1biW6wD4z2fDu8zp3iU4ldNpst\nQMTX4QsTzsZee6lQoQQ6G+m3IOgQo7EKFEV6rxpMamyQbg2YW8WfypowlfGXAAJ8\nx5mm+tdN4D6uvJidX2MyKAiAspP12Jc2T+Bap+e5TLi955Lk/RzcXgn7MUAZOD74\nHND3/SFOg6mrZT1FPj4Qe5bArjONogYJoHo4/sNJMxKJ2g7WrBAILpgQBAHUts1f\nB+AZoShaFZ9UqAhnspzvI03XU9PUhPN50djdv1NachiRo6KNKCadvdWBo1ZAZsos\ncWLLFnx4miVcgRrmrErkQ/8tidtT3zRcEQE0DvWUat7nXJ7VVwbafhbTwA9v8O+U\n3b9pDjlPBcy3hN3NRd8043XVQOzhTxPFwAof6g6ChqT6ANrZ8JN8DyKwUKCje8SX\nr96otE0K2jEtvJiqXeFTNxMlJ9aO2kdwpnN+58fYAp6FGwXtIU7pNAOCEfaCTmo1\nLMuvW4WmjH6uvRlw8erhrUzqbikEzMwsxec02iZjfC8fhBBJxOZMDvPArjDKsYjl\nuBxYw6MBaBH4mkLQJWHubPQ6lUSEzEEtz76vfCQijh6JTI4kKH3MQfug11d3+NfP\nwfUCvYAaXxJlaY2kt9zY8GzUOy8AUuQN48cIlm70lGYYBE6lT+xgKqQMJ4Nn+m4t\ndecWOnlnzsFNBGCGrk4BEACTD/+Nk/tDzN3viBmw0GvgWWyeyfVKuhXTYgp1NA2Z\nugcsz9ZFjzQegH+jwekWc4JFSQTFHpxqog94eQ7UKzk3LaYeCMiPpuxyxsY8MSZo\noAOcysRabkvVHNLFhCKiiTu7E8NkOlCT9v2+f/1aatFnM+D///1/RTR0MJ7lz3Eu\nQWtC6gC0MQBydHoN9Ofov07j8RSVXBBf7TfZjl+uYfpYEkP5++bnWLw1WMv8Acea\nXyCjoJ/3L5GfrIHoNmpRujj8FLAZV0YOdpQCEwMn6gfJrcWXcPLcg3vmmYLhOWqj\n9kZoqE7Npejtzp9S4Yi9wM0ZTG+TTk2zec7dw7RstxTLEEJ8dx9IyXAkoNf8etlC\n9f9KuTnLK23lsi3cvjs58WzYxtl6MQS9x8U9QBlb86K8GMDYiwRrPyDusVvzwe0l\nZgrt7SboQP5+hD+wY92tJde9JQbYSVcIQwgRGPZGYIZ+DEo5g4SWBVp/y+pFTVd2\ndFmbu8D2RLunI+hy7zjBEXbdRCxhyI16/lGG5wecg6Y4N26w3trUHymeTdAPQ+5s\nwE9F2MTz1D/FQrrb/pGa/6FcgusLvAvTJNCK/NAQNWx9ZJ1/teGCO8n2vhPi2995\n0id4V93HdLcCy2PBAL4ltAp4gCBjXXRXZuou2jC+syfB/o8kln0/1sblBVlheopM\nbQARAQABwsF2BCgBCgAgFiEEZ58e6SsZYJ3oFv3oHbGY+TUl7BoFAmksXlcCHQIA\nCgkQHbGY+TUl7Br/gQ//dL3MGWJo5mjTCsZ+GG/faFGtzO2k6CbwDQooH4fq4ZUf\nI3yEFWDqm7lrKRvt40MnYmP6wDyObjcRXbbHoyXTZriDfz88u4tayVxLXa/t2hVB\n2WxUQ8pjobZrq2HXnRGyFZcQjaKhS1u6qKovp45nTuPgVHCr8d7tZYYnY5EGkNz9\nzUokkCc9yJNuS6VftyEZ7Lbv7kVluAz48Q5lJ2RBBOPa+a6SEI/Vlz431ZUCxnz8\nW/m6u4NgpvSFHjDvpr7N+NGNZM7tdjZy3HTG/k7vnxUqAYR2NNd/xXOFT6LUTuAK\nDlO4n08lPW+/DOlqynVJXamHjXvMKlMlVNRANb9C2xt9yEsIrl0+6jMM/IFdaONX\nB5uqDUciCgEYR032MAg7L88kgOC3pjUjNkOZQB6YColoRhmhKiA1f46AxLObUWVe\nXwDueyIbhPdFie91F02gGwvsXF+Gp4RmcbG1G98oCVMR5Qb/eklL1Xr4wr9geRaO\nR9mMX/L1HEWykMX/bmapa+fuXGlOxG+RnJuyFvUVnZmbqCyOmVCRSS55ykUyu5wf\nSoxqJrcmGclvlPvXBr6vmwtfLYUFbqudMULZAWqGI5TWxZlRQqEJmmAD3t5cHhWU\nIMP50VMrn8SuYMhviOkcKzdkB4qYjeebMbCLvWu9rhupeW4ysa3psWxSbE1Sa7fC\nw7IEGAEKACYWIQRnnx7pKxlgnegW/egdsZj5NSXsGgUCYIauTgIbAgUJCWYBgAJA\nCRAdsZj5NSXsGsF0IAQZAQoAHRYhBCsQQmd/2BkMe5/A3CFh1y59zUJYBQJghq5O\nAAoJECFh1y59zUJYd/YP/idnBZt7ClccnTBIf4xXqEfLY9kWU3Xk5B8iPd/piBhP\nJM5/kLqEi1FzxrD6TRP/clApBnqGX3wciUSN9PgGvX/vP2gPl4BfJVn7h9i7SsJ+\nRzwZ+10eiVv/sp0Nl35Ie+2ToXSAKOR8reC7VSseYIKCIZ3d0OnrjpuaB+PRf8Zg\nBtrZjFOM5Us+xHx0gDSWuk94hraJsF98IIWkj3LeS7WG6CFVoTN8jMbGv8V/+GyY\nJ4UenPw0yFIJvGa4BWaxPQBHf+zFs01tg5LIiZ1AFHhn95mnaYLi8L2xguqo4faT\noPqisiXysjlHTAASzRfhShc0MqbQV3hM8ZsM2xezcIng2p9lsuIj7PBagh0tdc7R\nusNwSDKx9VhxsaaRpz6ecxTUtvqQZxVkrZCcdpHvwOcIjbyGwm55qSL5txnpUI7I\npv9a5DYxWWI5fvAA/Vb7y4Rta76HYLw9BC+ktMAJ9+Hye5s0rTWfxtUZQqKewl7J\nQ+W/f14tWxB/8fqRTwzLiVQF25QFx+2SMAflZ0QDIJ09awrjQLD82xY7N1A3RI/H\nOba/Jwr7GxZfejxUVL3W+/bBKnSkXadZPPbmM2ZhEcObpjhbfHerRc/CdiekJ9O4\nbWSD6X/w9P4TJYFGTjk3UM6kA5JIJhBVvOOQb6bNO2xA/xwW+pN/olV5t0qCJNxG\njP8QAJ0nQTG8RSEsx3yUduU2kEHVqTzvLfceH3dMTIxpcFvyiydXRwk2RkcubXqW\npXpaRWbINBERPsKykIdgYYf98r8T4imyF8CBcIP5Qrth4nVYTEjw3NwIfrIyJn0m\nt9K/A/MQHfaXK7Fh1h4rpFwA5ehHLKtmpMe5s/m2Z0/3VI0Xo0Ls6xRX3jn5mWf6\nO/hnve1dDwxMapCChQxrvvp7JBA7NYJcW6duC90sMZpU83SVT//ysOe6UOl1JSWM\nAcosfYhKBHRQBqOwhNCcUB6vMTmlDYf5KPgIYamaYoGwiTWv9ZaW2Zo0QWPpBvp5\nQi4dk/69y1XFnDwj73B9OLW4Nu1irVlivsNUVvhgP6zp8/4e1GgQQ4t87iQ5BBQT\n5IYMfZFHEPvb+5gS67i5FeUxNJZ7Dk33tUiPWCEH+kwS4AoM5A5AqZTw9ZslDwQC\nadz7WfP3h3ZeHKrwUuTrYgV/jKlgI0N9+iDRIkMiqwvyFegBJuHKuWzD5p3aO7Rx\nN7xJOf101r7BtYfg8SZWrmWOP3OlhV7NjC3F0Y2Rnk1Yvo3769So4hdutmRo/BXv\nhquGBJz8qYrboUe6QwdrYF/ycAmX5SSfNKZws3vsF4A49i94TOMkX8COXxx2tLsF\n+iqdj/MS4Y81F1vz0NQPPIOvu1bQOEU27GDEm44+94lprE3g\n=1ETd\n-----END PGP PUBLIC KEY BLOCK-----\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/BindingCandidatesFinderTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.Set;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationScope;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin;\nimport org.sonarsource.sonarlint.core.serverapi.component.ServerProject;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass BindingCandidatesFinderTests {\n\n  @RegisterExtension\n  static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private ConfigurationRepository configRepository;\n  private BindingClueProvider bindingClueProvider;\n  private SonarProjectsCache sonarProjectsCache;\n  private SonarLintCancelMonitor cancelMonitor;\n\n  private BindingCandidatesFinder underTest;\n\n  @BeforeEach\n  void setUp() {\n    configRepository = mock(ConfigurationRepository.class);\n    bindingClueProvider = mock(BindingClueProvider.class);\n    sonarProjectsCache = mock(SonarProjectsCache.class);\n    cancelMonitor = mock(SonarLintCancelMonitor.class);\n    underTest = new BindingCandidatesFinder(configRepository, bindingClueProvider, sonarProjectsCache);\n  }\n\n  @Test\n  void should_mark_scope_as_shared_configuration_when_any_clue_has_shared_config_origin() {\n    var scope = new ConfigurationScope(\"scope1\", null, true, \"Test Scope\");\n    var sharedConfigClue = new BindingClueProvider.UnknownBindingClue(\"projectKey\", BindingSuggestionOrigin.SHARED_CONFIGURATION);\n    var propertiesFileClue = new BindingClueProvider.UnknownBindingClue(\"projectKey\", BindingSuggestionOrigin.PROPERTIES_FILE);\n    \n    when(configRepository.getAllBindableUnboundScopes()).thenReturn(List.of(scope));\n    when(bindingClueProvider.collectBindingCluesWithConnections(\"scope1\", Set.of(\"conn1\"), cancelMonitor))\n      .thenReturn(List.of(\n        new BindingClueProvider.BindingClueWithConnections(sharedConfigClue, Set.of(\"conn1\")),\n        new BindingClueProvider.BindingClueWithConnections(propertiesFileClue, Set.of(\"conn1\"))\n      ));\n\n    var candidates = underTest.findConfigScopesToBind(\"conn1\", \"projectKey\", cancelMonitor);\n\n    assertThat(candidates).hasSize(1);\n    var candidate = candidates.iterator().next();\n    assertThat(candidate.getConfigurationScope()).isEqualTo(scope);\n    assertThat(candidate.getOrigin()).isEqualTo(BindingSuggestionOrigin.SHARED_CONFIGURATION);\n  }\n\n  @Test\n  void should_not_mark_scope_as_shared_configuration_when_no_clue_has_shared_config_origin() {\n    var scope = new ConfigurationScope(\"scope1\", null, true, \"Test Scope\");\n    var propertiesFileClue = new BindingClueProvider.UnknownBindingClue(\"projectKey\", BindingSuggestionOrigin.PROPERTIES_FILE);\n    var remoteUrlClue = new BindingClueProvider.UnknownBindingClue(\"projectKey\", BindingSuggestionOrigin.REMOTE_URL);\n    \n    when(configRepository.getAllBindableUnboundScopes()).thenReturn(List.of(scope));\n    when(bindingClueProvider.collectBindingCluesWithConnections(\"scope1\", Set.of(\"conn1\"), cancelMonitor))\n      .thenReturn(List.of(\n        new BindingClueProvider.BindingClueWithConnections(propertiesFileClue, Set.of(\"conn1\")),\n        new BindingClueProvider.BindingClueWithConnections(remoteUrlClue, Set.of(\"conn1\"))\n      ));\n\n    var candidates = underTest.findConfigScopesToBind(\"conn1\", \"projectKey\", cancelMonitor);\n\n    assertThat(candidates).hasSize(1);\n    var candidate = candidates.iterator().next();\n    assertThat(candidate.getConfigurationScope()).isEqualTo(scope);\n    assertThat(candidate.getOrigin()).isEqualTo(BindingSuggestionOrigin.PROPERTIES_FILE);\n  }\n\n  @Test\n  void should_select_project_name_when_name_matches_and_no_shared_or_properties_file_clues() {\n    var scope = new ConfigurationScope(\"scope1\", null, true, \"MyProj\");\n\n    when(configRepository.getAllBindableUnboundScopes()).thenReturn(List.of(scope));\n    when(bindingClueProvider.collectBindingCluesWithConnections(\"scope1\", Set.of(\"conn1\"), cancelMonitor))\n      .thenReturn(List.of(\n      ));\n    when(sonarProjectsCache.getSonarProject(\"conn1\", \"projectKey\", cancelMonitor))\n      .thenReturn(Optional.of(new ServerProject(\"projectKey\", \"MyProj\", false)));\n\n    var candidates = underTest.findConfigScopesToBind(\"conn1\", \"projectKey\", cancelMonitor);\n\n    assertThat(candidates).hasSize(1);\n    var candidate = candidates.iterator().next();\n    assertThat(candidate.getConfigurationScope()).isEqualTo(scope);\n    assertThat(candidate.getOrigin()).isEqualTo(BindingSuggestionOrigin.PROJECT_NAME);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/BindingClueProviderTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport java.net.URI;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.Set;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.fs.ClientFile;\nimport org.sonarsource.sonarlint.core.fs.ClientFileSystemService;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarCloudConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarQubeConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass BindingClueProviderTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  public static final String SQ_CONNECTION_ID_1 = \"sq1\";\n  public static final String SQ_CONNECTION_ID_2 = \"sq2\";\n  public static final String SC_CONNECTION_ID_1 = \"sc1\";\n  public static final String SC_CONNECTION_ID_2 = \"sc2\";\n  private static final String PROJECT_KEY_1 = \"myproject1\";\n  public static final String MY_ORG_1 = \"myOrg1\";\n  public static final String MY_ORG_2 = \"myOrg2\";\n\n  public static final String CONFIG_SCOPE_ID = \"configScopeId\";\n  private final ConnectionConfigurationRepository connectionRepository = mock(ConnectionConfigurationRepository.class);\n  private final ClientFileSystemService clientFs = mock(ClientFileSystemService.class);\n  BindingClueProvider underTest = new BindingClueProvider(connectionRepository, clientFs, SonarCloudActiveEnvironment.prod());\n\n  @Test\n  void should_detect_sonar_scanner_for_sonarqube() {\n    mockFindFileByNamesInScope(List.of(buildClientFile(\"sonar-project.properties\", \"path/to/sonar-project.properties\", \"sonar.host.url=http://mysonarqube.org\\n\")));\n\n    when(connectionRepository.getConnectionById(SQ_CONNECTION_ID_1)).thenReturn(new SonarQubeConnectionConfiguration(SQ_CONNECTION_ID_1, \"http://mysonarqube.org\", true));\n\n    var bindingClueWithConnections = underTest.collectBindingCluesWithConnections(CONFIG_SCOPE_ID, Set.of(SQ_CONNECTION_ID_1), new SonarLintCancelMonitor());\n\n    assertThat(bindingClueWithConnections).hasSize(1);\n    var bindingClueWithConnections1 = bindingClueWithConnections.get(0);\n    assertThat(bindingClueWithConnections1.getBindingClue()).isInstanceOf(BindingClueProvider.SonarQubeBindingClue.class);\n    assertThat(bindingClueWithConnections1.getBindingClue().getSonarProjectKey()).isNull();\n    assertThat(bindingClueWithConnections1.getConnectionIds()).containsOnly(SQ_CONNECTION_ID_1);\n  }\n\n  @Test\n  void should_detect_sonar_scanner_for_sonarqube_with_project_key() {\n    mockFindFileByNamesInScope(\n      List.of(buildClientFile(\"sonar-project.properties\", \"path/to/sonar-project.properties\", \"sonar.host.url=http://mysonarqube.org\\nsonar.projectKey=\" + PROJECT_KEY_1)));\n\n    when(connectionRepository.getConnectionById(SQ_CONNECTION_ID_1)).thenReturn(new SonarQubeConnectionConfiguration(SQ_CONNECTION_ID_1, \"http://mysonarqube.org\", true));\n\n    var bindingClueWithConnections = underTest.collectBindingCluesWithConnections(CONFIG_SCOPE_ID, Set.of(SQ_CONNECTION_ID_1), new SonarLintCancelMonitor());\n\n    assertThat(bindingClueWithConnections).hasSize(1);\n    var bindingClueWithConnections1 = bindingClueWithConnections.get(0);\n    assertThat(bindingClueWithConnections1.getBindingClue().getSonarProjectKey()).isEqualTo(PROJECT_KEY_1);\n  }\n\n  @Test\n  void should_match_multiple_connections() {\n    mockFindFileByNamesInScope(List.of(buildClientFile(\"sonar-project.properties\", \"path/to/sonar-project.properties\", \"sonar.host.url=http://mysonarqube.org\\n\")));\n\n    when(connectionRepository.getConnectionById(SQ_CONNECTION_ID_1)).thenReturn(new SonarQubeConnectionConfiguration(SQ_CONNECTION_ID_1, \"http://mysonarqube.org\", true));\n    when(connectionRepository.getConnectionById(SQ_CONNECTION_ID_2)).thenReturn(new SonarQubeConnectionConfiguration(SQ_CONNECTION_ID_2, \"http://Mysonarqube.org/\", true));\n\n    var bindingClueWithConnections = underTest.collectBindingCluesWithConnections(CONFIG_SCOPE_ID, Set.of(SQ_CONNECTION_ID_1, SQ_CONNECTION_ID_2), new SonarLintCancelMonitor());\n\n    assertThat(bindingClueWithConnections).hasSize(1);\n    var bindingClueWithConnections1 = bindingClueWithConnections.get(0);\n    assertThat(bindingClueWithConnections1.getConnectionIds()).containsOnly(SQ_CONNECTION_ID_1, SQ_CONNECTION_ID_2);\n  }\n\n  @Test\n  void should_detect_sonar_scanner_for_sonarcloud_based_on_url() {\n    mockFindFileByNamesInScope(\n      List.of(buildClientFile(\"sonar-project.properties\", \"path/to/sonar-project.properties\", \"sonar.host.url=https://sonarcloud.io\\nsonar.projectKey=\" + PROJECT_KEY_1)));\n\n    when(connectionRepository.getConnectionById(SC_CONNECTION_ID_1)).thenReturn(new SonarCloudConnectionConfiguration(SonarCloudRegion.EU.getProductionUri(), SonarCloudRegion.EU.getApiProductionUri(), SC_CONNECTION_ID_1, MY_ORG_1, SonarCloudRegion.EU, true));\n    when(connectionRepository.getConnectionById(SC_CONNECTION_ID_2)).thenReturn(new SonarCloudConnectionConfiguration(SonarCloudRegion.EU.getProductionUri(), SonarCloudRegion.EU.getApiProductionUri(), SC_CONNECTION_ID_2, MY_ORG_2, SonarCloudRegion.EU, true));\n\n    var bindingClueWithConnections = underTest.collectBindingCluesWithConnections(CONFIG_SCOPE_ID, Set.of(SC_CONNECTION_ID_1, SC_CONNECTION_ID_2), new SonarLintCancelMonitor());\n\n    assertThat(bindingClueWithConnections).hasSize(1);\n    var bindingClueWithConnections1 = bindingClueWithConnections.get(0);\n    assertThat(bindingClueWithConnections1.getBindingClue()).isInstanceOf(BindingClueProvider.SonarCloudBindingClue.class);\n    assertThat(bindingClueWithConnections1.getBindingClue().getSonarProjectKey()).isEqualTo(PROJECT_KEY_1);\n    assertThat(bindingClueWithConnections1.getConnectionIds()).containsOnly(SC_CONNECTION_ID_1, SC_CONNECTION_ID_2);\n  }\n\n  @Test\n  void should_detect_sonar_scanner_for_sonarcloud_based_on_url_and_region() {\n    mockFindFileByNamesInScope(\n      List.of(buildClientFile(\"sonar-project.properties\", \"path/to/sonar-project.properties\", \"sonar.host.url=https://sonarcloud.io\\nsonar.projectKey=\" + PROJECT_KEY_1 + \"\\nsonar.region=US\")));\n\n    when(connectionRepository.getConnectionById(SC_CONNECTION_ID_1)).thenReturn(new SonarCloudConnectionConfiguration(SonarCloudRegion.US.getProductionUri(), SonarCloudRegion.US.getApiProductionUri(), SC_CONNECTION_ID_1, MY_ORG_1, SonarCloudRegion.US, true));\n\n    var bindingClueWithConnections = underTest.collectBindingCluesWithConnections(CONFIG_SCOPE_ID, Set.of(SC_CONNECTION_ID_1), new SonarLintCancelMonitor());\n\n    assertThat(bindingClueWithConnections).hasSize(1);\n    var bindingClueWithConnections1 = bindingClueWithConnections.get(0);\n    assertThat(bindingClueWithConnections1.getBindingClue()).isInstanceOf(BindingClueProvider.SonarCloudBindingClue.class);\n    assertThat(bindingClueWithConnections1.getBindingClue().getSonarProjectKey()).isEqualTo(PROJECT_KEY_1);\n    assertThat(bindingClueWithConnections1.getConnectionIds()).containsOnly(SC_CONNECTION_ID_1);\n    assertThat(bindingClueWithConnections1.getBindingClue().getClass()).isEqualTo(BindingClueProvider.SonarCloudBindingClue.class);\n    var sonarCloudBindingClue = (BindingClueProvider.SonarCloudBindingClue) bindingClueWithConnections1.getBindingClue();\n    assertThat(sonarCloudBindingClue.getRegion().name()).isEqualTo(SonarCloudRegion.US.name());\n  }\n\n  @Test\n  void should_detect_sonar_scanner_for_sonarcloud_based_on_organization() {\n    mockFindFileByNamesInScope(List.of(buildClientFile(\"sonar-project.properties\", \"path/to/sonar-project.properties\", \"sonar.organization=\" + MY_ORG_2)));\n\n    when(connectionRepository.getConnectionById(SC_CONNECTION_ID_1)).thenReturn(new SonarCloudConnectionConfiguration(SonarCloudRegion.EU.getProductionUri(), SonarCloudRegion.EU.getApiProductionUri(), SC_CONNECTION_ID_1, MY_ORG_1, SonarCloudRegion.EU, true));\n    when(connectionRepository.getConnectionById(SC_CONNECTION_ID_2)).thenReturn(new SonarCloudConnectionConfiguration(SonarCloudRegion.EU.getProductionUri(), SonarCloudRegion.EU.getApiProductionUri(), SC_CONNECTION_ID_2, MY_ORG_2, SonarCloudRegion.EU, true));\n\n    var bindingClueWithConnections = underTest.collectBindingCluesWithConnections(CONFIG_SCOPE_ID, Set.of(SC_CONNECTION_ID_1, SC_CONNECTION_ID_2), new SonarLintCancelMonitor());\n\n    assertThat(bindingClueWithConnections).hasSize(1);\n    var bindingClueWithConnections1 = bindingClueWithConnections.get(0);\n    assertThat(bindingClueWithConnections1.getBindingClue()).isInstanceOf(BindingClueProvider.SonarCloudBindingClue.class);\n    assertThat(bindingClueWithConnections1.getBindingClue().getSonarProjectKey()).isNull();\n    assertThat(bindingClueWithConnections1.getConnectionIds()).containsOnly(SC_CONNECTION_ID_2);\n  }\n\n  @Test\n  void should_detect_autoscan_for_sonarcloud() {\n    mockFindFileByNamesInScope(List.of(buildClientFile(\".sonarcloud.properties\", \"path/to/.sonarcloud.properties\", \"sonar.projectKey=\" + PROJECT_KEY_1)));\n\n    when(connectionRepository.getConnectionById(SC_CONNECTION_ID_1)).thenReturn(new SonarCloudConnectionConfiguration(SonarCloudRegion.EU.getProductionUri(), SonarCloudRegion.EU.getApiProductionUri(), SC_CONNECTION_ID_1, MY_ORG_1, SonarCloudRegion.EU, true));\n    when(connectionRepository.getConnectionById(SQ_CONNECTION_ID_1)).thenReturn(new SonarQubeConnectionConfiguration(SQ_CONNECTION_ID_1, \"http://mysonarqube.org\", true));\n\n    var bindingClueWithConnections = underTest.collectBindingCluesWithConnections(CONFIG_SCOPE_ID, Set.of(SC_CONNECTION_ID_1, SQ_CONNECTION_ID_1), new SonarLintCancelMonitor());\n\n    assertThat(bindingClueWithConnections).hasSize(1);\n    var bindingClueWithConnections1 = bindingClueWithConnections.get(0);\n    assertThat(bindingClueWithConnections1.getBindingClue()).isInstanceOf(BindingClueProvider.SonarCloudBindingClue.class);\n    assertThat(bindingClueWithConnections1.getBindingClue().getSonarProjectKey()).isEqualTo(PROJECT_KEY_1);\n    assertThat(bindingClueWithConnections1.getConnectionIds()).containsOnly(SC_CONNECTION_ID_1);\n  }\n\n  @Test\n  void should_detect_autoscan_for_sonarcloud_and_region() {\n    mockFindFileByNamesInScope(List.of(buildClientFile(\".sonarcloud.properties\", \"path/to/.sonarcloud.properties\", \"sonar.projectKey=\" + PROJECT_KEY_1 + \"\\nsonar.region=US\")));\n\n    when(connectionRepository.getConnectionById(SC_CONNECTION_ID_1)).thenReturn(new SonarCloudConnectionConfiguration(SonarCloudRegion.US.getProductionUri(), SonarCloudRegion.US.getApiProductionUri(), SC_CONNECTION_ID_1, MY_ORG_1, SonarCloudRegion.US, true));\n\n    var bindingClueWithConnections = underTest.collectBindingCluesWithConnections(CONFIG_SCOPE_ID, Set.of(SC_CONNECTION_ID_1, SQ_CONNECTION_ID_1), new SonarLintCancelMonitor());\n\n    assertThat(bindingClueWithConnections).hasSize(1);\n    var bindingClueWithConnections1 = bindingClueWithConnections.get(0);\n    assertThat(bindingClueWithConnections1.getBindingClue()).isInstanceOf(BindingClueProvider.SonarCloudBindingClue.class);\n    assertThat(bindingClueWithConnections1.getBindingClue().getSonarProjectKey()).isEqualTo(PROJECT_KEY_1);\n    assertThat(bindingClueWithConnections1.getConnectionIds()).containsOnly(SC_CONNECTION_ID_1);\n    assertThat(bindingClueWithConnections1.getBindingClue().getClass()).isEqualTo(BindingClueProvider.SonarCloudBindingClue.class);\n    var sonarCloudBindingClue = (BindingClueProvider.SonarCloudBindingClue) bindingClueWithConnections1.getBindingClue();\n    assertThat(sonarCloudBindingClue.getRegion().name()).isEqualTo(SonarCloudRegion.US.name());\n  }\n\n  @Test\n  void should_detect_unknown_with_project_key() {\n    mockFindFileByNamesInScope(List.of(buildClientFile(\"sonar-project.properties\", \"path/to/sonar-project.properties\", \"sonar.projectKey=\" + PROJECT_KEY_1)));\n\n    when(connectionRepository.getConnectionById(SC_CONNECTION_ID_1)).thenReturn(new SonarCloudConnectionConfiguration(SonarCloudRegion.EU.getProductionUri(), SonarCloudRegion.EU.getApiProductionUri(), SC_CONNECTION_ID_1, MY_ORG_1, SonarCloudRegion.EU, true));\n    when(connectionRepository.getConnectionById(SQ_CONNECTION_ID_1)).thenReturn(new SonarQubeConnectionConfiguration(SQ_CONNECTION_ID_1, \"http://mysonarqube.org\", true));\n\n    var bindingClueWithConnections = underTest.collectBindingCluesWithConnections(CONFIG_SCOPE_ID, Set.of(SC_CONNECTION_ID_1, SQ_CONNECTION_ID_1), new SonarLintCancelMonitor());\n\n    assertThat(bindingClueWithConnections).hasSize(1);\n    var bindingClueWithConnections1 = bindingClueWithConnections.get(0);\n    assertThat(bindingClueWithConnections1.getBindingClue()).isInstanceOf(BindingClueProvider.UnknownBindingClue.class);\n    assertThat(bindingClueWithConnections1.getBindingClue().getSonarProjectKey()).isEqualTo(PROJECT_KEY_1);\n    assertThat(bindingClueWithConnections1.getConnectionIds()).containsOnly(SC_CONNECTION_ID_1, SQ_CONNECTION_ID_1);\n  }\n\n  @Test\n  void ignore_scanner_file_without_clue() {\n    mockFindFileByNamesInScope(List.of(buildClientFile(\"sonar-project.properties\", \"path/to/sonar-project.properties\", \"sonar.sources=src\")));\n\n    when(connectionRepository.getConnectionById(SC_CONNECTION_ID_1)).thenReturn(new SonarCloudConnectionConfiguration(SonarCloudRegion.EU.getProductionUri(), SonarCloudRegion.EU.getApiProductionUri(), SC_CONNECTION_ID_1, MY_ORG_1, SonarCloudRegion.EU, true));\n    when(connectionRepository.getConnectionById(SQ_CONNECTION_ID_1)).thenReturn(new SonarQubeConnectionConfiguration(SQ_CONNECTION_ID_1, \"http://mysonarqube.org\", true));\n\n    var bindingClueWithConnections = underTest.collectBindingCluesWithConnections(CONFIG_SCOPE_ID, Set.of(SC_CONNECTION_ID_1, SQ_CONNECTION_ID_1), new SonarLintCancelMonitor());\n\n    assertThat(bindingClueWithConnections).isEmpty();\n  }\n\n  @Test\n  void ignore_scanner_file_invalid_content() {\n    mockFindFileByNamesInScope(List.of(buildClientFile(\"sonar-project.properties\", \"path/to/sonar-project.properties\", \"\\\\usonar.projectKey=\" + PROJECT_KEY_1)));\n\n    when(connectionRepository.getConnectionById(SC_CONNECTION_ID_1)).thenReturn(new SonarCloudConnectionConfiguration(SonarCloudRegion.EU.getProductionUri(), SonarCloudRegion.EU.getApiProductionUri(), SC_CONNECTION_ID_1, MY_ORG_1, SonarCloudRegion.EU, true));\n    when(connectionRepository.getConnectionById(SQ_CONNECTION_ID_1)).thenReturn(new SonarQubeConnectionConfiguration(SQ_CONNECTION_ID_1, \"http://mysonarqube.org\", true));\n\n    var bindingClueWithConnections = underTest.collectBindingCluesWithConnections(CONFIG_SCOPE_ID, Set.of(SC_CONNECTION_ID_1, SQ_CONNECTION_ID_1), new SonarLintCancelMonitor());\n\n    assertThat(bindingClueWithConnections).isEmpty();\n    assertThat(logTester.logs(LogOutput.Level.ERROR)).contains(\"Unable to parse content of file 'file://path/to/sonar-project.properties'\");\n  }\n\n  @Test\n  void should_not_detect_sonarlint_configuration_file_if_wrong_content() {\n    mockFindSonarlintConfigurationFilesByScope(List.of(buildClientFile(\"connectedMode.json\", \"/path/to/.sonarlint/connectedMode.json\", \"{\\\"sonarCloudOrganization\\\": \\\"org\\\",\\\"sonarQubeUri\\\": \\\"http://mysonarqube.org\\\"}\")));\n\n    when(connectionRepository.getConnectionById(SQ_CONNECTION_ID_1)).thenReturn(new SonarQubeConnectionConfiguration(SQ_CONNECTION_ID_1, \"http://mysonarqube.org\", true));\n\n    var bindingClueWithConnections = underTest.collectBindingCluesWithConnections(CONFIG_SCOPE_ID, Set.of(SQ_CONNECTION_ID_1), new SonarLintCancelMonitor());\n\n    assertThat(bindingClueWithConnections).isEmpty();\n  }\n\n  @Test\n  void should_not_detect_sonarlint_configuration_file_if_not_in_right_folder() {\n    mockFindSonarlintConfigurationFilesByScope(List.of(buildClientFile(\"connectedMode.json\", \"/path/to/connections/connectedMode.json\", \"{\\\"projectKey\\\": \\\"pKey\\\",\\\"sonarQubeUri\\\": \\\"http://mysonarqube.org\\\"}\")));\n\n    when(connectionRepository.getConnectionById(SQ_CONNECTION_ID_1)).thenReturn(new SonarQubeConnectionConfiguration(SQ_CONNECTION_ID_1, \"http://mysonarqube.org\", true));\n\n    var bindingClueWithConnections = underTest.collectBindingCluesWithConnections(CONFIG_SCOPE_ID, Set.of(SQ_CONNECTION_ID_1), new SonarLintCancelMonitor());\n\n    assertThat(bindingClueWithConnections).isEmpty();\n  }\n\n  @Test\n  void should_not_detect_sonarlint_configuration_file_if_not_json() {\n    var file = new ClientFile(URI.create(\"/path/to/.sonarlint/connectedMode.txt\"), CONFIG_SCOPE_ID, Path.of(\"/path/to/.sonarlint/connectedMode.txt\"), false, null, null, null, true);\n\n    assertThat(file.isSonarlintConfigurationFile()).isFalse();\n  }\n\n  @Test\n  void should_not_detect_sonarlint_configuration_file_if_wrong_folder() {\n    var file = new ClientFile(URI.create(\"/path/to/.sonarlint/connectedMode.json\"), CONFIG_SCOPE_ID, Path.of(\"/path/to/.sonarlint2/connectedMode.json\"), false, null, null, null, true);\n\n    assertThat(file.isSonarlintConfigurationFile()).isFalse();\n  }\n\n  @Test\n  void should_set_origin_properties_file_when_clue_created_from_properties() {\n    mockFindFileByNamesInScope(List.of(\n      buildClientFile(\"sonar-project.properties\", \"path/to/sonar-project.properties\", \"sonar.host.url=http://mysonarqube.org\\nsonar.projectKey=\" + PROJECT_KEY_1)\n    ));\n\n    when(connectionRepository.getConnectionById(SQ_CONNECTION_ID_1)).thenReturn(new SonarQubeConnectionConfiguration(SQ_CONNECTION_ID_1, \"http://mysonarqube.org\", true));\n\n    var cluesWithConn = underTest.collectBindingCluesWithConnections(CONFIG_SCOPE_ID, Set.of(SQ_CONNECTION_ID_1), new SonarLintCancelMonitor());\n\n    assertThat(cluesWithConn).hasSize(1);\n    var clue = cluesWithConn.get(0).getBindingClue();\n    assertThat(clue.getOrigin()).isEqualTo(BindingSuggestionOrigin.PROPERTIES_FILE);\n  }\n\n  @Test\n  void should_set_origin_shared_configuration_when_clue_created_from_shared_config() {\n    // Simulate a shared configuration file\n    var file = new ClientFile(URI.create(\"file:///path/to/.sonarlint/connectedMode.json\"), CONFIG_SCOPE_ID, Paths.get(\"/path/to/.sonarlint/connectedMode.json\"), false, null, null, null, true);\n    file.setDirty(\"{\\\"projectKey\\\": \\\"\" + PROJECT_KEY_1 + \"\\\", \\\"sonarQubeUri\\\": \\\"http://mysonarqube.org\\\"}\");\n    mockFindSonarlintConfigurationFilesByScope(List.of(file));\n\n    when(connectionRepository.getConnectionById(SQ_CONNECTION_ID_1)).thenReturn(new SonarQubeConnectionConfiguration(SQ_CONNECTION_ID_1, \"http://mysonarqube.org\", true));\n\n    var cluesWithConn = underTest.collectBindingCluesWithConnections(CONFIG_SCOPE_ID, Set.of(SQ_CONNECTION_ID_1), new SonarLintCancelMonitor());\n\n    assertThat(cluesWithConn).hasSize(1);\n    var clue = cluesWithConn.get(0).getBindingClue();\n    assertThat(clue.getOrigin()).isEqualTo(BindingSuggestionOrigin.SHARED_CONFIGURATION);\n  }\n\n  private ClientFile buildClientFile(String filename, String relativePath, String content) {\n    var file = new ClientFile(URI.create(\"file://\" + relativePath), CONFIG_SCOPE_ID, Paths.get(relativePath), false, null, null, null, true);\n    file.setDirty(content);\n    return file;\n  }\n\n  private void mockFindFileByNamesInScope(List<ClientFile> files) {\n    when(clientFs.findFilesByNamesInScope(any(), any())).thenReturn(files);\n  }\n\n  private void mockFindSonarlintConfigurationFilesByScope(List<ClientFile> files) {\n    when(clientFs.findSonarlintConfigurationFilesByScope(any())).thenReturn(files);\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/BindingSuggestionProviderTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport com.google.common.collect.ImmutableSortedSet;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.mockito.ArgumentCaptor;\nimport org.sonarsource.sonarlint.core.commons.ConnectionKind;\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.commons.util.git.GitService;\nimport org.sonarsource.sonarlint.core.event.BindingConfigChangedEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationAddedEvent;\nimport org.sonarsource.sonarlint.core.fs.ClientFileSystemService;\nimport org.sonarsource.sonarlint.core.repository.config.BindingConfiguration;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationScope;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarCloudConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarQubeConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.SuggestBindingParams;\nimport org.sonarsource.sonarlint.core.serverapi.component.ServerProject;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryService;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.mockStatic;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin.PROJECT_NAME;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin.REMOTE_URL;\n\nclass BindingSuggestionProviderTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  public static final String SQ_1_ID = \"sq1\";\n  public static final String SC_1_ID = \"sc1\";\n  public static final String SQ_2_ID = \"sq2\";\n  public static final SonarQubeConnectionConfiguration SQ_1 = new SonarQubeConnectionConfiguration(SQ_1_ID, \"http://mysonarqube.com\", true);\n  public static final SonarCloudConnectionConfiguration SC_1 = new SonarCloudConnectionConfiguration(SonarCloudRegion.EU.getProductionUri(), SonarCloudRegion.EU.getApiProductionUri(), SC_1_ID, \"myorg\", SonarCloudRegion.EU, true);\n  public static final String CONFIG_SCOPE_ID_1 = \"configScope1\";\n  public static final String PROJECT_KEY_1 = \"projectKey1\";\n  public static final ServerProject SERVER_PROJECT_1 = serverProject(PROJECT_KEY_1, \"Project 1\");\n\n  private final ConfigurationRepository configRepository = mock(ConfigurationRepository.class);\n  private final ConnectionConfigurationRepository connectionRepository = mock(ConnectionConfigurationRepository.class);\n  private final SonarLintRpcClient client = mock(SonarLintRpcClient.class);\n  private final BindingClueProvider bindingClueProvider = mock(BindingClueProvider.class);\n  private final SonarProjectsCache sonarProjectsCache = mock(SonarProjectsCache.class);\n  private final SonarQubeClientManager sonarQubeClientManager = mock(SonarQubeClientManager.class);\n  private final ClientFileSystemService clientFs = mock(ClientFileSystemService.class);\n  private final TelemetryService telemetryService = mock(TelemetryService.class);\n\n  private final BindingSuggestionProvider underTest = new BindingSuggestionProvider(configRepository, connectionRepository, client, bindingClueProvider, sonarProjectsCache, sonarQubeClientManager, clientFs, telemetryService);\n\n  @BeforeEach\n  void setup() {\n    when(sonarProjectsCache.getTextSearchIndex(anyString(), any(SonarLintCancelMonitor.class))).thenReturn(new TextSearchIndex<>());\n    logTester.clear();\n  }\n\n  @Test\n  void trigger_suggest_binding_if_config_flag_turned_on() {\n    when(connectionRepository.getConnectionsById()).thenReturn(Map.of(SQ_1_ID, SQ_1));\n\n    underTest.bindingConfigChanged(new BindingConfigChangedEvent(CONFIG_SCOPE_ID_1, BindingConfiguration.noBinding(true),\n      BindingConfiguration.noBinding()));\n\n    assertThat(logTester.logs(LogOutput.Level.DEBUG)).contains(\"Binding suggestion computation queued for config scopes '\" + CONFIG_SCOPE_ID_1 + \"'...\");\n  }\n\n  @Test\n  void dont_trigger_suggest_binding_if_config_flag_turned_off() {\n    when(connectionRepository.getConnectionsById()).thenReturn(Map.of(SQ_1_ID, SQ_1));\n\n    underTest.bindingConfigChanged(new BindingConfigChangedEvent(CONFIG_SCOPE_ID_1, BindingConfiguration.noBinding(),\n      BindingConfiguration.noBinding(true)));\n\n    assertThat(logTester.logs()).isEmpty();\n  }\n\n  @Test\n  void trigger_suggest_binding_if_connection_added_and_at_least_one_config_scope() {\n    when(connectionRepository.getConnectionById(SQ_1_ID)).thenReturn(SQ_1);\n    when(configRepository.getConfigScopeIds()).thenReturn(Set.of(\"id1\"));\n    underTest.connectionAdded(new ConnectionConfigurationAddedEvent(SQ_1_ID, ConnectionKind.SONARQUBE));\n\n    assertThat(logTester.logs(LogOutput.Level.DEBUG)).contains(\"Binding suggestions computation queued for connection '\" + SQ_1_ID + \"'...\");\n  }\n\n  @Test\n  void dont_trigger_suggest_binding_if_connection_added_but_no_config_scopes() {\n    when(connectionRepository.getConnectionById(SQ_1_ID)).thenReturn(SQ_1);\n    when(configRepository.getConfigScopeIds()).thenReturn(Set.of());\n\n    underTest.connectionAdded(new ConnectionConfigurationAddedEvent(SQ_1_ID, ConnectionKind.SONARQUBE));\n\n    assertThat(logTester.logs()).isEmpty();\n  }\n\n  @Test\n  void dont_trigger_suggest_binding_if_connection_added_but_then_gone() {\n    when(connectionRepository.getConnectionById(SQ_1_ID)).thenReturn(null);\n\n    underTest.connectionAdded(new ConnectionConfigurationAddedEvent(SQ_1_ID, ConnectionKind.SONARQUBE));\n\n    assertThat(logTester.logs(LogOutput.Level.DEBUG)).isEmpty();\n  }\n\n  @Test\n  void skip_suggestions_for_non_eligible_config_scopes() {\n    when(connectionRepository.getConnectionsById()).thenReturn(Map.of(SQ_1_ID, SQ_1));\n    when(connectionRepository.getConnectionById(SQ_1_ID)).thenReturn(SQ_1);\n\n    when(configRepository.getConfigurationScope(\"configScopeWithNoBinding\")).thenReturn(new ConfigurationScope(\"configScopeWithNoBinding\", null, true, \"Binding gone!\"));\n\n    when(configRepository.getBindingConfiguration(\"configScopeWithNoConfig\")).thenReturn(BindingConfiguration.noBinding());\n\n    when(configRepository.getConfigurationScope(\"configScopeNotBindable\")).thenReturn(new ConfigurationScope(\"configScopeNotBindable\", null, false, \"Not bindable\"));\n    when(configRepository.getBindingConfiguration(\"configScopeNotBindable\")).thenReturn(BindingConfiguration.noBinding());\n\n    when(configRepository.getConfigurationScope(\"alreadyBound\")).thenReturn(new ConfigurationScope(\"alreadyBound\", null, true, \"Already bound\"));\n    when(configRepository.getBindingConfiguration(\"alreadyBound\")).thenReturn(new BindingConfiguration(SQ_1_ID, PROJECT_KEY_1, false));\n\n    when(configRepository.getConfigurationScope(\"suggestionsDisabled\")).thenReturn(new ConfigurationScope(\"suggestionsDisabled\", null, true, \"Suggestion disabled\"));\n    when(configRepository.getBindingConfiguration(\"suggestionsDisabled\")).thenReturn(BindingConfiguration.noBinding(true));\n\n    underTest.suggestBindingForGivenScopesAndAllConnections(ImmutableSortedSet.of(\"configScopeWithNoBinding\", \"configScopeWithNoConfig\", \"configScopeNotBindable\", \"alreadyBound\", \"suggestionsDisabled\"));\n\n    await().untilAsserted(() -> assertThat(logTester.logs(LogOutput.Level.DEBUG))\n      .contains(\n        \"Configuration scope 'configScopeWithNoBinding' is gone.\",\n        \"Configuration scope 'configScopeWithNoConfig' is gone.\",\n        \"Configuration scope 'configScopeNotBindable' is not bindable.\",\n        \"Configuration scope 'alreadyBound' is already bound.\",\n        \"Configuration scope 'suggestionsDisabled' has binding suggestions disabled.\"));\n  }\n\n  @Test\n  void compute_suggestions_for_config_scope_with_invalid_binding() {\n    when(connectionRepository.getConnectionsById()).thenReturn(Map.of(SQ_1_ID, SQ_1));\n    when(connectionRepository.getConnectionById(SQ_1_ID)).thenReturn(SQ_1);\n\n    when(configRepository.getConfigurationScope(\"brokenBinding1\")).thenReturn(new ConfigurationScope(\"brokenBinding1\", null, true, \"Already bound\"));\n    when(configRepository.getBindingConfiguration(\"brokenBinding1\")).thenReturn(new BindingConfiguration(null, PROJECT_KEY_1, false));\n\n    when(configRepository.getConfigurationScope(\"brokenBinding2\")).thenReturn(new ConfigurationScope(\"brokenBinding2\", null, true, \"Already bound\"));\n    when(configRepository.getBindingConfiguration(\"brokenBinding2\")).thenReturn(new BindingConfiguration(SQ_1_ID, null, false));\n\n    when(configRepository.getConfigurationScope(\"connectionGone\")).thenReturn(new ConfigurationScope(\"connectionGone\", null, true, \"Already bound\"));\n    when(configRepository.getBindingConfiguration(\"connectionGone\")).thenReturn(new BindingConfiguration(SQ_2_ID, PROJECT_KEY_1, false));\n\n    underTest.suggestBindingForGivenScopesAndAllConnections(ImmutableSortedSet.of(\"brokenBinding1\", \"brokenBinding2\", \"connectionGone\"));\n\n    await().untilAsserted(() -> assertThat(logTester.logs(LogOutput.Level.DEBUG))\n      .contains(\n        \"Found 0 suggestions for configuration scope 'brokenBinding1'\",\n        \"Found 0 suggestions for configuration scope 'brokenBinding2'\",\n        \"Found 0 suggestions for configuration scope 'connectionGone'\"));\n  }\n\n  @Test\n  void compute_suggestions_favor_search_by_project_key() {\n    when(connectionRepository.getConnectionsById()).thenReturn(Map.of(SQ_1_ID, SQ_1));\n    when(connectionRepository.getConnectionById(SQ_1_ID)).thenReturn(SQ_1);\n\n    when(configRepository.getConfigurationScope(CONFIG_SCOPE_ID_1)).thenReturn(new ConfigurationScope(CONFIG_SCOPE_ID_1, null, true, \"Config scope\"));\n    when(configRepository.getBindingConfiguration(CONFIG_SCOPE_ID_1)).thenReturn(BindingConfiguration.noBinding());\n\n    when(bindingClueProvider.collectBindingCluesWithConnections(eq(CONFIG_SCOPE_ID_1), eq(Set.of(SQ_1_ID)), any(SonarLintCancelMonitor.class)))\n      .thenReturn(List.of(new BindingClueProvider.BindingClueWithConnections(new BindingClueProvider.UnknownBindingClue(PROJECT_KEY_1, PROJECT_NAME), Set.of(SQ_1_ID))));\n\n    when(sonarProjectsCache.getSonarProject(eq(SQ_1_ID), eq(PROJECT_KEY_1), any(SonarLintCancelMonitor.class))).thenReturn(Optional.of(SERVER_PROJECT_1));\n\n    underTest.suggestBindingForGivenScopesAndAllConnections(Set.of(CONFIG_SCOPE_ID_1));\n\n    var captor = ArgumentCaptor.forClass(SuggestBindingParams.class);\n    verify(client, timeout(1000)).suggestBinding(captor.capture());\n\n    assertThat(logTester.logs(LogOutput.Level.DEBUG))\n      .containsExactly(\n        \"Binding suggestion computation queued for config scopes '\" + CONFIG_SCOPE_ID_1 + \"'...\",\n        \"Found 1 suggestion for configuration scope '\" + CONFIG_SCOPE_ID_1 + \"'\");\n\n    verify(sonarProjectsCache, never()).getTextSearchIndex(anyString(), any(SonarLintCancelMonitor.class));\n\n    var params = captor.getValue();\n    assertThat(params.getSuggestions()).containsOnlyKeys(CONFIG_SCOPE_ID_1);\n    assertThat(params.getSuggestions().get(CONFIG_SCOPE_ID_1))\n      .extracting(BindingSuggestionDto::getConnectionId, BindingSuggestionDto::getSonarProjectKey, BindingSuggestionDto::getSonarProjectName)\n      .containsOnly(tuple(SQ_1_ID, PROJECT_KEY_1, \"Project 1\"));\n  }\n\n  @Test\n  void compute_suggestions_fallback_to_text_search_all_connections_if_no_matches_by_projectKey() {\n    when(connectionRepository.getConnectionsById()).thenReturn(Map.of(SQ_1_ID, SQ_1, SC_1_ID, SC_1));\n    when(connectionRepository.getConnectionById(SQ_1_ID)).thenReturn(SQ_1);\n    when(connectionRepository.getConnectionById(SC_1_ID)).thenReturn(SC_1);\n\n    when(configRepository.getConfigurationScope(CONFIG_SCOPE_ID_1)).thenReturn(new ConfigurationScope(CONFIG_SCOPE_ID_1, null, true, \"KEYWORD\"));\n    when(configRepository.getBindingConfiguration(CONFIG_SCOPE_ID_1)).thenReturn(BindingConfiguration.noBinding());\n\n    when(bindingClueProvider.collectBindingCluesWithConnections(eq(CONFIG_SCOPE_ID_1), eq(Set.of(SQ_1_ID)), any(SonarLintCancelMonitor.class)))\n      .thenReturn(List.of(new BindingClueProvider.BindingClueWithConnections(new BindingClueProvider.UnknownBindingClue(PROJECT_KEY_1, PROJECT_NAME), Set.of(SQ_1_ID))));\n\n    when(sonarProjectsCache.getSonarProject(eq(SQ_1_ID), eq(PROJECT_KEY_1), any(SonarLintCancelMonitor.class))).thenReturn(Optional.empty());\n    when(sonarProjectsCache.getSonarProject(eq(SC_1_ID), eq(PROJECT_KEY_1), any(SonarLintCancelMonitor.class))).thenReturn(Optional.empty());\n    var searchIndex = new TextSearchIndex<ServerProject>();\n    searchIndex.index(SERVER_PROJECT_1, \"foo bar keyword\");\n    when(sonarProjectsCache.getTextSearchIndex(eq(SC_1_ID), any(SonarLintCancelMonitor.class))).thenReturn(searchIndex);\n\n    underTest.suggestBindingForGivenScopesAndAllConnections(Set.of(CONFIG_SCOPE_ID_1));\n\n    var captor = ArgumentCaptor.forClass(SuggestBindingParams.class);\n    verify(client, timeout(1000)).suggestBinding(captor.capture());\n\n    assertThat(logTester.logs(LogOutput.Level.DEBUG))\n      .containsExactlyInAnyOrder(\n        \"Binding suggestion computation queued for config scopes '\" + CONFIG_SCOPE_ID_1 + \"'...\",\n        \"Attempt to find a good match for 'KEYWORD' on connection '\" + SQ_1_ID + \"'...\",\n        \"Attempt to find a good match for 'KEYWORD' on connection '\" + SC_1_ID + \"'...\",\n        \"Best score = 0.33\",\n        \"Found 1 suggestion for configuration scope '\" + CONFIG_SCOPE_ID_1 + \"'\");\n\n    var params = captor.getValue();\n    assertThat(params.getSuggestions()).containsOnlyKeys(CONFIG_SCOPE_ID_1);\n    assertThat(params.getSuggestions().get(CONFIG_SCOPE_ID_1))\n      .extracting(BindingSuggestionDto::getConnectionId, BindingSuggestionDto::getSonarProjectKey, BindingSuggestionDto::getSonarProjectName)\n      .containsOnly(tuple(SC_1_ID, PROJECT_KEY_1, \"Project 1\"));\n  }\n\n  @Test\n  void compute_suggestions_fallback_to_text_search_all_connections_if_no_matches_by_projectKey_and_no_other_clue() {\n    when(connectionRepository.getConnectionsById()).thenReturn(Map.of(SQ_1_ID, SQ_1, SC_1_ID, SC_1));\n    when(connectionRepository.getConnectionById(SQ_1_ID)).thenReturn(SQ_1);\n    when(connectionRepository.getConnectionById(SC_1_ID)).thenReturn(SC_1);\n\n    when(configRepository.getConfigurationScope(CONFIG_SCOPE_ID_1)).thenReturn(new ConfigurationScope(CONFIG_SCOPE_ID_1, null, true, \"KEYWORD\"));\n    when(configRepository.getBindingConfiguration(CONFIG_SCOPE_ID_1)).thenReturn(BindingConfiguration.noBinding());\n\n    when(bindingClueProvider.collectBindingCluesWithConnections(eq(CONFIG_SCOPE_ID_1), eq(Set.of(SQ_1_ID)), any(SonarLintCancelMonitor.class)))\n      .thenReturn(List.of(new BindingClueProvider.BindingClueWithConnections(new BindingClueProvider.UnknownBindingClue(PROJECT_KEY_1, PROJECT_NAME), Set.of(SQ_1_ID))));\n\n    when(sonarProjectsCache.getSonarProject(eq(SQ_1_ID), eq(PROJECT_KEY_1), any(SonarLintCancelMonitor.class))).thenReturn(Optional.empty());\n    when(sonarProjectsCache.getSonarProject(eq(SC_1_ID), eq(PROJECT_KEY_1), any(SonarLintCancelMonitor.class))).thenReturn(Optional.empty());\n    var searchIndex = new TextSearchIndex<ServerProject>();\n    searchIndex.index(SERVER_PROJECT_1, \"foo bar keyword\");\n    when(sonarProjectsCache.getTextSearchIndex(eq(SC_1_ID), any(SonarLintCancelMonitor.class))).thenReturn(searchIndex);\n\n    underTest.suggestBindingForGivenScopesAndAllConnections(Set.of(CONFIG_SCOPE_ID_1));\n\n    var captor = ArgumentCaptor.forClass(SuggestBindingParams.class);\n    verify(client, timeout(1000)).suggestBinding(captor.capture());\n\n    assertThat(logTester.logs(LogOutput.Level.DEBUG))\n      .containsExactlyInAnyOrder(\n        \"Binding suggestion computation queued for config scopes '\" + CONFIG_SCOPE_ID_1 + \"'...\",\n        \"Attempt to find a good match for 'KEYWORD' on connection '\" + SQ_1_ID + \"'...\",\n        \"Attempt to find a good match for 'KEYWORD' on connection '\" + SC_1_ID + \"'...\",\n        \"Best score = 0.33\",\n        \"Found 1 suggestion for configuration scope '\" + CONFIG_SCOPE_ID_1 + \"'\");\n\n    var params = captor.getValue();\n    assertThat(params.getSuggestions()).containsOnlyKeys(CONFIG_SCOPE_ID_1);\n    assertThat(params.getSuggestions().get(CONFIG_SCOPE_ID_1))\n      .extracting(BindingSuggestionDto::getConnectionId, BindingSuggestionDto::getSonarProjectKey, BindingSuggestionDto::getSonarProjectName)\n      .containsOnly(tuple(SC_1_ID, PROJECT_KEY_1, \"Project 1\"));\n  }\n\n  @Test\n  void get_suggested_binding() {\n    var cancelMonitor = new SonarLintCancelMonitor();\n    when(connectionRepository.getConnectionById(SQ_1_ID)).thenReturn(SQ_1);\n    when(configRepository.getConfigurationScope(CONFIG_SCOPE_ID_1)).thenReturn(new ConfigurationScope(CONFIG_SCOPE_ID_1, null, true, \"foo-bar\"));\n    when(configRepository.getBindingConfiguration(CONFIG_SCOPE_ID_1)).thenReturn(BindingConfiguration.noBinding());\n    when(bindingClueProvider.collectBindingCluesWithConnections(CONFIG_SCOPE_ID_1, Set.of(SQ_1_ID), cancelMonitor))\n      .thenReturn(List.of(\n        new BindingClueProvider.BindingClueWithConnections(new BindingClueProvider.SonarQubeBindingClue(null, null, PROJECT_NAME), Set.of(SQ_1_ID))));\n    var searchIndex = new TextSearchIndex<ServerProject>();\n    searchIndex.index(SERVER_PROJECT_1, \"foo bar garbage1\");\n    when(sonarProjectsCache.getTextSearchIndex(SQ_1_ID, cancelMonitor)).thenReturn(searchIndex);\n    when(sonarProjectsCache.getSonarProject(SQ_1_ID, PROJECT_KEY_1, cancelMonitor)).thenReturn(Optional.empty());\n\n    var bindingSuggestions = underTest.getBindingSuggestions(CONFIG_SCOPE_ID_1, SQ_1_ID, cancelMonitor);\n    assertThat(bindingSuggestions).hasSize(1);\n    assertThat(bindingSuggestions.get(CONFIG_SCOPE_ID_1))\n      .extracting(BindingSuggestionDto::getConnectionId, BindingSuggestionDto::getSonarProjectKey, BindingSuggestionDto::getSonarProjectName)\n      .containsOnly(\n        tuple(SQ_1_ID, PROJECT_KEY_1, \"Project 1\"));\n  }\n\n  @Test\n  void search_only_among_connection_candidates() {\n    when(connectionRepository.getConnectionsById()).thenReturn(Map.of(SQ_1_ID, SQ_1, SC_1_ID, SC_1));\n    when(connectionRepository.getConnectionById(SQ_1_ID)).thenReturn(SQ_1);\n    when(connectionRepository.getConnectionById(SC_1_ID)).thenReturn(SC_1);\n\n    when(configRepository.getConfigurationScope(CONFIG_SCOPE_ID_1)).thenReturn(new ConfigurationScope(CONFIG_SCOPE_ID_1, null, true, \"foo-bar\"));\n    when(configRepository.getConfigScopeIds()).thenReturn(Set.of(CONFIG_SCOPE_ID_1));\n    when(configRepository.getBindingConfiguration(CONFIG_SCOPE_ID_1)).thenReturn(BindingConfiguration.noBinding());\n\n    when(bindingClueProvider.collectBindingCluesWithConnections(eq(CONFIG_SCOPE_ID_1), eq(Set.of(SQ_1_ID)), any(SonarLintCancelMonitor.class)))\n      .thenReturn(List.of(\n        new BindingClueProvider.BindingClueWithConnections(new BindingClueProvider.UnknownBindingClue(PROJECT_KEY_1, PROJECT_NAME), Set.of(SQ_1_ID, SC_1_ID))));\n\n    when(sonarProjectsCache.getSonarProject(eq(SQ_1_ID), eq(PROJECT_KEY_1), any(SonarLintCancelMonitor.class))).thenReturn(Optional.empty());\n    when(sonarProjectsCache.getSonarProject(eq(SC_1_ID), eq(PROJECT_KEY_1), any(SonarLintCancelMonitor.class))).thenReturn(Optional.empty());\n\n    var searchIndex = new TextSearchIndex<ServerProject>();\n    searchIndex.index(SERVER_PROJECT_1, \"foo bar garbage1\");\n    searchIndex.index(serverProject(\"key2\", \"Project 2\"), \"foo bar garbage2\");\n    searchIndex.index(serverProject(\"key3\", \"Project 3\"), \"foo bar more garbage\");\n    when(sonarProjectsCache.getTextSearchIndex(eq(SC_1_ID), any(SonarLintCancelMonitor.class))).thenReturn(searchIndex);\n    when(sonarProjectsCache.getTextSearchIndex(eq(SQ_1_ID), any(SonarLintCancelMonitor.class))).thenReturn(searchIndex);\n\n    underTest.connectionAdded(new ConnectionConfigurationAddedEvent(SQ_1_ID, ConnectionKind.SONARQUBE));\n\n    var captor = ArgumentCaptor.forClass(SuggestBindingParams.class);\n    verify(client, timeout(1000)).suggestBinding(captor.capture());\n\n    var params = captor.getValue();\n    assertThat(params.getSuggestions()).containsOnlyKeys(CONFIG_SCOPE_ID_1);\n    assertThat(params.getSuggestions().get(CONFIG_SCOPE_ID_1))\n      .extracting(BindingSuggestionDto::getConnectionId, BindingSuggestionDto::getSonarProjectKey, BindingSuggestionDto::getSonarProjectName)\n      .containsOnly(\n        tuple(SQ_1_ID, PROJECT_KEY_1, \"Project 1\"),\n        tuple(SQ_1_ID, \"key2\", \"Project 2\"));\n  }\n\n  @Test\n  void text_search_should_retain_only_top_scores() {\n    when(connectionRepository.getConnectionsById()).thenReturn(Map.of(SQ_1_ID, SQ_1));\n    when(connectionRepository.getConnectionById(SQ_1_ID)).thenReturn(SQ_1);\n\n    when(configRepository.getConfigurationScope(CONFIG_SCOPE_ID_1)).thenReturn(new ConfigurationScope(CONFIG_SCOPE_ID_1, null, true, \"foo-bar\"));\n    when(configRepository.getBindingConfiguration(CONFIG_SCOPE_ID_1)).thenReturn(BindingConfiguration.noBinding());\n\n    when(bindingClueProvider.collectBindingCluesWithConnections(eq(CONFIG_SCOPE_ID_1), eq(Set.of(SQ_1_ID)), any(SonarLintCancelMonitor.class)))\n      .thenReturn(List.of(\n        new BindingClueProvider.BindingClueWithConnections(new BindingClueProvider.UnknownBindingClue(PROJECT_KEY_1, PROJECT_NAME), Set.of(SQ_1_ID, SC_1_ID)),\n        new BindingClueProvider.BindingClueWithConnections(new BindingClueProvider.SonarCloudBindingClue(null, null,  null, PROJECT_NAME), Set.of(SC_1_ID))));\n\n    when(sonarProjectsCache.getSonarProject(eq(SQ_1_ID), eq(PROJECT_KEY_1), any(SonarLintCancelMonitor.class))).thenReturn(Optional.empty());\n    when(sonarProjectsCache.getSonarProject(eq(SC_1_ID), eq(PROJECT_KEY_1), any(SonarLintCancelMonitor.class))).thenReturn(Optional.empty());\n\n    var searchIndex = new TextSearchIndex<ServerProject>();\n    searchIndex.index(SERVER_PROJECT_1, \"foo bar garbage1\");\n    searchIndex.index(serverProject(\"key2\", \"Project 2\"), \"foo bar garbage2\");\n    searchIndex.index(serverProject(\"key3\", \"Project 3\"), \"foo bar more garbage\");\n    when(sonarProjectsCache.getTextSearchIndex(eq(SC_1_ID), any(SonarLintCancelMonitor.class))).thenReturn(searchIndex);\n\n    underTest.suggestBindingForGivenScopesAndAllConnections(Set.of(CONFIG_SCOPE_ID_1));\n\n    var captor = ArgumentCaptor.forClass(SuggestBindingParams.class);\n    verify(client, timeout(1000)).suggestBinding(captor.capture());\n\n    assertThat(logTester.logs(LogOutput.Level.DEBUG))\n      .containsExactly(\n        \"Binding suggestion computation queued for config scopes '\" + CONFIG_SCOPE_ID_1 + \"'...\",\n        \"Attempt to find a good match for 'foo-bar' on connection '\" + SC_1_ID + \"'...\",\n        \"Best score = 0.67\",\n        \"Found 2 suggestions for configuration scope '\" + CONFIG_SCOPE_ID_1 + \"'\");\n\n    var params = captor.getValue();\n    assertThat(params.getSuggestions()).containsOnlyKeys(CONFIG_SCOPE_ID_1);\n    assertThat(params.getSuggestions().get(CONFIG_SCOPE_ID_1))\n      .extracting(BindingSuggestionDto::getConnectionId, BindingSuggestionDto::getSonarProjectKey, BindingSuggestionDto::getSonarProjectName)\n      .containsOnly(\n        tuple(SC_1_ID, PROJECT_KEY_1, \"Project 1\"),\n        tuple(SC_1_ID, \"key2\", \"Project 2\"));\n  }\n\n  @Test\n  void search_by_remote_url_should_return_suggestion_when_project_found_for_sonarcloud() {\n    var cancelMonitor = new SonarLintCancelMonitor();\n    var baseDir = Path.of(\"repo\");\n\n    when(connectionRepository.getConnectionsById()).thenReturn(Map.of(SC_1_ID, SC_1));\n    when(connectionRepository.getConnectionById(SC_1_ID)).thenReturn(SC_1);\n\n    when(configRepository.getConfigurationScope(CONFIG_SCOPE_ID_1))\n      .thenReturn(new ConfigurationScope(CONFIG_SCOPE_ID_1, null, true, \"Some project\"));\n    when(configRepository.getBindingConfiguration(CONFIG_SCOPE_ID_1)).thenReturn(BindingConfiguration.noBinding());\n\n    when(bindingClueProvider.collectBindingCluesWithConnections(eq(CONFIG_SCOPE_ID_1), eq(Set.of(SC_1_ID)), any(SonarLintCancelMonitor.class)))\n      .thenReturn(List.of());\n\n    when(clientFs.getBaseDir(CONFIG_SCOPE_ID_1)).thenReturn(baseDir);\n\n    try (var gitServiceMock = mockStatic(GitService.class)) {\n      gitServiceMock.when(() -> GitService.getRemoteUrl(baseDir)).thenReturn(\"git@github.com:myorg/myproj.git\");\n\n      when(sonarQubeClientManager.withActiveClientFlatMapOptionalAndReturn(eq(SC_1_ID), any()))\n        .thenReturn(Optional.of(new BindingSuggestionDto(SC_1_ID, PROJECT_KEY_1, \"Project 1\", REMOTE_URL)));\n\n      var result = underTest.getBindingSuggestions(CONFIG_SCOPE_ID_1, SC_1_ID, cancelMonitor);\n\n      assertThat(result).containsOnlyKeys(CONFIG_SCOPE_ID_1);\n      assertThat(result.get(CONFIG_SCOPE_ID_1))\n        .extracting(BindingSuggestionDto::getConnectionId, BindingSuggestionDto::getSonarProjectKey, BindingSuggestionDto::getSonarProjectName)\n        .containsOnly(tuple(SC_1_ID, PROJECT_KEY_1, \"Project 1\"));\n    }\n  }\n\n  @Test\n  void search_by_remote_url_should_return_suggestion_when_project_found() {\n    var cancelMonitor = new SonarLintCancelMonitor();\n    Path baseDir = Path.of(\"repo\");\n\n    when(connectionRepository.getConnectionsById()).thenReturn(Map.of(SQ_1_ID, SQ_1));\n    when(connectionRepository.getConnectionById(SQ_1_ID)).thenReturn(SQ_1);\n\n    when(configRepository.getConfigurationScope(CONFIG_SCOPE_ID_1))\n      .thenReturn(new ConfigurationScope(CONFIG_SCOPE_ID_1, null, true, \"Some project\"));\n    when(configRepository.getBindingConfiguration(CONFIG_SCOPE_ID_1)).thenReturn(BindingConfiguration.noBinding());\n\n    when(bindingClueProvider.collectBindingCluesWithConnections(eq(CONFIG_SCOPE_ID_1), eq(Set.of(SQ_1_ID)), any(SonarLintCancelMonitor.class)))\n      .thenReturn(List.of());\n\n    when(clientFs.getBaseDir(CONFIG_SCOPE_ID_1)).thenReturn(baseDir);\n\n    try (var gitServiceMock = mockStatic(GitService.class)) {\n      gitServiceMock.when(() -> GitService.getRemoteUrl(baseDir)).thenReturn(\"git@github.com:myorg/myproj.git\");\n\n      when(sonarQubeClientManager.withActiveClientFlatMapOptionalAndReturn(eq(SQ_1_ID), any()))\n        .thenReturn(Optional.of(new BindingSuggestionDto(SQ_1_ID, PROJECT_KEY_1, \"Project 1\", REMOTE_URL)));\n\n      var result = underTest.getBindingSuggestions(CONFIG_SCOPE_ID_1, SQ_1_ID, cancelMonitor);\n\n      assertThat(result).containsOnlyKeys(CONFIG_SCOPE_ID_1);\n      assertThat(result.get(CONFIG_SCOPE_ID_1))\n        .extracting(BindingSuggestionDto::getConnectionId, BindingSuggestionDto::getSonarProjectKey, BindingSuggestionDto::getSonarProjectName)\n        .containsOnly(tuple(SQ_1_ID, PROJECT_KEY_1, \"Project 1\"));\n    }\n  }\n\n  @Test\n  void should_set_origin_remote_url_on_remote_url_based_suggestion() {\n    var cancelMonitor = new SonarLintCancelMonitor();\n    Path baseDir = Path.of(\"repo\");\n\n    when(connectionRepository.getConnectionsById()).thenReturn(Map.of(SQ_1_ID, SQ_1));\n    when(connectionRepository.getConnectionById(SQ_1_ID)).thenReturn(SQ_1);\n\n    when(configRepository.getConfigurationScope(CONFIG_SCOPE_ID_1))\n      .thenReturn(new ConfigurationScope(CONFIG_SCOPE_ID_1, null, true, \"Some project\"));\n    when(configRepository.getBindingConfiguration(CONFIG_SCOPE_ID_1)).thenReturn(BindingConfiguration.noBinding());\n\n    when(bindingClueProvider.collectBindingCluesWithConnections(eq(CONFIG_SCOPE_ID_1), eq(Set.of(SQ_1_ID)), any(SonarLintCancelMonitor.class)))\n      .thenReturn(List.of());\n\n    when(clientFs.getBaseDir(CONFIG_SCOPE_ID_1)).thenReturn(baseDir);\n\n    try (var gitServiceMock = mockStatic(GitService.class)) {\n      gitServiceMock.when(() -> GitService.getRemoteUrl(baseDir)).thenReturn(\"git@github.com:myorg/myproj.git\");\n\n      when(sonarQubeClientManager.withActiveClientFlatMapOptionalAndReturn(eq(SQ_1_ID), any()))\n        .thenReturn(Optional.of(new BindingSuggestionDto(SQ_1_ID, PROJECT_KEY_1, \"Project 1\", REMOTE_URL)));\n\n      var result = underTest.getBindingSuggestions(CONFIG_SCOPE_ID_1, SQ_1_ID, cancelMonitor);\n\n      var suggestions = result.get(CONFIG_SCOPE_ID_1);\n      assertThat(suggestions).hasSize(1);\n      assertThat(suggestions.get(0).getOrigin()).isEqualTo(REMOTE_URL);\n    }\n  }\n\n  @Test\n  void legacy_isFromSharedConfiguration_boolean_remains_false_for_remote_url() {\n    var cancelMonitor = new SonarLintCancelMonitor();\n    Path baseDir = Path.of(\"repo\");\n\n    when(connectionRepository.getConnectionsById()).thenReturn(Map.of(SQ_1_ID, SQ_1));\n    when(connectionRepository.getConnectionById(SQ_1_ID)).thenReturn(SQ_1);\n\n    when(configRepository.getConfigurationScope(CONFIG_SCOPE_ID_1))\n      .thenReturn(new ConfigurationScope(CONFIG_SCOPE_ID_1, null, true, \"Some project\"));\n    when(configRepository.getBindingConfiguration(CONFIG_SCOPE_ID_1)).thenReturn(BindingConfiguration.noBinding());\n\n    when(bindingClueProvider.collectBindingCluesWithConnections(eq(CONFIG_SCOPE_ID_1), eq(Set.of(SQ_1_ID)), any(SonarLintCancelMonitor.class)))\n      .thenReturn(List.of());\n\n    when(clientFs.getBaseDir(CONFIG_SCOPE_ID_1)).thenReturn(baseDir);\n\n    try (var gitServiceMock = mockStatic(GitService.class)) {\n      gitServiceMock.when(() -> GitService.getRemoteUrl(baseDir)).thenReturn(\"git@github.com:myorg/myproj.git\");\n\n      when(sonarQubeClientManager.withActiveClientFlatMapOptionalAndReturn(eq(SQ_1_ID), any()))\n        .thenReturn(Optional.of(new BindingSuggestionDto(SQ_1_ID, PROJECT_KEY_1, \"Project 1\", REMOTE_URL)));\n\n      var result = underTest.getBindingSuggestions(CONFIG_SCOPE_ID_1, SQ_1_ID, cancelMonitor);\n\n      var suggestions = result.get(CONFIG_SCOPE_ID_1);\n      assertThat(suggestions).hasSize(1);\n      assertThat(suggestions.get(0).isFromSharedConfiguration()).isFalse();\n    }\n  }\n\n  @Test\n  void should_set_origin_project_name_on_project_name_based_suggestion() {\n    when(connectionRepository.getConnectionsById()).thenReturn(Map.of(SQ_1_ID, SQ_1));\n    when(connectionRepository.getConnectionById(SQ_1_ID)).thenReturn(SQ_1);\n\n    when(configRepository.getConfigurationScope(CONFIG_SCOPE_ID_1)).thenReturn(new ConfigurationScope(CONFIG_SCOPE_ID_1, null, true, \"foo-bar\"));\n    when(configRepository.getBindingConfiguration(CONFIG_SCOPE_ID_1)).thenReturn(BindingConfiguration.noBinding());\n\n    when(bindingClueProvider.collectBindingCluesWithConnections(eq(CONFIG_SCOPE_ID_1), eq(Set.of(SQ_1_ID)), any(SonarLintCancelMonitor.class)))\n      .thenReturn(List.of(\n        new BindingClueProvider.BindingClueWithConnections(new BindingClueProvider.UnknownBindingClue(PROJECT_KEY_1, PROJECT_NAME), Set.of(SQ_1_ID))));\n\n    var searchIndex = new TextSearchIndex<ServerProject>();\n    searchIndex.index(SERVER_PROJECT_1, \"foo bar garbage1\");\n    when(sonarProjectsCache.getTextSearchIndex(eq(SQ_1_ID), any(SonarLintCancelMonitor.class))).thenReturn(searchIndex);\n    when(sonarProjectsCache.getSonarProject(eq(SQ_1_ID), eq(PROJECT_KEY_1), any(SonarLintCancelMonitor.class))).thenReturn(Optional.empty());\n\n    var suggestionsMap = underTest.getBindingSuggestions(CONFIG_SCOPE_ID_1, SQ_1_ID, new SonarLintCancelMonitor());\n\n    var suggestions = suggestionsMap.get(CONFIG_SCOPE_ID_1);\n    assertThat(suggestions).isNotEmpty();\n    assertThat(suggestions)\n      .extracting(BindingSuggestionDto::getOrigin)\n      .containsOnly(PROJECT_NAME);\n  }\n\n  @Test\n  void search_by_remote_url_should_do_nothing_when_no_remote_url() {\n    var cancelMonitor = new SonarLintCancelMonitor();\n    var baseDir = Path.of(\"repo\");\n\n    when(connectionRepository.getConnectionsById()).thenReturn(Map.of(SQ_1_ID, SQ_1));\n    when(connectionRepository.getConnectionById(SQ_1_ID)).thenReturn(SQ_1);\n\n    when(configRepository.getConfigurationScope(CONFIG_SCOPE_ID_1))\n      .thenReturn(new ConfigurationScope(CONFIG_SCOPE_ID_1, null, true, \"Some project\"));\n    when(configRepository.getBindingConfiguration(CONFIG_SCOPE_ID_1)).thenReturn(BindingConfiguration.noBinding());\n\n    when(bindingClueProvider.collectBindingCluesWithConnections(eq(CONFIG_SCOPE_ID_1), eq(Set.of(SQ_1_ID)), any(SonarLintCancelMonitor.class)))\n      .thenReturn(List.of());\n\n    when(clientFs.getBaseDir(CONFIG_SCOPE_ID_1)).thenReturn(baseDir);\n\n    try (var gitServiceMock = mockStatic(GitService.class)) {\n      gitServiceMock.when(() -> GitService.getRemoteUrl(baseDir)).thenReturn(null);\n\n      var result = underTest.getBindingSuggestions(CONFIG_SCOPE_ID_1, SQ_1_ID, cancelMonitor);\n\n      assertThat(result).isEmpty();\n    }\n  }\n\n  @Test\n  void search_by_remote_url_should_do_nothing_when_no_project_id_found() {\n    var cancelMonitor = new SonarLintCancelMonitor();\n    var baseDir = Path.of(\"repo\");\n\n    when(connectionRepository.getConnectionsById()).thenReturn(Map.of(SQ_1_ID, SQ_1));\n    when(connectionRepository.getConnectionById(SQ_1_ID)).thenReturn(SQ_1);\n\n    when(configRepository.getConfigurationScope(CONFIG_SCOPE_ID_1))\n      .thenReturn(new ConfigurationScope(CONFIG_SCOPE_ID_1, null, true, \"Some project\"));\n    when(configRepository.getBindingConfiguration(CONFIG_SCOPE_ID_1)).thenReturn(BindingConfiguration.noBinding());\n\n    when(bindingClueProvider.collectBindingCluesWithConnections(eq(CONFIG_SCOPE_ID_1), eq(Set.of(SQ_1_ID)), any(SonarLintCancelMonitor.class)))\n      .thenReturn(List.of());\n\n    when(clientFs.getBaseDir(CONFIG_SCOPE_ID_1)).thenReturn(baseDir);\n\n    try (var gitServiceMock = mockStatic(GitService.class)) {\n      gitServiceMock.when(() -> GitService.getRemoteUrl(baseDir)).thenReturn(\"git@github.com:myorg/myproj.git\");\n\n      when(sonarQubeClientManager.withActiveClientFlatMapOptionalAndReturn(eq(SQ_1_ID), any()))\n        .thenReturn(Optional.empty());\n\n      var result = underTest.getBindingSuggestions(CONFIG_SCOPE_ID_1, SQ_1_ID, cancelMonitor);\n\n      assertThat(result).isEmpty();\n    }\n  }\n\n  private static ServerProject serverProject(String projectKey, String name) {\n    return new ServerProject(projectKey, name, false);\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/ConfigurationServiceTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport java.util.List;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Mockito;\nimport org.sonarsource.sonarlint.core.commons.BoundScope;\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.event.BindingConfigChangedEvent;\nimport org.sonarsource.sonarlint.core.event.ConfigurationScopesAddedWithBindingEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationRemovedEvent;\nimport org.sonarsource.sonarlint.core.repository.config.BindingConfiguration;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.springframework.context.ApplicationEventPublisher;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\n\nclass ConfigurationServiceTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private static final String CONNECTION_1 = \"connection1\";\n  private static final String CONNECTION_2 = \"connection2\";\n  public static final BindingConfigurationDto BINDING_DTO_1 = new BindingConfigurationDto(CONNECTION_1, \"projectKey1\", false);\n  public static final BindingConfigurationDto BINDING_DTO_2 = new BindingConfigurationDto(CONNECTION_1, \"projectKey2\", true);\n  public static final BindingConfigurationDto BINDING_DTO_3 = new BindingConfigurationDto(CONNECTION_2, \"projectKey3\", true);\n  public static final ConfigurationScopeDto CONFIG_DTO_1 = new ConfigurationScopeDto(\"id1\", null, true, \"Scope 1\", BINDING_DTO_1);\n  public static final ConfigurationScopeDto CONFIG_DTO_1_DUP = new ConfigurationScopeDto(\"id1\", null, false, \"Scope 1 dup\", BINDING_DTO_2);\n  public static final ConfigurationScopeDto CONFIG_DTO_2 = new ConfigurationScopeDto(\"id2\", null, true, \"Scope 2\", BINDING_DTO_2);\n  public static final ConfigurationScopeDto CONFIG_DTO_3 = new ConfigurationScopeDto(\"id3\", null, true, \"Scope 2\", BINDING_DTO_3);\n  private final ConfigurationRepository repository = new ConfigurationRepository();\n\n  private ApplicationEventPublisher eventPublisher;\n  private ConfigurationService underTest;\n\n  @BeforeEach\n  void setUp() {\n    eventPublisher = mock(ApplicationEventPublisher.class);\n    underTest = new ConfigurationService(eventPublisher, repository);\n  }\n\n  @Test\n  void initialize_empty() {\n    assertThat(repository.getConfigScopeIds()).isEmpty();\n  }\n\n  @Test\n  void get_binding_of_unknown_config_returns_null() {\n    assertThat(repository.getBindingConfiguration(\"not_found\")).isNull();\n  }\n\n  @Test\n  void add_configuration_should_post_event() {\n    underTest.didAddConfigurationScopes(List.of(CONFIG_DTO_2));\n\n    assertThat(repository.getConfigScopeIds()).containsOnly(\"id2\");\n    assertThat(repository.getBindingConfiguration(\"id2\")).usingRecursiveComparison().isEqualTo(BINDING_DTO_2);\n\n    ArgumentCaptor<ConfigurationScopesAddedWithBindingEvent> captor = ArgumentCaptor.forClass(ConfigurationScopesAddedWithBindingEvent.class);\n    verify(eventPublisher).publishEvent(captor.capture());\n    var event = captor.getValue();\n\n    assertThat(event.getConfigScopeIds()).containsOnly(\"id2\");\n  }\n\n  @Test\n  void add_multiple_configurations_should_post_batch_event() {\n    underTest.didAddConfigurationScopes(List.of(CONFIG_DTO_1, CONFIG_DTO_2));\n\n    assertThat(repository.getConfigScopeIds()).containsOnly(\"id1\", \"id2\");\n    assertThat(repository.getBindingConfiguration(\"id1\")).usingRecursiveComparison().isEqualTo(BINDING_DTO_1);\n    assertThat(repository.getBindingConfiguration(\"id2\")).usingRecursiveComparison().isEqualTo(BINDING_DTO_2);\n\n    ArgumentCaptor<ConfigurationScopesAddedWithBindingEvent> captor = ArgumentCaptor.forClass(ConfigurationScopesAddedWithBindingEvent.class);\n    verify(eventPublisher).publishEvent(captor.capture());\n    var event = captor.getValue();\n\n    assertThat(event.getConfigScopeIds()).containsOnly(\"id1\", \"id2\");\n  }\n\n  @Test\n  void add_duplicate_should_log_and_update() {\n    underTest.didAddConfigurationScopes(List.of(CONFIG_DTO_1));\n    assertThat(repository.getBindingConfiguration(\"id1\")).usingRecursiveComparison().isEqualTo(BINDING_DTO_1);\n\n    underTest.didAddConfigurationScopes(List.of(CONFIG_DTO_1_DUP));\n\n    assertThat(repository.getConfigScopeIds()).containsOnly(\"id1\");\n    assertThat(repository.getBindingConfiguration(\"id1\")).usingRecursiveComparison().isEqualTo(BINDING_DTO_2);\n\n    assertThat(logTester.logs(LogOutput.Level.ERROR)).containsExactly(\"Duplicate configuration scope registered: id1\");\n  }\n\n  @Test\n  void remove_configuration() {\n    underTest.didAddConfigurationScopes(List.of(CONFIG_DTO_1));\n    assertThat(repository.getConfigScopeIds()).containsOnly(\"id1\");\n\n    underTest.didRemoveConfigurationScope(\"id1\");\n\n    assertThat(repository.getConfigScopeIds()).isEmpty();\n  }\n\n  @Test\n  void remove_unknown_configuration_should_log() {\n    underTest.didAddConfigurationScopes(List.of(CONFIG_DTO_1));\n    assertThat(repository.getConfigScopeIds()).containsOnly(\"id1\");\n\n    underTest.didRemoveConfigurationScope(\"id2\");\n\n    assertThat(repository.getConfigScopeIds()).containsOnly(\"id1\");\n    assertThat(logTester.logs(LogOutput.Level.DEBUG)).contains(\"Attempt to remove configuration scope 'id2' that was not registered\");\n    assertThat(logTester.logs(LogOutput.Level.ERROR)).isEmpty();\n  }\n\n  @Test\n  void update_binding_config_and_post_event() {\n    underTest.didAddConfigurationScopes(List.of(CONFIG_DTO_1));\n    assertThat(repository.getBindingConfiguration(\"id1\")).usingRecursiveComparison().isEqualTo(BINDING_DTO_1);\n\n    // Ignore add event\n    Mockito.reset(eventPublisher);\n\n    underTest.didUpdateBinding(\"id1\", BINDING_DTO_2);\n\n    assertThat(repository.getConfigScopeIds()).containsOnly(\"id1\");\n    assertThat(repository.getBindingConfiguration(\"id1\")).usingRecursiveComparison().isEqualTo(BINDING_DTO_2);\n\n    ArgumentCaptor<BindingConfigChangedEvent> captor = ArgumentCaptor.forClass(BindingConfigChangedEvent.class);\n    verify(eventPublisher).publishEvent(captor.capture());\n    var event = captor.getValue();\n\n    assertThat(event.configScopeId()).isEqualTo(\"id1\");\n    assertThat(event.previousConfig().connectionId()).isEqualTo(CONNECTION_1);\n    assertThat(event.previousConfig().sonarProjectKey()).isEqualTo(\"projectKey1\");\n    assertThat(event.previousConfig().bindingSuggestionDisabled()).isFalse();\n\n    assertThat(event.newConfig().connectionId()).isEqualTo(CONNECTION_1);\n    assertThat(event.newConfig().sonarProjectKey()).isEqualTo(\"projectKey2\");\n    assertThat(event.newConfig().bindingSuggestionDisabled()).isTrue();\n  }\n\n  @Test\n  void update_binding_config_for_unknown_config_scope_should_log() {\n    underTest.didAddConfigurationScopes(List.of(CONFIG_DTO_1));\n\n    underTest.didUpdateBinding(\"id2\", BINDING_DTO_2);\n\n    assertThat(logTester.logs(LogOutput.Level.ERROR)).containsExactly(\"Attempt to update binding in configuration scope 'id2' that was not registered\");\n  }\n\n  @Test\n  void should_clear_binding_if_connection_removed() {\n    underTest.didAddConfigurationScopes(List.of(CONFIG_DTO_1, CONFIG_DTO_3));\n    assertThat(repository.getConfigScopeIds()).containsOnly(\"id1\", \"id3\");\n\n    underTest.connectionRemoved(new ConnectionConfigurationRemovedEvent(CONNECTION_1));\n    assertThat(repository.getAllBoundScopes()).hasSize(1);\n    assertThat(repository.getBoundScope(\"id3\"))\n      .isNotNull()\n      .extracting(BoundScope::getConnectionId).isEqualTo(CONNECTION_2);\n    assertThat(repository.getBindingConfiguration(CONFIG_DTO_1.getId()))\n      .extracting(BindingConfiguration::connectionId, BindingConfiguration::sonarProjectKey, BindingConfiguration::bindingSuggestionDisabled)\n      .containsExactly(null, null, false);\n    assertThat(repository.getBindingConfiguration(CONFIG_DTO_3.getId()))\n      .extracting(BindingConfiguration::connectionId, BindingConfiguration::sonarProjectKey, BindingConfiguration::bindingSuggestionDisabled)\n      .containsExactly(BINDING_DTO_3.getConnectionId(), BINDING_DTO_3.getSonarProjectKey(), BINDING_DTO_3.isBindingSuggestionDisabled());\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/ConnectionServiceTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport java.util.List;\nimport java.util.Map;\nimport org.assertj.core.api.InstanceOfAssertFactories;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseError;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.mockito.ArgumentCaptor;\nimport org.sonarsource.sonarlint.core.commons.ConnectionKind;\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationAddedEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationRemovedEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationUpdatedEvent;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarCloudConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarQubeConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth.HelpGenerateUserTokenParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth.HelpGenerateUserTokenResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarCloudConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarQubeConnectionConfigurationDto;\nimport org.springframework.context.ApplicationEventPublisher;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowableOfType;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.when;\n\nclass ConnectionServiceTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private final ConnectionConfigurationRepository repository = new ConnectionConfigurationRepository();\n\n  public static final SonarQubeConnectionConfigurationDto SQ_DTO_1 = new SonarQubeConnectionConfigurationDto(\"sq1\", \"http://url1/\", true);\n  public static final SonarQubeConnectionConfigurationDto SQ_DTO_1_DUP = new SonarQubeConnectionConfigurationDto(\"sq1\", \"http://url1_dup/\", true);\n  public static final SonarQubeConnectionConfigurationDto SQ_DTO_2 = new SonarQubeConnectionConfigurationDto(\"sq2\", \"url2\", true);\n  public static final SonarCloudConnectionConfigurationDto SC_DTO_1 = new SonarCloudConnectionConfigurationDto(\"sc1\", \"org1\", org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.EU, true);\n  public static final SonarCloudConnectionConfigurationDto SC_DTO_2 = new SonarCloudConnectionConfigurationDto(\"sc2\", \"org2\", org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.EU, true);\n  private static final String EXPECTED_MESSAGE = \"UTM parameters should match regular expression: [a-z0-9\\\\-]+\";\n\n  ApplicationEventPublisher eventPublisher;\n  ConnectionService underTest;\n\n  @BeforeEach\n  void setUp() {\n    eventPublisher = mock(ApplicationEventPublisher.class);\n  }\n\n  @Test\n  void initialize_provide_connections() {\n    underTest = new ConnectionService(eventPublisher, repository, List.of(SQ_DTO_1, SQ_DTO_2), List.of(SC_DTO_1, SC_DTO_2), SonarCloudActiveEnvironment.prod(), null, null);\n\n    assertThat(repository.getConnectionsById()).containsOnlyKeys(\"sq1\", \"sq2\", \"sc1\", \"sc2\");\n  }\n\n  @Test\n  void generate_user_token_should_ignore_null_utm() {\n    SonarLintCancelMonitor cancelMonitor = new SonarLintCancelMonitor();\n    TokenGeneratorHelper mockedHelper = mock(TokenGeneratorHelper.class);\n    when(mockedHelper.helpGenerateUserToken(\"serverUrl\", null, cancelMonitor))\n      .thenReturn(new HelpGenerateUserTokenResponse(\"TOKEN\"));\n    underTest = new ConnectionService(eventPublisher, repository, List.of(), List.of(), SonarCloudActiveEnvironment.prod(), null, mockedHelper);\n\n    var response = underTest.helpGenerateUserToken(\"serverUrl\", null, cancelMonitor);\n\n    assertThat(response.getToken()).isEqualTo(\"TOKEN\");\n  }\n\n  @Test\n  void generate_user_token_should_throw_validation_error_for_all() {\n    TokenGeneratorHelper mockedHelper = mock(TokenGeneratorHelper.class);\n    underTest = new ConnectionService(eventPublisher, repository, List.of(), List.of(), SonarCloudActiveEnvironment.prod(), null, mockedHelper);\n    HelpGenerateUserTokenParams.Utm invalidParams = new HelpGenerateUserTokenParams.Utm(\"medium wrong\", \"source/\", \"contENT\", \"t.e.r.m\");\n    SonarLintCancelMonitor cancelMonitor = new SonarLintCancelMonitor();\n\n    ResponseErrorException exception = catchThrowableOfType(ResponseErrorException.class, () ->\n      underTest.helpGenerateUserToken(\"serverUrl\", invalidParams, cancelMonitor));\n    ResponseError innerError = exception.getResponseError();\n\n    assertThat(exception).hasMessage(EXPECTED_MESSAGE);\n    assertThat(innerError).extracting(\"message\").isEqualTo(EXPECTED_MESSAGE);\n    assertThat(innerError).extracting(\"code\").isEqualTo(ResponseErrorCode.InvalidParams.getValue());\n    assertThat(innerError).extracting(\"data\").asInstanceOf(InstanceOfAssertFactories.array(String[].class))\n      .containsExactlyInAnyOrder(\"utm_medium\", \"utm_source\", \"utm_content\", \"utm_term\");\n    verifyNoInteractions(mockedHelper);\n  }\n\n  @Test\n  void generate_user_token_should_throw_validation_error_for_two() {\n    TokenGeneratorHelper mockedHelper = mock(TokenGeneratorHelper.class);\n    underTest = new ConnectionService(eventPublisher, repository, List.of(), List.of(), SonarCloudActiveEnvironment.prod(), null, mockedHelper);\n    HelpGenerateUserTokenParams.Utm invalidParams = new HelpGenerateUserTokenParams.Utm(\"medium wrong\", \"source\", \"cont-ent\", \"t.e.r.m\");\n    SonarLintCancelMonitor cancelMonitor = new SonarLintCancelMonitor();\n\n    ResponseErrorException exception = catchThrowableOfType(ResponseErrorException.class, () ->\n      underTest.helpGenerateUserToken(\"serverUrl\", invalidParams, cancelMonitor));\n    ResponseError innerError = exception.getResponseError();\n\n    assertThat(exception).hasMessage(EXPECTED_MESSAGE);\n    assertThat(innerError).extracting(\"message\").isEqualTo(EXPECTED_MESSAGE);\n    assertThat(innerError).extracting(\"code\").isEqualTo(ResponseErrorCode.InvalidParams.getValue());\n    assertThat(innerError).extracting(\"data\").asInstanceOf(InstanceOfAssertFactories.array(String[].class))\n      .containsExactlyInAnyOrder(\"utm_medium\", \"utm_term\");\n    verifyNoInteractions(mockedHelper);\n  }\n\n  @Test\n  void add_new_connection_and_post_event() {\n    underTest = new ConnectionService(eventPublisher, repository, List.of(), List.of(), SonarCloudActiveEnvironment.prod(), null, null);\n\n    underTest.didUpdateConnections(List.of(SQ_DTO_1), List.of());\n    assertThat(repository.getConnectionsById()).containsOnlyKeys(\"sq1\");\n    assertThat(repository.getConnectionById(\"sq1\"))\n      .asInstanceOf(InstanceOfAssertFactories.type(SonarQubeConnectionConfiguration.class))\n      .extracting(SonarQubeConnectionConfiguration::getConnectionId, SonarQubeConnectionConfiguration::getUrl, SonarQubeConnectionConfiguration::isDisableNotifications,\n        SonarQubeConnectionConfiguration::getKind)\n      .containsOnly(\"sq1\", \"http://url1\", true, ConnectionKind.SONARQUBE);\n\n    underTest.didUpdateConnections(List.of(SQ_DTO_1, SQ_DTO_2), List.of());\n    assertThat(repository.getConnectionsById()).containsOnlyKeys(\"sq1\", \"sq2\");\n\n    underTest.didUpdateConnections(List.of(SQ_DTO_1, SQ_DTO_2), List.of(SC_DTO_1));\n    assertThat(repository.getConnectionsById()).containsOnlyKeys(\"sq1\", \"sq2\", \"sc1\");\n    assertThat(repository.getConnectionById(\"sc1\"))\n      .asInstanceOf(InstanceOfAssertFactories.type(SonarCloudConnectionConfiguration.class))\n      .extracting(SonarCloudConnectionConfiguration::getConnectionId, SonarCloudConnectionConfiguration::getUrl, SonarCloudConnectionConfiguration::isDisableNotifications,\n        SonarCloudConnectionConfiguration::getKind, SonarCloudConnectionConfiguration::getOrganization)\n      .containsOnly(\"sc1\", \"https://sonarcloud.io\", true, ConnectionKind.SONARCLOUD, \"org1\");\n\n    var captor = ArgumentCaptor.forClass(ConnectionConfigurationAddedEvent.class);\n    verify(eventPublisher, times(3)).publishEvent(captor.capture());\n    var events = captor.getAllValues();\n\n    assertThat(events).extracting(ConnectionConfigurationAddedEvent::addedConnectionId).containsExactly(\"sq1\", \"sq2\", \"sc1\");\n  }\n\n  @Test\n  void multiple_connections_with_same_id_should_log_and_ignore() {\n    underTest = new ConnectionService(eventPublisher, repository, List.of(), List.of(), SonarCloudActiveEnvironment.prod(), null, null);\n    underTest.didUpdateConnections(List.of(SQ_DTO_1), List.of());\n\n    underTest.didUpdateConnections(List.of(SQ_DTO_1, SQ_DTO_1_DUP), List.of());\n\n    assertThat(repository.getConnectionById(\"sq1\"))\n      .asInstanceOf(InstanceOfAssertFactories.type(SonarQubeConnectionConfiguration.class))\n      .extracting(SonarQubeConnectionConfiguration::getConnectionId, SonarQubeConnectionConfiguration::getUrl, SonarQubeConnectionConfiguration::isDisableNotifications,\n        SonarQubeConnectionConfiguration::getKind)\n      .containsOnly(\"sq1\", \"http://url1_dup\", true, ConnectionKind.SONARQUBE);\n\n    assertThat(logTester.logs(LogOutput.Level.ERROR)).containsExactly(\"Duplicate connection registered: sq1\");\n  }\n\n  @Test\n  void remove_connection() {\n    underTest = new ConnectionService(eventPublisher, repository, List.of(SQ_DTO_1), List.of(SC_DTO_1), SonarCloudActiveEnvironment.prod(), null, null);\n    assertThat(repository.getConnectionsById()).containsKeys(\"sq1\", \"sc1\");\n\n    underTest.didUpdateConnections(List.of(SQ_DTO_1), List.of());\n    assertThat(repository.getConnectionsById()).containsKeys(\"sq1\");\n\n    underTest.didUpdateConnections(List.of(), List.of());\n    assertThat(repository.getConnectionsById()).isEmpty();\n\n    var captor = ArgumentCaptor.forClass(ConnectionConfigurationRemovedEvent.class);\n    verify(eventPublisher, times(2)).publishEvent(captor.capture());\n    var events = captor.getAllValues();\n\n    assertThat(events).extracting(ConnectionConfigurationRemovedEvent::removedConnectionId).containsExactly(\"sc1\", \"sq1\");\n  }\n\n  @Test\n  void remove_connection_should_log_if_unknown_connection_and_ignore() {\n    var mockedRepo = mock(ConnectionConfigurationRepository.class);\n    underTest = new ConnectionService(eventPublisher, mockedRepo, List.of(), List.of(), SonarCloudActiveEnvironment.prod(), null, null);\n\n    // Emulate a race condition on the repository: the connection is gone between get and remove\n    when(mockedRepo.getConnectionsById()).thenReturn(Map.of(\"id\", new SonarQubeConnectionConfiguration(\"id\", \"http://foo\", true)));\n    when(mockedRepo.remove(\"id\")).thenReturn(null);\n\n    underTest.didUpdateConnections(List.of(), List.of());\n\n    assertThat(logTester.logs(LogOutput.Level.DEBUG)).containsExactly(\"Attempt to remove connection 'id' that was not registered. Possibly a race condition?\");\n  }\n\n  @Test\n  void update_connection() {\n    underTest = new ConnectionService(eventPublisher, repository, List.of(SQ_DTO_1), List.of(), SonarCloudActiveEnvironment.prod(), null, null);\n\n    underTest.didUpdateConnections(List.of(SQ_DTO_1_DUP), List.of());\n\n    assertThat(repository.getConnectionById(\"sq1\"))\n      .asInstanceOf(InstanceOfAssertFactories.type(SonarQubeConnectionConfiguration.class))\n      .extracting(SonarQubeConnectionConfiguration::getConnectionId, SonarQubeConnectionConfiguration::getUrl, SonarQubeConnectionConfiguration::isDisableNotifications,\n        SonarQubeConnectionConfiguration::getKind)\n      .containsOnly(\"sq1\", \"http://url1_dup\", true, ConnectionKind.SONARQUBE);\n\n    var captor = ArgumentCaptor.forClass(ConnectionConfigurationUpdatedEvent.class);\n    verify(eventPublisher, times(1)).publishEvent(captor.capture());\n    var events = captor.getAllValues();\n\n    assertThat(events).extracting(ConnectionConfigurationUpdatedEvent::updatedConnectionId).containsExactly(\"sq1\");\n  }\n\n  @Test\n  void update_connection_should_log_if_unknown_connection_and_add() {\n    var mockedRepo = mock(ConnectionConfigurationRepository.class);\n    underTest = new ConnectionService(eventPublisher, mockedRepo, List.of(), List.of(), SonarCloudActiveEnvironment.prod(), null, null);\n\n    // Emulate a race condition on the repository: the connection is gone between get and add\n    when(mockedRepo.getConnectionsById()).thenReturn(Map.of(SQ_DTO_2.getConnectionId(), new SonarQubeConnectionConfiguration(SQ_DTO_2.getConnectionId(), \"http://foo\", true)));\n    when(mockedRepo.addOrReplace(any())).thenReturn(null);\n\n    underTest.didUpdateConnections(List.of(SQ_DTO_2), List.of());\n\n    assertThat(logTester.logs(LogOutput.Level.DEBUG)).containsExactly(\"Attempt to update connection 'sq2' that was not registered. Possibly a race condition?\");\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/DtoMapperTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.tracking.TrackedIssue;\n\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.Mockito.mock;\n\nclass DtoMapperTests {\n\n  @Test\n  void should_throw_if_hotspot_has_no_vulnerability_probability() {\n    var trackedIssue = mock(TrackedIssue.class);\n\n    assertThrows(IllegalStateException.class, () -> DtoMapper.toRaisedHotspotDto(trackedIssue, null, true));\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/SonarCloudActiveEnvironmentTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport java.net.URI;\nimport java.util.Map;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.SonarQubeCloudRegionDto;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass SonarCloudActiveEnvironmentTests {\n  private static URI baseUri = URI.create(\"baseUri\");\n  private static URI apiUri = URI.create(\"apiUri\");\n  private static URI webSocketUri = URI.create(\"webSocketUri\");\n  \n  private static SonarQubeCloudRegionDto regionWithBaseUri = new SonarQubeCloudRegionDto(baseUri, null, null);\n  private static SonarQubeCloudRegionDto regionWithApiUri = new SonarQubeCloudRegionDto(null, apiUri, null);\n  private static SonarQubeCloudRegionDto regionWithWebSocketUri = new SonarQubeCloudRegionDto(null, null, webSocketUri);\n  \n  @Test\n  void test_getUri() {\n    assertThat(SonarCloudActiveEnvironment.prod().getUri(SonarCloudRegion.EU))\n      .isEqualTo(SonarCloudRegion.EU.getProductionUri());\n    assertThat(SonarCloudActiveEnvironment.prod().getUri(SonarCloudRegion.US))\n      .isEqualTo(SonarCloudRegion.US.getProductionUri());\n\n    assertThat(new SonarCloudActiveEnvironment(\n      Map.of(org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.EU, regionWithBaseUri))\n      .getUri(SonarCloudRegion.EU))\n      .isEqualTo(baseUri);\n    assertThat(new SonarCloudActiveEnvironment(\n      Map.of(org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.US, regionWithBaseUri))\n      .getUri(SonarCloudRegion.US))\n      .isEqualTo(baseUri);\n\n    assertThat(new SonarCloudActiveEnvironment(\n      Map.of(org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.EU, regionWithApiUri))\n      .getUri(SonarCloudRegion.EU))\n      .isEqualTo(SonarCloudRegion.EU.getProductionUri());\n    assertThat(new SonarCloudActiveEnvironment(\n      Map.of(org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.US, regionWithApiUri))\n      .getUri(SonarCloudRegion.US))\n      .isEqualTo(SonarCloudRegion.US.getProductionUri());\n  }\n\n  @Test\n  void test_getApiUri() {\n    assertThat(SonarCloudActiveEnvironment.prod().getApiUri(SonarCloudRegion.EU))\n      .isEqualTo(SonarCloudRegion.EU.getApiProductionUri());\n    assertThat(SonarCloudActiveEnvironment.prod().getApiUri(SonarCloudRegion.US))\n      .isEqualTo(SonarCloudRegion.US.getApiProductionUri());\n\n    assertThat(new SonarCloudActiveEnvironment(\n      Map.of(org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.EU, regionWithApiUri))\n      .getApiUri(SonarCloudRegion.EU))\n      .isEqualTo(apiUri);\n    assertThat(new SonarCloudActiveEnvironment(\n      Map.of(org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.US, regionWithApiUri))\n      .getApiUri(SonarCloudRegion.US))\n      .isEqualTo(apiUri);\n\n    assertThat(new SonarCloudActiveEnvironment(\n      Map.of(org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.EU, regionWithBaseUri))\n      .getApiUri(SonarCloudRegion.EU))\n      .isEqualTo(SonarCloudRegion.EU.getApiProductionUri());\n    assertThat(new SonarCloudActiveEnvironment(\n      Map.of(org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.US, regionWithBaseUri))\n      .getApiUri(SonarCloudRegion.US))\n      .isEqualTo(SonarCloudRegion.US.getApiProductionUri());\n  }\n\n  @Test\n  void test_getWebSocketsEndpointUri() {\n    assertThat(SonarCloudActiveEnvironment.prod().getWebSocketsEndpointUri(SonarCloudRegion.EU))\n      .isEqualTo(SonarCloudRegion.EU.getWebSocketUri());\n    assertThat(SonarCloudActiveEnvironment.prod().getWebSocketsEndpointUri(SonarCloudRegion.US))\n      .isEqualTo(SonarCloudRegion.US.getWebSocketUri());\n\n    assertThat(new SonarCloudActiveEnvironment(\n      Map.of(org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.EU, regionWithWebSocketUri)).\n      getWebSocketsEndpointUri(SonarCloudRegion.EU))\n      .isEqualTo(webSocketUri);\n    assertThat(new SonarCloudActiveEnvironment(\n      Map.of(org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.US, regionWithWebSocketUri))\n      .getWebSocketsEndpointUri(SonarCloudRegion.US))\n      .isEqualTo(webSocketUri);\n\n    assertThat(new SonarCloudActiveEnvironment(\n      Map.of(org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.EU, regionWithApiUri))\n      .getWebSocketsEndpointUri(SonarCloudRegion.EU))\n      .isEqualTo(SonarCloudRegion.EU.getWebSocketUri());\n    assertThat(new SonarCloudActiveEnvironment(\n      Map.of(org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.US, regionWithApiUri))\n      .getWebSocketsEndpointUri(SonarCloudRegion.US))\n      .isEqualTo(SonarCloudRegion.US.getWebSocketUri());\n  }\n  \n  @Test\n  void test_isSonarQubeCloud() {\n    assertThat(SonarCloudActiveEnvironment.prod().isSonarQubeCloud(\"aaaa\")).isFalse();\n\n    assertThat(SonarCloudActiveEnvironment.prod()\n      .isSonarQubeCloud(SonarCloudRegion.EU.getProductionUri().toString())).isTrue();\n    assertThat(SonarCloudActiveEnvironment.prod()\n      .isSonarQubeCloud(SonarCloudRegion.US.getProductionUri().toString())).isTrue();\n\n    assertThat(new SonarCloudActiveEnvironment(\n      Map.of(org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.EU, regionWithBaseUri))\n      .isSonarQubeCloud(baseUri.toString())).isTrue();\n    assertThat(new SonarCloudActiveEnvironment(\n      Map.of(org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.EU, regionWithApiUri))\n      .isSonarQubeCloud(SonarCloudRegion.EU.getProductionUri().toString())).isTrue();\n    assertThat(new SonarCloudActiveEnvironment(\n      Map.of(org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.US, regionWithBaseUri))\n      .isSonarQubeCloud(baseUri.toString())).isTrue();\n    assertThat(new SonarCloudActiveEnvironment(\n      Map.of(org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.US, regionWithApiUri))\n      .isSonarQubeCloud(SonarCloudRegion.US.getProductionUri().toString())).isTrue();\n  }\n\n  @Test\n  void test_getRegionOrThrow() {\n    assertThatThrownBy(() -> SonarCloudActiveEnvironment.prod().getRegionOrThrow(\"aaaa\"))\n      .isInstanceOf(IllegalArgumentException.class);\n\n    assertThat(SonarCloudActiveEnvironment.prod()\n      .getRegionOrThrow(SonarCloudRegion.EU.getProductionUri().toString())).isEqualTo(SonarCloudRegion.EU);\n    assertThat(SonarCloudActiveEnvironment.prod()\n      .getRegionOrThrow(SonarCloudRegion.US.getProductionUri().toString())).isEqualTo(SonarCloudRegion.US);\n\n    assertThat(new SonarCloudActiveEnvironment(\n      Map.of(org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.EU, regionWithBaseUri))\n      .getRegionOrThrow(baseUri.toString())).isEqualTo(SonarCloudRegion.EU);\n    assertThat(new SonarCloudActiveEnvironment(\n      Map.of(org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.US, regionWithBaseUri))\n      .getRegionOrThrow(baseUri.toString())).isEqualTo(SonarCloudRegion.US);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/SonarProjectsCacheTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.function.Function;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.mockito.Mockito;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationRemovedEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationUpdatedEvent;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.SonarProjectDto;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.component.ServerProject;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass SonarProjectsCacheTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  public static final String SQ_1 = \"sq1\";\n  public static final String PROJECT_KEY_1 = \"projectKey1\";\n  public static final String PROJECT_KEY_2 = \"projectKey2\";\n  public static final String PROJECT_NAME_1 = \"Project 1\";\n  public static final String PROJECT_NAME_2 = \"Project 2\";\n  public static final ServerProject PROJECT_1 = new ServerProject(PROJECT_KEY_1, PROJECT_NAME_1, false);\n  public static final ServerProject PROJECT_1_CHANGED = new ServerProject(PROJECT_KEY_1, PROJECT_NAME_2, false);\n  public static final ServerProject PROJECT_2 = new ServerProject(PROJECT_KEY_2, PROJECT_NAME_2, false);\n  private final ServerApi serverApi = mock(ServerApi.class, Mockito.RETURNS_DEEP_STUBS);\n  private final SonarQubeClientManager sonarQubeClientManager = mock(SonarQubeClientManager.class);\n  private final SonarProjectsCache underTest = new SonarProjectsCache(sonarQubeClientManager);\n\n  @BeforeEach\n  void setup() {\n    when(sonarQubeClientManager.withActiveClientAndReturn(any(), any())).thenAnswer(\n      invocation -> Optional.ofNullable(((Function<ServerApi, Object>) invocation.getArguments()[1]).apply(serverApi)));\n  }\n\n  @Test\n  void getSonarProject_should_query_server_once() {\n    when(serverApi.component().getProject(eq(PROJECT_KEY_1), any(SonarLintCancelMonitor.class)))\n      .thenReturn(Optional.of(PROJECT_1))\n      .thenThrow(new AssertionError(\"Should only be called once\"));\n\n    var sonarProjectCall1 = underTest.getSonarProject(SQ_1, PROJECT_KEY_1, new SonarLintCancelMonitor());\n\n    assertThat(sonarProjectCall1).isPresent();\n    assertThat(sonarProjectCall1.get().key()).isEqualTo(PROJECT_KEY_1);\n    assertThat(sonarProjectCall1.get().name()).isEqualTo(PROJECT_NAME_1);\n\n    var sonarProjectCall2 = underTest.getSonarProject(SQ_1, PROJECT_KEY_1, new SonarLintCancelMonitor());\n\n    assertThat(sonarProjectCall2).isPresent();\n    assertThat(sonarProjectCall2.get().key()).isEqualTo(PROJECT_KEY_1);\n    assertThat(sonarProjectCall2.get().name()).isEqualTo(PROJECT_NAME_1);\n    verify(serverApi.component(), times(1)).getProject(eq(PROJECT_KEY_1), any(SonarLintCancelMonitor.class));\n  }\n\n  @Test\n  void getSonarProject_should_cache_failure() {\n    when(serverApi.component().getProject(eq(PROJECT_KEY_1), any(SonarLintCancelMonitor.class)))\n      .thenThrow(new RuntimeException(\"Unable to fetch project\"))\n      .thenReturn(Optional.of(PROJECT_1));\n\n    var sonarProjectCall1 = underTest.getSonarProject(SQ_1, PROJECT_KEY_1, new SonarLintCancelMonitor());\n\n    assertThat(sonarProjectCall1).isEmpty();\n\n    var sonarProjectCall2 = underTest.getSonarProject(SQ_1, PROJECT_KEY_1, new SonarLintCancelMonitor());\n\n    assertThat(sonarProjectCall2).isEmpty();\n    verify(serverApi.component(), times(1)).getProject(eq(PROJECT_KEY_1), any(SonarLintCancelMonitor.class));\n  }\n\n  @Test\n  void evict_cache_if_connection_removed_to_save_memory() {\n    when(serverApi.component().getProject(eq(PROJECT_KEY_1), any(SonarLintCancelMonitor.class)))\n      .thenReturn(Optional.of(PROJECT_1));\n\n    var sonarProjectCall1 = underTest.getSonarProject(SQ_1, PROJECT_KEY_1, new SonarLintCancelMonitor());\n\n    assertThat(sonarProjectCall1).isPresent();\n    assertThat(sonarProjectCall1.get().key()).isEqualTo(PROJECT_KEY_1);\n    assertThat(sonarProjectCall1.get().name()).isEqualTo(PROJECT_NAME_1);\n\n    underTest.connectionRemoved(new ConnectionConfigurationRemovedEvent(SQ_1));\n\n    var sonarProjectCall2 = underTest.getSonarProject(SQ_1, PROJECT_KEY_1, new SonarLintCancelMonitor());\n\n    assertThat(sonarProjectCall2).isPresent();\n    assertThat(sonarProjectCall2.get().key()).isEqualTo(PROJECT_KEY_1);\n    assertThat(sonarProjectCall2.get().name()).isEqualTo(PROJECT_NAME_1);\n    verify(serverApi.component(), times(2)).getProject(eq(PROJECT_KEY_1), any(SonarLintCancelMonitor.class));\n  }\n\n  @Test\n  void evict_cache_if_connection_updated_to_refresh_on_next_get() {\n    when(serverApi.component().getProject(eq(PROJECT_KEY_1), any(SonarLintCancelMonitor.class)))\n      .thenReturn(Optional.of(PROJECT_1))\n      .thenReturn(Optional.of(PROJECT_1_CHANGED));\n\n    var sonarProjectCall1 = underTest.getSonarProject(SQ_1, PROJECT_KEY_1, new SonarLintCancelMonitor());\n\n    assertThat(sonarProjectCall1).isPresent();\n    assertThat(sonarProjectCall1.get().key()).isEqualTo(PROJECT_KEY_1);\n    assertThat(sonarProjectCall1.get().name()).isEqualTo(PROJECT_NAME_1);\n\n    underTest.connectionUpdated(new ConnectionConfigurationUpdatedEvent(SQ_1));\n\n    var sonarProjectCall2 = underTest.getSonarProject(SQ_1, PROJECT_KEY_1, new SonarLintCancelMonitor());\n\n    assertThat(sonarProjectCall2).isPresent();\n    assertThat(sonarProjectCall2.get().key()).isEqualTo(PROJECT_KEY_1);\n    assertThat(sonarProjectCall2.get().name()).isEqualTo(PROJECT_NAME_2);\n    verify(serverApi.component(), times(2)).getProject(eq(PROJECT_KEY_1), any(SonarLintCancelMonitor.class));\n  }\n\n  @Test\n  void getTextSearchIndex_should_query_server_once() {\n    when(serverApi.component().getAllProjects(any()))\n      .thenReturn(List.of(PROJECT_1, PROJECT_2))\n      .thenThrow(new AssertionError(\"Should only be called once\"));\n\n    var searchIndex1 = underTest.getTextSearchIndex(SQ_1, new SonarLintCancelMonitor());\n\n    assertThat(searchIndex1.size()).isEqualTo(2);\n\n    var searchIndex2 = underTest.getTextSearchIndex(SQ_1, new SonarLintCancelMonitor());\n\n    assertThat(searchIndex2.size()).isEqualTo(2);\n    verify(serverApi.component(), times(1)).getAllProjects(any());\n  }\n\n  @Test\n  void getTextSearchIndex_should_return_empty_index_if_no_projects() {\n    when(serverApi.component().getAllProjects(any()))\n      .thenReturn(List.of())\n      .thenThrow(new AssertionError(\"Should only be called once\"));\n\n    var searchIndex1 = underTest.getTextSearchIndex(SQ_1, new SonarLintCancelMonitor());\n\n    assertThat(searchIndex1.isEmpty()).isTrue();\n\n    underTest.getTextSearchIndex(SQ_1, new SonarLintCancelMonitor());\n\n    assertThat(searchIndex1.isEmpty()).isTrue();\n    verify(serverApi.component(), times(1)).getAllProjects(any());\n  }\n\n  @Test\n  void getTextSearchIndex_should_cache_failure() {\n    when(serverApi.component().getAllProjects(any()))\n      .thenThrow(new RuntimeException(\"Unable to fetch projects\"))\n      .thenReturn(List.of(PROJECT_1, PROJECT_2));\n\n    var searchIndex1 = underTest.getTextSearchIndex(SQ_1, new SonarLintCancelMonitor());\n\n    assertThat(searchIndex1.isEmpty()).isTrue();\n\n    underTest.getTextSearchIndex(SQ_1, new SonarLintCancelMonitor());\n\n    assertThat(searchIndex1.isEmpty()).isTrue();\n    verify(serverApi.component(), times(1)).getAllProjects(any());\n  }\n\n  @Test\n  void fuzzySearchProjects_should_search_by_both_key_and_name_splitting_by_underscore() {\n    var project1 = new ServerProject(\"mySearchTerm\", \"project\", false);\n    var project2 = new ServerProject(\"key\", \"searchTerm__00\", false);\n    var projectNotFound = new ServerProject(\"SonarSource_peachee-dotnet\", \"DriAutomation.NET\", false);\n    var projectFound = new ServerProject(\"SonarSource_sonarsource-infra-peach\", \"sonarsource-infra-peach\", false);\n    when(serverApi.component().getAllProjects(any()))\n      .thenReturn(List.of(project1, project2, projectNotFound, projectFound));\n\n    var actual = underTest.fuzzySearchProjects(SQ_1, \"peach\", new SonarLintCancelMonitor());\n\n    assertThat(actual).containsExactlyInAnyOrder(\n      new SonarProjectDto(\"SonarSource_peachee-dotnet\", \"DriAutomation.NET\"),\n      new SonarProjectDto(\"SonarSource_sonarsource-infra-peach\", \"sonarsource-infra-peach\")\n    );\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/SonarQubeClientManagerTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport java.net.URI;\nimport java.util.Optional;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.http.HttpClient;\nimport org.sonarsource.sonarlint.core.http.HttpClientProvider;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarCloudConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarQubeConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.GetCredentialsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.sync.InvalidTokenParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.serverapi.exception.UnauthorizedException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.refEq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass SonarQubeClientManagerTests {\n  private static final String API_SYSTEM_STATUS = \"/api/system/status\";\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private final ConnectionConfigurationRepository connectionRepository = mock(ConnectionConfigurationRepository.class);\n  private final HttpClientProvider httpClientProvider = mock(HttpClientProvider.class);\n  private SonarLintRpcClient client;\n  private SonarQubeClientManager underTest;\n\n  @BeforeEach\n  void setUp() {\n    client = mock(SonarLintRpcClient.class);\n    when(client.getCredentials(any())).thenReturn(CompletableFuture.completedFuture(new GetCredentialsResponse(new TokenDto(\"token\"))));\n    underTest = new SonarQubeClientManager(connectionRepository, httpClientProvider, SonarCloudActiveEnvironment.prod(), client);\n  }\n\n  @Test\n  void getValidClientOrThrow_for_sonarqube() {\n    setupServerConnection(\"sqs1\", \"serverUrl\");\n\n    var connection = underTest.getValidClientOrThrow(\"sqs1\");\n\n    assertThat(connection.isActive()).isTrue();\n  }\n\n  @Test\n  void getValidClientOrThrow_for_sonarcloud() {\n    setupCloudConnection(\"sqc1\", SonarCloudRegion.EU.getProductionUri(), SonarCloudRegion.EU.getApiProductionUri());\n\n    var connection = underTest.getValidClientOrThrow(\"sqc1\");\n\n    assertThat(connection.isActive()).isTrue();\n  }\n\n  @Test\n  void getValidClientOrThrow_for_sonarcloud_with_trailing_slash_notConnected() {\n    var uriWithSlash = URI.create(SonarCloudRegion.EU.getProductionUri() + \"/\");\n    setupCloudConnection(\"sqc-with-slash\", uriWithSlash, SonarCloudRegion.EU.getApiProductionUri());\n\n    var connection = underTest.getValidClientOrThrow(\"sqc-with-slash\");\n\n    assertThat(connection.isActive()).isTrue();\n  }\n\n  @Test\n  void getValidClientOrThrow_should_throw_if_connection_doesnt_exist() {\n    var throwable = catchThrowable(() -> underTest.getValidClientOrThrow(\"sqc1\"));\n\n    assertThat(throwable.getMessage()).isEqualTo(\"Connection 'sqc1' is not valid\");\n  }\n\n  @Test\n  void withActiveClient_should_execute_consumer_when_valid_client_exists() {\n    setupServerConnection(\"sqs1\", \"serverUrl\");\n    var consumerExecuted = new AtomicBoolean(false);\n\n    underTest.withActiveClient(\"sqs1\", api -> consumerExecuted.set(true));\n\n    assertThat(consumerExecuted.get()).isTrue();\n  }\n\n  @Test\n  void withActiveClient_should_not_execute_consumer_when_connection_not_found() {\n    when(connectionRepository.getConnectionById(\"nonexistent\")).thenReturn(null);\n    var consumerExecuted = new AtomicBoolean(false);\n\n    underTest.withActiveClient(\"nonexistent\", api -> consumerExecuted.set(true));\n\n    assertThat(consumerExecuted.get()).isFalse();\n    assertThat(logTester.logs()).contains(\"Connection 'nonexistent' is gone\");\n  }\n\n  @Test\n  void withActiveClient_should_not_execute_consumer_and_notify_user_when_client_becomes_inactive() {\n    setupServerConnection(\"sqs1\", \"serverUrl\");\n    var consumerExecuted = new AtomicBoolean(false);\n\n    underTest.withActiveClient(\"sqs1\", api -> {\n      throw new UnauthorizedException(\"401\");\n    });\n    underTest.withActiveClient(\"sqs1\", api -> consumerExecuted.set(true));\n\n    assertThat(consumerExecuted.get()).isFalse();\n    assertThat(logTester.logs()).contains(\"Connection 'sqs1' is invalid\");\n    verify(client, times(1)).invalidToken(any());\n  }\n\n  @Test\n  void withActiveClient_should_cache_clients_and_reuse_them() {\n    setupServerConnection(\"sqs1\", \"serverUrl\");\n    var executionCount = new AtomicInteger(0);\n\n    underTest.withActiveClient(\"sqs1\", api -> executionCount.incrementAndGet());\n    underTest.withActiveClient(\"sqs1\", api -> executionCount.incrementAndGet());\n    underTest.withActiveClient(\"sqs1\", api -> executionCount.incrementAndGet());\n\n    assertThat(executionCount.get()).isEqualTo(3);\n    verify(httpClientProvider, times(1)).getHttpClientWithPreemptiveAuth(\"token\", false);\n  }\n\n  @Test\n  void withActiveClientAndReturn_should_return_value_when_valid_client_exists() {\n    setupServerConnection(\"sqs1\", \"serverUrl\");\n\n    var result = underTest.withActiveClientAndReturn(\"sqs1\", api -> \"test-result\");\n\n    assertThat(result).isPresent().get().isEqualTo(\"test-result\");\n  }\n\n  @Test\n  void withActiveClientAndReturn_should_return_empty_when_connection_not_found() {\n    when(connectionRepository.getConnectionById(\"nonexistent\")).thenReturn(null);\n    var result = underTest.withActiveClientAndReturn(\"nonexistent\", api -> \"test-result\");\n    assertThat(result).isEmpty();\n  }\n\n  @Test\n  void withActiveClientAndReturn_should_return_empty_when_client_inactive() {\n    setupServerConnection(\"sqs1\", \"serverUrl\");\n\n    underTest.withActiveClient(\"sqs1\", api -> {\n      throw new UnauthorizedException(\"401\");\n    });\n\n    var result = underTest.withActiveClientAndReturn(\"sq1\", api -> \"test-result\");\n    assertThat(result).isEmpty();\n  }\n\n  @Test\n  void withActiveClientFlatMapOptionalAndReturn_should_return_optional_when_valid_client_exists() {\n    setupServerConnection(\"sqs1\", \"serverUrl\");\n\n    var result = underTest.withActiveClientFlatMapOptionalAndReturn(\"sqs1\", api -> Optional.of(\"test-result\"));\n\n    assertThat(result).isPresent().get().isEqualTo(\"test-result\");\n  }\n\n  @Test\n  void withActiveClientFlatMapOptionalAndReturn_should_return_empty_when_function_returns_empty() {\n    setupServerConnection(\"sqs1\", \"serverUrl\");\n\n    var result = underTest.withActiveClientFlatMapOptionalAndReturn(\"sqs1\", api -> Optional.empty());\n\n    assertThat(result).isEmpty();\n  }\n\n  @Test\n  void withActiveClientFlatMapOptionalAndReturn_should_return_empty_when_connection_not_found() {\n    var result = underTest.withActiveClientFlatMapOptionalAndReturn(\"nonexistent\", api -> Optional.of(\"test-result\"));\n\n    assertThat(result).isEmpty();\n  }\n\n  @Test\n  void withActiveClient_should_not_execute_consumer_when_invalid_credentials() {\n    var httpClient = mock(HttpClient.class);\n    when(httpClientProvider.getHttpClientWithoutAuth()).thenReturn(httpClient);\n    setupSuccessfulStatusResponse(httpClient, \"serverUrl\" + API_SYSTEM_STATUS);\n    when(connectionRepository.getConnectionById(\"connectionId\"))\n      .thenReturn(new SonarQubeConnectionConfiguration(\"connectionId\", \"serverUrl\", true));\n    when(client.getCredentials(any())).thenReturn(CompletableFuture.completedFuture(new GetCredentialsResponse(new TokenDto(null))));\n\n    var consumerExecuted = new AtomicBoolean(false);\n\n    underTest.withActiveClient(\"connectionId\", api -> consumerExecuted.set(true));\n\n    assertThat(consumerExecuted.get()).isFalse();\n    verify(client).invalidToken(refEq(new InvalidTokenParams(\"connectionId\")));\n  }\n\n  private void setupServerConnection(String connectionId, String serverUrl) {\n    when(connectionRepository.getConnectionById(connectionId))\n      .thenReturn(new SonarQubeConnectionConfiguration(connectionId, serverUrl, true));\n    var httpClient = mock(HttpClient.class);\n    when(httpClientProvider.getHttpClientWithPreemptiveAuth(\"token\", false)).thenReturn(httpClient);\n    when(httpClientProvider.getHttpClientWithoutAuth()).thenReturn(httpClient);\n    setupSuccessfulStatusResponse(httpClient, serverUrl + API_SYSTEM_STATUS);\n  }\n\n  private void setupCloudConnection(String connectionId, URI prodUri, URI apiUri) {\n    when(connectionRepository.getConnectionById(connectionId))\n      .thenReturn(new SonarCloudConnectionConfiguration(prodUri, apiUri, connectionId, \"organizationKey\", SonarCloudRegion.EU, false));\n    var httpClient = mock(HttpClient.class);\n    when(httpClientProvider.getHttpClientWithPreemptiveAuth(\"token\", true)).thenReturn(httpClient);\n    when(httpClientProvider.getHttpClientWithoutAuth()).thenReturn(httpClient);\n    setupSuccessfulStatusResponse(httpClient, API_SYSTEM_STATUS);\n  }\n\n  private void setupSuccessfulStatusResponse(HttpClient httpClient, String statusPath) {\n    var httpResponse = mock(HttpClient.Response.class);\n    when(httpResponse.isSuccessful()).thenReturn(true);\n    when(httpResponse.bodyAsString()).thenReturn(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"9.9\\\",\\\"status\\\": \\\"UP\\\"}\");\n    when(httpClient.getAsyncAnonymous(statusPath)).thenReturn(CompletableFuture.completedFuture(httpResponse));\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/TelemetryServerAttributesProviderTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.active.rules.ActiveRulesService;\nimport org.sonarsource.sonarlint.core.analysis.NodeJsService;\nimport org.sonarsource.sonarlint.core.commons.BoundScope;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.repository.config.BindingConfiguration;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationScope;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarCloudConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarQubeConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.repository.rules.RulesRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.StandaloneRuleConfigDto;\nimport org.sonarsource.sonarlint.core.rule.extractor.SonarLintRuleDefinition;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryServerAttributesProvider;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass TelemetryServerAttributesProviderTests {\n\n  @Test\n  void it_should_calculate_connectedMode_usesSC_notDisabledNotifications_telemetry_attrs() {\n    var configurationScopeId = \"scopeId\";\n    var connectionId = \"connectionId\";\n    var projectKey = \"projectKey\";\n\n    var configurationRepository = mock(ConfigurationRepository.class);\n    when(configurationRepository.getAllBoundScopes()).thenReturn(Set.of(new BoundScope(configurationScopeId, connectionId, projectKey)));\n\n    var connectionConfigurationRepository = mock(ConnectionConfigurationRepository.class);\n    when(connectionConfigurationRepository.getConnectionById(connectionId)).thenReturn(new SonarCloudConnectionConfiguration(SonarCloudRegion.EU.getProductionUri(), SonarCloudRegion.EU.getApiProductionUri(), connectionId, \"myTestOrg\", SonarCloudRegion.EU, false));\n    var underTest = new TelemetryServerAttributesProvider(configurationRepository, connectionConfigurationRepository, mock(ActiveRulesService.class), mock(RulesRepository.class), mock(NodeJsService.class), mock(StorageService.class));\n\n    var telemetryLiveAttributes = underTest.getTelemetryServerLiveAttributes();\n    assertThat(telemetryLiveAttributes.usesConnectedMode()).isTrue();\n    assertThat(telemetryLiveAttributes.usesSonarCloud()).isTrue();\n    assertThat(telemetryLiveAttributes.childBindingCount()).isZero();\n    assertThat(telemetryLiveAttributes.sonarQubeServerBindingCount()).isZero();\n    assertThat(telemetryLiveAttributes.sonarQubeCloudEUBindingCount()).isEqualTo(1);\n    assertThat(telemetryLiveAttributes.sonarQubeCloudUSBindingCount()).isZero();\n    assertThat(telemetryLiveAttributes.devNotificationsDisabled()).isFalse();\n    assertThat(telemetryLiveAttributes.nonDefaultEnabledRules()).isEmpty();\n    assertThat(telemetryLiveAttributes.defaultDisabledRules()).isEmpty();\n  }\n\n  @Test\n  void it_should_calculate_connectedMode_notUsesSC_disabledDevNotifications_telemetry_attrs() {\n    var configurationScopeId1 = \"scopeId_1\";\n    var configurationScopeId2 = \"scopeId_2\";\n    var configurationScopeId3 = \"scopeId_3\";\n    var connectionId1 = \"connectionId_1\";\n    var connectionId2 = \"connectionId_2\";\n    var projectKey1 = \"projectKey1\";\n    var projectKey2 = \"projectKey2\";\n\n    var configurationRepository = mock(ConfigurationRepository.class);\n    when(configurationRepository.getAllBoundScopes()).thenReturn(Set.of(\n      new BoundScope(configurationScopeId1, connectionId1, projectKey1),\n      new BoundScope(configurationScopeId2, connectionId2, projectKey2)));\n\n    when(configurationRepository.getLeafConfigScopeIds()).thenReturn(Set.of(configurationScopeId2, configurationScopeId3));\n\n    when(configurationRepository.getConfigurationScope(configurationScopeId1)).thenReturn(new ConfigurationScope(configurationScopeId1, null, false, \"1\"));\n    when(configurationRepository.getConfigurationScope(configurationScopeId2)).thenReturn(new ConfigurationScope(configurationScopeId2, configurationScopeId1, false, \"2\"));\n    when(configurationRepository.getConfigurationScope(configurationScopeId3)).thenReturn(new ConfigurationScope(configurationScopeId3, configurationScopeId1, false, \"3\"));\n    when(configurationRepository.getBindingConfiguration(configurationScopeId1)).thenReturn(new BindingConfiguration(configurationScopeId1, projectKey1, false));\n    when(configurationRepository.getBindingConfiguration(configurationScopeId2)).thenReturn(new BindingConfiguration(configurationScopeId2, projectKey2, false));\n    when(configurationRepository.getBindingConfiguration(configurationScopeId3)).thenReturn(new BindingConfiguration(null, null, false));\n\n    var connectionConfigurationRepository = mock(ConnectionConfigurationRepository.class);\n    when(connectionConfigurationRepository.getConnectionById(connectionId1)).thenReturn(new SonarQubeConnectionConfiguration(connectionId1, \"www.squrl1.org\", false));\n    when(connectionConfigurationRepository.getConnectionById(connectionId2)).thenReturn(new SonarQubeConnectionConfiguration(connectionId2, \"www.squrl2.org\", true));\n    var underTest = new TelemetryServerAttributesProvider(configurationRepository, connectionConfigurationRepository, mock(ActiveRulesService.class), mock(RulesRepository.class), mock(NodeJsService.class), mock(StorageService.class));\n\n    var telemetryLiveAttributes = underTest.getTelemetryServerLiveAttributes();\n    assertThat(telemetryLiveAttributes.usesConnectedMode()).isTrue();\n    assertThat(telemetryLiveAttributes.usesSonarCloud()).isFalse();\n    assertThat(telemetryLiveAttributes.childBindingCount()).isEqualTo(1);\n    assertThat(telemetryLiveAttributes.sonarQubeServerBindingCount()).isEqualTo(2);\n    assertThat(telemetryLiveAttributes.sonarQubeCloudEUBindingCount()).isZero();\n    assertThat(telemetryLiveAttributes.sonarQubeCloudUSBindingCount()).isZero();\n    assertThat(telemetryLiveAttributes.devNotificationsDisabled()).isTrue();\n    assertThat(telemetryLiveAttributes.nonDefaultEnabledRules()).isEmpty();\n    assertThat(telemetryLiveAttributes.defaultDisabledRules()).isEmpty();\n  }\n\n  @Test\n  void it_should_calculate_disabledRules_enabledRules_telemetry_attrs() {\n    var activeRulesService = mock(ActiveRulesService.class);\n    when(activeRulesService.getStandaloneRuleConfig()).thenReturn(\n      Map.of(\"ruleKey_1\", new StandaloneRuleConfigDto(true, Map.of()),\n        \"ruleKey_2\", new StandaloneRuleConfigDto(true, Map.of()),\n        \"ruleKey_3\", new StandaloneRuleConfigDto(false, Map.of()),\n        \"ruleKey_4\", new StandaloneRuleConfigDto(false, Map.of())));\n\n    var rulesRepository = mock(RulesRepository.class);\n    var sonarLintRuleDefinition1 = getSonarLintRuleDefinition(true);\n    var sonarLintRuleDefinition2 = getSonarLintRuleDefinition(false);\n    var sonarLintRuleDefinition3 = getSonarLintRuleDefinition(true);\n    var sonarLintRuleDefinition4 = getSonarLintRuleDefinition(false);\n    when(rulesRepository.getEmbeddedRule(\"ruleKey_1\")).thenReturn(sonarLintRuleDefinition1);\n    when(rulesRepository.getEmbeddedRule(\"ruleKey_2\")).thenReturn(sonarLintRuleDefinition2);\n    when(rulesRepository.getEmbeddedRule(\"ruleKey_3\")).thenReturn(sonarLintRuleDefinition3);\n    when(rulesRepository.getEmbeddedRule(\"ruleKey_4\")).thenReturn(sonarLintRuleDefinition4);\n\n    var underTest = new TelemetryServerAttributesProvider(mock(ConfigurationRepository.class), mock(ConnectionConfigurationRepository.class), activeRulesService, rulesRepository, mock(NodeJsService.class), mock(StorageService.class));\n    var telemetryLiveAttributes = underTest.getTelemetryServerLiveAttributes();\n\n    assertThat(telemetryLiveAttributes.nonDefaultEnabledRules()).containsExactly(\"ruleKey_2\");\n    assertThat(telemetryLiveAttributes.defaultDisabledRules()).containsExactly(\"ruleKey_3\");\n    assertThat(telemetryLiveAttributes.usesConnectedMode()).isFalse();\n    assertThat(telemetryLiveAttributes.childBindingCount()).isZero();\n    assertThat(telemetryLiveAttributes.sonarQubeServerBindingCount()).isZero();\n    assertThat(telemetryLiveAttributes.sonarQubeCloudEUBindingCount()).isZero();\n    assertThat(telemetryLiveAttributes.sonarQubeCloudUSBindingCount()).isZero();\n    assertThat(telemetryLiveAttributes.usesSonarCloud()).isFalse();\n    assertThat(telemetryLiveAttributes.devNotificationsDisabled()).isFalse();\n  }\n\n  @Test\n  void it_should_test_nodejs_version_telemetry_attr() {\n    var nodeJsService = mock(NodeJsService.class);\n    var version = \"3.1.4.159\";\n    when(nodeJsService.getActiveNodeJsVersion()).thenReturn(Optional.of(Version.create(version)));\n    var underTest = new TelemetryServerAttributesProvider(mock(ConfigurationRepository.class), mock(ConnectionConfigurationRepository.class),  mock(ActiveRulesService.class), mock(RulesRepository.class), nodeJsService, mock(StorageService.class));\n\n    assertThat(underTest.getTelemetryServerLiveAttributes().nodeVersion()).isEqualTo(version);\n  }\n\n  @NotNull\n  private static Optional<SonarLintRuleDefinition> getSonarLintRuleDefinition(boolean isActiveByDefault) {\n    var sonarLintRuleDefinition = mock(SonarLintRuleDefinition.class);\n    when(sonarLintRuleDefinition.isActiveByDefault()).thenReturn(isActiveByDefault);\n    return Optional.of(sonarLintRuleDefinition);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/UserPathsTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.TelemetryClientConstantAttributesDto;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass UserPathsTests {\n\n  @Test\n  void default_home_should_be_in_user_home() {\n    assertThat(UserPaths.computeUserHome(null)).isEqualTo(Paths.get(System.getProperty(\"user.home\")).resolve(\".sonarlint\"));\n  }\n\n  @Test\n  void env_setting_should_override_default_home() {\n    assertThat(UserPaths.computeUserHome(\"clientPath\")).isEqualTo(Paths.get(\"clientPath\"));\n  }\n\n  @Test\n  void should_return_telemetry_home() {\n    var initializeParams = mock(InitializeParams.class);\n    when(initializeParams.getSonarlintUserHome()).thenReturn(\"~/.sonarlint\");\n    when(initializeParams.getTelemetryConstantAttributes()).thenReturn(\n      new TelemetryClientConstantAttributesDto(\"eclipse\", \"---\", \"1.2.3\", \"4.5.6\", null));\n\n    var userPaths = UserPaths.from(initializeParams);\n    var telemetryDir = userPaths.getHomeIdeSpecificDir(\"telemetry\");\n\n    assertThat(telemetryDir)\n      .isEqualTo(Path.of(\"~/.sonarlint/telemetry/eclipse\"));\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/VersionSoonUnsupportedHelperTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core;\n\nimport java.util.Set;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.event.BindingConfigChangedEvent;\nimport org.sonarsource.sonarlint.core.event.ConfigurationScopesAddedWithBindingEvent;\nimport org.sonarsource.sonarlint.core.repository.config.BindingConfiguration;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationScope;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationScopeWithBinding;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarCloudConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarQubeConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverconnection.VersionUtils;\nimport org.sonarsource.sonarlint.core.sync.SynchronizationService;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n@Disabled(\"SLCORE-685 Some tests fail depending on the current date\")\nclass VersionSoonUnsupportedHelperTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private static final String CONFIG_SCOPE_ID = \"configScopeId\";\n  private static final String CONFIG_SCOPE_ID_2 = \"configScopeId2\";\n  private static final String SQ_CONNECTION_ID = \"sqConnectionId\";\n  private static final String SQ_CONNECTION_ID_2 = \"sqConnectionId2\";\n  private static final String SC_CONNECTION_ID = \"scConnectionId\";\n  private static final SonarQubeConnectionConfiguration SQ_CONNECTION = new SonarQubeConnectionConfiguration(SQ_CONNECTION_ID, \"https://mysonarqube.com\", true);\n  private static final SonarQubeConnectionConfiguration SQ_CONNECTION_2 = new SonarQubeConnectionConfiguration(SQ_CONNECTION_ID_2, \"https://mysonarqube2.com\", true);\n  private static final SonarCloudConnectionConfiguration SC_CONNECTION = new SonarCloudConnectionConfiguration(SonarCloudRegion.EU.getProductionUri(),\n    SonarCloudRegion.EU.getApiProductionUri(), SC_CONNECTION_ID, \"https://sonarcloud.com\", SonarCloudRegion.EU, true);\n\n  private final SonarLintRpcClient client = mock(SonarLintRpcClient.class);\n  private final SynchronizationService synchronizationService = mock(SynchronizationService.class);\n\n  private ConfigurationRepository configRepository;\n  private ConnectionConfigurationRepository connectionRepository;\n  private VersionSoonUnsupportedHelper underTest;\n\n  @BeforeEach\n  void init() {\n    configRepository = new ConfigurationRepository();\n    connectionRepository = new ConnectionConfigurationRepository();\n    underTest = new VersionSoonUnsupportedHelper(client, configRepository, mock(SonarQubeClientManager.class), connectionRepository, synchronizationService);\n  }\n\n  @Test\n  void should_trigger_notification_when_new_binding_to_previous_lts_detected_on_config_scope_event() {\n    var bindingConfiguration = new BindingConfiguration(SQ_CONNECTION_ID, \"\", true);\n    configRepository.addOrReplace(new ConfigurationScope(CONFIG_SCOPE_ID, null, false, \"\"), bindingConfiguration);\n    configRepository.addOrReplace(new ConfigurationScope(CONFIG_SCOPE_ID_2, null, false, \"\"), bindingConfiguration);\n    connectionRepository.addOrReplace(SQ_CONNECTION);\n    when(synchronizationService.readOrSynchronizeServerVersion(eq(SQ_CONNECTION_ID), any(), any(SonarLintCancelMonitor.class)))\n      .thenReturn(VersionUtils.getMinimalSupportedVersion());\n\n    underTest.configurationScopesAdded(new ConfigurationScopesAddedWithBindingEvent(Set.of(\n      new ConfigurationScopeWithBinding(\n        new ConfigurationScope(CONFIG_SCOPE_ID, null, true, \"scope1\"),\n        BindingConfiguration.noBinding()),\n      new ConfigurationScopeWithBinding(\n        new ConfigurationScope(CONFIG_SCOPE_ID_2, null, true, \"scope2\"),\n        BindingConfiguration.noBinding()))));\n\n    await().untilAsserted(() -> assertThat(logTester.logs(LogOutput.Level.DEBUG))\n      .containsOnly(\"Connection '\" + SQ_CONNECTION_ID + \"' with version '\" + VersionUtils.getMinimalSupportedVersion().getName() + \"' is detected to be soon unsupported\"));\n  }\n\n  @Test\n  void should_trigger_multiple_notification_when_new_bindings_to_previous_lts_detected_on_config_scope_event() {\n    var bindingConfiguration = new BindingConfiguration(SQ_CONNECTION_ID, \"\", true);\n    var bindingConfiguration2 = new BindingConfiguration(SQ_CONNECTION_ID_2, \"\", true);\n    configRepository.addOrReplace(new ConfigurationScope(CONFIG_SCOPE_ID, null, false, \"\"), bindingConfiguration);\n    configRepository.addOrReplace(new ConfigurationScope(CONFIG_SCOPE_ID_2, null, false, \"\"), bindingConfiguration2);\n    connectionRepository.addOrReplace(SQ_CONNECTION);\n    connectionRepository.addOrReplace(SQ_CONNECTION_2);\n    var serverApi = mock(ServerApi.class);\n    var serverApi2 = mock(ServerApi.class);\n    when(synchronizationService.readOrSynchronizeServerVersion(eq(SQ_CONNECTION_ID), eq(serverApi), any(SonarLintCancelMonitor.class)))\n      .thenReturn(VersionUtils.getMinimalSupportedVersion());\n    when(synchronizationService.readOrSynchronizeServerVersion(eq(SQ_CONNECTION_ID_2), eq(serverApi2), any(SonarLintCancelMonitor.class)))\n      .thenReturn(Version.create(VersionUtils.getMinimalSupportedVersion() + \".9\"));\n\n    underTest.configurationScopesAdded(new ConfigurationScopesAddedWithBindingEvent(Set.of(\n      new ConfigurationScopeWithBinding(\n        new ConfigurationScope(CONFIG_SCOPE_ID, null, true, \"scope1\"),\n        BindingConfiguration.noBinding()),\n      new ConfigurationScopeWithBinding(\n        new ConfigurationScope(CONFIG_SCOPE_ID_2, null, true, \"scope2\"),\n        BindingConfiguration.noBinding()))));\n\n    await().untilAsserted(() -> assertThat(logTester.logs(LogOutput.Level.DEBUG))\n      .containsOnly(\n        \"Connection '\" + SQ_CONNECTION_ID + \"' with version '\" + VersionUtils.getMinimalSupportedVersion().getName() + \"' is detected to be soon unsupported\",\n        \"Connection '\" + SQ_CONNECTION_ID_2 + \"' with version '\" + VersionUtils.getMinimalSupportedVersion() + \".9' is detected to be soon unsupported\"));\n  }\n\n  @Test\n  void should_not_trigger_notification_when_config_scope_has_no_effective_binding() {\n    underTest.configurationScopesAdded(new ConfigurationScopesAddedWithBindingEvent(Set.of(\n      new ConfigurationScopeWithBinding(\n        new ConfigurationScope(CONFIG_SCOPE_ID, null, true, \"scope1\"),\n        BindingConfiguration.noBinding()))));\n\n    assertThat(logTester.logs()).isEmpty();\n  }\n\n  @Test\n  void should_trigger_notification_when_new_binding_to_previous_lts_detected() {\n    connectionRepository.addOrReplace(SQ_CONNECTION);\n    var serverApi = mock(ServerApi.class);\n    when(synchronizationService.readOrSynchronizeServerVersion(eq(SQ_CONNECTION_ID), eq(serverApi), any(SonarLintCancelMonitor.class)))\n      .thenReturn(VersionUtils.getMinimalSupportedVersion());\n\n    underTest.bindingConfigChanged(new BindingConfigChangedEvent(CONFIG_SCOPE_ID, null,\n      new BindingConfiguration(SQ_CONNECTION_ID, \"\", false)));\n\n    await().untilAsserted(() -> assertThat(logTester.logs(LogOutput.Level.DEBUG))\n      .containsOnly(\"Connection '\" + SQ_CONNECTION_ID + \"' with version '\" + VersionUtils.getMinimalSupportedVersion().getName() + \"' is detected to be soon unsupported\"));\n  }\n\n  @Test\n  void should_trigger_once_when_same_binding_to_previous_lts_detected_twice() {\n    connectionRepository.addOrReplace(SQ_CONNECTION);\n    var serverApi = mock(ServerApi.class);\n    when(synchronizationService.readOrSynchronizeServerVersion(eq(SQ_CONNECTION_ID), eq(serverApi), any(SonarLintCancelMonitor.class)))\n      .thenReturn(VersionUtils.getMinimalSupportedVersion());\n\n    underTest.bindingConfigChanged(new BindingConfigChangedEvent(CONFIG_SCOPE_ID, null,\n      new BindingConfiguration(SQ_CONNECTION_ID, \"\", false)));\n    underTest.bindingConfigChanged(new BindingConfigChangedEvent(CONFIG_SCOPE_ID, null,\n      new BindingConfiguration(SQ_CONNECTION_ID, \"\", false)));\n\n    await().untilAsserted(() -> assertThat(logTester.logs(LogOutput.Level.DEBUG))\n      .containsOnly(\"Connection '\" + SQ_CONNECTION_ID + \"' with version '\" + VersionUtils.getMinimalSupportedVersion().getName() + \"' is detected to be soon unsupported\"));\n  }\n\n  @Test\n  void should_trigger_notification_when_new_binding_to_in_between_lts_detected() {\n    connectionRepository.addOrReplace(SQ_CONNECTION);\n    var serverApi = mock(ServerApi.class);\n    when(synchronizationService.readOrSynchronizeServerVersion(eq(SQ_CONNECTION_ID), eq(serverApi), any(SonarLintCancelMonitor.class)))\n      .thenReturn(Version.create(VersionUtils.getMinimalSupportedVersion().getName() + \".9\"));\n\n    underTest.bindingConfigChanged(new BindingConfigChangedEvent(CONFIG_SCOPE_ID, null,\n      new BindingConfiguration(SQ_CONNECTION_ID, \"\", false)));\n\n    await().untilAsserted(() -> assertThat(logTester.logs(LogOutput.Level.DEBUG))\n      .containsOnly(\"Connection '\" + SQ_CONNECTION_ID + \"' with version '\" + VersionUtils.getMinimalSupportedVersion().getName() + \".9' is detected to be soon unsupported\"));\n  }\n\n  @Test\n  void should_not_trigger_notification_when_new_binding_to_current_lts_detected() {\n    connectionRepository.addOrReplace(SQ_CONNECTION);\n    var serverApi = mock(ServerApi.class);\n    when(synchronizationService.readOrSynchronizeServerVersion(eq(SQ_CONNECTION_ID), eq(serverApi), any(SonarLintCancelMonitor.class))).thenReturn(VersionUtils.getCurrentLts());\n\n    underTest.bindingConfigChanged(new BindingConfigChangedEvent(CONFIG_SCOPE_ID, null,\n      new BindingConfiguration(SQ_CONNECTION_ID, \"\", false)));\n\n    assertThat(logTester.logs()).isEmpty();\n  }\n\n  @Test\n  void should_not_trigger_notification_when_sonarcloud_binding_detected() {\n    connectionRepository.addOrReplace(SC_CONNECTION);\n\n    underTest.bindingConfigChanged(new BindingConfigChangedEvent(CONFIG_SCOPE_ID, null,\n      new BindingConfiguration(SC_CONNECTION_ID, \"\", false)));\n\n    assertThat(logTester.logs()).isEmpty();\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/ai/ide/AiHookServiceTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.ai.ide;\n\nimport java.util.Optional;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.embedded.server.EmbeddedServer;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.AiAgent;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryService;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass AiHookServiceTests {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @Test\n  void it_should_generate_nodejs_script_when_nodejs_detected() {\n    var embeddedServer = mock(EmbeddedServer.class);\n    var telemetryService = mock(TelemetryService.class);\n    var executableLocator = mock(ExecutableLocator.class);\n\n    when(embeddedServer.getPort()).thenReturn(64120);\n    when(executableLocator.detectBestExecutable()).thenReturn(Optional.of(HookScriptType.NODEJS));\n\n    var service = new AiHookService(embeddedServer, telemetryService, executableLocator);\n    var response = service.getHookScriptContent(AiAgent.WINDSURF);\n\n    assertThat(response.getScriptFileName()).isEqualTo(\"sonarqube_analysis_hook.js\");\n    assertThat(response.getScriptContent())\n      .contains(\"#!/usr/bin/env node\")\n      .contains(\"hostname: 'localhost'\")\n      .contains(\"STARTING_PORT = 64120\")\n      .contains(\"ENDING_PORT = 64130\")\n      .contains(\"path: '/sonarlint/api/analysis/files'\")\n      .contains(\"path: '/sonarlint/api/status'\");\n    assertThat(response.getConfigFileName()).isEqualTo(\"hooks.json\");\n    assertThat(response.getConfigContent())\n      .contains(\"\\\"post_write_code\\\"\")\n      .contains(\"{{SCRIPT_PATH}}\")\n      .contains(\"\\\"show_output\\\": true\");\n    verify(telemetryService).aiHookInstalled(AiAgent.WINDSURF);\n  }\n\n  @Test\n  void it_should_generate_python_script_when_python_detected() {\n    var embeddedServer = mock(EmbeddedServer.class);\n    var telemetryService = mock(TelemetryService.class);\n    var executableLocator = mock(ExecutableLocator.class);\n\n    when(embeddedServer.getPort()).thenReturn(64121);\n    when(executableLocator.detectBestExecutable()).thenReturn(Optional.of(HookScriptType.PYTHON));\n\n    var service = new AiHookService(embeddedServer, telemetryService, executableLocator);\n    var response = service.getHookScriptContent(AiAgent.WINDSURF);\n\n    assertThat(response.getScriptFileName()).isEqualTo(\"sonarqube_analysis_hook.py\");\n    assertThat(response.getScriptContent())\n      .contains(\"#!/usr/bin/env python3\")\n      .contains(\"STARTING_PORT = 64120\")\n      .contains(\"ENDING_PORT = 64130\")\n      .contains(\"/sonarlint/api/analysis/files\")\n      .contains(\"/sonarlint/api/status\");\n    assertThat(response.getConfigFileName()).isEqualTo(\"hooks.json\");\n    assertThat(response.getConfigContent())\n      .contains(\"\\\"post_write_code\\\"\")\n      .contains(\"{{SCRIPT_PATH}}\")\n      .contains(\"\\\"show_output\\\": true\");\n    verify(telemetryService).aiHookInstalled(AiAgent.WINDSURF);\n  }\n\n  @Test\n  void it_should_generate_bash_script_when_bash_detected() {\n    var embeddedServer = mock(EmbeddedServer.class);\n    var telemetryService = mock(TelemetryService.class);\n    var executableLocator = mock(ExecutableLocator.class);\n\n    when(embeddedServer.getPort()).thenReturn(64122);\n    when(executableLocator.detectBestExecutable()).thenReturn(Optional.of(HookScriptType.BASH));\n\n    var service = new AiHookService(embeddedServer, telemetryService, executableLocator);\n    var response = service.getHookScriptContent(AiAgent.WINDSURF);\n\n    assertThat(response.getScriptFileName()).isEqualTo(\"sonarqube_analysis_hook.sh\");\n    assertThat(response.getScriptContent())\n      .contains(\"#!/bin/bash\")\n      .contains(\"STARTING_PORT=64120\")\n      .contains(\"ENDING_PORT=64130\")\n      .contains(\"/sonarlint/api/analysis/files\")\n      .contains(\"/sonarlint/api/status\");\n    assertThat(response.getConfigFileName()).isEqualTo(\"hooks.json\");\n    assertThat(response.getConfigContent())\n      .contains(\"\\\"post_write_code\\\"\")\n      .contains(\"{{SCRIPT_PATH}}\")\n      .contains(\"\\\"show_output\\\": true\");\n    verify(telemetryService).aiHookInstalled(AiAgent.WINDSURF);\n  }\n\n  @Test\n  void it_should_throw_exception_when_embedded_server_not_started() {\n    var embeddedServer = mock(EmbeddedServer.class);\n    var telemetryService = mock(TelemetryService.class);\n    var executableLocator = mock(ExecutableLocator.class);\n\n    when(embeddedServer.getPort()).thenReturn(-1);\n\n    var service = new AiHookService(embeddedServer, telemetryService, executableLocator);\n\n    assertThatThrownBy(() -> service.getHookScriptContent(AiAgent.WINDSURF))\n      .isInstanceOf(IllegalStateException.class)\n      .hasMessageContaining(\"Embedded server is not started\");\n  }\n\n  @Test\n  void it_should_throw_exception_when_no_executable_found() {\n    var embeddedServer = mock(EmbeddedServer.class);\n    var telemetryService = mock(TelemetryService.class);\n    var executableLocator = mock(ExecutableLocator.class);\n\n    when(embeddedServer.getPort()).thenReturn(64120);\n    when(executableLocator.detectBestExecutable()).thenReturn(Optional.empty());\n\n    var service = new AiHookService(embeddedServer, telemetryService, executableLocator);\n\n    assertThatThrownBy(() -> service.getHookScriptContent(AiAgent.WINDSURF))\n      .isInstanceOf(IllegalStateException.class)\n      .hasMessageContaining(\"No suitable executable found\");\n  }\n\n  @Test\n  void it_should_embed_correct_agent_in_script_comment_for_windsurf() {\n    var embeddedServer = mock(EmbeddedServer.class);\n    var telemetryService = mock(TelemetryService.class);\n    var executableLocator = mock(ExecutableLocator.class);\n\n    when(embeddedServer.getPort()).thenReturn(64120);\n    when(executableLocator.detectBestExecutable()).thenReturn(Optional.of(HookScriptType.NODEJS));\n\n    var service = new AiHookService(embeddedServer, telemetryService, executableLocator);\n    var response = service.getHookScriptContent(AiAgent.WINDSURF);\n\n    assertThat(response.getScriptContent())\n      .contains(\"SonarQube for IDE Windsurf Hook\")\n      .contains(\"EXPECTED_IDE_NAME = 'Windsurf'\");\n    verify(telemetryService).aiHookInstalled(AiAgent.WINDSURF);\n  }\n\n  @Test\n  void it_should_throw_exception_for_unsupported_cursor_agent() {\n    var embeddedServer = mock(EmbeddedServer.class);\n    var telemetryService = mock(TelemetryService.class);\n    var executableLocator = mock(ExecutableLocator.class);\n\n    when(embeddedServer.getPort()).thenReturn(64120);\n    when(executableLocator.detectBestExecutable()).thenReturn(Optional.of(HookScriptType.PYTHON));\n\n    var service = new AiHookService(embeddedServer, telemetryService, executableLocator);\n\n    assertThatThrownBy(() -> service.getHookScriptContent(AiAgent.CURSOR))\n      .isInstanceOf(UnsupportedOperationException.class)\n      .hasMessageContaining(\"hook configuration not yet implemented\");\n  }\n\n  @Test\n  void it_should_throw_exception_for_unsupported_kiro_agent() {\n    var embeddedServer = mock(EmbeddedServer.class);\n    var telemetryService = mock(TelemetryService.class);\n    var executableLocator = mock(ExecutableLocator.class);\n\n    when(embeddedServer.getPort()).thenReturn(64120);\n    when(executableLocator.detectBestExecutable()).thenReturn(Optional.of(HookScriptType.PYTHON));\n\n    var service = new AiHookService(embeddedServer, telemetryService, executableLocator);\n\n    assertThatThrownBy(() -> service.getHookScriptContent(AiAgent.KIRO))\n      .isInstanceOf(UnsupportedOperationException.class)\n      .hasMessageContaining(\"hook configuration not yet implemented\");\n  }\n\n  @Test\n  void it_should_throw_exception_for_unsupported_github_copilot_agent() {\n    var embeddedServer = mock(EmbeddedServer.class);\n    var telemetryService = mock(TelemetryService.class);\n    var executableLocator = mock(ExecutableLocator.class);\n\n    when(embeddedServer.getPort()).thenReturn(64120);\n    when(executableLocator.detectBestExecutable()).thenReturn(Optional.of(HookScriptType.BASH));\n\n    var service = new AiHookService(embeddedServer, telemetryService, executableLocator);\n\n    assertThatThrownBy(() -> service.getHookScriptContent(AiAgent.GITHUB_COPILOT))\n      .isInstanceOf(UnsupportedOperationException.class)\n      .hasMessageContaining(\"GitHub Copilot does not support hooks\");\n  }\n\n}\n\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/ai/ide/ExecutableLocatorTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.ai.ide;\n\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledOnOs;\nimport org.junit.jupiter.api.condition.OS;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonar.api.utils.System2;\nimport org.sonar.api.utils.command.Command;\nimport org.sonar.api.utils.command.CommandExecutor;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.nodejs.InstalledNodeJs;\nimport org.sonarsource.sonarlint.core.nodejs.NodeJsHelper;\nimport org.sonar.api.utils.command.StreamConsumer;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyLong;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.mockStatic;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass ExecutableLocatorTests {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @Test\n  void it_should_find_nodejs_when_available() {\n    var system2 = mock(System2.class);\n    var commandExecutor = mock(CommandExecutor.class);\n    var nodeJsHelper = mock(NodeJsHelper.class);\n    var pathHelper = Paths.get(\"/usr/libexec/path_helper\");\n\n    when(nodeJsHelper.autoDetect()).thenReturn(new InstalledNodeJs(Paths.get(\"/usr/bin/node\"), Version.create(\"18.0.0\")));\n\n    var locator = new ExecutableLocator(system2, pathHelper, commandExecutor, nodeJsHelper);\n    var result = locator.detectBestExecutable();\n\n    assertThat(result)\n      .isPresent()\n      .contains(HookScriptType.NODEJS);\n  }\n\n  @Test\n  void it_should_find_python_when_nodejs_not_available() {\n    var system2 = mock(System2.class);\n    var commandExecutor = mock(CommandExecutor.class);\n    var nodeJsHelper = mock(NodeJsHelper.class);\n    var pathHelper = Paths.get(\"/usr/libexec/path_helper\");\n\n    when(nodeJsHelper.autoDetect()).thenReturn(null);\n    when(system2.isOsWindows()).thenReturn(false);\n    when(commandExecutor.execute(any(Command.class), any(), any(), anyLong())).thenReturn(0);\n\n    var locator = new ExecutableLocator(system2, pathHelper, commandExecutor, nodeJsHelper) {\n      @Override\n      String runSimpleCommand(@NotNull Command command) {\n        if (command.toCommandLine().contains(\"python3\")) {\n          return \"/usr/bin/python3\";\n        }\n        return null;\n      }\n    };\n\n    var result = locator.detectBestExecutable();\n\n    assertThat(result)\n      .isPresent()\n      .contains(HookScriptType.PYTHON);\n  }\n\n  @Test\n  @EnabledOnOs(value = OS.LINUX)\n  void it_should_fallback_to_bash_on_unix_when_no_nodejs_or_python() {\n    var system2 = mock(System2.class);\n    var commandExecutor = mock(CommandExecutor.class);\n    var nodeJsHelper = mock(NodeJsHelper.class);\n    var pathHelper = Paths.get(\"/usr/libexec/path_helper\");\n\n    when(nodeJsHelper.autoDetect()).thenReturn(null);\n    when(system2.isOsWindows()).thenReturn(false);\n\n    var locator = new ExecutableLocator(system2, pathHelper, commandExecutor, nodeJsHelper) {\n      @Override\n      String runSimpleCommand(@NotNull Command command) {\n        return null; // Python not found\n      }\n    };\n\n    var result = locator.detectBestExecutable();\n\n    assertThat(result)\n      .isPresent()\n      .contains(HookScriptType.BASH);\n  }\n\n  @Test\n  @EnabledOnOs(value = OS.WINDOWS)\n  void it_should_return_empty_on_windows_when_only_bash_available() {\n    var system2 = mock(System2.class);\n    var commandExecutor = mock(CommandExecutor.class);\n    var nodeJsHelper = mock(NodeJsHelper.class);\n    var pathHelper = Paths.get(\"/usr/libexec/path_helper\");\n\n    when(nodeJsHelper.autoDetect()).thenReturn(null);\n    when(system2.isOsWindows()).thenReturn(true);\n\n    var locator = new ExecutableLocator(system2, pathHelper, commandExecutor, nodeJsHelper) {\n      @Override\n      String runSimpleCommand(@NotNull Command command) {\n        return null; // Neither Python nor Bash found on Windows\n      }\n    };\n\n    var result = locator.detectBestExecutable();\n\n    assertThat(result).isEmpty();\n  }\n\n  @Test\n  void it_should_cache_detection_result() {\n    var system2 = mock(System2.class);\n    var commandExecutor = mock(CommandExecutor.class);\n    var nodeJsHelper = mock(NodeJsHelper.class);\n    var pathHelper = Paths.get(\"/usr/libexec/path_helper\");\n\n    when(nodeJsHelper.autoDetect()).thenReturn(new InstalledNodeJs(Paths.get(\"/usr/bin/node\"), Version.create(\"18.0.0\")));\n\n    var locator = new ExecutableLocator(system2, pathHelper, commandExecutor, nodeJsHelper);\n    \n    // Call twice\n    var result1 = locator.detectBestExecutable();\n    var result2 = locator.detectBestExecutable();\n\n    assertThat(result1).isPresent();\n    assertThat(result2).isPresent();\n    assertThat(result1).contains(HookScriptType.NODEJS);\n    assertThat(result2).contains(HookScriptType.NODEJS);\n\n    // Verify autoDetect was only called once due to caching\n    verify(nodeJsHelper, times(1)).autoDetect();\n  }\n\n  @Test\n  void it_should_fallback_to_python_when_python3_not_found() {\n    var system2 = mock(System2.class);\n    var commandExecutor = mock(CommandExecutor.class);\n    var nodeJsHelper = mock(NodeJsHelper.class);\n    var pathHelper = Paths.get(\"/usr/libexec/path_helper\");\n\n    when(nodeJsHelper.autoDetect()).thenReturn(null);\n    when(system2.isOsWindows()).thenReturn(false);\n\n    var locator = new ExecutableLocator(system2, pathHelper, commandExecutor, nodeJsHelper) {\n      @Override\n      String runSimpleCommand(@NotNull Command command) {\n        if (command.toCommandLine().contains(\"python3\")) {\n          return null; // python3 not found\n        }\n        if (command.toCommandLine().contains(\"python\")) {\n          return \"/usr/bin/python\";\n        }\n        return null;\n      }\n    };\n\n    var result = locator.detectBestExecutable();\n\n    assertThat(result)\n      .isPresent()\n      .contains(HookScriptType.PYTHON);\n  }\n\n  @Test\n  @EnabledOnOs(value = OS.WINDOWS)\n  void it_should_detect_bash_on_windows_when_available() {\n    var system2 = mock(System2.class);\n    var commandExecutor = mock(CommandExecutor.class);\n    var nodeJsHelper = mock(NodeJsHelper.class);\n    var pathHelper = Paths.get(\"/usr/libexec/path_helper\");\n\n    when(nodeJsHelper.autoDetect()).thenReturn(null);\n    when(system2.isOsWindows()).thenReturn(true);\n\n    var locator = new ExecutableLocator(system2, pathHelper, commandExecutor, nodeJsHelper) {\n      @Override\n      String runSimpleCommand(@NotNull Command command) {\n        if (command.toCommandLine().contains(\"bash.exe\")) {\n          return \"C:\\\\Program Files\\\\Git\\\\bin\\\\bash.exe\";\n        }\n        return null;\n      }\n    };\n\n    var result = locator.detectBestExecutable();\n\n    assertThat(result)\n      .isPresent()\n      .contains(HookScriptType.BASH);\n  }\n\n  @Test\n  void it_should_handle_nodejs_detection_error_gracefully() {\n    var system2 = mock(System2.class);\n    var commandExecutor = mock(CommandExecutor.class);\n    var nodeJsHelper = mock(NodeJsHelper.class);\n    var pathHelper = Paths.get(\"/usr/libexec/path_helper\");\n\n    when(nodeJsHelper.autoDetect()).thenThrow(new RuntimeException(\"Node.js detection failed\"));\n    when(system2.isOsWindows()).thenReturn(false);\n\n    var locator = new ExecutableLocator(system2, pathHelper, commandExecutor, nodeJsHelper) {\n      @Override\n      String runSimpleCommand(@NotNull Command command) {\n        if (command.toCommandLine().contains(\"python3\")) {\n          return \"/usr/bin/python3\";\n        }\n        return null;\n      }\n    };\n\n    var result = locator.detectBestExecutable();\n\n    // Should fall back to Python\n    assertThat(result)\n      .isPresent()\n      .contains(HookScriptType.PYTHON);\n  }\n\n  @Test\n  void runSimpleCommand_should_return_first_line_of_stdout_on_success() {\n    var system2 = mock(System2.class);\n    var commandExecutor = mock(CommandExecutor.class);\n    var nodeJsHelper = mock(NodeJsHelper.class);\n    var pathHelper = Paths.get(\"/usr/libexec/path_helper\");\n\n    when(commandExecutor.execute(any(Command.class), any(), any(), anyLong())).thenAnswer(invocation -> {\n      var stdOutConsumer = (StreamConsumer) invocation.getArgument(1);\n      stdOutConsumer.consumeLine(\"/usr/bin/python3\");\n      stdOutConsumer.consumeLine(\"additional output\");\n      return 0;\n    });\n\n    var locator = new ExecutableLocator(system2, pathHelper, commandExecutor, nodeJsHelper);\n    var command = Command.create(\"which\").addArgument(\"python3\");\n    var result = locator.runSimpleCommand(command);\n\n    assertThat(result).isEqualTo(\"/usr/bin/python3\");\n  }\n\n  @Test\n  void runSimpleCommand_should_return_null_on_non_zero_exit_code() {\n    var system2 = mock(System2.class);\n    var commandExecutor = mock(CommandExecutor.class);\n    var nodeJsHelper = mock(NodeJsHelper.class);\n    var pathHelper = Paths.get(\"/usr/libexec/path_helper\");\n\n    when(commandExecutor.execute(any(Command.class), any(), any(), anyLong())).thenAnswer(invocation -> {\n      var stdOutConsumer = (StreamConsumer) invocation.getArgument(1);\n      stdOutConsumer.consumeLine(\"some output\");\n      return 1; // Non-zero exit code\n    });\n\n    var locator = new ExecutableLocator(system2, pathHelper, commandExecutor, nodeJsHelper);\n    var command = Command.create(\"which\").addArgument(\"nonexistent\");\n    var result = locator.runSimpleCommand(command);\n\n    assertThat(result).isNull();\n  }\n\n  @Test\n  void runSimpleCommand_should_return_null_on_empty_stdout() {\n    var system2 = mock(System2.class);\n    var commandExecutor = mock(CommandExecutor.class);\n    var nodeJsHelper = mock(NodeJsHelper.class);\n    var pathHelper = Paths.get(\"/usr/libexec/path_helper\");\n\n    when(commandExecutor.execute(any(Command.class), any(), any(), anyLong())).thenReturn(0);\n\n    var locator = new ExecutableLocator(system2, pathHelper, commandExecutor, nodeJsHelper);\n    var command = Command.create(\"echo\").addArgument(\"\");\n    var result = locator.runSimpleCommand(command);\n\n    assertThat(result).isNull();\n  }\n\n  @Test\n  void runSimpleCommand_should_return_null_on_command_exception() {\n    var system2 = mock(System2.class);\n    var commandExecutor = mock(CommandExecutor.class);\n    var nodeJsHelper = mock(NodeJsHelper.class);\n    var pathHelper = Paths.get(\"/usr/libexec/path_helper\");\n\n    when(commandExecutor.execute(any(Command.class), any(), any(), anyLong()))\n      .thenThrow(new org.sonar.api.utils.command.CommandException(Command.create(\"test\"), \"Command failed\", null));\n\n    var locator = new ExecutableLocator(system2, pathHelper, commandExecutor, nodeJsHelper);\n    var command = Command.create(\"invalid_command\");\n    var result = locator.runSimpleCommand(command);\n\n    assertThat(result).isNull();\n  }\n\n  @Test\n  void runSimpleCommand_should_log_stdout_and_stderr() {\n    var system2 = mock(System2.class);\n    var commandExecutor = mock(CommandExecutor.class);\n    var nodeJsHelper = mock(NodeJsHelper.class);\n    var pathHelper = Paths.get(\"/usr/libexec/path_helper\");\n\n    when(commandExecutor.execute(any(Command.class), any(), any(), anyLong())).thenAnswer(invocation -> {\n      var stdOutConsumer = (StreamConsumer) invocation.getArgument(1);\n      var stdErrConsumer = (StreamConsumer) invocation.getArgument(2);\n      stdOutConsumer.consumeLine(\"/usr/bin/test\");\n      stdErrConsumer.consumeLine(\"warning message\");\n      return 0;\n    });\n\n    var locator = new ExecutableLocator(system2, pathHelper, commandExecutor, nodeJsHelper);\n    var command = Command.create(\"test_command\");\n    var result = locator.runSimpleCommand(command);\n\n    assertThat(result).isEqualTo(\"/usr/bin/test\");\n    assertThat(logTester.logs()).anyMatch(log -> log.contains(\"stdout: /usr/bin/test\"));\n    assertThat(logTester.logs()).anyMatch(log -> log.contains(\"stderr: warning message\"));\n  }\n\n  @Test\n  void it_should_return_empty_when_no_executable_found() {\n    var system2 = mock(System2.class);\n    var commandExecutor = mock(CommandExecutor.class);\n    var nodeJsHelper = mock(NodeJsHelper.class);\n    var pathHelper = Paths.get(\"/usr/libexec/path_helper\");\n\n    when(nodeJsHelper.autoDetect()).thenReturn(null);\n    when(system2.isOsWindows()).thenReturn(true);\n\n    var locator = new ExecutableLocator(system2, pathHelper, commandExecutor, nodeJsHelper) {\n      @Override\n      String runSimpleCommand(@NotNull Command command) {\n        // Simulate no executable found for any command\n        return null;\n      }\n    };\n\n    var result = locator.detectBestExecutable();\n\n    assertThat(result).isEmpty();\n    assertThat(logTester.logs()).anyMatch(log -> \n      log.contains(\"No suitable executable found\") || \n      log.contains(\"not found\") ||\n      log.contains(\"not available\"));\n  }\n\n  @Test\n  void it_should_not_set_path_env_when_path_helper_does_not_exist() {\n    var system2 = mock(System2.class);\n    var commandExecutor = mock(CommandExecutor.class);\n    var nodeJsHelper = mock(NodeJsHelper.class);\n    var pathHelper = Paths.get(\"/nonexistent/path_helper\");\n\n    when(system2.isOsMac()).thenReturn(true);\n\n    var locator = new ExecutableLocator(system2, pathHelper, commandExecutor, nodeJsHelper);\n\n    var testCommand = Command.create(\"test\");\n    var commandLineBefore = testCommand.toCommandLine();\n    locator.computePathEnvForMacOs(testCommand);\n    var commandLineAfter = testCommand.toCommandLine();\n\n    // PATH should not be set\n    assertThat(commandLineAfter).isEqualTo(commandLineBefore);\n  }\n\n  @Test\n  void it_should_not_set_path_env_when_not_on_macos(@TempDir File tempDir) {\n    var system2 = mock(System2.class);\n    var commandExecutor = mock(CommandExecutor.class);\n    var nodeJsHelper = mock(NodeJsHelper.class);\n    var pathHelper = new File(tempDir, \"path_helper\").toPath();\n\n    when(system2.isOsMac()).thenReturn(false);\n\n    try (var filesMock = mockStatic(Files.class)) {\n      filesMock.when(() -> Files.exists(pathHelper)).thenReturn(true);\n\n      var locator = new ExecutableLocator(system2, pathHelper, commandExecutor, nodeJsHelper);\n\n      var testCommand = Command.create(\"test\");\n      var commandLineBefore = testCommand.toCommandLine();\n      locator.computePathEnvForMacOs(testCommand);\n      var commandLineAfter = testCommand.toCommandLine();\n\n      // PATH should not be set when not on macOS\n      assertThat(commandLineAfter).isEqualTo(commandLineBefore);\n    }\n  }\n\n}\n\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/analysis/AnalysisSchedulerCacheTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport java.nio.file.Path;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.UserPaths;\nimport org.sonarsource.sonarlint.core.fs.ClientFileSystemService;\nimport org.sonarsource.sonarlint.core.plugin.PluginLifecycleService;\nimport org.sonarsource.sonarlint.core.plugin.PluginsService;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\n\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass AnalysisSchedulerCacheTest {\n\n  @Test\n  void reloadStandalonePlugins_evicts_caches_when_no_scheduler_started(@TempDir Path tempDir) {\n    var userPaths = mockUserPaths(tempDir);\n    var pluginLifecycleService = mock(PluginLifecycleService.class);\n\n    var cache = new AnalysisSchedulerCache(userPaths,\n      mock(ConfigurationRepository.class), mock(NodeJsService.class),\n      mock(PluginsService.class), pluginLifecycleService,\n      mock(ClientFileSystemService.class));\n\n    cache.reloadStandalonePlugins();\n\n    verify(pluginLifecycleService).unloadEmbeddedPluginsAndEvictCaches();\n  }\n\n  @Test\n  void reloadPlugins_evicts_caches_when_no_connected_scheduler_exists(@TempDir Path tempDir) {\n    var userPaths = mockUserPaths(tempDir);\n    var pluginLifecycleService = mock(PluginLifecycleService.class);\n\n    var cache = new AnalysisSchedulerCache(userPaths,\n      mock(ConfigurationRepository.class), mock(NodeJsService.class),\n      mock(PluginsService.class), pluginLifecycleService,\n      mock(ClientFileSystemService.class));\n\n    cache.reloadPlugins(\"conn1\");\n\n    verify(pluginLifecycleService).unloadPluginsAndEvictCaches(\"conn1\");\n  }\n\n  private static UserPaths mockUserPaths(Path tempDir) {\n    var userPaths = mock(UserPaths.class);\n    when(userPaths.getWorkDir()).thenReturn(tempDir.resolve(\"work\"));\n    return userPaths;\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/analysis/BackendInputFileTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport java.io.File;\nimport java.net.URI;\nimport java.nio.file.Path;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.fs.ClientFile;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass BackendInputFileTests {\n\n  @Test\n  void ascii_path_should_be_the_same() {\n    var path = Path.of(\"/test/file.php\");\n    var pathAsString = path.toString().replace(File.separatorChar, '/');\n    var clientFile = new ClientFile(URI.create(\"file://\" + pathAsString), \"configScopeId\", path, false, null, null, null, true);\n\n    var inputFile = new BackendInputFile(clientFile);\n\n    assertThat(inputFile.getPath().replace(File.separatorChar, '/')).endsWith(pathAsString);\n  }\n\n  @Test\n  void non_ascii_path_should_be_the_same() {\n    var path = Path.of(\"/中文字符/file.php\");\n    var pathAsString = path.toString().replace(File.separatorChar, '/');\n    var clientFile = new ClientFile(URI.create(\"file://\" + pathAsString), \"configScopeId\", path, false, null, null, null, true);\n\n    var inputFile = new BackendInputFile(clientFile);\n\n    assertThat(inputFile.getPath().replace(File.separatorChar, '/')).endsWith(pathAsString);\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/analysis/ClientAnalysisPropertiesServiceTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.analysis;\n\nimport java.util.Map;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ClientAnalysisPropertiesServiceTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  private static final String CONFIG_SCOPE_ID = \"scope-id\";\n  private static final String ANOTHER_CONFIG_SCOPE_ID = \"another-scope-id\";\n  UserAnalysisPropertiesRepository underTest;\n\n  @BeforeEach\n  void setup() {\n    underTest = new UserAnalysisPropertiesRepository();\n  }\n\n  @Test\n  void it_should_remove_previous_config_and_set_provided_user_properties() {\n    var properties = underTest.getUserProperties(CONFIG_SCOPE_ID);\n    assertThat(properties).isEmpty();\n\n    underTest.setUserProperties(CONFIG_SCOPE_ID, Map.of(\"key1\", \"value1\", \"key2\", \"value2\"));\n    properties = underTest.getUserProperties(CONFIG_SCOPE_ID);\n    assertThat(properties).hasSize(2).containsEntry(\"key1\", \"value1\").containsEntry(\"key2\", \"value2\");\n\n    underTest.setUserProperties(CONFIG_SCOPE_ID, Map.of(\"key2\", \"new-value2\", \"key3\", \"new-value3\"));\n\n    properties = underTest.getUserProperties(CONFIG_SCOPE_ID);\n    assertThat(properties).hasSize(2).containsEntry(\"key2\", \"new-value2\").containsEntry(\"key3\", \"new-value3\");\n  }\n\n  @Test\n  void it_should_not_modify_other_config_scope_properties() {\n    var properties = underTest.getUserProperties(CONFIG_SCOPE_ID);\n    assertThat(properties).isEmpty();\n\n    underTest.setUserProperties(CONFIG_SCOPE_ID, Map.of(\"key1\", \"value1\", \"key2\", \"value2\"));\n    underTest.setUserProperties(ANOTHER_CONFIG_SCOPE_ID, Map.of(\"key1\", \"value1\"));\n    properties = underTest.getUserProperties(CONFIG_SCOPE_ID);\n    assertThat(properties).hasSize(2).containsEntry(\"key1\", \"value1\").containsEntry(\"key2\", \"value2\");\n\n    underTest.setUserProperties(CONFIG_SCOPE_ID, Map.of(\"key2\", \"new-value2\", \"key3\", \"new-value3\"));\n\n    properties = underTest.getUserProperties(CONFIG_SCOPE_ID);\n    assertThat(properties).hasSize(2).containsEntry(\"key2\", \"new-value2\").containsEntry(\"key3\", \"new-value3\");\n    var anotherProperties = underTest.getUserProperties(ANOTHER_CONFIG_SCOPE_ID);\n    assertThat(anotherProperties).hasSize(1).containsEntry(\"key1\", \"value1\");\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/branch/SonarProjectBranchTrackingServiceTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.branch;\n\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.CompletableFuture;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.event.ConfigurationScopesAddedWithBindingEvent;\nimport org.sonarsource.sonarlint.core.repository.config.BindingConfiguration;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationScope;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationScopeWithBinding;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.branch.MatchSonarProjectBranchResponse;\nimport org.sonarsource.sonarlint.core.serverconnection.ProjectBranches;\nimport org.sonarsource.sonarlint.core.serverconnection.ProjectBranchesStorage;\nimport org.sonarsource.sonarlint.core.serverconnection.SonarProjectStorage;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.springframework.context.ApplicationEventPublisher;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass SonarProjectBranchTrackingServiceTests {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester(true);\n\n  public static final String CONNECTION_ID = \"connectionId\";\n  public static final String PROJECT_KEY = \"projectKey\";\n  public static final String CONFIG_SCOPE_ID = \"configScopeId\";\n  private SonarProjectBranchTrackingService underTest;\n  private final SonarLintRpcClient sonarLintRpcClient = mock(SonarLintRpcClient.class);\n  private final StorageService storageService = mock(StorageService.class);\n  private final ConfigurationRepository configurationRepository = mock(ConfigurationRepository.class);\n  private ProjectBranchesStorage projectBranchesStorage;\n\n  @BeforeEach\n  void prepare() {\n    when(configurationRepository.getConfigurationScope(CONFIG_SCOPE_ID)).thenReturn(new ConfigurationScope(CONFIG_SCOPE_ID, null, true, \"Test config scope\"));\n    var binding = new Binding(CONNECTION_ID, PROJECT_KEY);\n    when(configurationRepository.getEffectiveBinding(CONFIG_SCOPE_ID)).thenReturn(Optional.of(binding));\n    var sonarProjectStorage = mock(SonarProjectStorage.class);\n    when(storageService.binding(binding)).thenReturn(sonarProjectStorage);\n    projectBranchesStorage = mock(ProjectBranchesStorage.class);\n    when(sonarProjectStorage.branches()).thenReturn(projectBranchesStorage);\n    underTest = new SonarProjectBranchTrackingService(sonarLintRpcClient, storageService, configurationRepository, mock(ApplicationEventPublisher.class));\n  }\n\n  @AfterEach\n  void shutdown() {\n    underTest.shutdown();\n  }\n\n  @Test\n  void shouldCancelPreviousJobIfNewOneIsSubmitted() {\n    when(projectBranchesStorage.exists()).thenReturn(true);\n    when(projectBranchesStorage.read()).thenReturn(new ProjectBranches(Set.of(\"main\", \"feature\"), \"main\"));\n\n    var firstFuture = new CompletableFuture<MatchSonarProjectBranchResponse>();\n    when(sonarLintRpcClient.matchSonarProjectBranch(any()))\n      // Emulate a long response for the first request\n      .thenReturn(firstFuture)\n      .thenReturn(CompletableFuture.completedFuture(new MatchSonarProjectBranchResponse(\"feature\")));\n\n    // This should queue a first branch matching\n    underTest.onConfigurationScopesAdded(new ConfigurationScopesAddedWithBindingEvent(Set.of(\n      new ConfigurationScopeWithBinding(\n        new ConfigurationScope(CONFIG_SCOPE_ID, null, true, \"scope\"),\n        BindingConfiguration.noBinding()\n      ))));\n    // Wait for the RPC client to be called\n    verify(sonarLintRpcClient, timeout(1000)).matchSonarProjectBranch(any());\n\n    // This should cancel the previous branch matching, and queue a new one\n    underTest.didVcsRepositoryChange(CONFIG_SCOPE_ID);\n\n    assertThat(underTest.awaitEffectiveSonarProjectBranch(CONFIG_SCOPE_ID)).contains(\"feature\");\n\n    assertThat(firstFuture).isCancelled();\n\n    verify(sonarLintRpcClient, timeout(1000).times(1)).didChangeMatchedSonarProjectBranch(any());\n  }\n\n  @Test\n  void shouldUnlockThoseAwaitingForBranchOnErrorAndDefaultToMain() {\n    when(projectBranchesStorage.exists()).thenReturn(true);\n    when(projectBranchesStorage.read()).thenReturn(new ProjectBranches(Set.of(\"main\", \"feature\"), \"main\"));\n\n    var rpcFuture = new CompletableFuture<MatchSonarProjectBranchResponse>();\n    when(sonarLintRpcClient.matchSonarProjectBranch(any()))\n      .thenReturn(rpcFuture);\n\n    // This should queue a first branch matching\n    underTest.onConfigurationScopesAdded(new ConfigurationScopesAddedWithBindingEvent(Set.of(\n      new ConfigurationScopeWithBinding(\n        new ConfigurationScope(CONFIG_SCOPE_ID, null, true, \"scope\"),\n        BindingConfiguration.noBinding()\n      ))));\n    // Wait for the RPC client to be called\n    verify(sonarLintRpcClient, timeout(1000)).matchSonarProjectBranch(any());\n\n    rpcFuture.completeExceptionally(new RuntimeException(\"Unexpected error\"));\n\n    assertThat(underTest.awaitEffectiveSonarProjectBranch(CONFIG_SCOPE_ID)).contains(\"main\");\n\n    await().untilAsserted(() -> assertThat(logTester.logs())\n      .contains(\"Matched Sonar project branch for configuration scope 'configScopeId' changed from 'null' to 'main'\"));\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/commons/SmartCancelableLoadingCacheTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.commons;\n\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.BiFunction;\nimport javax.annotation.Nullable;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.mockito.stubbing.Answer;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport testutils.TakeThreadDumpAfter;\nimport testutils.ThreadDumpExtension;\n\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\nimport static org.mockito.Mockito.when;\n\n@ExtendWith(ThreadDumpExtension.class)\nclass SmartCancelableLoadingCacheTests {\n\n  public static final String ANOTHER_VALUE = \"anotherValue\";\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester(true);\n\n  public static final String A_VALUE = \"aValue\";\n  public static final String A_KEY = \"aKey\";\n  public static final String ANOTHER_KEY = \"anotherKey\";\n  private final SmartCancelableLoadingCache.Listener<String, String> listener = mock(SmartCancelableLoadingCache.Listener.class);\n  private final BiFunction<String, SonarLintCancelMonitor, String> computer = mock(BiFunction.class);\n  private final SmartCancelableLoadingCache<String, String> underTest = new SmartCancelableLoadingCache<>(\"test\", computer, listener);\n\n  @AfterEach\n  void close() {\n    underTest.close();\n  }\n\n  @Test\n  @TakeThreadDumpAfter(seconds = 10)\n  void should_cache_value_and_notify_listener_once() {\n    when(computer.apply(eq(A_KEY), any(SonarLintCancelMonitor.class))).thenReturn(A_VALUE);\n\n    assertThat(underTest.get(A_KEY)).isEqualTo(A_VALUE);\n    assertThat(underTest.get(A_KEY)).isEqualTo(A_VALUE);\n    assertThat(underTest.get(A_KEY)).isEqualTo(A_VALUE);\n\n    verify(listener).afterCachedValueRefreshed(A_KEY, null, A_VALUE);\n    verify(computer, times(1)).apply(eq(A_KEY), any());\n  }\n\n  @Test\n  @TakeThreadDumpAfter(seconds = 10)\n  void should_wait_for_long_computation() {\n    when(computer.apply(eq(A_KEY), any(SonarLintCancelMonitor.class))).thenAnswer(invocation -> {\n      Thread.sleep(100);\n      return A_VALUE;\n    });\n\n    assertThat(underTest.get(A_KEY)).isEqualTo(A_VALUE);\n  }\n\n  @Test\n  @TakeThreadDumpAfter(seconds = 10)\n  void should_throw_if_failure_while_loading() {\n    when(computer.apply(eq(A_KEY), any(SonarLintCancelMonitor.class))).thenThrow(new RuntimeException(\"boom\"));\n\n    assertThrows(RuntimeException.class, () -> underTest.get(A_KEY));\n    assertThrows(RuntimeException.class, () -> underTest.get(A_KEY));\n    assertThrows(RuntimeException.class, () -> underTest.get(A_KEY));\n\n    verify(computer, times(1)).apply(eq(A_KEY), any());\n    verifyNoInteractions(listener);\n  }\n\n  @Test\n  @TakeThreadDumpAfter(seconds = 10)\n  void should_refresh_value() {\n    when(computer.apply(eq(A_KEY), any(SonarLintCancelMonitor.class)))\n      .thenReturn(A_VALUE)\n      .thenReturn(ANOTHER_VALUE);\n\n    assertThat(underTest.get(A_KEY)).isEqualTo(A_VALUE);\n    verify(listener).afterCachedValueRefreshed(A_KEY, null, A_VALUE);\n\n    underTest.refreshAsync(A_KEY);\n\n    verify(listener, timeout(1000)).afterCachedValueRefreshed(A_KEY, A_VALUE, ANOTHER_VALUE);\n    assertThat(underTest.get(A_KEY)).isEqualTo(ANOTHER_VALUE);\n\n    verify(computer, times(2)).apply(eq(A_KEY), any());\n  }\n\n  @Test\n  @TakeThreadDumpAfter(seconds = 10)\n  void should_cancel_previous_computation_on_refresh() throws InterruptedException {\n    var firstComputationStarted = new CountDownLatch(1);\n    var cancelled = new AtomicBoolean();\n    when(computer.apply(eq(A_KEY), any(SonarLintCancelMonitor.class)))\n      .thenAnswer(waitingForCancellation(firstComputationStarted, cancelled))\n      .thenReturn(ANOTHER_VALUE);\n\n    // Queue a first computation\n    underTest.refreshAsync(A_KEY);\n    firstComputationStarted.await();\n\n    // Queue a second computation\n    underTest.refreshAsync(A_KEY);\n\n    assertThat(underTest.get(A_KEY)).isEqualTo(ANOTHER_VALUE);\n    assertThat(cancelled.get()).isTrue();\n\n    verify(computer, times(2)).apply(eq(A_KEY), any());\n  }\n\n  @Test\n  @TakeThreadDumpAfter(seconds = 10)\n  void should_cancel_previous_computation_on_clear() throws InterruptedException {\n    var firstComputationStarted = new CountDownLatch(1);\n    var cancelled = new AtomicBoolean();\n    when(computer.apply(eq(A_KEY), any(SonarLintCancelMonitor.class)))\n      .thenAnswer(waitingForCancellation(firstComputationStarted, cancelled));\n\n    // Queue a first computation\n    underTest.refreshAsync(A_KEY);\n    firstComputationStarted.await();\n\n    underTest.clear(A_KEY);\n\n    await().untilAsserted(() -> assertThat(cancelled.get()).isTrue());\n\n    verify(computer, times(1)).apply(eq(A_KEY), any());\n  }\n\n  @Test\n  @TakeThreadDumpAfter(seconds = 10)\n  void should_cancel_all_previous_computation_on_close() throws InterruptedException {\n    var key1ComputationStarted = new CountDownLatch(1);\n    var cancelledKey1 = new AtomicBoolean();\n    when(computer.apply(eq(A_KEY), any(SonarLintCancelMonitor.class)))\n      .thenAnswer(waitingForCancellation(key1ComputationStarted, cancelledKey1));\n\n    // Queue a computation of key1\n    underTest.refreshAsync(A_KEY);\n    key1ComputationStarted.await();\n\n    var key2ComputationStarted = new CountDownLatch(1);\n    var cancelledKey2 = new AtomicBoolean();\n    when(computer.apply(eq(ANOTHER_KEY), any(SonarLintCancelMonitor.class)))\n      .thenAnswer(waitingForCancellation(key2ComputationStarted, cancelledKey2));\n\n    // Queue a computation of key2, that will only start after computation of key1 because the executor service is single threaded\n    underTest.refreshAsync(ANOTHER_KEY);\n\n    underTest.close();\n\n    await().untilAsserted(() -> assertThat(cancelledKey1.get()).isTrue());\n    // Second computation was cancelled early, because calling the computer\n    await().untilAsserted(() -> assertThat(key2ComputationStarted.getCount()).isEqualTo(1));\n\n    verify(computer, times(1)).apply(eq(A_KEY), any());\n    verifyNoMoreInteractions(computer);\n  }\n\n  @Test\n  @TakeThreadDumpAfter(seconds = 10)\n  void previously_queued_get_should_receive_latest_value_on_cancellation() throws InterruptedException {\n    var firstComputationStarted = new CountDownLatch(1);\n    var cancelled = new AtomicBoolean();\n    when(computer.apply(eq(A_KEY), any(SonarLintCancelMonitor.class)))\n      .thenAnswer(waitingForCancellation(firstComputationStarted, cancelled))\n      .thenReturn(ANOTHER_VALUE);\n\n    // Queue a first computation\n    AtomicReference<String> value = new AtomicReference<>();\n    var t = new Thread(() -> {\n      value.set(underTest.get(A_KEY));\n    });\n    t.start();\n    firstComputationStarted.await();\n\n    // Queue a second computation\n    underTest.refreshAsync(A_KEY);\n\n    assertThat(underTest.get(A_KEY)).isEqualTo(ANOTHER_VALUE);\n    t.join();\n    assertThat(value.get()).isEqualTo(ANOTHER_VALUE);\n    assertThat(cancelled.get()).isTrue();\n  }\n\n\n  @Test\n  @TakeThreadDumpAfter(seconds = 10)\n  void should_notify_once_in_case_of_cancellation() throws InterruptedException {\n    var firstComputationStarted = new CountDownLatch(1);\n    when(computer.apply(eq(A_KEY), any(SonarLintCancelMonitor.class)))\n      .thenAnswer(waitingForCancellation(firstComputationStarted, null))\n      .thenReturn(ANOTHER_VALUE);\n\n    underTest.refreshAsync(A_KEY);\n    firstComputationStarted.await();\n\n    underTest.refreshAsync(A_KEY);\n\n    verify(listener, timeout(1000).times(1)).afterCachedValueRefreshed(A_KEY, null, ANOTHER_VALUE);\n    verifyNoMoreInteractions(listener);\n  }\n\n  @Test\n  void should_notify_once_if_multiple_refresh() {\n    when(computer.apply(eq(A_KEY), any(SonarLintCancelMonitor.class)))\n      .thenReturn(A_VALUE)\n      .thenReturn(ANOTHER_VALUE);\n\n    assertThat(underTest.get(A_KEY)).isEqualTo(A_VALUE);\n    verify(listener, timeout(1000).times(1)).afterCachedValueRefreshed(A_KEY, null, A_VALUE);\n\n    underTest.refreshAsync(A_KEY);\n    underTest.refreshAsync(A_KEY);\n    underTest.refreshAsync(A_KEY);\n    underTest.refreshAsync(A_KEY);\n    underTest.refreshAsync(A_KEY);\n\n    verify(listener, timeout(1000).times(1)).afterCachedValueRefreshed(A_KEY, A_VALUE, ANOTHER_VALUE);\n    verifyNoMoreInteractions(listener);\n  }\n\n\n  private static Answer<String> waitingForCancellation(CountDownLatch startedLatch, @Nullable AtomicBoolean wasCancelled) {\n    return invocation -> {\n      var cancelChecker = (SonarLintCancelMonitor) invocation.getArgument(1);\n      startedLatch.countDown();\n      while (!cancelChecker.isCanceled()) {\n        Thread.sleep(100);\n      }\n      if (wasCancelled != null) {\n        wasCancelled.set(true);\n      }\n      throw new CancellationException();\n    };\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/embedded/server/AnalyzeFileListRequestHandlerTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server;\n\nimport com.google.gson.Gson;\nimport java.io.IOException;\nimport java.net.URI;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.CompletableFuture;\nimport org.apache.hc.core5.http.HttpException;\nimport org.apache.hc.core5.http.HttpStatus;\nimport org.apache.hc.core5.http.Method;\nimport org.apache.hc.core5.http.io.entity.StringEntity;\nimport org.apache.hc.core5.http.message.BasicClassicHttpRequest;\nimport org.apache.hc.core5.http.message.BasicClassicHttpResponse;\nimport org.apache.hc.core5.http.protocol.HttpContext;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.analysis.AnalysisResult;\nimport org.sonarsource.sonarlint.core.analysis.AnalysisService;\nimport org.sonarsource.sonarlint.core.analysis.RawIssue;\nimport org.sonarsource.sonarlint.core.analysis.api.TriggerType;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.fs.ClientFileSystemService;\nimport org.sonarsource.sonarlint.core.tracking.TaintVulnerabilityTrackingService;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.ArgumentMatchers.anyMap;\nimport static org.mockito.ArgumentMatchers.anySet;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.when;\n\nclass AnalyzeFileListRequestHandlerTests {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester(true);\n  private AnalyzeFileListRequestHandler analyzeFileListRequestHandler;\n  private AnalysisService analysisService;\n  private ClientFileSystemService clientFileSystemService;\n\n  @BeforeEach\n  void setup() {\n    analysisService = mock(AnalysisService.class);\n    clientFileSystemService = mock(ClientFileSystemService.class);\n    var taintVulnerabilityTrackingService = mock(TaintVulnerabilityTrackingService.class);\n    analyzeFileListRequestHandler = spy(new AnalyzeFileListRequestHandler(analysisService, clientFileSystemService, taintVulnerabilityTrackingService));\n  }\n\n  @Test\n  void should_reject_non_post_requests() throws HttpException, IOException {\n    var request = new BasicClassicHttpRequest(Method.GET, \"/analyze\");\n    var response = new BasicClassicHttpResponse(200);\n    var context = mock(HttpContext.class);\n\n    analyzeFileListRequestHandler.handle(request, response, context);\n\n    assertThat(response.getCode()).isEqualTo(HttpStatus.SC_BAD_REQUEST);\n  }\n\n  @Test\n  void should_reject_invalid_json_request() throws HttpException, IOException {\n    var request = new BasicClassicHttpRequest(Method.POST, \"/analyze\");\n    request.setEntity(new StringEntity(\"invalid json\", StandardCharsets.UTF_8));\n    var response = new BasicClassicHttpResponse(200);\n    var context = mock(HttpContext.class);\n\n    analyzeFileListRequestHandler.handle(request, response, context);\n\n    assertThat(response.getCode()).isEqualTo(HttpStatus.SC_BAD_REQUEST);\n    assertThat(logTester.logs()).contains(\"Failed to parse analyze file list request\");\n  }\n\n  @Test\n  void should_reject_null_request_body() throws HttpException, IOException {\n    var request = new BasicClassicHttpRequest(Method.POST, \"/analyze\");\n    request.setEntity(new StringEntity(\"null\", StandardCharsets.UTF_8));\n    var response = new BasicClassicHttpResponse(200);\n    var context = mock(HttpContext.class);\n\n    analyzeFileListRequestHandler.handle(request, response, context);\n\n    assertThat(response.getCode()).isEqualTo(HttpStatus.SC_BAD_REQUEST);\n  }\n\n  @Test\n  void should_reject_empty_file_list() throws HttpException, IOException {\n    var requestJson = new Gson().toJson(new AnalyzeFileListRequestHandler.AnalyzeFileListRequest(Collections.emptyList()));\n    var request = new BasicClassicHttpRequest(Method.POST, \"/analyze\");\n    request.setEntity(new StringEntity(requestJson, StandardCharsets.UTF_8));\n    var response = new BasicClassicHttpResponse(200);\n    var context = mock(HttpContext.class);\n\n    analyzeFileListRequestHandler.handle(request, response, context);\n\n    assertThat(response.getCode()).isEqualTo(HttpStatus.SC_BAD_REQUEST);\n  }\n\n  @Test\n  void should_handle_issues_with_null_severity_and_file_path() throws HttpException, IOException {\n    var analysisRequest = new AnalyzeFileListRequestHandler.AnalyzeFileListRequest(List.of(\"/path/to/file.java\"));\n    var requestJson = new Gson().toJson(analysisRequest);\n    var request = new BasicClassicHttpRequest(Method.POST, \"/analyze\");\n    request.setEntity(new StringEntity(requestJson, StandardCharsets.UTF_8));\n    var response = new BasicClassicHttpResponse(200);\n    var context = mock(HttpContext.class);\n    var filesByScope = Map.of(\"scope1\", Set.of(URI.create(\"file:///path/to/file.java\")));\n    when(clientFileSystemService.groupFilesByConfigScope(anySet())).thenReturn(filesByScope);\n    var mockIssue = createMockRawIssue();\n    var scanResults = mock(AnalysisResult.class);\n    when(scanResults.rawIssues()).thenReturn(List.of(mockIssue));\n    when(analysisService.scheduleAnalysis(anyString(), any(UUID.class), anySet(), anyMap(), \n      anyBoolean(), eq(TriggerType.FORCED), any())).thenReturn(CompletableFuture.completedFuture(scanResults));\n\n    analyzeFileListRequestHandler.handle(request, response, context);\n\n    assertThat(response.getCode()).isEqualTo(200);\n    var responseContent = new String(response.getEntity().getContent().readAllBytes(), StandardCharsets.UTF_8);\n    var analysisResult = new Gson().fromJson(responseContent, AnalyzeFileListRequestHandler.AnalyzeFileListResult.class);\n    assertThat(analysisResult.findings()).hasSize(1);\n    var issue = analysisResult.findings().get(0);\n    assertThat(issue.ruleKey()).isEqualTo(\"java:S123\");\n    assertThat(issue.severity()).isNull();\n    assertThat(issue.filePath()).isNull();\n    assertThat(issue.textRange()).isNull();\n  }\n\n  private RawIssue createMockRawIssue() {\n    var mockIssue = mock(RawIssue.class);\n    when(mockIssue.getRuleKey()).thenReturn(\"java:S123\");\n    when(mockIssue.getMessage()).thenReturn(\"Test message\");\n    when(mockIssue.getSeverity()).thenReturn(null);\n    when(mockIssue.getFileUri()).thenReturn(null);\n    when(mockIssue.getTextRange()).thenReturn(null);\n    return mockIssue;\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/embedded/server/ToggleAutomaticAnalysisRequestHandlerTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server;\n\nimport com.google.gson.Gson;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport org.apache.hc.core5.http.HttpException;\nimport org.apache.hc.core5.http.HttpStatus;\nimport org.apache.hc.core5.http.Method;\nimport org.apache.hc.core5.http.message.BasicClassicHttpRequest;\nimport org.apache.hc.core5.http.message.BasicClassicHttpResponse;\nimport org.apache.hc.core5.http.protocol.HttpContext;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.analysis.AnalysisService;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verifyNoInteractions;\n\nclass ToggleAutomaticAnalysisRequestHandlerTests {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester(true);\n\n  private final Gson gson = new Gson();\n  private AnalysisService analysisService;\n  private ToggleAutomaticAnalysisRequestHandler toggleAutomaticAnalysisRequestHandler;\n  private HttpContext context;\n\n  @BeforeEach\n  void setup() {\n    analysisService = mock(AnalysisService.class);\n    context = mock(HttpContext.class);\n    toggleAutomaticAnalysisRequestHandler = new ToggleAutomaticAnalysisRequestHandler(analysisService);\n  }\n\n  @Test\n  void should_reject_non_post_requests() throws HttpException, IOException {\n    var request = new BasicClassicHttpRequest(Method.GET, \"/analysis/automatic/config\");\n    var response = new BasicClassicHttpResponse(200);\n\n    toggleAutomaticAnalysisRequestHandler.handle(request, response, context);\n\n    assertThat(response.getCode()).isEqualTo(HttpStatus.SC_BAD_REQUEST);\n    verifyNoInteractions(analysisService);\n  }\n\n  @Test\n  void should_reject_invalid_enabled_parameter() throws HttpException, IOException {\n    var request = new BasicClassicHttpRequest(Method.POST, \"/analysis/automatic/config?invalid=param\");\n    var response = new BasicClassicHttpResponse(200);\n\n    toggleAutomaticAnalysisRequestHandler.handle(request, response, context);\n\n    assertThat(response.getCode()).isEqualTo(HttpStatus.SC_BAD_REQUEST);\n    verifyNoInteractions(analysisService);\n  }\n\n  @Test\n  void should_handle_analysis_service_exception() throws HttpException, IOException {\n    var request = new BasicClassicHttpRequest(Method.POST, \"/analysis/automatic/config?enabled=true\");\n    var response = new BasicClassicHttpResponse(200);\n    var exception = new RuntimeException(\"Analysis service failed\");\n    doThrow(exception).when(analysisService).didChangeAutomaticAnalysisSetting(anyBoolean());\n\n    toggleAutomaticAnalysisRequestHandler.handle(request, response, context);\n\n    assertThat(response.getCode()).isEqualTo(HttpStatus.SC_INTERNAL_SERVER_ERROR);\n    assertThat(response.getEntity()).isNotNull();\n    var responseContent = new String(response.getEntity().getContent().readAllBytes(), StandardCharsets.UTF_8);\n    var errorMessage = gson.fromJson(responseContent, ToggleAutomaticAnalysisRequestHandler.ErrorMessage.class);\n    assertThat(errorMessage.message()).contains(\"Failed to toggle automatic analysis\");\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/embedded/server/filter/CspFilterTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server.filter;\n\nimport java.io.IOException;\nimport org.apache.hc.core5.http.ClassicHttpRequest;\nimport org.apache.hc.core5.http.ClassicHttpResponse;\nimport org.apache.hc.core5.http.HttpException;\nimport org.apache.hc.core5.http.io.HttpFilterChain;\nimport org.apache.hc.core5.http.message.BasicClassicHttpRequest;\nimport org.apache.hc.core5.http.message.BasicClassicHttpResponse;\nimport org.apache.hc.core5.http.protocol.HttpContext;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Mockito;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.eq;\nimport static org.mockito.Mockito.verify;\n\nclass CspFilterTest {\n\n  private CspFilter cspFilter;\n  private HttpFilterChain.ResponseTrigger mockResponseTrigger;\n  private HttpFilterChain mockFilterChain;\n  private HttpContext mockContext;\n  private ClassicHttpRequest mockRequest;\n\n  @BeforeEach\n  void setUp() {\n    cspFilter = new CspFilter();\n    mockResponseTrigger = Mockito.mock(HttpFilterChain.ResponseTrigger.class);\n    mockFilterChain = Mockito.mock(HttpFilterChain.class);\n    mockContext = Mockito.mock(HttpContext.class);\n    mockRequest = new BasicClassicHttpRequest(\"GET\", \"http://localhost:64120/sonarlint/api/endpoint\");\n  }\n\n  @Test\n  void it_should_add_csp_header_to_the_response_when_response_is_successful() throws HttpException, IOException {\n    doAnswer(invocation -> {\n      HttpFilterChain.ResponseTrigger trigger = invocation.getArgument(1);\n      var mockResponse = new BasicClassicHttpResponse(200);\n      trigger.submitResponse(mockResponse);\n      trigger.sendInformation(mockResponse);\n      return null;\n    }).when(mockFilterChain).proceed(eq(mockRequest), any(), eq(mockContext));\n\n    cspFilter.handle(mockRequest, mockResponseTrigger, mockContext, mockFilterChain);\n\n    var captor = ArgumentCaptor.forClass(ClassicHttpResponse.class);\n    verify(mockResponseTrigger).submitResponse(captor.capture());\n    var response = captor.getValue();\n    var cspHeader = response.getHeader(\"Content-Security-Policy-Report-Only\").getValue();\n\n    assertThat(cspHeader).isEqualTo(\"connect-src 'self' http://localhost:64120;\");\n    verify(mockResponseTrigger).sendInformation(any());\n  }\n\n  @ParameterizedTest\n  @ValueSource(strings = {\"400\", \"401\", \"403\", \"404\", \"500\"})\n  void it_should_not_add_csp_header_to_the_response_when_response_is_unsuccessful(String responseCode) throws HttpException, IOException {\n    doAnswer(invocation -> {\n      HttpFilterChain.ResponseTrigger trigger = invocation.getArgument(1);\n      var mockResponse = new BasicClassicHttpResponse(Integer.parseInt(responseCode));\n      trigger.submitResponse(mockResponse);\n      return null;\n    }).when(mockFilterChain).proceed(eq(mockRequest), any(), eq(mockContext));\n\n    cspFilter.handle(mockRequest, mockResponseTrigger, mockContext, mockFilterChain);\n\n    var captor = ArgumentCaptor.forClass(ClassicHttpResponse.class);\n    verify(mockResponseTrigger).submitResponse(captor.capture());\n    var response = captor.getValue();\n    assertThat(response.getHeader(\"Content-Security-Policy-Report-Only\")).isNull();\n  }\n}\n\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/embedded/server/filter/RateLimitFilterTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server.filter;\n\nimport org.apache.hc.core5.http.ClassicHttpRequest;\nimport org.apache.hc.core5.http.HttpException;\nimport org.apache.hc.core5.http.io.HttpFilterChain;\nimport org.apache.hc.core5.http.message.BasicHeader;\nimport org.apache.hc.core5.http.protocol.HttpContext;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\n\nimport static org.mockito.Mockito.*;\n\nclass RateLimitFilterTests {\n\n  private final ClassicHttpRequest request = mock(ClassicHttpRequest.class);\n  private final HttpFilterChain.ResponseTrigger responseTrigger = mock(HttpFilterChain.ResponseTrigger.class);\n  private final HttpContext context = mock(HttpContext.class);\n  private final HttpFilterChain chain = mock(HttpFilterChain.class);\n  private RateLimitFilter filter;\n\n  @BeforeEach\n  void init() {\n    filter = new RateLimitFilter();\n  }\n\n  @Test\n  void should_not_proceed_with_request_if_origin_is_null() throws HttpException, IOException {\n    when(request.getHeader(\"Origin\")).thenReturn(null);\n\n    filter.handle(request, responseTrigger, context, chain);\n\n    verify(responseTrigger).submitResponse(any());\n    verify(chain, never()).proceed(any(), any(), any());\n  }\n\n  @Test\n  void should_proceed_when_request_is_valid() throws HttpException, IOException {\n    when(request.getHeader(\"Origin\")).thenReturn(new BasicHeader(\"Origin\", \"https://example.com\"));\n\n    filter.handle(request, responseTrigger, context, chain);\n\n    verify(responseTrigger, never()).submitResponse(any());\n    verify(chain).proceed(any(), any(), any());\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/embedded/server/handler/ShowFixSuggestionRequestHandlerTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server.handler;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\nimport org.apache.hc.core5.http.ClassicHttpRequest;\nimport org.apache.hc.core5.http.ClassicHttpResponse;\nimport org.apache.hc.core5.http.HttpException;\nimport org.apache.hc.core5.http.Method;\nimport org.apache.hc.core5.http.io.entity.StringEntity;\nimport org.apache.hc.core5.http.message.BasicClassicHttpRequest;\nimport org.apache.hc.core5.http.protocol.HttpContext;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.BindingCandidatesFinder;\nimport org.sonarsource.sonarlint.core.BindingSuggestionProvider;\nimport org.sonarsource.sonarlint.core.SonarCloudActiveEnvironment;\nimport org.sonarsource.sonarlint.core.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.commons.BoundScope;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.embedded.server.AttributeUtils;\nimport org.sonarsource.sonarlint.core.embedded.server.RequestHandlerBindingAssistant;\nimport org.sonarsource.sonarlint.core.event.FixSuggestionReceivedEvent;\nimport org.sonarsource.sonarlint.core.file.FilePathTranslation;\nimport org.sonarsource.sonarlint.core.file.PathTranslationService;\nimport org.sonarsource.sonarlint.core.fs.ClientFile;\nimport org.sonarsource.sonarlint.core.fs.ClientFileSystemService;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarCloudConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.serverconnection.ProjectBranches;\nimport org.sonarsource.sonarlint.core.sync.SonarProjectBranchesSynchronizationService;\nimport org.springframework.context.ApplicationEventPublisher;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass ShowFixSuggestionRequestHandlerTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester(true);\n\n  private ShowFixSuggestionRequestHandler showFixSuggestionRequestHandler;\n\n  private ConnectionConfigurationRepository connectionConfigurationRepository;\n  private ConfigurationRepository configurationRepository;\n  private SonarLintRpcClient sonarLintRpcClient;\n  private ApplicationEventPublisher eventPublisher;\n  private ClientFile clientFile;\n  private FilePathTranslation filePathTranslation;\n\n  @BeforeEach\n  void setup() {\n    connectionConfigurationRepository = mock(ConnectionConfigurationRepository.class);\n    configurationRepository = mock(ConfigurationRepository.class);\n    var bindingSuggestionProvider = mock(BindingSuggestionProvider.class);\n    var bindingCandidatesFinder = mock(BindingCandidatesFinder.class);\n    sonarLintRpcClient = mock(SonarLintRpcClient.class);\n    filePathTranslation = mock(FilePathTranslation.class);\n    var pathTranslationService = mock(PathTranslationService.class);\n    when(pathTranslationService.getOrComputePathTranslation(any())).thenReturn(Optional.of(filePathTranslation));\n    var sonarCloudActiveEnvironment = SonarCloudActiveEnvironment.prod();\n    eventPublisher = mock(ApplicationEventPublisher.class);\n    var sonarProjectBranchesSynchronizationService = mock(SonarProjectBranchesSynchronizationService.class);\n    when(sonarProjectBranchesSynchronizationService.getProjectBranches(any(), any(), any())).thenReturn(new ProjectBranches(Set.of(), \"main\"));\n    clientFile = mock(ClientFile.class);\n    var clientFs = mock(ClientFileSystemService.class);\n    when(clientFs.getFiles(any())).thenReturn(List.of(clientFile));\n    var connectionConfiguration = mock(ConnectionConfigurationRepository.class);\n    when(connectionConfiguration.hasConnectionWithOrigin(SonarCloudRegion.EU.getProductionUri().toString())).thenReturn(true);\n\n    showFixSuggestionRequestHandler = new ShowFixSuggestionRequestHandler(sonarLintRpcClient, eventPublisher,\n      new RequestHandlerBindingAssistant(bindingSuggestionProvider, bindingCandidatesFinder, sonarLintRpcClient, connectionConfigurationRepository, configurationRepository,\n        sonarCloudActiveEnvironment, connectionConfiguration),\n      pathTranslationService, sonarCloudActiveEnvironment, clientFs);\n  }\n\n  @Test\n  void should_trigger_telemetry() throws URISyntaxException, HttpException, IOException {\n    var request = mock(ClassicHttpRequest.class);\n    when(request.getUri()).thenReturn(URI.create(\"/sonarlint/api/fix/show\" +\n      \"?project=org.sonarsource.sonarlint.core%3Asonarlint-core-parent\" +\n      \"&issue=AX2VL6pgAvx3iwyNtLyr&branch=branch\" +\n      \"&organizationKey=sample-organization\"));\n    when(request.getMethod()).thenReturn(Method.POST.name());\n    when(request.getEntity()).thenReturn(new StringEntity(\"\"\"\n      {\n        \"fileEdit\": {\n          \"path\": \"src/main/java/Main.java\",\n          \"changes\": [{\n            \"beforeLineRange\": {\n              \"startLine\": 0,\n              \"endLine\": 1\n            },\n            \"before\": \"\",\n            \"after\": \"var fix = 1;\"\n          }]\n        },\n        \"suggestionId\": \"eb93b2b4-f7b0-4b5c-9460-50893968c264\",\n        \"explanation\": \"Modifying the variable name is good\"\n      }\n      \"\"\"));\n    var response = mock(ClassicHttpResponse.class);\n    var context = mock(HttpContext.class);\n    when(context.getAttribute(AttributeUtils.PARAMS_ATTRIBUTE))\n      .thenReturn(Map.of(\n        \"project\", \"org.sonarsource.sonarlint.core:sonarlint-core-parent\",\n        \"issue\", \"AX2VL6pgAvx3iwyNtLyr\",\n        \"branch\", \"branch\",\n        \"organizationKey\", \"sample-organization\"\n      ));\n    when(context.getAttribute(AttributeUtils.ORIGIN_ATTRIBUTE))\n      .thenReturn(SonarCloudRegion.EU.getProductionUri().toString());\n\n    showFixSuggestionRequestHandler.handle(request, response, context);\n\n    verify(eventPublisher, times(1)).publishEvent(any(FixSuggestionReceivedEvent.class));\n  }\n\n  @Test\n  void should_extract_query_from_sc_request_without_token() throws HttpException, IOException {\n    var request = new BasicClassicHttpRequest(\"POST\", \"/sonarlint/api/fix/show\" +\n      \"?project=org.sonarsource.sonarlint.core%3Asonarlint-core-parent\" +\n      \"&issue=AX2VL6pgAvx3iwyNtLyr&branch=branch\" +\n      \"&organizationKey=sample-organization\");\n    request.setEntity(new StringEntity(\"\"\"\n      {\n        \"fileEdit\": {\n          \"path\": \"src/main/java/Main.java\",\n          \"changes\": [{\n            \"beforeLineRange\": {\n              \"startLine\": 0,\n              \"endLine\": 1\n            },\n            \"before\": \"\",\n            \"after\": \"var fix = 1;\"\n          }]\n        },\n        \"suggestionId\": \"eb93b2b4-f7b0-4b5c-9460-50893968c264\",\n        \"explanation\": \"Modifying the variable name is good\"\n      }\n      \"\"\"));\n    var params = Map.of(\n      \"project\", \"org.sonarsource.sonarlint.core:sonarlint-core-parent\",\n      \"issue\", \"AX2VL6pgAvx3iwyNtLyr\",\n      \"branch\", \"branch\",\n      \"organizationKey\", \"sample-organization\"\n    );\n    \n    var showFixSuggestionQuery = showFixSuggestionRequestHandler.extractQuery(request, SonarCloudRegion.EU.getProductionUri().toString(), params);\n    \n    assertThat(showFixSuggestionQuery.getServerUrl()).isEqualTo(\"https://sonarcloud.io\");\n    assertThat(showFixSuggestionQuery.getProjectKey()).isEqualTo(\"org.sonarsource.sonarlint.core:sonarlint-core-parent\");\n    assertThat(showFixSuggestionQuery.getIssueKey()).isEqualTo(\"AX2VL6pgAvx3iwyNtLyr\");\n    assertThat(showFixSuggestionQuery.getOrganizationKey()).isEqualTo(\"sample-organization\");\n    assertThat(showFixSuggestionQuery.getBranch()).isEqualTo(\"branch\");\n    assertThat(showFixSuggestionQuery.getTokenName()).isNull();\n    assertThat(showFixSuggestionQuery.getTokenValue()).isNull();\n    assertThat(showFixSuggestionQuery.getFixSuggestion().suggestionId()).isEqualTo(\"eb93b2b4-f7b0-4b5c-9460-50893968c264\");\n    assertThat(showFixSuggestionQuery.getFixSuggestion().explanation()).isEqualTo(\"Modifying the variable name is good\");\n    assertThat(showFixSuggestionQuery.getFixSuggestion().fileEdit().path()).isEqualTo(\"src/main/java/Main.java\");\n    assertThat(showFixSuggestionQuery.getFixSuggestion().fileEdit().changes().get(0).before()).isEmpty();\n    assertThat(showFixSuggestionQuery.getFixSuggestion().fileEdit().changes().get(0).after()).isEqualTo(\"var fix = 1;\");\n    assertThat(showFixSuggestionQuery.getFixSuggestion().fileEdit().changes().get(0).beforeLineRange().startLine()).isZero();\n    assertThat(showFixSuggestionQuery.getFixSuggestion().fileEdit().changes().get(0).beforeLineRange().endLine()).isEqualTo(1);\n  }\n\n  @Test\n  void should_extract_query_from_sc_request_with_token() throws HttpException, IOException {\n    var request = new BasicClassicHttpRequest(\"POST\", \"/sonarlint/api/fix/show\" +\n      \"?project=org.sonarsource.sonarlint.core%3Asonarlint-core-parent\" +\n      \"&issue=AX2VL6pgAvx3iwyNtLyr&tokenName=abc\" +\n      \"&organizationKey=sample-organization\" +\n      \"&tokenValue=123\");\n    request.setEntity(new StringEntity(\"\"\"\n      {\n        \"fileEdit\": {\n          \"path\": \"src/main/java/Main.java\",\n          \"changes\": [{\n            \"beforeLineRange\": {\n              \"startLine\": 0,\n              \"endLine\": 1\n            },\n            \"before\": \"\",\n            \"after\": \"var fix = 1;\"\n          }]\n        },\n        \"suggestionId\": \"eb93b2b4-f7b0-4b5c-9460-50893968c264\",\n        \"explanation\": \"Modifying the variable name is good\"\n      }\n      \"\"\"));\n    var params = Map.of(\n      \"project\", \"org.sonarsource.sonarlint.core:sonarlint-core-parent\",\n      \"issue\", \"AX2VL6pgAvx3iwyNtLyr\",\n      \"tokenName\", \"abc\",\n      \"organizationKey\", \"sample-organization\",\n      \"tokenValue\", \"123\");\n\n    var showFixSuggestionQuery = showFixSuggestionRequestHandler.extractQuery(request, SonarCloudRegion.EU.getProductionUri().toString(), params);\n    \n    assertThat(showFixSuggestionQuery.getServerUrl()).isEqualTo(\"https://sonarcloud.io\");\n    assertThat(showFixSuggestionQuery.getProjectKey()).isEqualTo(\"org.sonarsource.sonarlint.core:sonarlint-core-parent\");\n    assertThat(showFixSuggestionQuery.getIssueKey()).isEqualTo(\"AX2VL6pgAvx3iwyNtLyr\");\n    assertThat(showFixSuggestionQuery.getTokenName()).isEqualTo(\"abc\");\n    assertThat(showFixSuggestionQuery.getOrganizationKey()).isEqualTo(\"sample-organization\");\n    assertThat(showFixSuggestionQuery.getTokenValue()).isEqualTo(\"123\");\n    assertThat(showFixSuggestionQuery.getFixSuggestion().suggestionId()).isEqualTo(\"eb93b2b4-f7b0-4b5c-9460-50893968c264\");\n    assertThat(showFixSuggestionQuery.getFixSuggestion().explanation()).isEqualTo(\"Modifying the variable name is good\");\n    assertThat(showFixSuggestionQuery.getFixSuggestion().fileEdit().path()).isEqualTo(\"src/main/java/Main.java\");\n    assertThat(showFixSuggestionQuery.getFixSuggestion().fileEdit().changes().get(0).before()).isEmpty();\n    assertThat(showFixSuggestionQuery.getFixSuggestion().fileEdit().changes().get(0).after()).isEqualTo(\"var fix = 1;\");\n    assertThat(showFixSuggestionQuery.getFixSuggestion().fileEdit().changes().get(0).beforeLineRange().startLine()).isZero();\n    assertThat(showFixSuggestionQuery.getFixSuggestion().fileEdit().changes().get(0).beforeLineRange().endLine()).isEqualTo(1);\n  }\n\n  @Test\n  void should_validate_fix_suggestion_query_for_sc() {\n    assertThat(new ShowFixSuggestionRequestHandler.ShowFixSuggestionQuery(null, \"project\", \"issue\", \"branch\", \"name\", \"value\",\n      \"organizationKey\", true, generateFixSuggestionPayload()).isValid()).isTrue();\n    assertThat(\n      new ShowFixSuggestionRequestHandler.ShowFixSuggestionQuery(null, \"project\", \"issue\", \"branch\", \"name\", \"value\", null, true, generateFixSuggestionPayload()).isValid())\n        .isFalse();\n  }\n\n  @Test\n  void should_show_fix_suggestion() throws HttpException, IOException {\n    var request = new BasicClassicHttpRequest(\"POST\", \"/sonarlint/api/fix/show\" +\n      \"?project=org.sonarsource.sonarlint.core%3Asonarlint-core-parent\" +\n      \"&issue=AX2VL6pgAvx3iwyNtLyr\" +\n      \"&organizationKey=sample-organization\");\n    request.setEntity(new StringEntity(\"\"\"\n      {\n        \"fileEdit\": {\n          \"path\": \"src/main/java/Main.java\",\n          \"changes\": [{\n            \"beforeLineRange\": {\n              \"startLine\": 0,\n              \"endLine\": 1\n            },\n            \"before\": \"\",\n            \"after\": \"var fix = 1;\"\n          }]\n        },\n        \"suggestionId\": \"eb93b2b4-f7b0-4b5c-9460-50893968c264\",\n        \"explanation\": \"Modifying the variable name is good\"\n      }\n      \"\"\"));\n    var response = mock(ClassicHttpResponse.class);\n    var context = mock(HttpContext.class);\n    when(context.getAttribute(AttributeUtils.PARAMS_ATTRIBUTE))\n      .thenReturn(Map.of(\n        \"project\", \"org.sonarsource.sonarlint.core:sonarlint-core-parent\",\n        \"issue\", \"AX2VL6pgAvx3iwyNtLyr\",\n        \"branch\", \"branch\",\n        \"organizationKey\", \"sample-organization\"\n      ));\n    when(context.getAttribute(AttributeUtils.ORIGIN_ATTRIBUTE))\n      .thenReturn(SonarCloudRegion.EU.getProductionUri().toString());\n\n    when(clientFile.getUri()).thenReturn(URI.create(\"file:///src/main/java/Main.java\"));\n    when(filePathTranslation.serverToIdePath(any())).thenReturn(Path.of(\"src/main/java/Main.java\"));\n    when(connectionConfigurationRepository.findByOrganization(any())).thenReturn(List.of(\n      new SonarCloudConnectionConfiguration(SonarCloudRegion.EU.getProductionUri(), SonarCloudRegion.EU.getApiProductionUri(), \"name\", \"organizationKey\", SonarCloudRegion.EU,\n        false)));\n    when(configurationRepository.getBoundScopesToConnectionAndSonarProject(any(), any())).thenReturn(List.of(new BoundScope(\"configScope\", \"connectionId\", \"projectKey\")));\n\n    showFixSuggestionRequestHandler.handle(request, response, context);\n\n    await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> verify(sonarLintRpcClient).showFixSuggestion(any()));\n  }\n\n  private static ShowFixSuggestionRequestHandler.FixSuggestionPayload generateFixSuggestionPayload() {\n    return new ShowFixSuggestionRequestHandler.FixSuggestionPayload(\n      new ShowFixSuggestionRequestHandler.FileEditPayload(\n        List.of(new ShowFixSuggestionRequestHandler.ChangesPayload(\n          new ShowFixSuggestionRequestHandler.TextRangePayload(0, 1),\n          \"before\",\n          \"after\")),\n        \"path\"),\n      \"suggestionId\",\n      \"explanation\");\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/embedded/server/handler/ShowIssueRequestHandlerTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.embedded.server.handler;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Function;\nimport org.apache.hc.core5.http.ClassicHttpRequest;\nimport org.apache.hc.core5.http.ClassicHttpResponse;\nimport org.apache.hc.core5.http.HttpException;\nimport org.apache.hc.core5.http.Method;\nimport org.apache.hc.core5.http.message.BasicClassicHttpRequest;\nimport org.apache.hc.core5.http.protocol.HttpContext;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.BindingCandidatesFinder;\nimport org.sonarsource.sonarlint.core.BindingSuggestionProvider;\nimport org.sonarsource.sonarlint.core.SonarCloudActiveEnvironment;\nimport org.sonarsource.sonarlint.core.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.commons.BoundScope;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.embedded.server.AttributeUtils;\nimport org.sonarsource.sonarlint.core.embedded.server.RequestHandlerBindingAssistant;\nimport org.sonarsource.sonarlint.core.file.FilePathTranslation;\nimport org.sonarsource.sonarlint.core.file.PathTranslationService;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.SonarCloudConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.IssueDetailsDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.ShowIssueParams;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.issue.IssueApi;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues;\nimport org.sonarsource.sonarlint.core.serverconnection.ProjectBranches;\nimport org.sonarsource.sonarlint.core.serverconnection.ProjectBranchesStorage;\nimport org.sonarsource.sonarlint.core.serverconnection.SonarProjectStorage;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.sonarsource.sonarlint.core.sync.SonarProjectBranchesSynchronizationService;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryService;\nimport org.springframework.context.ApplicationEventPublisher;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\nimport static org.mockito.Mockito.when;\n\nclass ShowIssueRequestHandlerTests {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester(true);\n\n  private ShowIssueRequestHandler showIssueRequestHandler;\n\n  private ConnectionConfigurationRepository connectionConfigurationRepository;\n  private ConfigurationRepository configurationRepository;\n  private SonarLintRpcClient sonarLintRpcClient;\n  private ProjectBranchesStorage branchesStorage;\n  private IssueApi issueApi;\n  private TelemetryService telemetryService;\n\n  @BeforeEach\n  void setup() {\n    connectionConfigurationRepository = mock(ConnectionConfigurationRepository.class);\n    configurationRepository = mock(ConfigurationRepository.class);\n    var bindingSuggestionProvider = mock(BindingSuggestionProvider.class);\n    var bindingCandidatesFinder = mock(BindingCandidatesFinder.class);\n    sonarLintRpcClient = mock(SonarLintRpcClient.class);\n    var filePathTranslation = mock(FilePathTranslation.class);\n    var pathTranslationService = mock(PathTranslationService.class);\n    when(pathTranslationService.getOrComputePathTranslation(any())).thenReturn(Optional.of(filePathTranslation));\n    var sonarCloudActiveEnvironment = SonarCloudActiveEnvironment.prod();\n    telemetryService = mock(TelemetryService.class);\n    issueApi = mock(IssueApi.class);\n    var serverApi = mock(ServerApi.class);\n    when(serverApi.issue()).thenReturn(issueApi);\n    var connectionManager = mock(SonarQubeClientManager.class);\n    when(connectionManager.withActiveClientFlatMapOptionalAndReturn(any(), any())).thenAnswer(\n      invocation -> ((Function<ServerApi, Optional<Object>>) invocation.getArguments()[1]).apply(serverApi));\n    when(connectionManager.withActiveClientAndReturn(any(), any())).thenAnswer(\n      invocation -> Optional.ofNullable(((Function<ServerApi, Object>) invocation.getArguments()[1]).apply(serverApi)));\n    branchesStorage = mock(ProjectBranchesStorage.class);\n    var storageService = mock(StorageService.class);\n    var sonarStorage = mock(SonarProjectStorage.class);\n    var eventPublisher = mock(ApplicationEventPublisher.class);\n    var sonarProjectBranchesSynchronizationService = spy(new SonarProjectBranchesSynchronizationService(storageService, connectionManager, eventPublisher));\n    doReturn(new ProjectBranches(Set.of(), \"main\")).when(sonarProjectBranchesSynchronizationService).getProjectBranches(any(), any(),\n      any());\n    when(storageService.binding(any())).thenReturn(sonarStorage);\n    when(sonarStorage.branches()).thenReturn(branchesStorage);\n    var connectionConfiguration = mock(ConnectionConfigurationRepository.class);\n    when(connectionConfiguration.hasConnectionWithOrigin(SonarCloudRegion.EU.getProductionUri().toString())).thenReturn(true);\n\n    showIssueRequestHandler = spy(new ShowIssueRequestHandler(\n      sonarLintRpcClient, connectionManager, telemetryService, new RequestHandlerBindingAssistant(bindingSuggestionProvider, bindingCandidatesFinder, sonarLintRpcClient,\n        connectionConfigurationRepository, configurationRepository, sonarCloudActiveEnvironment, connectionConfiguration),\n      pathTranslationService, sonarCloudActiveEnvironment, sonarProjectBranchesSynchronizationService));\n  }\n\n  @Test\n  void should_transform_ServerIssueDetail_to_ShowIssueParams() {\n    var connectionId = \"connectionId\";\n    var configScopeId = \"configScopeId\";\n    var issueKey = \"issueKey\";\n    var issueCreationDate = \"2023-05-13T17:55:39+0200\";\n    var issueMessage = \"issue message\";\n    var issuePath = Paths.get(\"home/file.java\");\n    var issueRuleKey = \"javasecurity:S3649\";\n    var flowLocationPath1 = \"home/file_1.java\";\n    var flowLocationPath2 = \"home/file_2.java\";\n    var issueTextRange = Common.TextRange.newBuilder().setStartLine(1).setEndLine(2).setStartOffset(3).setEndOffset(4).build();\n    var locationTextRange1 = Common.TextRange.newBuilder().setStartLine(5).setEndLine(5).setStartOffset(10).setEndOffset(20).build();\n    var locationTextRange2 = Common.TextRange.newBuilder().setStartLine(50).setEndLine(50).setStartOffset(42).setEndOffset(52).build();\n    var locationMessage1 = \"locationMessage_1\";\n    var locationComponentKey1 = \"LocationComponentKey_1\";\n    var locationComponentKey2 = \"LocationComponentKey_2\";\n    var locationCodeSnippet1 = \"//todo comment\";\n    var issueComponentKey = \"IssueComponentKey\";\n    var codeSnippet = \"//todo remove this\";\n\n    when(issueApi.getCodeSnippet(eq(locationComponentKey1), any(), any(), any(), any())).thenReturn(Optional.of(locationCodeSnippet1));\n\n    var flow = Common.Flow.newBuilder()\n      .addLocations(Common.Location.newBuilder().setTextRange(locationTextRange1).setComponent(locationComponentKey1).setMsg(locationMessage1))\n      .addLocations(Common.Location.newBuilder().setTextRange(locationTextRange2).setComponent(locationComponentKey2))\n      .build();\n    var issue = Issues.Issue.newBuilder()\n      .setKey(issueKey)\n      .setCreationDate(issueCreationDate)\n      .setRule(issueRuleKey)\n      .setMessage(issueMessage)\n      .setTextRange(issueTextRange)\n      .setComponent(issueComponentKey)\n      .addFlows(flow)\n      .build();\n    var components = List.of(\n      Issues.Component.newBuilder().setKey(issueComponentKey).setPath(issuePath.toString()).build(),\n      Issues.Component.newBuilder().setKey(locationComponentKey1).setPath(flowLocationPath1).build(),\n      Issues.Component.newBuilder().setKey(locationComponentKey2).setPath(flowLocationPath2).build());\n    var serverIssueDetails = new IssueApi.ServerIssueDetails(issue, issuePath, components, codeSnippet);\n\n    var showIssueParams = showIssueRequestHandler.getShowIssueParams(serverIssueDetails, connectionId, configScopeId, \"branch\", \"\",\n      new FilePathTranslation(Path.of(\"ide\"), Path.of(\"home\")), new SonarLintCancelMonitor());\n    assertThat(showIssueParams.getConfigurationScopeId()).isEqualTo(configScopeId);\n    var issueDetails = showIssueParams.getIssueDetails();\n    assertThat(issueDetails.getIssueKey()).isEqualTo(issueKey);\n    assertThat(issueDetails.getCreationDate()).isEqualTo(issueCreationDate);\n    assertThat(issueDetails.getRuleKey()).isEqualTo(issueRuleKey);\n    assertThat(issueDetails.isTaint()).isTrue();\n    assertThat(issueDetails.getMessage()).isEqualTo(issueMessage);\n    assertThat(issueDetails.getTextRange().getStartLine()).isEqualTo(1);\n    assertThat(issueDetails.getTextRange().getEndLine()).isEqualTo(2);\n    assertThat(issueDetails.getTextRange().getStartLineOffset()).isEqualTo(3);\n    assertThat(issueDetails.getTextRange().getEndLineOffset()).isEqualTo(4);\n    assertThat(issueDetails.getIdeFilePath()).isEqualTo(Paths.get(\"ide/file.java\"));\n    assertThat(issueDetails.getFlows()).hasSize(1);\n    assertThat(issueDetails.getCodeSnippet()).isEqualTo(codeSnippet);\n\n    var locations = issueDetails.getFlows().get(0).getLocations();\n    assertThat(locations).hasSize(2);\n    assertThat(locations.get(0).getTextRange().getStartLine()).isEqualTo(5);\n    assertThat(locations.get(0).getTextRange().getEndLine()).isEqualTo(5);\n    assertThat(locations.get(0).getTextRange().getStartLineOffset()).isEqualTo(10);\n    assertThat(locations.get(0).getTextRange().getEndLineOffset()).isEqualTo(20);\n    assertThat(locations.get(0).getIdeFilePath()).isEqualTo(Paths.get(\"ide/file_1.java\"));\n    assertThat(locations.get(0).getMessage()).isEqualTo(locationMessage1);\n    assertThat(locations.get(0).getCodeSnippet()).isEqualTo(locationCodeSnippet1);\n    assertThat(locations.get(1).getIdeFilePath()).isEqualTo(Paths.get(\"ide/file_2.java\"));\n    assertThat(locations.get(1).getCodeSnippet()).isEmpty();\n  }\n\n  @Test\n  void should_trigger_telemetry() throws HttpException, IOException, URISyntaxException {\n    var request = mock(ClassicHttpRequest.class);\n    var params = Map.of(\"project\", \"pk\",\n      \"issue\", \"ik\",\n      \"branch\", \"b\",\n      \"server\", \"s\");\n    when(request.getUri()).thenReturn(URI.create(\"http://localhost:8000/issue?project=pk&issue=ik&branch=b&server=s\"));\n    when(request.getMethod()).thenReturn(Method.GET.name());\n    var response = mock(ClassicHttpResponse.class);\n    var context = mock(HttpContext.class);\n    when(context.getAttribute(AttributeUtils.PARAMS_ATTRIBUTE))\n      .thenReturn(params);\n    when(context.getAttribute(AttributeUtils.ORIGIN_ATTRIBUTE))\n      .thenReturn(\"s\");\n    when(issueApi.getCodeSnippet(eq(\"comp\"), any(), any(), any(), any())).thenReturn(Optional.of(\"snippet\"));\n\n    showIssueRequestHandler.handle(request, response, context);\n\n    verify(telemetryService).showIssueRequestReceived();\n    verifyNoMoreInteractions(telemetryService);\n  }\n\n  @Test\n  void should_extract_query_from_sq_request_with_branch() {\n    var params = Map.of(\"server\", \"https://next.sonarqube.com/sonarqube\",\n      \"project\", \"org.sonarsource.sonarlint.core:sonarlint-core-parent\",\n      \"issue\", \"AX2VL6pgAvx3iwyNtLyr\",\n      \"tokenName\", \"abc\",\n      \"organizationKey\", \"sample-organization\",\n      \"tokenValue\", \"123\",\n      \"branch\", \"branch\");\n\n    var issueQuery = showIssueRequestHandler.extractQuery(\"https://next.sonarqube.com/\", params);\n\n    assertThat(issueQuery.getServerUrl()).isEqualTo(\"https://next.sonarqube.com/sonarqube\");\n    assertThat(issueQuery.getProjectKey()).isEqualTo(\"org.sonarsource.sonarlint.core:sonarlint-core-parent\");\n    assertThat(issueQuery.getIssueKey()).isEqualTo(\"AX2VL6pgAvx3iwyNtLyr\");\n    assertThat(issueQuery.getTokenName()).isEqualTo(\"abc\");\n    assertThat(issueQuery.getOrganizationKey()).isEqualTo(\"sample-organization\");\n    assertThat(issueQuery.getTokenValue()).isEqualTo(\"123\");\n    assertThat(issueQuery.getBranch()).isEqualTo(\"branch\");\n  }\n\n  @Test\n  void should_extract_query_from_sq_request_without_token() {\n    var params = Map.of(\"server\", \"https://next.sonarqube.com/sonarqube\",\n      \"project\", \"org.sonarsource.sonarlint.core:sonarlint-core-parent\",\n      \"issue\", \"AX2VL6pgAvx3iwyNtLyr\",\n      \"organizationKey\", \"sample-organization\");\n\n    var issueQuery = showIssueRequestHandler.extractQuery(\"https://next.sonarqube.com/\", params);\n\n    assertThat(issueQuery.getServerUrl()).isEqualTo(\"https://next.sonarqube.com/sonarqube\");\n    assertThat(issueQuery.getProjectKey()).isEqualTo(\"org.sonarsource.sonarlint.core:sonarlint-core-parent\");\n    assertThat(issueQuery.getIssueKey()).isEqualTo(\"AX2VL6pgAvx3iwyNtLyr\");\n    assertThat(issueQuery.getOrganizationKey()).isEqualTo(\"sample-organization\");\n    assertThat(issueQuery.getTokenName()).isNull();\n    assertThat(issueQuery.getTokenValue()).isNull();\n    assertThat(issueQuery.getBranch()).isNull();\n  }\n\n  @Test\n  void should_extract_query_from_sq_request_with_token() {\n    var params = Map.of(\"server\", \"https://next.sonarqube.com/sonarqube\",\n      \"project\", \"org.sonarsource.sonarlint.core:sonarlint-core-parent\",\n      \"issue\", \"AX2VL6pgAvx3iwyNtLyr\",\n      \"tokenName\", \"abc\",\n      \"organizationKey\", \"sample-organization\",\n      \"tokenValue\", \"123\");\n\n    var issueQuery = showIssueRequestHandler.extractQuery(\"https://next.sonarqube.com/\", params);\n\n    assertThat(issueQuery.getServerUrl()).isEqualTo(\"https://next.sonarqube.com/sonarqube\");\n    assertThat(issueQuery.getProjectKey()).isEqualTo(\"org.sonarsource.sonarlint.core:sonarlint-core-parent\");\n    assertThat(issueQuery.getIssueKey()).isEqualTo(\"AX2VL6pgAvx3iwyNtLyr\");\n    assertThat(issueQuery.getTokenName()).isEqualTo(\"abc\");\n    assertThat(issueQuery.getOrganizationKey()).isEqualTo(\"sample-organization\");\n    assertThat(issueQuery.getTokenValue()).isEqualTo(\"123\");\n    assertThat(issueQuery.getBranch()).isNull();\n  }\n\n  @Test\n  void should_extract_query_from_sc_request_without_token() {\n    var params = Map.of(      \"project\", \"org.sonarsource.sonarlint.core:sonarlint-core-parent\",\n      \"issue\", \"AX2VL6pgAvx3iwyNtLyr\",\n      \"organizationKey\", \"sample-organization\");\n\n    var issueQuery = showIssueRequestHandler.extractQuery(SonarCloudRegion.EU.getProductionUri().toString(), params);\n\n    assertThat(issueQuery.getServerUrl()).isEqualTo(\"https://sonarcloud.io\");\n    assertThat(issueQuery.getProjectKey()).isEqualTo(\"org.sonarsource.sonarlint.core:sonarlint-core-parent\");\n    assertThat(issueQuery.getIssueKey()).isEqualTo(\"AX2VL6pgAvx3iwyNtLyr\");\n    assertThat(issueQuery.getOrganizationKey()).isEqualTo(\"sample-organization\");\n    assertThat(issueQuery.getTokenName()).isNull();\n    assertThat(issueQuery.getTokenValue()).isNull();\n    assertThat(issueQuery.getBranch()).isNull();\n  }\n\n  @Test\n  void should_extract_query_from_sc_request_with_token() {\n    var params = Map.of(\"project\", \"org.sonarsource.sonarlint.core:sonarlint-core-parent\",\n      \"issue\", \"AX2VL6pgAvx3iwyNtLyr\", \"tokenName\", \"abc\",\n      \"organizationKey\", \"sample-organization\",\n      \"tokenValue\", \"123\");\n\n    var issueQuery = showIssueRequestHandler.extractQuery( SonarCloudRegion.EU.getProductionUri().toString(), params);\n\n    assertThat(issueQuery.getServerUrl()).isEqualTo(\"https://sonarcloud.io\");\n    assertThat(issueQuery.getProjectKey()).isEqualTo(\"org.sonarsource.sonarlint.core:sonarlint-core-parent\");\n    assertThat(issueQuery.getIssueKey()).isEqualTo(\"AX2VL6pgAvx3iwyNtLyr\");\n    assertThat(issueQuery.getTokenName()).isEqualTo(\"abc\");\n    assertThat(issueQuery.getOrganizationKey()).isEqualTo(\"sample-organization\");\n    assertThat(issueQuery.getTokenValue()).isEqualTo(\"123\");\n    assertThat(issueQuery.getBranch()).isNull();\n  }\n\n  @Test\n  void should_validate_issue_query_for_sq() {\n    assertThat(new ShowIssueRequestHandler.ShowIssueQuery(\"serverUrl\", \"project\", \"issue\", \"branch\", \"pullRequest\", null, null, null,\n      SonarCloudRegion.US, false).isValid()).isTrue();\n    assertThat(new ShowIssueRequestHandler.ShowIssueQuery(\"serverUrl\", \"project\", \"issue\", \"\", \"pullRequest\", null, null, null, SonarCloudRegion.EU, false).isValid()).isTrue();\n    assertThat(new ShowIssueRequestHandler.ShowIssueQuery(\"serverUrl\", \"project\", \"issue\", null, \"pullRequest\", null, null, null, SonarCloudRegion.EU, false).isValid()).isTrue();\n\n    assertThat(new ShowIssueRequestHandler.ShowIssueQuery(\"\", \"project\", \"issue\", \"branch\", \"pullRequest\", null, null, null, SonarCloudRegion.EU, false).isValid()).isFalse();\n    assertThat(new ShowIssueRequestHandler.ShowIssueQuery(\"serverUrl\", \"\", \"issue\", \"branch\", \"pullRequest\", null, null, null, SonarCloudRegion.EU, false).isValid()).isFalse();\n    assertThat(new ShowIssueRequestHandler.ShowIssueQuery(\"serverUrl\", \"project\", \"\", \"branch\", \"pullRequest\", null, null, null, SonarCloudRegion.EU, false).isValid()).isFalse();\n    assertThat(new ShowIssueRequestHandler.ShowIssueQuery(\"serverUrl\", \"project\", \"issue\", \"\", \"\", null, null, null, SonarCloudRegion.EU, false).isValid()).isFalse();\n    assertThat(new ShowIssueRequestHandler.ShowIssueQuery(\"serverUrl\", \"project\", \"issue\", \"branch\", null, null, null, null, SonarCloudRegion.EU, false).isValid()).isTrue();\n\n    assertThat(new ShowIssueRequestHandler.ShowIssueQuery(\"serverUrl\", \"project\", \"issue\", \"branch\", \"pullRequest\", \"name\", null, null,\n      SonarCloudRegion.US, false).isValid()).isFalse();\n    assertThat(new ShowIssueRequestHandler.ShowIssueQuery(\"serverUrl\", \"project\", \"issue\", \"branch\", \"pullRequest\", null, \"value\", null,\n      SonarCloudRegion.US, false).isValid()).isFalse();\n    assertThat(new ShowIssueRequestHandler.ShowIssueQuery(\"serverUrl\", \"project\", \"issue\", \"branch\", \"pullRequest\", \"name\", \"\", null,\n      SonarCloudRegion.US, false).isValid()).isFalse();\n    assertThat(new ShowIssueRequestHandler.ShowIssueQuery(\"serverUrl\", \"project\", \"issue\", \"branch\", \"pullRequest\", \"\", \"value\", null,\n      SonarCloudRegion.US, false).isValid()).isFalse();\n    assertThat(new ShowIssueRequestHandler.ShowIssueQuery(\"serverUrl\", \"project\", \"issue\", \"branch\", \"pullRequest\", \"name\", \"value\", null, SonarCloudRegion.US, false).isValid())\n      .isTrue();\n  }\n\n  @Test\n  void should_validate_issue_query_for_sc() {\n    assertThat(new ShowIssueRequestHandler.ShowIssueQuery(null, \"project\", \"issue\", \"branch\", \"pullRequest\", \"name\", \"value\",\n      \"organizationKey\", SonarCloudRegion.US, true).isValid()).isTrue();\n    assertThat(new ShowIssueRequestHandler.ShowIssueQuery(null, \"project\", \"issue\", \"\", \"pullRequest\", \"name\", \"value\", \"organizationKey\", SonarCloudRegion.US, true).isValid())\n      .isTrue();\n    assertThat(new ShowIssueRequestHandler.ShowIssueQuery(null, \"project\", \"issue\", null, \"pullRequest\", \"name\", \"value\",\n      \"organizationKey\", SonarCloudRegion.US, true).isValid()).isTrue();\n\n    assertThat(new ShowIssueRequestHandler.ShowIssueQuery(null, \"project\", \"issue\", \"branch\", \"pullRequest\", \"name\", \"value\", null, SonarCloudRegion.EU, true).isValid()).isFalse();\n  }\n\n  @Test\n  void should_detect_taint_issues() {\n    assertThat(ShowIssueRequestHandler.isIssueTaint(\"java:S1144\")).isFalse();\n    assertThat(ShowIssueRequestHandler.isIssueTaint(\"javasecurity:S3649\")).isTrue();\n  }\n\n  @Test\n  void should_find_main_branch_when_branch_is_not_provided() throws HttpException, IOException {\n    var request = new BasicClassicHttpRequest(\"GET\", \"/sonarlint/api/issues/show\" +\n      \"?server=https%3A%2F%2Fnext.sonarqube.com%2Fsonarqube\" +\n      \"&project=org.sonarsource.sonarlint.core%3Asonarlint-core-parent\" +\n      \"&issue=AX2VL6pgAvx3iwyNtLyr&tokenName=abc\" +\n      \"&organizationKey=sample-organization\" +\n      \"&tokenValue=123\");\n    var params = Map.of(\"server\", \"https://next.sonarqube.com/sonarqube\",\n      \"project\", \"org.sonarsource.sonarlint.core:sonarlint-core-parent\",\n      \"issue\", \"AX2VL6pgAvx3iwyNtLyr\",\n      \"tokenName\", \"abc\",\n      \"organizationKey\", \"sample-organization\",\n      \"tokenValue\", \"123\");\n    var response = mock(ClassicHttpResponse.class);\n    var context = mock(HttpContext.class);\n    when(context.getAttribute(AttributeUtils.PARAMS_ATTRIBUTE))\n      .thenReturn(params);\n    when(context.getAttribute(AttributeUtils.ORIGIN_ATTRIBUTE))\n      .thenReturn(SonarCloudRegion.EU.getProductionUri().toString());\n\n    when(connectionConfigurationRepository.findByOrganization(any())).thenReturn(List.of(\n      new SonarCloudConnectionConfiguration(SonarCloudRegion.EU.getProductionUri(), SonarCloudRegion.EU.getApiProductionUri(), \"name\", \"organizationKey\", SonarCloudRegion.EU,\n        false)));\n    when(configurationRepository.getBoundScopesToConnectionAndSonarProject(any(), any())).thenReturn(List.of(new BoundScope(\"configScope\", \"connectionId\", \"projectKey\")));\n    when(branchesStorage.exists()).thenReturn(true);\n    when(branchesStorage.read()).thenReturn(new ProjectBranches(Set.of(), \"main\"));\n    var serverIssueDetails = mock(IssueApi.ServerIssueDetails.class);\n    when(issueApi.fetchServerIssue(any(), any(), any(), any(), any())).thenReturn(Optional.of(serverIssueDetails));\n    var issueDetails = mock(IssueDetailsDto.class);\n    doReturn(new ShowIssueParams(\"configScope\", issueDetails)).when(showIssueRequestHandler).getShowIssueParams(any(), any(), any(),\n      any(), any(), any(), any());\n\n    showIssueRequestHandler.handle(request, response, context);\n\n    await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> verify(sonarLintRpcClient).showIssue(any()));\n  }\n\n  @Test\n  void should_find_main_branch_when_not_provided_and_not_stored() throws HttpException, IOException {\n    var request = new BasicClassicHttpRequest(\"GET\", \"/sonarlint/api/issues/show\" +\n      \"?server=https%3A%2F%2Fnext.sonarqube.com%2Fsonarqube\" +\n      \"&project=org.sonarsource.sonarlint.core%3Asonarlint-core-parent\" +\n      \"&issue=AX2VL6pgAvx3iwyNtLyr&tokenName=abc\" +\n      \"&organizationKey=sample-organization\" +\n      \"&tokenValue=123\");\n    var params = Map.of(\"server\", \"https://next.sonarqube.com/sonarqube\",\n      \"project\", \"org.sonarsource.sonarlint.core:sonarlint-core-parent\",\n      \"issue\", \"AX2VL6pgAvx3iwyNtLyr\",\n      \"tokenName\", \"abc\",\n      \"organizationKey\", \"sample-organization\",\n      \"tokenValue\", \"123\");\n    var response = mock(ClassicHttpResponse.class);\n    var context = mock(HttpContext.class);\n    when(context.getAttribute(AttributeUtils.PARAMS_ATTRIBUTE))\n      .thenReturn(params);\n    when(context.getAttribute(AttributeUtils.ORIGIN_ATTRIBUTE))\n      .thenReturn(SonarCloudRegion.EU.getProductionUri().toString());\n\n    when(connectionConfigurationRepository.findByOrganization(any())).thenReturn(List.of(\n      new SonarCloudConnectionConfiguration(SonarCloudRegion.EU.getProductionUri(), SonarCloudRegion.EU.getApiProductionUri(), \"name\", \"organizationKey\", SonarCloudRegion.EU,\n        false)));\n    when(configurationRepository.getBoundScopesToConnectionAndSonarProject(any(), any())).thenReturn(List.of(new BoundScope(\"configScope\", \"connectionId\", \"projectKey\")));\n    when(branchesStorage.exists()).thenReturn(false);\n    var serverIssueDetails = mock(IssueApi.ServerIssueDetails.class);\n    when(issueApi.fetchServerIssue(any(), any(), any(), any(), any())).thenReturn(Optional.of(serverIssueDetails));\n    var issueDetails = mock(IssueDetailsDto.class);\n    doReturn(new ShowIssueParams(\"configScope\", issueDetails)).when(showIssueRequestHandler).getShowIssueParams(any(), any(), any(),\n      any(), any(), any(), any());\n\n    showIssueRequestHandler.handle(request, response, context);\n\n    await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> verify(sonarLintRpcClient).showIssue(any()));\n  }\n\n  @Test\n  void should_verify_missing_origin() throws HttpException, IOException {\n    var request = new BasicClassicHttpRequest(\"GET\", \"/sonarlint/api/issues/show\" +\n      \"?server=https%3A%2F%2Fnext.sonarqube.com%2Fsonarqube\" +\n      \"&project=org.sonarsource.sonarlint.core%3Asonarlint-core-parent\" +\n      \"&issue=AX2VL6pgAvx3iwyNtLyr&tokenName=abc\" +\n      \"&organizationKey=sample-organization\" +\n      \"&tokenValue=123\");\n    var response = mock(ClassicHttpResponse.class);\n    var context = mock(HttpContext.class);\n\n    showIssueRequestHandler.handle(request, response, context);\n\n    verifyNoMoreInteractions(sonarLintRpcClient);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/file/FilePathTranslationTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.file;\n\nimport java.nio.file.Path;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThat;\n\nclass FilePathTranslationTests {\n\n  @Test\n  void serverToPathTranslation() {\n    var underTest = new FilePathTranslation(Path.of(\"/foo\"), Path.of(\"/bar\"));\n\n    assertThat(underTest.serverToIdePath(Path.of(\"/baz\"))).isEqualTo(Path.of(\"/baz\"));\n    assertThat(underTest.serverToIdePath(Path.of(\"/bar/baz\"))).isEqualTo(Path.of(\"/foo/baz\"));\n  }\n\n  @Test\n  void serverToPathTranslationWhenPrefixIsEmpty() {\n    var underTest = new FilePathTranslation(Path.of(\"ide\"), Path.of(\"\"));\n\n    assertThat(underTest.serverToIdePath(Path.of(\"baz\"))).isEqualTo(Path.of(\"ide/baz\"));\n    assertThat(underTest.serverToIdePath(Path.of(\"bar/baz\"))).isEqualTo(Path.of(\"ide/bar/baz\"));\n  }\n\n  @Test\n  void ideToServerPathTranslation() {\n    var underTest = new FilePathTranslation(Path.of(\"/foo\"), Path.of(\"/bar\"));\n\n    assertThat(underTest.ideToServerPath(Path.of(\"/baz\"))).isEqualTo(Path.of(\"/baz\"));\n    assertThat(underTest.ideToServerPath(Path.of(\"/foo/baz\"))).isEqualTo(Path.of(\"/bar/baz\"));\n  }\n\n  @Test\n  void ideToServerPathTranslationWhenPrefixIsEmpty() {\n    var underTest = new FilePathTranslation(Path.of(\"\"), Path.of(\"server\"));\n\n    assertThat(underTest.ideToServerPath(Path.of(\"baz\"))).isEqualTo(Path.of(\"server/baz\"));\n    assertThat(underTest.ideToServerPath(Path.of(\"foo/baz\"))).isEqualTo(Path.of(\"server/foo/baz\"));\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/file/PathTranslationServiceTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.file;\n\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.Optional;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.event.BindingConfigChangedEvent;\nimport org.sonarsource.sonarlint.core.fs.ClientFile;\nimport org.sonarsource.sonarlint.core.fs.ClientFileSystemService;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass PathTranslationServiceTests {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  private static final String CONFIG_SCOPE = \"configScopeA\";\n  private static final Binding BINDING = new Binding(\"connectionA\", \"sonarProjectA\");\n  private final ClientFileSystemService clientFs = mock(ClientFileSystemService.class);\n  private final ConfigurationRepository configurationRepository = mock(ConfigurationRepository.class);\n  private final ServerFilePathsProvider serverFilePathsProvider = mock(ServerFilePathsProvider.class);\n  private final PathTranslationService underTest = new PathTranslationService(clientFs, configurationRepository, serverFilePathsProvider);\n\n  @BeforeEach\n  void prepare() {\n    when(configurationRepository.getEffectiveBinding(CONFIG_SCOPE)).thenReturn(Optional.of(BINDING));\n  }\n\n  @Test\n  void shouldComputePathTranslations() {\n    mockServerFilePaths(BINDING, \"moduleA/src/Foo.java\");\n    mockClientFilePaths(\"src/Foo.java\");\n\n    var result = underTest.getOrComputePathTranslation(CONFIG_SCOPE);\n\n    assertThat(result).isPresent();\n    assertThat(result.get())\n      .usingRecursiveComparison()\n      .isEqualTo(new FilePathTranslation(Paths.get(\"\"), Paths.get(\"moduleA\")));\n  }\n\n  private void mockServerFilePaths(Binding binding, String... paths) {\n    when(serverFilePathsProvider.getServerPaths(eq(binding), any(SonarLintCancelMonitor.class)))\n      .thenReturn(Optional.of(Arrays.stream(paths).map(Paths::get).toList()));\n  }\n\n  @Test\n  void shouldCachePathTranslations() {\n    mockServerFilePaths(BINDING, \"moduleA/src/Foo.java\");\n    mockClientFilePaths(\"src/Foo.java\");\n\n    var result1 = underTest.getOrComputePathTranslation(CONFIG_SCOPE);\n\n    assertThat(result1).isPresent();\n    assertThat(result1.get())\n      .usingRecursiveComparison()\n      .isEqualTo(new FilePathTranslation(Paths.get(\"\"), Paths.get(\"moduleA\")));\n\n    var result2 = underTest.getOrComputePathTranslation(CONFIG_SCOPE);\n\n    assertThat(result2).isPresent();\n    assertThat(result2.get())\n      .usingRecursiveComparison()\n      .isEqualTo(new FilePathTranslation(Paths.get(\"\"), Paths.get(\"moduleA\")));\n\n    verify(clientFs, times(1)).getFiles(any());\n  }\n\n  @Test\n  void shouldRecomputePathTranslationsAfterBindingChange() {\n    mockServerFilePaths(BINDING, \"moduleA/src/Foo.java\");\n    mockClientFilePaths(\"src/Foo.java\");\n\n    var result1 = underTest.getOrComputePathTranslation(CONFIG_SCOPE);\n\n    assertThat(result1).isPresent();\n    assertThat(result1.get())\n      .usingRecursiveComparison()\n      .isEqualTo(new FilePathTranslation(Paths.get(\"\"), Paths.get(\"moduleA\")));\n\n    Binding newBinding = mock(Binding.class);\n    when(configurationRepository.getEffectiveBinding(CONFIG_SCOPE)).thenReturn(Optional.of(newBinding));\n    mockServerFilePaths(newBinding, \"moduleB/src/Foo.java\");\n\n    underTest.onBindingChanged(new BindingConfigChangedEvent(CONFIG_SCOPE, null, null));\n\n    var result2 = underTest.getOrComputePathTranslation(CONFIG_SCOPE);\n\n    assertThat(result2).isPresent();\n    assertThat(result2.get())\n      .usingRecursiveComparison()\n      .isEqualTo(new FilePathTranslation(Paths.get(\"\"), Paths.get(\"moduleB\")));\n  }\n\n  private void mockClientFilePaths(String... paths) {\n    doReturn(Arrays.stream(paths)\n      .map(path -> new ClientFile(null, null, Paths.get(path), null, null, null, null, true))\n      .toList())\n      .when(clientFs)\n      .getFiles(CONFIG_SCOPE);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProviderTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.file;\n\nimport java.io.BufferedWriter;\nimport java.io.File;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.nio.charset.Charset;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.function.Function;\nimport org.apache.commons.io.FileUtils;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.UserPaths;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.component.ComponentApi;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\nimport static org.mockito.Mockito.when;\n\nclass ServerFilePathsProviderTest {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private static final String CONNECTION_A = \"connection_A\";\n  private static final String CONNECTION_B = \"connection_B\";\n  public static final String PROJECT_KEY = \"projectKey\";\n\n  private Path cacheDirectory;\n  private final SonarQubeClientManager sonarQubeClientManager = mock(SonarQubeClientManager.class);\n  private final ServerApi serverApi_A = mock(ServerApi.class);\n  private final ServerApi serverApi_B = mock(ServerApi.class);\n  private final SonarLintCancelMonitor cancelMonitor = mock(SonarLintCancelMonitor.class);\n  private final ComponentApi componentApi_A = mock(ComponentApi.class);\n  private final ComponentApi componentApi_B = mock(ComponentApi.class);\n  private ServerFilePathsProvider underTest;\n\n  @BeforeEach\n  void before(@TempDir Path storageDir) throws IOException {\n    cacheDirectory = storageDir.resolve(\"cache\");\n    Files.createDirectories(cacheDirectory);\n    when(serverApi_A.component()).thenReturn(componentApi_A);\n    when(serverApi_B.component()).thenReturn(componentApi_B);\n    when(sonarQubeClientManager.withActiveClientAndReturn(eq(CONNECTION_A), any())).thenAnswer(\n      invocation -> Optional.ofNullable(((Function<ServerApi, Object>) invocation.getArguments()[1]).apply(serverApi_A)));\n    when(sonarQubeClientManager.withActiveClientAndReturn(eq(CONNECTION_B), any())).thenAnswer(\n      invocation -> Optional.ofNullable(((Function<ServerApi, Object>) invocation.getArguments()[1]).apply(serverApi_B)));\n    mockServerFilePaths(componentApi_A, \"pathA\", \"pathB\");\n    mockServerFilePaths(componentApi_B, \"pathC\", \"pathD\");\n    var userPaths = mock(UserPaths.class);\n    when(userPaths.getStorageRoot()).thenReturn(storageDir);\n    underTest = new ServerFilePathsProvider(sonarQubeClientManager, userPaths);\n\n    cacheDirectory = storageDir.resolve(\"cache\");\n  }\n\n  @Test\n  void clear_cache_directory_after_initialization(@TempDir Path storageDir) throws IOException {\n    cacheDirectory = storageDir.resolve(\"cache\");\n    Files.createDirectories(cacheDirectory);\n    assertThat(cacheDirectory.toFile()).exists();\n    var userPaths = mock(UserPaths.class);\n    when(userPaths.getStorageRoot()).thenReturn(storageDir);\n\n    new ServerFilePathsProvider(null, userPaths);\n\n    assertThat(cacheDirectory.toFile()).doesNotExist();\n  }\n\n  @Test\n  void write_to_cache_file_after_fetch() throws IOException {\n    underTest.getServerPaths(new Binding(CONNECTION_A, PROJECT_KEY), cancelMonitor);\n\n    assertThat(cacheDirectory.toFile().listFiles()).hasSize(1);\n    File file = Objects.requireNonNull(cacheDirectory.toFile().listFiles())[0];\n    List<String> paths = FileUtils.readLines(file, Charset.defaultCharset());\n    assertThat(paths).hasSize(2);\n    assertThat(paths.get(0)).isEqualTo(\"pathA\");\n    assertThat(paths.get(1)).isEqualTo(\"pathB\");\n  }\n\n  @Test\n  void fetch_from_in_memory_for_the_second_attempt() throws IOException {\n    underTest.getServerPaths(new Binding(CONNECTION_A, PROJECT_KEY), cancelMonitor);\n\n    verify(componentApi_A, times(1)).getAllFileKeys(PROJECT_KEY, cancelMonitor);\n    verifyNoMoreInteractions(componentApi_A);\n    FileUtils.deleteDirectory(cacheDirectory.toFile());\n\n    underTest.getServerPaths(new Binding(CONNECTION_A, PROJECT_KEY), cancelMonitor);\n\n    assertThat(cacheDirectory.toFile()).doesNotExist();\n  }\n\n  @Test\n  void fetch_from_file_when_cache_timeout() throws IOException {\n    underTest.getServerPaths(new Binding(CONNECTION_A, PROJECT_KEY), cancelMonitor);\n\n    File file = Objects.requireNonNull(cacheDirectory.toFile().listFiles())[0];\n    BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file, true));\n    bufferedWriter.write(\"NewPath\");\n    bufferedWriter.newLine();\n    bufferedWriter.close();\n\n    underTest.clearInMemoryCache();\n\n    List<Path> paths = underTest.getServerPaths(new Binding(CONNECTION_A, PROJECT_KEY), cancelMonitor).get();\n    assertThat(paths).hasSize(3);\n    assertThat(paths.get(0)).hasToString(\"pathA\");\n    assertThat(paths.get(1)).hasToString(\"pathB\");\n    assertThat(paths.get(2)).hasToString(\"NewPath\");\n  }\n\n  @Test\n  void write_to_two_cache_files_for_different_request() {\n    underTest.getServerPaths(new Binding(CONNECTION_A, PROJECT_KEY), cancelMonitor);\n    underTest.getServerPaths(new Binding(CONNECTION_B, PROJECT_KEY), cancelMonitor);\n\n    assertThat(cacheDirectory.toFile().listFiles()).hasSize(2);\n  }\n\n  @Test\n  void shouldLogAndIgnoreOtherErrors() {\n    when(serverApi_A.component().getAllFileKeys(PROJECT_KEY, cancelMonitor)).thenAnswer(invocation -> {\n      throw new IllegalStateException();\n    });\n\n    underTest.getServerPaths(new Binding(CONNECTION_A, PROJECT_KEY), cancelMonitor);\n\n    assertThat(logTester.logs())\n      .contains(\"Error while getting server file paths for project 'projectKey'\");\n  }\n\n  private void mockServerFilePaths(ComponentApi componentApi, String... paths) {\n    doReturn(Arrays.stream(paths).map(path -> PROJECT_KEY + \":\" + path).toList())\n      .when(componentApi)\n      .getAllFileKeys(PROJECT_KEY, cancelMonitor);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/fs/ClientFileTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.fs;\n\nimport java.net.URI;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ClientFileTests {\n\n  @Test\n  void dirty_file_larger_than_threshold_returns_true() throws Exception {\n    var uri = URI.create(\"file:///dirty.js\");\n    var clientFile = new ClientFile(uri, \"scope\", Paths.get(\"dirty.js\"), null, StandardCharsets.UTF_8, null, null, true);\n\n    var content = \"x\".repeat(2048);\n    clientFile.setDirty(content);\n\n    assertThat(clientFile.isLargerThan(1024)).isTrue();\n    assertThat(clientFile.isLargerThan(4096)).isFalse();\n  }\n\n  @Test\n  void clean_local_file_uses_files_size_and_non_local_returns_false() throws Exception {\n    var tempFile = Files.createTempFile(\"sl-clientfile-size\", \".txt\");\n    Files.write(tempFile, new byte[4096]);\n\n    var localUri = tempFile.toUri();\n    var localClientFile = new ClientFile(localUri, \"scope\", Paths.get(\"local.txt\"), null, StandardCharsets.UTF_8, tempFile, null, true);\n\n    var missingPath = tempFile.getParent().resolve(\"missing.txt\");\n    var nonLocalUri = missingPath.toUri();\n    var nonLocalClientFile = new ClientFile(nonLocalUri, \"scope\", Paths.get(\"missing.txt\"), null, StandardCharsets.UTF_8, null, null, true);\n\n    assertThat(localClientFile.isLargerThan(1024)).isTrue();\n    assertThat(localClientFile.isLargerThan(8192)).isFalse();\n    assertThat(nonLocalClientFile.isLargerThan(1)).isFalse();\n  }\n\n}\n\n\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/fs/FileExclusionServiceTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.fs;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.net.URI;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.CompletableFuture;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.mockito.Mockito;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.event.BindingConfigChangedEvent;\nimport org.sonarsource.sonarlint.core.file.PathTranslationService;\nimport org.sonarsource.sonarlint.core.repository.config.BindingConfiguration;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.GetFileExclusionsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.GetFileExclusionsResponse;\nimport org.sonarsource.sonarlint.core.serverconnection.AnalyzerConfigurationStorage;\nimport org.sonarsource.sonarlint.core.serverconnection.ConnectionStorage;\nimport org.sonarsource.sonarlint.core.serverconnection.SonarProjectStorage;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.anyLong;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass FileExclusionServiceTests {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private ConfigurationRepository configRepo;\n  private StorageService storageService;\n  private ClientFileSystemService clientFileSystemService;\n  private FileExclusionService underTest;\n  private SonarLintRpcClient client;\n\n  @BeforeEach\n  void setup() {\n    configRepo = mock(ConfigurationRepository.class);\n    storageService = mock(StorageService.class);\n    var pathTranslationService = mock(PathTranslationService.class);\n    clientFileSystemService = mock(ClientFileSystemService.class);\n    client = mock(SonarLintRpcClient.class);\n    \n    underTest = new FileExclusionService(configRepo, storageService, pathTranslationService, clientFileSystemService, client);\n  }\n\n  @Test\n  void should_return_false_and_log_warning_when_analyzer_storage_is_not_valid() {\n    var fileUri = URI.create(\"file:///path/to/file.java\");\n    var configScopeId = \"configScope1\";\n    var connectionId = \"connectionId\";\n    var projectKey = \"projectKey\";\n    var clientFile = mock(ClientFile.class);\n\n    when(clientFile.getConfigScopeId()).thenReturn(configScopeId);\n    var binding = new Binding(connectionId, projectKey);\n\n    var connectionStorage = mock(ConnectionStorage.class);\n    var projectStorage = mock(SonarProjectStorage.class);\n    var analyzerStorage = mock(AnalyzerConfigurationStorage.class);\n    \n    // Setup the mocks to return the invalid analyzer storage\n    when(clientFileSystemService.getClientFile(fileUri)).thenReturn(clientFile);\n    when(configRepo.getEffectiveBinding(configScopeId)).thenReturn(Optional.of(binding));\n    when(storageService.connection(connectionId)).thenReturn(connectionStorage);\n    when(connectionStorage.project(projectKey)).thenReturn(projectStorage);\n    when(projectStorage.analyzerConfiguration()).thenReturn(analyzerStorage);\n    when(analyzerStorage.isValid()).thenReturn(false); // This is the key setup for our test\n    var cancelMonitor = mock(SonarLintCancelMonitor.class);\n\n    var result = underTest.computeIfExcluded(fileUri, cancelMonitor);\n\n    assertThat(result).isFalse();\n    assertThat(logTester.logs()).contains(\"Unable to read settings in local storage, analysis storage is not ready\");\n  }\n\n  @Test\n  void should_return_false_when_no_client_file_found() {\n    var fileUri = URI.create(\"file:///path/to/nonexistent.java\");\n    var cancelMonitor = mock(SonarLintCancelMonitor.class);\n    when(clientFileSystemService.getClientFile(fileUri)).thenReturn(null);\n\n    var result = underTest.computeIfExcluded(fileUri, cancelMonitor);\n\n    assertThat(result).isFalse();\n    assertThat(logTester.logs()).contains(\"Unable to find client file for uri file:///path/to/nonexistent.java\");\n  }\n\n  @Test\n  void should_return_false_when_no_effective_binding() {\n    var fileUri = URI.create(\"file:///path/to/file.java\");\n    var configScopeId = \"configScope1\";\n    var cancelMonitor = mock(SonarLintCancelMonitor.class);\n    var clientFile = mock(ClientFile.class);\n    when(clientFile.getConfigScopeId()).thenReturn(configScopeId);\n    when(clientFileSystemService.getClientFile(fileUri)).thenReturn(clientFile);\n    when(configRepo.getEffectiveBinding(configScopeId)).thenReturn(Optional.empty());\n\n    var result = underTest.computeIfExcluded(fileUri, cancelMonitor);\n\n    assertThat(result).isFalse();\n  }\n\n  @Test\n  void should_filter_out_files_exceeding_5mb() throws IOException {\n    var configScopeId = \"scope\";\n    var baseDir = Files.createTempDirectory(\"sl-auto-size-base\");\n\n    // Create a small file (~10 KB) and a large file (~6 MB)\n    var smallFile = baseDir.resolve(\"small.js\");\n    var largeFile = baseDir.resolve(\"large.js\");\n    Files.write(smallFile, new byte[10 * 1024]);\n    Files.write(largeFile, new byte[6 * 1024 * 1024]);\n\n    var smallUri = smallFile.toUri();\n    var largeUri = largeFile.toUri();\n\n    var smallClientFile = mock(ClientFile.class);\n    when(smallClientFile.getUri()).thenReturn(smallUri);\n    when(smallClientFile.getClientRelativePath()).thenReturn(Paths.get(\"small.js\"));\n    when(smallClientFile.isUserDefined()).thenReturn(true);\n\n    var largeClientFile = mock(ClientFile.class);\n    when(largeClientFile.getUri()).thenReturn(largeUri);\n    when(largeClientFile.getClientRelativePath()).thenReturn(Paths.get(\"large.js\"));\n    when(largeClientFile.isUserDefined()).thenReturn(true);\n\n    when(clientFileSystemService.getClientFiles(configScopeId, smallUri)).thenReturn(smallClientFile);\n    when(clientFileSystemService.getClientFiles(configScopeId, largeUri)).thenReturn(largeClientFile);\n    when(client.getFileExclusions(any(GetFileExclusionsParams.class))).thenReturn(CompletableFuture.completedFuture(new GetFileExclusionsResponse(Collections.emptySet())));\n    when(smallClientFile.isLargerThan(anyLong())).thenReturn(false);\n    when(largeClientFile.isLargerThan(anyLong())).thenReturn(true);\n\n    // Avoid interference from server-side exclusions\n    var spy = Mockito.spy(underTest);\n    Mockito.doReturn(false).when(spy).isExcludedFromServer(any(URI.class));\n\n    var result = spy.filterOutExcludedFiles(configScopeId, baseDir, Set.of(smallUri, largeUri));\n\n    assertThat(result).extracting(ClientFile::getUri).containsExactlyInAnyOrder(smallUri);\n    assertThat(logTester.logs()).anySatisfy(s -> assertThat(s).contains(\"Filtered out URIs exceeding max allowed size\"));\n  }\n\n  @Test\n  void should_refresh_exclusions_for_inherited_descendant_scopes_when_binding_changes() {\n    var rootScope = \"rootScope\";\n    var childScope = \"childScope\";\n    var parentUri = URI.create(\"file:///p/Foo.java\");\n    var childUri = URI.create(\"file:///p/module/Bar.java\");\n\n    when(configRepo.getChildrenWithInheritedBinding(rootScope)).thenReturn(List.of(childScope));\n    var parentFile = mock(ClientFile.class);\n    when(parentFile.getUri()).thenReturn(parentUri);\n    var childFile = mock(ClientFile.class);\n    when(childFile.getUri()).thenReturn(childUri);\n    when(clientFileSystemService.getFiles(rootScope)).thenReturn(List.of(parentFile));\n    when(clientFileSystemService.getFiles(childScope)).thenReturn(List.of(childFile));\n\n    var connectionStorage = mock(ConnectionStorage.class);\n    var projectStorage = mock(SonarProjectStorage.class);\n    var analyzerStorage = mock(AnalyzerConfigurationStorage.class);\n    when(storageService.connection(\"conn\")).thenReturn(connectionStorage);\n    when(connectionStorage.project(\"pk\")).thenReturn(projectStorage);\n    when(projectStorage.analyzerConfiguration()).thenReturn(analyzerStorage);\n    when(analyzerStorage.isValid()).thenReturn(true);\n\n    var event = new BindingConfigChangedEvent(rootScope, BindingConfiguration.noBinding(false),\n      new BindingConfiguration(\"conn\", \"pk\", false));\n\n    underTest.onBindingChanged(event);\n\n    verify(clientFileSystemService).getFiles(rootScope);\n    verify(clientFileSystemService).getFiles(childScope);\n  }\n\n  @Test\n  void should_clear_exclusions_for_inherited_descendant_scopes_when_binding_removed() {\n    var rootScope = \"rootScope\";\n    var childScope = \"childScope\";\n    var parentUri = URI.create(\"file:///p/Foo.java\");\n    var childUri = URI.create(\"file:///p/module/Bar.java\");\n\n    when(configRepo.getChildrenWithInheritedBinding(rootScope)).thenReturn(List.of(childScope));\n    var parentFile = mock(ClientFile.class);\n    when(parentFile.getUri()).thenReturn(parentUri);\n    var childFile = mock(ClientFile.class);\n    when(childFile.getUri()).thenReturn(childUri);\n    when(clientFileSystemService.getFiles(rootScope)).thenReturn(List.of(parentFile));\n    when(clientFileSystemService.getFiles(childScope)).thenReturn(List.of(childFile));\n\n    var event = new BindingConfigChangedEvent(rootScope,\n      new BindingConfiguration(\"conn\", \"pk\", false),\n      BindingConfiguration.noBinding(false));\n\n    underTest.onBindingChanged(event);\n\n    verify(clientFileSystemService).getFiles(rootScope);\n    verify(clientFileSystemService).getFiles(childScope);\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/hotspot/HotspotServiceTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.hotspot;\n\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.serverapi.EndpointParams;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass HotspotServiceTests {\n\n  @Test\n  void testBuildSonarQubeHotspotUrl() {\n    assertThat(HotspotService.buildHotspotUrl(\"myProject\", \"myBranch\", \"hotspotKey\", new EndpointParams(\"http://foo.com\", \"\", false, null)))\n      .isEqualTo(\"http://foo.com/security_hotspots?id=myProject&branch=myBranch&hotspots=hotspotKey\");\n  }\n\n  @Test\n  void testBuildSonarCloudHotspotUrl() {\n    assertThat(HotspotService.buildHotspotUrl(\"myProject\", \"myBranch\", \"hotspotKey\", new EndpointParams(\"https://sonarcloud.io\", \"\", true, \"myOrg\")))\n      .isEqualTo(\"https://sonarcloud.io/project/security_hotspots?id=myProject&branch=myBranch&hotspots=hotspotKey\");\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/issue/matching/IssueMatcherTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.issue.matching;\n\nimport java.util.List;\nimport java.util.Optional;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.tracking.matching.IssueMatcher;\nimport org.sonarsource.sonarlint.core.tracking.matching.MatchingAttributesMapper;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass IssueMatcherTests {\n\n  private IssueMatcher<FakeIssueType, FakeIssueType> underTest;\n\n  private static class FakeIssueType {\n    private String ruleKey = \"dummy rule key\";\n    private Integer line;\n    private String textRangeHash;\n    private String lineHash;\n    private String message = \"dummy message\";\n    private String serverKey;\n\n    public FakeIssueType setRuleKey(String ruleKey) {\n      this.ruleKey = ruleKey;\n      return this;\n    }\n\n    public FakeIssueType setLine(Integer line) {\n      this.line = line;\n      return this;\n    }\n\n    public FakeIssueType setTextRangeHash(String hash) {\n      this.textRangeHash = hash;\n      return this;\n    }\n\n    public FakeIssueType setLineHash(String hash) {\n      this.lineHash = hash;\n      return this;\n    }\n\n    public FakeIssueType setMessage(String message) {\n      this.message = message;\n      return this;\n    }\n\n    public FakeIssueType setServerKey(String key) {\n      this.serverKey = key;\n      return this;\n    }\n  }\n\n  private static class FakeIssueMatchingAttributeMapper implements MatchingAttributesMapper<FakeIssueType> {\n\n    @Override\n    public String getRuleKey(FakeIssueType issue) {\n      return issue.ruleKey;\n    }\n\n    @Override\n    public Optional<Integer> getLine(FakeIssueType issue) {\n      return Optional.ofNullable(issue.line);\n    }\n\n    @Override\n    public Optional<String> getTextRangeHash(FakeIssueType issue) {\n      return Optional.ofNullable(issue.textRangeHash);\n    }\n\n    @Override\n    public Optional<String> getLineHash(FakeIssueType issue) {\n      return Optional.ofNullable(issue.lineHash);\n    }\n\n    @Override\n    public String getMessage(FakeIssueType issue) {\n      return issue.message;\n    }\n\n    @Override\n    public Optional<String> getServerIssueKey(FakeIssueType issue) {\n      return Optional.ofNullable(issue.serverKey);\n    }\n\n  }\n\n  @Test\n  void should_not_match_issues_with_different_rule_key() {\n    var issueForRuleA = new FakeIssueType().setRuleKey(\"ruleA\");\n    var issueForRuleB = new FakeIssueType().setRuleKey(\"ruleB\");\n    underTest = new IssueMatcher<>(new FakeIssueMatchingAttributeMapper(), List.of(issueForRuleB));\n\n    var result = underTest.matchWith(new FakeIssueMatchingAttributeMapper(), List.of(issueForRuleA));\n\n    assertThat(result.getMatchedLefts()).isEmpty();\n  }\n\n  @Test\n  void should_match_by_line_and_text_range_hash() {\n    var baseIssue = new FakeIssueType().setLine(7).setTextRangeHash(\"same range hash\");\n\n    var differentLine = new FakeIssueType().setLine(8).setTextRangeHash(\"same range hash\");\n    var differentTextRangeHash = new FakeIssueType().setLine(7).setTextRangeHash(\"different range hash\");\n    var differentBoth = new FakeIssueType().setLine(8).setTextRangeHash(\"different range hash\");\n    var same = new FakeIssueType().setLine(7).setTextRangeHash(\"same range hash\");\n    underTest = new IssueMatcher<>(new FakeIssueMatchingAttributeMapper(), List.of(baseIssue));\n\n    var result = underTest.matchWith(new FakeIssueMatchingAttributeMapper(), List.of(differentLine, differentTextRangeHash, differentBoth, same));\n\n    assertThat(result.getMatchedLefts()).hasSize(1);\n    assertThat(result.getMatch(same)).isEqualTo(baseIssue);\n  }\n\n  @Test\n  void should_match_by_line_and_line_hash_even_if_different_message_and_text_range() {\n    var baseIssue = new FakeIssueType().setLine(7).setLineHash(\"same line hash\").setMessage(\"different message\").setTextRangeHash(\"different range hash\");\n\n    var differentLine = new FakeIssueType().setLine(8).setLineHash(\"same line hash\");\n    var differentLineHash = new FakeIssueType().setLine(7).setLineHash(\"different line hash\");\n    var differentBoth = new FakeIssueType().setLine(8).setLineHash(\"different line hash\");\n    var same = new FakeIssueType().setLine(7).setLineHash(\"same line hash\");\n    underTest = new IssueMatcher<>(new FakeIssueMatchingAttributeMapper(), List.of(baseIssue));\n\n    var result = underTest.matchWith(new FakeIssueMatchingAttributeMapper(), List.of(differentLine, differentLineHash, differentBoth, same));\n\n    assertThat(result.getMatchedLefts()).hasSize(1);\n    assertThat(result.getMatch(same)).isEqualTo(baseIssue);\n  }\n\n  @Test\n  void should_match_by_line_and_message_even_if_different_hash() {\n    var baseIssue = new FakeIssueType().setLine(7).setMessage(\"same message\").setTextRangeHash(\"different range hash\");\n    var differentLine = new FakeIssueType().setLine(8).setMessage(\"same message\");\n    var differentMessage = new FakeIssueType().setLine(7).setMessage(\"different message\");\n    var differentBoth = new FakeIssueType().setLine(8).setMessage(\"different message\");\n    var same = new FakeIssueType().setLine(7).setMessage(\"same message\");\n    underTest = new IssueMatcher<>(new FakeIssueMatchingAttributeMapper(), List.of(baseIssue));\n\n    var result = underTest.matchWith(new FakeIssueMatchingAttributeMapper(), List.of(differentLine, differentMessage, differentBoth, same));\n\n    assertThat(result.getMatchedLefts()).hasSize(1);\n    assertThat(result.getMatch(same)).isEqualTo(baseIssue);\n  }\n\n  @Test\n  void should_match_by_text_range_hash_even_if_no_line_number_before() {\n    var baseIssueWithNoLine = new FakeIssueType().setTextRangeHash(\"same range hash\");\n    var differentLine = new FakeIssueType().setLine(8).setTextRangeHash(\"same range hash\");\n    underTest = new IssueMatcher<>(new FakeIssueMatchingAttributeMapper(), List.of(baseIssueWithNoLine));\n\n    var result = underTest.matchWith(new FakeIssueMatchingAttributeMapper(), List.of(differentLine));\n\n    assertThat(result.getMatchedLefts()).hasSize(1);\n    assertThat(result.getMatch(differentLine)).isEqualTo(baseIssueWithNoLine);\n  }\n\n  @Test\n  void should_match_by_text_range_hash_even_if_different_line_number() {\n    var baseIssue = new FakeIssueType().setLine(7).setTextRangeHash(\"same range hash\");\n    var differentLine = new FakeIssueType().setLine(8).setTextRangeHash(\"same range hash\");\n    underTest = new IssueMatcher<>(new FakeIssueMatchingAttributeMapper(), List.of(baseIssue));\n\n    var result = underTest.matchWith(new FakeIssueMatchingAttributeMapper(), List.of(differentLine));\n\n    assertThat(result.getMatchedLefts()).hasSize(1);\n    assertThat(result.getMatch(differentLine)).isEqualTo(baseIssue);\n  }\n\n  @Test\n  void should_match_by_line_hash_even_if_no_line_number_before() {\n    var baseIssueWithNoLine = new FakeIssueType().setLineHash(\"same line hash\");\n    var differentLine = new FakeIssueType().setLine(8).setLineHash(\"same line hash\");\n    underTest = new IssueMatcher<>(new FakeIssueMatchingAttributeMapper(), List.of(baseIssueWithNoLine));\n\n    var result = underTest.matchWith(new FakeIssueMatchingAttributeMapper(), List.of(differentLine));\n\n    assertThat(result.getMatchedLefts()).hasSize(1);\n    assertThat(result.getMatch(differentLine)).isEqualTo(baseIssueWithNoLine);\n  }\n\n  @Test\n  void should_match_by_line_hash_even_if_different_line_number() {\n    var baseIssue = new FakeIssueType().setLine(7).setLineHash(\"same line hash\");\n    var differentLine = new FakeIssueType().setLine(8).setLineHash(\"same line hash\");\n    underTest = new IssueMatcher<>(new FakeIssueMatchingAttributeMapper(), List.of(baseIssue));\n\n    var result = underTest.matchWith(new FakeIssueMatchingAttributeMapper(), List.of(differentLine));\n\n    assertThat(result.getMatchedLefts()).hasSize(1);\n    assertThat(result.getMatch(differentLine)).isEqualTo(baseIssue);\n  }\n\n  @Test\n  void should_match_by_serverKey_even_if_no_line_number_before() {\n    var baseIssueWithNoLine = new FakeIssueType().setServerKey(\"same key\");\n    var differentLine = new FakeIssueType().setLine(8).setServerKey(\"same key\");\n    underTest = new IssueMatcher<>(new FakeIssueMatchingAttributeMapper(), List.of(baseIssueWithNoLine));\n\n    var result = underTest.matchWith(new FakeIssueMatchingAttributeMapper(), List.of(differentLine));\n\n    assertThat(result.getMatchedLefts()).hasSize(1);\n    assertThat(result.getMatch(differentLine)).isEqualTo(baseIssueWithNoLine);\n  }\n\n  @Test\n  void should_match_by_serverKey_even_if_different_line_number() {\n    var baseIssue = new FakeIssueType().setLine(7).setServerKey(\"same key\");\n    var differentLine = new FakeIssueType().setLine(8).setServerKey(\"same key\");\n    underTest = new IssueMatcher<>(new FakeIssueMatchingAttributeMapper(), List.of(baseIssue));\n\n    var result = underTest.matchWith(new FakeIssueMatchingAttributeMapper(), List.of(differentLine));\n\n    assertThat(result.getMatchedLefts()).hasSize(1);\n    assertThat(result.getMatch(differentLine)).isEqualTo(baseIssue);\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/monitoring/MonitoringUserIdStoreTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.monitoring;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Base64;\nimport java.util.List;\nimport java.util.UUID;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.stream.IntStream;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.UserPaths;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass MonitoringUserIdStoreTests {\n\n  @RegisterExtension\n  static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private Path userHome;\n  private Path userIdFilePath;\n\n  @BeforeEach\n  void setUp(@TempDir Path temp) {\n    userHome = temp;\n    userIdFilePath = userHome.resolve(\"id\");\n  }\n\n  private MonitoringUserIdStore createStore() {\n    var userPaths = mock(UserPaths.class);\n    when(userPaths.getUserHome()).thenReturn(userHome);\n    return new MonitoringUserIdStore(userPaths);\n  }\n\n  @Test\n  void should_create_file_and_uuid_on_first_call() throws IOException {\n    var store = createStore();\n\n    assertThat(userIdFilePath).doesNotExist();\n\n    var userId = store.getOrCreate();\n\n    assertThat(userId).isPresent();\n    assertThat(userIdFilePath).exists();\n    var decodedContent = new String(Base64.getDecoder().decode(Files.readString(userIdFilePath)), StandardCharsets.UTF_8);\n    assertThat(decodedContent).isEqualTo(userId.get().toString());\n  }\n\n  @Test\n  void should_reuse_existing_uuid() throws IOException {\n    var existingUuid = UUID.randomUUID();\n    writeEncodedUuid(existingUuid);\n\n    var store = createStore();\n    var userId = store.getOrCreate();\n\n    assertThat(userId)\n      .isPresent()\n      .contains(existingUuid);\n  }\n\n  @Test\n  void should_return_cached_uuid_on_subsequent_calls() {\n    var store = createStore();\n\n    var firstCall = store.getOrCreate();\n    var secondCall = store.getOrCreate();\n\n    assertThat(firstCall).isPresent();\n    assertThat(secondCall).isPresent();\n    assertThat(firstCall).contains(secondCall.get());\n  }\n\n  @Test\n  void should_overwrite_invalid_content_with_new_uuid() throws IOException {\n    Files.writeString(userIdFilePath, \"not-a-valid-base64-or-uuid\");\n\n    var store = createStore();\n    var userId = store.getOrCreate();\n\n    assertThat(userId).isPresent();\n    var decodedContent = new String(Base64.getDecoder().decode(Files.readString(userIdFilePath)), StandardCharsets.UTF_8);\n    assertThat(decodedContent).isEqualTo(userId.get().toString());\n  }\n\n  @Test\n  void should_overwrite_empty_file_with_new_uuid() throws IOException {\n    Files.writeString(userIdFilePath, \"\");\n\n    var store = createStore();\n    var userId = store.getOrCreate();\n\n    assertThat(userId).isPresent();\n    var decodedContent = new String(Base64.getDecoder().decode(Files.readString(userIdFilePath)), StandardCharsets.UTF_8);\n    assertThat(decodedContent).isEqualTo(userId.get().toString());\n  }\n\n  @Test\n  void should_trim_whitespace_when_reading_uuid() throws IOException {\n    var existingUuid = UUID.randomUUID();\n    var encoded = Base64.getEncoder().encodeToString((\"  \" + existingUuid.toString() + \"\\n  \").getBytes(StandardCharsets.UTF_8));\n    Files.writeString(userIdFilePath, encoded);\n\n    var store = createStore();\n    var userId = store.getOrCreate();\n\n    assertThat(userId)\n      .isPresent()\n      .contains(existingUuid);\n  }\n\n  private void writeEncodedUuid(UUID uuid) throws IOException {\n    var encoded = Base64.getEncoder().encodeToString(uuid.toString().getBytes(StandardCharsets.UTF_8));\n    Files.writeString(userIdFilePath, encoded);\n  }\n\n  @Test\n  void concurrent_calls_should_return_same_uuid() {\n    var store = createStore();\n\n    int numberOfThreads = 10;\n    var executorService = Executors.newFixedThreadPool(numberOfThreads);\n    CountDownLatch latch = new CountDownLatch(1);\n    List<Future<UUID>> futures = new ArrayList<>();\n\n    IntStream.range(0, numberOfThreads).forEach(i -> {\n      futures.add(executorService.submit(() -> {\n        try {\n          latch.await();\n        } catch (InterruptedException e) {\n          Thread.currentThread().interrupt();\n        }\n        return store.getOrCreate().orElse(null);\n      }));\n    });\n\n    latch.countDown();\n\n    var results = futures.stream().map(f -> {\n      try {\n        return f.get();\n      } catch (ExecutionException e) {\n        fail(e.getCause());\n        return null;\n      } catch (InterruptedException e) {\n        Thread.currentThread().interrupt();\n        return null;\n      }\n    }).toList();\n\n    assertThat(results)\n      .hasSize(numberOfThreads)\n      .allMatch(uuid -> uuid != null && uuid.equals(results.get(0)));\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/newcode/NewCodeServiceTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.newcode;\n\nimport java.util.Optional;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.NewCodeDefinition;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.newcode.GetNewCodeDefinitionResponse;\nimport org.sonarsource.sonarlint.core.serverconnection.SonarProjectStorage;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.NewCodeDefinitionStorage;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryService;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass NewCodeServiceTests {\n\n  private ConfigurationRepository mockConfigRepository;\n  private StorageService mockStorageService;\n\n  private NewCodeService underTest;\n\n  @BeforeEach\n  void setup() {\n    mockConfigRepository = mock(ConfigurationRepository.class);\n    mockStorageService = mock(StorageService.class);\n    underTest = new NewCodeService(mockConfigRepository, mockStorageService, mock(TelemetryService.class));\n  }\n\n  @Test\n  void getNewCodeDefinition_noBinding() {\n    var ncd = underTest.getNewCodeDefinition(\"scope\");\n    assertThat(ncd).extracting(GetNewCodeDefinitionResponse::getDescription, GetNewCodeDefinitionResponse::isSupported)\n      .containsExactly(\"From last 30 days\", true);\n  }\n\n  @Test\n  void getNewCodeDefinition_noNcdSynchronized() {\n    String scopeId = \"scope\";\n    var effectiveBinding = mock(Binding.class);\n    when(mockConfigRepository.getEffectiveBinding(scopeId))\n      .thenReturn(Optional.of(effectiveBinding));\n    var storage = mock(SonarProjectStorage.class);\n    when(mockStorageService.binding(effectiveBinding))\n      .thenReturn(storage);\n    var newCodeDefStorage = mock(NewCodeDefinitionStorage.class);\n    when(storage.newCodeDefinition()).thenReturn(newCodeDefStorage);\n    var ncd = underTest.getNewCodeDefinition(\"scope\");\n    assertThat(ncd).extracting(GetNewCodeDefinitionResponse::getDescription, GetNewCodeDefinitionResponse::isSupported)\n      .containsExactly(\"No new code definition found\", false);\n  }\n\n  @Test\n  void getNewCodeDefinition_readFromStorage() {\n    String scopeId = \"scope\";\n    var effectiveBinding = mock(Binding.class);\n    when(mockConfigRepository.getEffectiveBinding(scopeId))\n      .thenReturn(Optional.of(effectiveBinding));\n    var storage = mock(SonarProjectStorage.class);\n    when(mockStorageService.binding(effectiveBinding))\n      .thenReturn(storage);\n    var newCodeDefStorage = mock(NewCodeDefinitionStorage.class);\n    when(storage.newCodeDefinition()).thenReturn(newCodeDefStorage);\n    var newCodeDefinition = NewCodeDefinition.withNumberOfDaysWithDate(42, 1234567890123L);\n    when(newCodeDefStorage.read()).thenReturn(Optional.of(newCodeDefinition));\n    var ncd = underTest.getNewCodeDefinition(\"scope\");\n    assertThat(ncd).extracting(GetNewCodeDefinitionResponse::getDescription, GetNewCodeDefinitionResponse::isSupported)\n      .containsExactly(\"From last 42 days\", true);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/nodejs/NodeJsHelperTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.nodejs;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Objects;\nimport java.util.function.BiFunction;\nimport java.util.function.Predicate;\nimport java.util.stream.Stream;\nimport javax.annotation.Nullable;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.mockito.stubbing.Answer;\nimport org.sonar.api.utils.System2;\nimport org.sonar.api.utils.command.Command;\nimport org.sonar.api.utils.command.CommandExecutor;\nimport org.sonar.api.utils.command.StreamConsumer;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.fail;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyLong;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass NodeJsHelperTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  private static final Path DUMMY_FILE_HELPER_LOCATION = Paths.get(\"\");\n\n  private static final Path FAKE_NODE_PATH = Paths.get(\"foo/node\");\n\n  private final System2 system2 = mock(System2.class);\n\n  private CommandExecutor commandExecutor;\n\n  private final Map<Predicate<Command>, BiFunction<StreamConsumer, StreamConsumer, Integer>> registeredCommandAnswers = new LinkedHashMap<>();\n\n  @BeforeEach\n  void prepare() {\n    commandExecutor = mock(CommandExecutor.class);\n    when(commandExecutor.execute(any(), any(), any(), anyLong())).thenAnswer((Answer<Integer>) invocation -> {\n      var c = invocation.getArgument(0, Command.class);\n      for (Entry<Predicate<Command>, BiFunction<StreamConsumer, StreamConsumer, Integer>> answer : registeredCommandAnswers.entrySet()) {\n        if (answer.getKey().test(c)) {\n          var stdOut = invocation.getArgument(1, StreamConsumer.class);\n          var stdErr = invocation.getArgument(2, StreamConsumer.class);\n          return answer.getValue().apply(stdOut, stdErr);\n        }\n      }\n      return fail(\"No answers registered for command: \" + c.toString());\n    });\n  }\n\n  @Test\n  void usePropertyWhenProvidedToResolveNodePath() {\n\n    registerNodeVersionAnswer(\"v10.5.4\");\n\n    var underTest = new NodeJsHelper(system2, DUMMY_FILE_HELPER_LOCATION, commandExecutor);\n    var result = underTest.detect(FAKE_NODE_PATH);\n\n    assertThat(logTester.logs()).containsExactly(\n      \"Node.js path provided by configuration: \" + FAKE_NODE_PATH,\n      \"Checking node version...\",\n      \"Execute command '\" + FAKE_NODE_PATH + \" -v'...\",\n      \"Command '\" + FAKE_NODE_PATH + \" -v' exited with 0\\nstdout: v10.5.4\",\n      \"Detected node version: 10.5.4\");\n    assertThat(result).isNotNull();\n    assertThat(result.getPath()).isEqualTo(FAKE_NODE_PATH);\n    assertThat(result.getVersion()).isEqualTo(Version.create(\"10.5.4\"));\n  }\n\n  @Test\n  void supportNightlyBuilds() {\n\n    registerNodeVersionAnswer(\"v15.0.0-nightly20200921039c274dde\");\n\n    var underTest = new NodeJsHelper(system2, DUMMY_FILE_HELPER_LOCATION, commandExecutor);\n    var result = underTest.detect(FAKE_NODE_PATH);\n\n    assertThat(logTester.logs()).containsExactly(\n      \"Node.js path provided by configuration: \" + FAKE_NODE_PATH,\n      \"Checking node version...\",\n      \"Execute command '\" + FAKE_NODE_PATH + \" -v'...\",\n      \"Command '\" + FAKE_NODE_PATH + \" -v' exited with 0\\nstdout: v15.0.0-nightly20200921039c274dde\",\n      \"Detected node version: 15.0.0-nightly20200921039c274dde\");\n    assertThat(result).isNotNull();\n    assertThat(result.getPath()).isEqualTo(FAKE_NODE_PATH);\n    assertThat(result.getVersion()).isEqualTo(Version.create(\"15.0.0-nightly20200921039c274dde\"));\n  }\n\n  @Test\n  void ignoreCommandExecutionError() {\n    registeredCommandAnswers.put(c -> true, (stdOut, stdErr) -> {\n      stdErr.consumeLine(\"error\");\n      return -1;\n    });\n\n    var underTest = new NodeJsHelper(system2, DUMMY_FILE_HELPER_LOCATION, commandExecutor);\n    var result = underTest.detect(FAKE_NODE_PATH);\n\n    assertThat(logTester.logs()).containsExactly(\n      \"Node.js path provided by configuration: \" + FAKE_NODE_PATH,\n      \"Checking node version...\",\n      \"Execute command '\" + FAKE_NODE_PATH + \" -v'...\",\n      \"Command '\" + FAKE_NODE_PATH + \" -v' exited with -1\\nstderr: error\",\n      \"Unable to query node version\");\n    assertThat(result).isNull();\n  }\n\n  @Test\n  void handleErrorDuringVersionCheck() {\n    registerNodeVersionAnswer(\"wrong_version\");\n\n    var underTest = new NodeJsHelper(system2, DUMMY_FILE_HELPER_LOCATION, commandExecutor);\n    var result = underTest.detect(FAKE_NODE_PATH);\n\n    assertThat(logTester.logs()).containsExactly(\n      \"Node.js path provided by configuration: \" + FAKE_NODE_PATH,\n      \"Checking node version...\",\n      \"Execute command '\" + FAKE_NODE_PATH + \" -v'...\",\n      \"Command '\" + FAKE_NODE_PATH + \" -v' exited with 0\\nstdout: wrong_version\",\n      \"Unable to parse node version: wrong_version\",\n      \"Unable to query node version\");\n    assertThat(result).isNull();\n  }\n\n  @Test\n  void useWhichOnLinuxToResolveNodePath() {\n    registerWhichAnswer(FAKE_NODE_PATH.toString());\n    registerNodeVersionAnswer(\"v10.5.4\");\n\n    var underTest = new NodeJsHelper(system2, DUMMY_FILE_HELPER_LOCATION, commandExecutor);\n    var result = underTest.detect(null);\n\n    assertThat(logTester.logs()).containsExactly(\n      \"Looking for node in the PATH\",\n      \"Execute command '/usr/bin/which node'...\",\n      \"Command '/usr/bin/which node' exited with 0\\nstdout: \" + FAKE_NODE_PATH,\n      \"Found node at \" + FAKE_NODE_PATH,\n      \"Checking node version...\",\n      \"Execute command '\" + FAKE_NODE_PATH + \" -v'...\",\n      \"Command '\" + FAKE_NODE_PATH + \" -v' exited with 0\\nstdout: v10.5.4\",\n      \"Detected node version: 10.5.4\");\n    assertThat(result).isNotNull();\n    assertThat(result.getPath()).isEqualTo(FAKE_NODE_PATH);\n    assertThat(result.getVersion()).isEqualTo(Version.create(\"10.5.4\"));\n  }\n\n  @Test\n  void handleErrorDuringPathCheck() {\n    registeredCommandAnswers.put(c -> true, (stdOut, stdErr) -> {\n      stdErr.consumeLine(\"error\");\n      return -1;\n    });\n\n    var underTest = new NodeJsHelper(system2, DUMMY_FILE_HELPER_LOCATION, commandExecutor);\n    var result = underTest.detect(null);\n\n    assertThat(logTester.logs()).containsExactly(\n      \"Looking for node in the PATH\",\n      \"Execute command '/usr/bin/which node'...\",\n      \"Command '/usr/bin/which node' exited with -1\\nstderr: error\",\n      \"Unable to locate node\");\n    assertThat(result).isNull();\n  }\n\n  @Test\n  void handleEmptyResponseDuringPathCheck() {\n    when(system2.isOsWindows()).thenReturn(true);\n\n    registerWhereAnswer();\n\n    var underTest = new NodeJsHelper(system2, DUMMY_FILE_HELPER_LOCATION, commandExecutor);\n    var result = underTest.detect(null);\n\n    assertThat(logTester.logs()).containsExactly(\n      \"Looking for node in the PATH\",\n      \"Execute command 'C:\\\\Windows\\\\System32\\\\where.exe $PATH:node.exe'...\",\n      \"Command 'C:\\\\Windows\\\\System32\\\\where.exe $PATH:node.exe' exited with 0\",\n      \"Unable to locate node\");\n    assertThat(result).isNull();\n  }\n\n  @Test\n  void useWhereOnWindowsToResolveNodePath() {\n    when(system2.isOsWindows()).thenReturn(true);\n\n    registerWhereAnswer(FAKE_NODE_PATH.toString());\n    registerNodeVersionAnswer(\"v10.5.4\");\n\n    var underTest = new NodeJsHelper(system2, DUMMY_FILE_HELPER_LOCATION, commandExecutor);\n    var result = underTest.detect(null);\n\n    assertThat(logTester.logs()).containsExactly(\n      \"Looking for node in the PATH\",\n      \"Execute command 'C:\\\\Windows\\\\System32\\\\where.exe $PATH:node.exe'...\",\n      \"Command 'C:\\\\Windows\\\\System32\\\\where.exe $PATH:node.exe' exited with 0\\nstdout: \" + FAKE_NODE_PATH,\n      \"Found node at \" + FAKE_NODE_PATH,\n      \"Checking node version...\",\n      \"Execute command '\" + FAKE_NODE_PATH + \" -v'...\",\n      \"Command '\" + FAKE_NODE_PATH + \" -v' exited with 0\\nstdout: v10.5.4\",\n      \"Detected node version: 10.5.4\");\n    assertThat(result).isNotNull();\n    assertThat(result.getPath()).isEqualTo(FAKE_NODE_PATH);\n    assertThat(result.getVersion()).isEqualTo(Version.create(\"10.5.4\"));\n  }\n\n  // SLCORE-281\n  @Test\n  void whereOnWindowsCanReturnMultipleCandidates() {\n    when(system2.isOsWindows()).thenReturn(true);\n\n    var fake_node_path2 = Paths.get(\"foo2/node\");\n\n    registerWhereAnswer(FAKE_NODE_PATH.toString(), fake_node_path2.toString());\n    registerNodeVersionAnswer(\"v10.5.4\");\n\n    var underTest = new NodeJsHelper(system2, DUMMY_FILE_HELPER_LOCATION, commandExecutor);\n    var result = underTest.detect(null);\n\n    assertThat(logTester.logs()).containsExactly(\n      \"Looking for node in the PATH\",\n      \"Execute command 'C:\\\\Windows\\\\System32\\\\where.exe $PATH:node.exe'...\",\n      \"Command 'C:\\\\Windows\\\\System32\\\\where.exe $PATH:node.exe' exited with 0\\nstdout: \"\n        + FAKE_NODE_PATH + \"\\n\" + fake_node_path2,\n      \"Found node at \" + FAKE_NODE_PATH,\n      \"Checking node version...\",\n      \"Execute command '\" + FAKE_NODE_PATH + \" -v'...\",\n      \"Command '\" + FAKE_NODE_PATH + \" -v' exited with 0\\nstdout: v10.5.4\",\n      \"Detected node version: 10.5.4\");\n    assertThat(result).isNotNull();\n    assertThat(result.getPath()).isEqualTo(FAKE_NODE_PATH);\n    assertThat(result.getVersion()).isEqualTo(Version.create(\"10.5.4\"));\n  }\n\n  @Test\n  void usePathHelperOnMacToResolveNodePath(@TempDir Path tempDir) throws IOException {\n    when(system2.isOsMac()).thenReturn(true);\n\n    registerPathHelperAnswer(\"PATH=\\\"/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin/node\\\"; export PATH;\");\n    registerWhichAnswerIfPathIsSet(FAKE_NODE_PATH.toString(), \"/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin/node\");\n    registerNodeVersionAnswer(\"v10.5.4\");\n\n    // Need a true file since we are checking if file exists\n    var fakePathHelper = tempDir.resolve(\"path_helper.sh\");\n    Files.createFile(fakePathHelper);\n    var underTest = new NodeJsHelper(system2, fakePathHelper, commandExecutor);\n    var result = underTest.detect(null);\n\n    assertThat(logTester.logs()).containsExactly(\n      \"Looking for node in the PATH\",\n      \"Execute command '\" + fakePathHelper + \" -s'...\",\n      \"Command '\" + fakePathHelper + \" -s' exited with 0\\nstdout: PATH=\\\"/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin/node\\\"; export PATH;\",\n      \"Execute command '/usr/bin/which node'...\",\n      \"Command '/usr/bin/which node' exited with 0\\nstdout: \" + FAKE_NODE_PATH,\n      \"Found node at \" + FAKE_NODE_PATH,\n      \"Checking node version...\",\n      \"Execute command '\" + FAKE_NODE_PATH + \" -v'...\",\n      \"Command '\" + FAKE_NODE_PATH + \" -v' exited with 0\\nstdout: v10.5.4\",\n      \"Detected node version: 10.5.4\");\n    assertThat(result).isNotNull();\n    assertThat(result.getPath()).isEqualTo(FAKE_NODE_PATH);\n    assertThat(result.getVersion()).isEqualTo(Version.create(\"10.5.4\"));\n  }\n\n  @Test\n  void ignoreWrongPathHelperOutputOnMac(@TempDir Path tempDir) throws IOException {\n    when(system2.isOsMac()).thenReturn(true);\n    registerPathHelperAnswer(\"wrong \\n output\");\n    registerWhichAnswerIfPathIsSet(FAKE_NODE_PATH.toString(), System.getenv(\"PATH\"));\n    registerNodeVersionAnswer(\"v10.5.4\");\n\n    // Need a true file since we are checking if file exists\n    var fakePathHelper = tempDir.resolve(\"path_helper.sh\");\n    Files.createFile(fakePathHelper);\n    var underTest = new NodeJsHelper(system2, fakePathHelper, commandExecutor);\n    var result = underTest.detect(null);\n\n    assertThat(logTester.logs()).containsExactly(\n      \"Looking for node in the PATH\",\n      \"Execute command '\" + fakePathHelper + \" -s'...\",\n      \"Command '\" + fakePathHelper + \" -s' exited with 0\\nstdout: wrong \\n output\",\n      \"Execute command '/usr/bin/which node'...\",\n      \"Command '/usr/bin/which node' exited with 0\\nstdout: \" + FAKE_NODE_PATH,\n      \"Found node at \" + FAKE_NODE_PATH,\n      \"Checking node version...\",\n      \"Execute command '\" + FAKE_NODE_PATH + \" -v'...\",\n      \"Command '\" + FAKE_NODE_PATH + \" -v' exited with 0\\nstdout: v10.5.4\",\n      \"Detected node version: 10.5.4\");\n    assertThat(result).isNotNull();\n    assertThat(result.getPath()).isEqualTo(FAKE_NODE_PATH);\n    assertThat(result.getVersion()).isEqualTo(Version.create(\"10.5.4\"));\n  }\n\n  @Test\n  void ignorePathHelperOnMacIfMissing() {\n    when(system2.isOsMac()).thenReturn(true);\n\n    registerPathHelperAnswer(\"wrong \\n output\");\n    registerWhichAnswerIfPathIsSet(FAKE_NODE_PATH.toString(), System.getenv(\"PATH\"));\n    registerNodeVersionAnswer(\"v10.5.4\");\n\n    var underTest = new NodeJsHelper(system2, Paths.get(\"not_exists\"), commandExecutor);\n    var result = underTest.detect(null);\n\n    assertThat(logTester.logs()).containsExactly(\n      \"Looking for node in the PATH\",\n      \"Execute command '/usr/bin/which node'...\",\n      \"Command '/usr/bin/which node' exited with 0\\nstdout: \" + FAKE_NODE_PATH,\n      \"Found node at \" + FAKE_NODE_PATH,\n      \"Checking node version...\",\n      \"Execute command '\" + FAKE_NODE_PATH + \" -v'...\",\n      \"Command '\" + FAKE_NODE_PATH + \" -v' exited with 0\\nstdout: v10.5.4\",\n      \"Detected node version: 10.5.4\");\n    assertThat(result).isNotNull();\n    assertThat(result.getPath()).isEqualTo(FAKE_NODE_PATH);\n    assertThat(result.getVersion()).isEqualTo(Version.create(\"10.5.4\"));\n  }\n\n  @Test\n  void logWhenUnableToGetNodeVersion() {\n    var underTest = new NodeJsHelper();\n    var result = underTest.detect(Paths.get(\"not_node\"));\n\n    assertThat(logTester.logs()).anyMatch(s -> s.startsWith(\"Unable to execute the command\"));\n    assertThat(result).isNull();\n  }\n\n  private void registerNodeVersionAnswer(String version) {\n    registeredCommandAnswers.put(c -> c.toString().endsWith(FAKE_NODE_PATH + \" -v\"), (stdOut, stdErr) -> {\n      stdOut.consumeLine(version);\n      return 0;\n    });\n  }\n\n  private void registerWhichAnswer(String whichOutput) {\n    registeredCommandAnswers.put(c -> c.toString().endsWith(\"which node\"), (stdOut, stdErr) -> {\n      stdOut.consumeLine(whichOutput);\n      return 0;\n    });\n  }\n\n  private void registerWhichAnswerIfPathIsSet(String whichOutput, @Nullable String expectedPath) {\n    registeredCommandAnswers.put(c -> c.toString().endsWith(\"which node\") && Objects.equals(expectedPath, c.getEnvironmentVariables().get(\"PATH\")), (stdOut, stdErr) -> {\n      stdOut.consumeLine(whichOutput);\n      return 0;\n    });\n  }\n\n  private void registerWhereAnswer(String... whereOutput) {\n    registeredCommandAnswers.put(c -> c.toString().endsWith(\"C:\\\\Windows\\\\System32\\\\where.exe $PATH:node.exe\"), (stdOut, stdErr) -> {\n      Stream.of(whereOutput).forEach(stdOut::consumeLine);\n      return 0;\n    });\n  }\n\n  private void registerPathHelperAnswer(String output) {\n    registeredCommandAnswers.put(c -> c.toString().endsWith(\"path_helper.sh -s\"), (stdOut, stdErr) -> {\n      stdOut.consumeLine(output);\n      return 0;\n    });\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/plugin/DotnetSupportTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Set;\nimport java.util.stream.Stream;\nimport javax.annotation.Nullable;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass DotnetSupportTest {\n  private static final Path somePath = Paths.get(\"folder\", \"file.txt\");\n  private InitializeParams initializeParams;\n\n  static Stream<Arguments> provideTestArguments() {\n    return Stream.of(\n      Arguments.of(Language.CS, null, true, true),\n      Arguments.of(Language.VBNET, null, true, true),\n      Arguments.of(Language.CS, somePath, true, false),\n      Arguments.of(Language.VBNET, somePath, true, false),\n      Arguments.of(Language.CS, somePath, false, true),\n      Arguments.of(Language.VBNET, somePath, false, true),\n      Arguments.of(Language.CS, somePath, false, false),\n      Arguments.of(Language.VBNET, somePath, false, false),\n      Arguments.of(Language.COBOL, somePath, false, false)\n    );\n  }\n\n  @BeforeEach\n  void prepare() {\n    initializeParams = mock(InitializeParams.class);\n  }\n\n  @ParameterizedTest\n  @MethodSource(\"provideTestArguments\")\n  void should_initialize_properties_as_expected(Language language, @Nullable Path csharpAnalyzerPath, boolean shouldUseCsharpEnterprise, boolean shouldUseVbNetEnterprise) {\n    mockEnabledLanguages(language);\n\n    var underTest = new DotnetSupport(initializeParams, csharpAnalyzerPath, shouldUseCsharpEnterprise, shouldUseVbNetEnterprise);\n\n    assertThat(underTest.isSupportsCsharp()).isEqualTo(language == Language.CS);\n    assertThat(underTest.isSupportsVbNet()).isEqualTo(language == Language.VBNET);\n    assertThat(underTest.getActualCsharpAnalyzerPath()).isEqualTo(csharpAnalyzerPath);\n    assertThat(underTest.isShouldUseCsharpEnterprise()).isEqualTo(shouldUseCsharpEnterprise);\n    assertThat(underTest.isShouldUseVbNetEnterprise()).isEqualTo(shouldUseVbNetEnterprise);\n  }\n\n  private void mockEnabledLanguages(Language... languages) {\n    when(initializeParams.getEnabledLanguagesInStandaloneMode()).thenReturn(Set.of(languages));\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/plugin/PluginJarUtilsTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin;\n\nimport java.nio.file.Path;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport org.sonarsource.sonarlint.core.plugin.commons.loading.PluginInfo;\n\nclass PluginJarUtilsTest {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @TempDir\n  Path tempDir;\n\n  @Test\n  void should_return_null_when_jar_does_not_exist() {\n    var missingJar = tempDir.resolve(\"missing.jar\");\n\n    var version = PluginJarUtils.readVersion(missingJar);\n\n    assertThat(version).isNull();\n  }\n\n  @Test\n  void should_return_null_when_jar_is_invalid() throws IOException {\n    var corruptedJar = tempDir.resolve(\"corrupted.jar\");\n    Files.writeString(corruptedJar, \"not a jar\");\n\n    var version = PluginJarUtils.readVersion(corruptedJar);\n\n    assertThat(version).isNull();\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/plugin/PluginLifecycleServiceTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport java.util.Map;\nimport org.sonarsource.sonarlint.core.active.rules.ActiveRulesService;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.plugin.commons.LoadedPlugins;\nimport org.sonarsource.sonarlint.core.repository.rules.RulesRepository;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass PluginLifecycleServiceTest {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private PluginsService pluginsService;\n  private RulesRepository rulesRepository;\n  private ActiveRulesService activeRulesService;\n  private PluginLifecycleService underTest;\n\n  @BeforeEach\n  void setUp() {\n    pluginsService = mock(PluginsService.class);\n    rulesRepository = mock(RulesRepository.class);\n    activeRulesService = mock(ActiveRulesService.class);\n    underTest = new PluginLifecycleService(pluginsService, rulesRepository, activeRulesService);\n  }\n\n  @Test\n  void unloadPluginsAndEvictCaches_delegates_to_all_three_services() {\n    var connectionId = \"conn1\";\n\n    underTest.unloadPluginsAndEvictCaches(connectionId);\n\n    verify(pluginsService).unloadPlugins(connectionId);\n    verify(rulesRepository).evictFor(connectionId);\n    verify(activeRulesService).evictFor(connectionId);\n  }\n\n  @Test\n  void reloadPluginsAndEvictCaches_unloads_then_loads_and_returns_new_plugins() {\n    var connectionId = \"conn1\";\n    var loadedPlugins = mock(LoadedPlugins.class);\n    when(pluginsService.getPlugins(connectionId)).thenReturn(new PluginsConfiguration(null, loadedPlugins, Map.of()));\n\n    var result = underTest.reloadPluginsAndEvictCaches(connectionId);\n\n    verify(pluginsService).unloadPlugins(connectionId);\n    verify(rulesRepository).evictFor(connectionId);\n    verify(activeRulesService).evictFor(connectionId);\n    verify(pluginsService).getPlugins(connectionId);\n    assertThat(result.plugins()).isSameAs(loadedPlugins);\n  }\n\n  @Test\n  void unloadEmbeddedPluginsAndEvictCaches_delegates_to_all_three_services() {\n    underTest.unloadEmbeddedPluginsAndEvictCaches();\n\n    verify(pluginsService).unloadEmbeddedPlugins();\n    verify(rulesRepository).evictEmbedded();\n    verify(activeRulesService).evictStandalone();\n  }\n\n  @Test\n  void reloadEmbeddedPluginsAndEvictCaches_unloads_then_loads_and_returns_new_embedded_plugins() {\n    var loadedPlugins = mock(LoadedPlugins.class);\n    when(pluginsService.getEmbeddedPlugins()).thenReturn(new PluginsConfiguration(null, loadedPlugins, Map.of()));\n\n    var result = underTest.reloadEmbeddedPluginsAndEvictCaches();\n\n    verify(pluginsService).unloadEmbeddedPlugins();\n    verify(rulesRepository).evictEmbedded();\n    verify(activeRulesService).evictStandalone();\n    verify(pluginsService).getEmbeddedPlugins();\n    assertThat(result.plugins()).isSameAs(loadedPlugins);\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/plugin/PluginStatusNotifierServiceTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactState;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactOrigin;\n\nimport java.util.List;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.repository.config.BindingConfiguration;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationScope;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.ArtifactSourceDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.PluginStateDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.PluginStatusDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.plugin.DidChangePluginStatusesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass PluginStatusNotifierServiceTest {\n\n  private static final String SCOPE_1 = \"scope1\";\n  private static final String SCOPE_2 = \"scope2\";\n  private static final String CONNECTION_1 = \"conn1\";\n\n  private final SonarLintRpcClient client = mock(SonarLintRpcClient.class);\n  private final PluginsService pluginsService = mock(PluginsService.class);\n  private final ConfigurationRepository configurationRepository = new ConfigurationRepository();\n  private final PluginStatusNotifierService underTest = new PluginStatusNotifierService(pluginsService, client, configurationRepository);\n\n  private final PluginStatus standaloneStatus = PluginStatus.forLanguage(SonarLanguage.JAVA, ArtifactState.ACTIVE, ArtifactOrigin.EMBEDDED, null, null, null, null);\n  private final PluginStatus connectedStatus = PluginStatus.forLanguage(SonarLanguage.JAVA, ArtifactState.ACTIVE, ArtifactOrigin.SONARQUBE_SERVER, null, null, null, \"10.1\");\n\n  @BeforeEach\n  void setUp() {\n    when(pluginsService.getPluginStatuses(null)).thenReturn(List.of(standaloneStatus));\n    when(pluginsService.getPluginStatuses(CONNECTION_1)).thenReturn(List.of(connectedStatus));\n  }\n\n  @Test\n  void should_notify_each_scope_with_its_effective_connection_statuses_in_standalone_mode() {\n    configurationRepository.addOrReplace(new ConfigurationScope(SCOPE_1, null, true, \"Scope 1\"), BindingConfiguration.noBinding());\n    configurationRepository.addOrReplace(new ConfigurationScope(SCOPE_2, null, true, \"Scope 2\"), new BindingConfiguration(CONNECTION_1, \"project1\", false));\n\n    var expectedScope1Params = new DidChangePluginStatusesParams(SCOPE_1, List.of(standaloneStatusDto()));\n    var expectedScope2Params = new DidChangePluginStatusesParams(SCOPE_2, List.of(connectedStatusDto()));\n\n    underTest.onPluginStatusesChanged(new PluginStatusesChangedEvent(null, List.of()));\n\n    var captor = ArgumentCaptor.forClass(DidChangePluginStatusesParams.class);\n    verify(client, times(2)).didChangePluginStatuses(captor.capture());\n    assertThat(captor.getAllValues())\n      .usingRecursiveComparison()\n      .ignoringCollectionOrder()\n      .isEqualTo(List.of(expectedScope1Params, expectedScope2Params));\n  }\n\n  @Test\n  void should_notify_only_bound_scopes_in_connected_mode() {\n    configurationRepository.addOrReplace(new ConfigurationScope(SCOPE_1, null, true, \"Scope 1\"), new BindingConfiguration(CONNECTION_1, \"project1\", false));\n    configurationRepository.addOrReplace(new ConfigurationScope(SCOPE_2, null, true, \"Scope 2\"), new BindingConfiguration(CONNECTION_1, \"project2\", false));\n    configurationRepository.addOrReplace(new ConfigurationScope(\"scope3\", null, true, \"Scope 3\"), BindingConfiguration.noBinding());\n\n    var expectedScope1Params = new DidChangePluginStatusesParams(SCOPE_1, List.of(connectedStatusDto()));\n    var expectedScope2Params = new DidChangePluginStatusesParams(SCOPE_2, List.of(connectedStatusDto()));\n\n    underTest.onPluginStatusesChanged(new PluginStatusesChangedEvent(CONNECTION_1, List.of(connectedStatus)));\n\n    var captor = ArgumentCaptor.forClass(DidChangePluginStatusesParams.class);\n    verify(client, times(2)).didChangePluginStatuses(captor.capture());\n    assertThat(captor.getAllValues())\n      .usingRecursiveComparison()\n      .ignoringCollectionOrder()\n      .isEqualTo(List.of(expectedScope1Params, expectedScope2Params));\n  }\n\n  private static PluginStatusDto standaloneStatusDto() {\n    return new PluginStatusDto(Language.JAVA, \"Java\", PluginStateDto.ACTIVE, ArtifactSourceDto.EMBEDDED, null, null, null);\n  }\n\n  private static PluginStatusDto connectedStatusDto() {\n    return new PluginStatusDto(Language.JAVA, \"Java\", PluginStateDto.ACTIVE, ArtifactSourceDto.SONARQUBE_SERVER, null, null, \"10.1\");\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/plugin/PluginsServiceTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport javax.annotation.Nullable;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.EnumSource;\nimport org.sonarsource.sonarlint.core.analysis.NodeJsService;\nimport org.sonarsource.sonarlint.core.commons.ConnectionKind;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.plugin.loading.strategy.ArtifactsLoadingResult;\nimport org.sonarsource.sonarlint.core.plugin.loading.strategy.ConnectedArtifactsLoadingStrategy;\nimport org.sonarsource.sonarlint.core.plugin.loading.strategy.ConnectedArtifactsLoadingStrategyFactory;\nimport org.sonarsource.sonarlint.core.plugin.loading.strategy.StandaloneArtifactsLoadingStrategy;\nimport org.sonarsource.sonarlint.core.plugin.skipped.SkippedPluginsRepository;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactOrigin;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactState;\nimport org.sonarsource.sonarlint.core.plugin.source.ResolvedArtifact;\nimport org.sonarsource.sonarlint.core.plugin.source.binaries.BinariesArtifactSource;\nimport org.sonarsource.sonarlint.core.repository.connection.AbstractConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.serverconnection.ConnectionStorage;\nimport org.sonarsource.sonarlint.core.serverconnection.StoredPlugin;\nimport org.sonarsource.sonarlint.core.serverconnection.StoredServerInfo;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.PluginsStorage;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ServerInfoStorage;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.springframework.context.ApplicationEventPublisher;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass PluginsServiceTest {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private static final Path ossPath = Paths.get(\"folder\", \"oss\");\n  private static final Path enterprisePath = Paths.get(\"folder\", \"enterprise\");\n  private PluginsService underTest;\n  private PluginsRepository pluginsRepository;\n  private ConnectionConfigurationRepository connectionConfigurationStorage;\n  private StorageService storageService;\n  private ConnectionStorage connectionStorage;\n  private ServerInfoStorage serverInfoStorage;\n  private PluginsStorage pluginStorage;\n  private InitializeParams initializeParams;\n  private ApplicationEventPublisher eventPublisher;\n  private ConnectedArtifactsLoadingStrategyFactory connectedArtifactsLoadingStrategyFactory;\n\n  @BeforeEach\n  void prepare() {\n    pluginsRepository = mock(PluginsRepository.class);\n    storageService = mock(StorageService.class);\n    connectionConfigurationStorage = mock(ConnectionConfigurationRepository.class);\n    connectionStorage = mock(ConnectionStorage.class);\n    serverInfoStorage = mock(ServerInfoStorage.class);\n    pluginStorage = mock(PluginsStorage.class);\n    when(connectionStorage.plugins()).thenReturn(pluginStorage);\n    initializeParams = mock(InitializeParams.class);\n    when(initializeParams.getDisabledPluginKeysForAnalysis()).thenReturn(Set.of());\n    eventPublisher = mock(ApplicationEventPublisher.class);\n    when(pluginStorage.getStoredPluginsByKey()).thenReturn(Map.of());\n\n    var standaloneArtifactsLoadingStrategy = mock(StandaloneArtifactsLoadingStrategy.class);\n    connectedArtifactsLoadingStrategyFactory = mock(ConnectedArtifactsLoadingStrategyFactory.class);\n    var connectedArtifactsLoadingStrategy = mock(ConnectedArtifactsLoadingStrategy.class);\n\n    var csharpArtifact = new ResolvedArtifact(ArtifactState.ACTIVE, ossPath, ArtifactOrigin.EMBEDDED, null, null);\n    when(standaloneArtifactsLoadingStrategy.resolveArtifacts()).thenReturn(new ArtifactsLoadingResult(Set.of(), Map.of(\"csharp\", csharpArtifact)));\n    when(connectedArtifactsLoadingStrategy.resolveArtifacts()).thenReturn(new ArtifactsLoadingResult(Set.of(), Map.of(\"csharp\", csharpArtifact)));\n    when(connectedArtifactsLoadingStrategyFactory.getOrCreate(any())).thenReturn(connectedArtifactsLoadingStrategy);\n\n    var binariesArtifactSource = mock(BinariesArtifactSource.class);\n    when(binariesArtifactSource.getOmnisharpExtraProperties()).thenReturn(Map.of());\n\n    underTest = new PluginsService(pluginsRepository, mock(SkippedPluginsRepository.class), storageService,\n      initializeParams, connectionConfigurationStorage, mock(NodeJsService.class), eventPublisher,\n      standaloneArtifactsLoadingStrategy, connectedArtifactsLoadingStrategyFactory, binariesArtifactSource);\n  }\n\n  @Test\n  void shouldUseEnterpriseCSharpAnalyzer_connectionDoesNotExist_returnsFalse() {\n    var connectionId = \"notExisting\";\n    mockNoConnection(connectionId);\n\n    var result = underTest.shouldUseEnterpriseCSharpAnalyzer(connectionId);\n\n    assertThat(result).isFalse();\n  }\n\n  @Test\n  void shouldUseEnterpriseCSharpAnalyzer_connectionIsToCloud_returnsTrue() {\n    var connectionId = \"SQC\";\n    var connection = createConnection(connectionId, ConnectionKind.SONARCLOUD);\n    mockConnection(connection);\n\n    var result = underTest.shouldUseEnterpriseCSharpAnalyzer(connectionId);\n\n    assertThat(result).isTrue();\n  }\n\n  @Test\n  void shouldUseEnterpriseCSharpAnalyzer_connectionIsToServerThatDoesNotExistOnStorage_returnsFalse() {\n    var connectionId = \"SQS\";\n    var connection = createConnection(connectionId, ConnectionKind.SONARQUBE);\n    mockConnection(connection);\n\n    var result = underTest.shouldUseEnterpriseCSharpAnalyzer(connectionId);\n\n    assertThat(result).isFalse();\n  }\n\n  @Test\n  void shouldUseEnterpriseCSharpAnalyzer_connectionIsToServer_Older_Than_10_8_returnsTrue() {\n    var connectionId = \"SQS\";\n    mockConnection(connectionId, ConnectionKind.SONARQUBE, Version.create(\"10.7\"));\n\n    var result = underTest.shouldUseEnterpriseCSharpAnalyzer(connectionId);\n\n    assertThat(result).isTrue();\n  }\n\n  @Test\n  void shouldUseEnterpriseCSharpAnalyzer_connectionIsToServerWithRepackagedPluginAndPluginIsNotPresentOnTheServer_returnsFalse() {\n    var connectionId = \"SQS\";\n    mockConnection(connectionId, ConnectionKind.SONARQUBE, Version.create(\"10.8\"));\n    mockPlugin(\"otherPlugin\");\n\n    var result = underTest.shouldUseEnterpriseCSharpAnalyzer(connectionId);\n\n    assertThat(result).isFalse();\n  }\n\n  @Test\n  void shouldUseEnterpriseCSharpAnalyzer_connectionIsToServerWithRepackagedPluginAndPluginIsPresentOnTheServer_returnsTrue() {\n    var connectionId = \"SQS\";\n    mockConnection(connectionId, ConnectionKind.SONARQUBE, Version.create(\"10.8\"));\n    mockPlugin(PluginsService.CSHARP_ENTERPRISE_PLUGIN_ID);\n\n    var result = underTest.shouldUseEnterpriseCSharpAnalyzer(connectionId);\n\n    assertThat(result).isTrue();\n  }\n\n  @Test\n  void shouldUseEnterpriseVbAnalyzer_connectionDoesNotExist_returnsFalse() {\n    var connectionId = \"notExisting\";\n    mockNoConnection(connectionId);\n\n    var result = underTest.shouldUseEnterpriseVbAnalyzer(connectionId);\n\n    assertThat(result).isFalse();\n  }\n\n  @Test\n  void shouldUseEnterpriseVbAnalyzer_connectionIsToCloud_returnsTrue() {\n    var connectionId = \"SQC\";\n    var connection = createConnection(connectionId, ConnectionKind.SONARCLOUD);\n    mockConnection(connection);\n\n    var result = underTest.shouldUseEnterpriseVbAnalyzer(connectionId);\n\n    assertThat(result).isTrue();\n  }\n\n  @Test\n  void shouldUseEnterpriseVbAnalyzer_connectionIsToServerThatDoesNotExistOnStorage_returnsFalse() {\n    var connectionId = \"SQS\";\n    var connection = createConnection(connectionId, ConnectionKind.SONARQUBE);\n    mockConnection(connection);\n\n    var result = underTest.shouldUseEnterpriseVbAnalyzer(connectionId);\n\n    assertThat(result).isFalse();\n  }\n\n  @Test\n  void shouldUseEnterpriseVbAnalyzer_connectionIsToServer_Older_Than_10_8_returnsTrue() {\n    var connectionId = \"SQS\";\n    mockConnection(connectionId, ConnectionKind.SONARQUBE, Version.create(\"10.7\"));\n\n    var result = underTest.shouldUseEnterpriseVbAnalyzer(connectionId);\n\n    assertThat(result).isTrue();\n  }\n\n  @Test\n  void shouldUseEnterpriseVbAnalyzer_connectionIsToServerWithRepackagedPluginAndPluginIsNotPresentOnTheServer_returnsFalse() {\n    var connectionId = \"SQS\";\n    mockConnection(connectionId, ConnectionKind.SONARQUBE, Version.create(\"10.8\"));\n    mockPlugin(\"otherPlugin\");\n\n    var result = underTest.shouldUseEnterpriseVbAnalyzer(connectionId);\n\n    assertThat(result).isFalse();\n  }\n\n  @Test\n  void shouldUseEnterpriseVbAnalyzer_connectionIsToServerWithRepackagedPluginAndPluginIsPresentOnTheServer_returnsTrue() {\n    var connectionId = \"SQS\";\n    mockConnection(connectionId, ConnectionKind.SONARQUBE, Version.create(\"10.8\"));\n    mockPlugin(PluginsService.VBNET_ENTERPRISE_PLUGIN_ID);\n\n    var result = underTest.shouldUseEnterpriseVbAnalyzer(connectionId);\n\n    assertThat(result).isTrue();\n  }\n\n  @ParameterizedTest\n  @EnumSource(value = Language.class, names = {\"CS\", \"VBNET\", \"COBOL\"})\n  void getEmbeddedPlugins_extraProperties_ReturnsExpectedDotnetProperties(Language language) {\n    mockEnabledLanguages(language);\n\n    var props = underTest.getEmbeddedPlugins().extraProperties();\n\n    assertThat(props)\n      .containsEntry(\"sonar.cs.internal.analyzerPath\", ossPath.toString());\n    if (language == Language.CS) {\n      assertThat(props).containsEntry(\"sonar.cs.internal.shouldUseCsharpEnterprise\", \"false\");\n    }\n    if (language == Language.VBNET) {\n      assertThat(props).containsEntry(\"sonar.cs.internal.shouldUseVbEnterprise\", \"false\");\n    }\n  }\n\n  @Test\n  void getPlugins_extraProperties_forCloud_fallsBackToOss_whenEnterpriseNotInStorage() {\n    var connectionId = \"SQC\";\n    var connection = createConnection(connectionId, ConnectionKind.SONARCLOUD);\n    mockConnection(connection);\n    mockEnabledLanguages(Language.CS);\n\n    var props = underTest.getPlugins(connectionId).extraProperties();\n\n    assertThat(props)\n      .containsEntry(\"sonar.cs.internal.analyzerPath\", ossPath.toString())\n      .containsEntry(\"sonar.cs.internal.shouldUseCsharpEnterprise\", \"true\");\n  }\n\n  @Test\n  void getPlugins_extraProperties_forCloud_ReturnsEnterpriseProperties() {\n    var connectionId = \"SQC\";\n    var connection = createConnection(connectionId, ConnectionKind.SONARCLOUD);\n    mockConnection(connection);\n    mockPlugin(PluginsService.CSHARP_ENTERPRISE_PLUGIN_ID, enterprisePath);\n    mockEnabledLanguages(Language.CS, Language.VBNET);\n\n    var props = underTest.getPlugins(connectionId).extraProperties();\n\n    assertThat(props)\n      .containsEntry(\"sonar.cs.internal.analyzerPath\", enterprisePath.toString())\n      .containsEntry(\"sonar.cs.internal.shouldUseCsharpEnterprise\", \"true\")\n      .containsEntry(\"sonar.cs.internal.shouldUseVbEnterprise\", \"true\");\n  }\n\n  @Test\n  void getPlugins_extraProperties_connectionIsToServer_Older_Than_10_8_ReturnsEnterpriseProperties() {\n    var connectionId = \"SQS\";\n    mockConnection(connectionId, ConnectionKind.SONARQUBE, Version.create(\"10.7\"));\n    mockPlugin(PluginsService.CSHARP_ENTERPRISE_PLUGIN_ID, enterprisePath);\n    mockEnabledLanguages(Language.CS, Language.VBNET);\n\n    var props = underTest.getPlugins(connectionId).extraProperties();\n\n    assertThat(props)\n      .containsEntry(\"sonar.cs.internal.analyzerPath\", enterprisePath.toString())\n      .containsEntry(\"sonar.cs.internal.shouldUseCsharpEnterprise\", \"true\")\n      .containsEntry(\"sonar.cs.internal.shouldUseVbEnterprise\", \"true\");\n  }\n\n  @Test\n  void getPlugins_extraProperties_connectionIsToServerWithRepackagedCsharpPlugin_ReturnsEnterprisePropertiesForCsharp() {\n    var connectionId = \"SQS\";\n    mockConnection(connectionId, ConnectionKind.SONARQUBE, Version.create(\"10.8\"));\n    mockPlugin(PluginsService.CSHARP_ENTERPRISE_PLUGIN_ID, enterprisePath);\n    mockEnabledLanguages(Language.CS, Language.VBNET);\n\n    var props = underTest.getPlugins(connectionId).extraProperties();\n\n    assertThat(props)\n      .containsEntry(\"sonar.cs.internal.analyzerPath\", enterprisePath.toString())\n      .containsEntry(\"sonar.cs.internal.shouldUseCsharpEnterprise\", \"true\")\n      .containsEntry(\"sonar.cs.internal.shouldUseVbEnterprise\", \"false\");\n  }\n\n  @Test\n  void getPlugins_extraProperties_connectionIsToServerWithRepackagedVbPlugin_ReturnsEnterprisePropertiesForVb() {\n    var connectionId = \"SQS\";\n    mockConnection(connectionId, ConnectionKind.SONARQUBE, Version.create(\"10.8\"));\n    mockPlugin(PluginsService.VBNET_ENTERPRISE_PLUGIN_ID);\n    mockEnabledLanguages(Language.CS, Language.VBNET);\n\n    var props = underTest.getPlugins(connectionId).extraProperties();\n\n    assertThat(props)\n      .containsEntry(\"sonar.cs.internal.analyzerPath\", ossPath.toString())\n      .containsEntry(\"sonar.cs.internal.shouldUseCsharpEnterprise\", \"false\")\n      .containsEntry(\"sonar.cs.internal.shouldUseVbEnterprise\", \"true\");\n  }\n\n  @Test\n  void should_return_list_size_equal_to_sonar_language_values() {\n    var connectionId = \"connection1\";\n    mockNoConnection(connectionId);\n\n    var result = underTest.getPluginStatuses(connectionId);\n\n    assertThat(result).hasSize(SonarLanguage.values().length);\n  }\n\n  @Test\n  void unloadPlugins_should_not_publish_event_when_no_plugins_were_loaded() {\n    var connectionId = \"connection1\";\n\n    underTest.unloadPlugins(connectionId);\n\n    verify(eventPublisher, never()).publishEvent(any());\n  }\n\n  @Test\n  void unloadPlugins_should_evict_connected_strategy_from_cache() {\n    var connectionId = \"connection1\";\n\n    underTest.unloadPlugins(connectionId);\n\n    verify(connectedArtifactsLoadingStrategyFactory).evict(connectionId);\n  }\n\n  @Test\n  void unloadEmbeddedPlugins_should_not_publish_event_when_no_embedded_plugins_were_loaded() {\n    underTest.unloadPlugins(null);\n\n    verify(eventPublisher, never()).publishEvent(any());\n  }\n\n  private void mockNoConnection(String connectionId) {\n    when(connectionStorage.serverInfo()).thenReturn(serverInfoStorage);\n    when(storageService.connection(connectionId)).thenReturn(connectionStorage);\n    when(connectionConfigurationStorage.getConnectionById(connectionId)).thenReturn(null);\n  }\n\n  private void mockConnection(String connectionId, ConnectionKind kind, Version version) {\n    var connection = createConnection(connectionId, kind);\n    mockConnection(connection);\n    mockConnectionVersion(version);\n  }\n\n  private AbstractConnectionConfiguration createConnection(String connectionId, ConnectionKind kind) {\n    var connection = mock(AbstractConnectionConfiguration.class);\n    when(connection.getConnectionId()).thenReturn(connectionId);\n    when(connection.getKind()).thenReturn(kind);\n    return connection;\n  }\n\n  private void mockConnection(AbstractConnectionConfiguration connection) {\n    when(connectionStorage.serverInfo()).thenReturn(serverInfoStorage);\n    when(storageService.connection(connection.getConnectionId())).thenReturn(connectionStorage);\n    when(connectionConfigurationStorage.getConnectionById(connection.getConnectionId())).thenReturn(connection);\n  }\n\n  private void mockPlugin(String pluginKey) {\n    mockPlugin(pluginKey, null);\n  }\n\n  private void mockPlugin(String pluginKey, @Nullable Path jarPath) {\n    var plugin = mock(StoredPlugin.class);\n    when(plugin.getKey()).thenReturn(pluginKey);\n    when(plugin.getJarPath()).thenReturn(jarPath);\n    when(pluginStorage.getStoredPlugins()).thenReturn(List.of(plugin));\n    when(pluginStorage.getStoredPluginsByKey()).thenReturn(Map.of(pluginKey, plugin));\n  }\n\n  private void mockConnectionVersion(Version version) {\n    var serverInfo = mock(StoredServerInfo.class);\n    when(serverInfo.version()).thenReturn(version);\n    when(serverInfoStorage.read()).thenReturn(Optional.of(serverInfo));\n  }\n\n  private void mockEnabledLanguages(Language... languages) {\n    when(initializeParams.getEnabledLanguagesInStandaloneMode()).thenReturn(Set.of(languages));\n    when(initializeParams.getBackendCapabilities()).thenReturn(Set.of());\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/plugin/loading/strategy/ConnectedArtifactsLoadingStrategyTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.loading.strategy;\n\nimport java.nio.file.Path;\nimport java.util.EnumSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPluginDependency;\nimport org.sonarsource.sonarlint.core.languages.LanguageSupportRepository;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactOrigin;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactState;\nimport org.sonarsource.sonarlint.core.plugin.source.AvailableArtifact;\nimport org.sonarsource.sonarlint.core.plugin.source.LoadResult;\nimport org.sonarsource.sonarlint.core.plugin.source.ResolvedArtifact;\nimport org.sonarsource.sonarlint.core.plugin.source.binaries.BinariesArtifactSource;\nimport org.sonarsource.sonarlint.core.plugin.source.server.ServerPluginSource;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.when;\n\nclass ConnectedArtifactsLoadingStrategyTest {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private ServerPluginSource serverSource;\n  private BinariesArtifactSource binariesSource;\n  private LanguageSupportRepository languageSupportRepository;\n  private InitializeParams params;\n\n  @BeforeEach\n  void setUp() {\n    serverSource = mock(ServerPluginSource.class);\n    binariesSource = mock(BinariesArtifactSource.class);\n    languageSupportRepository = mock(LanguageSupportRepository.class);\n    params = mock(InitializeParams.class);\n    when(params.getConnectedModeEmbeddedPluginPathsByKey()).thenReturn(Map.of());\n    when(serverSource.listAvailableArtifacts(any())).thenReturn(List.of());\n    when(serverSource.load(any())).thenAnswer(inv -> {\n      @SuppressWarnings(\"unchecked\") var keys = (Set<String>) inv.getArgument(0);\n      return new LoadResult(keys.stream().collect(Collectors.toMap(k -> k,\n        k -> new ResolvedArtifact(ArtifactState.DOWNLOADING, null, null, null, null))));\n    });\n    when(binariesSource.listAvailableArtifacts(any())).thenReturn(List.of());\n    when(binariesSource.load(any())).thenAnswer(inv -> {\n      @SuppressWarnings(\"unchecked\") var keys = (Set<String>) inv.getArgument(0);\n      return new LoadResult(keys.stream().collect(Collectors.toMap(k -> k,\n        k -> new ResolvedArtifact(ArtifactState.ACTIVE, null, ArtifactOrigin.ON_DEMAND, null, null))));\n    });\n    when(languageSupportRepository.getEnabledLanguagesInConnectedMode()).thenReturn(EnumSet.noneOf(SonarLanguage.class));\n  }\n\n  // --- Server plugin included ---\n\n  @Test\n  void resolvePlugins_should_include_java_from_server_when_listed_as_available() {\n    when(serverSource.listAvailableArtifacts(any())).thenReturn(List.of(new AvailableArtifact(SonarPlugin.JAVA.getKey(), null, false, Optional.empty())));\n    var strategy = createStrategy();\n\n    var result = strategy.resolveArtifacts();\n\n    assertThat(result.resolvedArtifactsByKey())\n      .containsEntry(SonarPlugin.JAVA.getKey(), new ResolvedArtifact(ArtifactState.DOWNLOADING, null, null, null, null));\n  }\n\n  // --- Binary fallback ---\n\n  @Test\n  void resolvePlugins_should_fall_back_to_binaries_when_server_does_not_list_language_plugin() {\n    when(binariesSource.listAvailableArtifacts(any())).thenReturn(List.of(new AvailableArtifact(SonarPlugin.JAVA.getKey(), null, false, Optional.empty())));\n    var strategy = createStrategy();\n\n    var result = strategy.resolveArtifacts();\n\n    assertThat(result.resolvedArtifactsByKey())\n      .containsEntry(SonarPlugin.JAVA.getKey(), new ResolvedArtifact(ArtifactState.ACTIVE, null, ArtifactOrigin.ON_DEMAND, null, null));\n  }\n\n  @Test\n  void resolvePlugins_should_not_include_plugin_not_listed_by_any_source() {\n    var strategy = createStrategy();\n\n    var result = strategy.resolveArtifacts();\n\n    assertThat(result.resolvedArtifactsByKey()).doesNotContainKey(SonarPlugin.COBOL.getKey());\n  }\n\n  // --- Enterprise deduplication (different-key variants: CS, VBNET) ---\n\n  @Test\n  void resolvePlugins_should_remove_base_key_when_enterprise_variant_is_present() {\n    when(binariesSource.listAvailableArtifacts(any())).thenReturn(List.of(new AvailableArtifact(SonarPlugin.CS_OSS.getKey(), null, false, Optional.empty())));\n    when(serverSource.listAvailableArtifacts(any())).thenReturn(List.of(new AvailableArtifact(SonarPlugin.CSHARP_ENTERPRISE.getKey(), null, true, Optional.empty())));\n    var strategy = createStrategy();\n\n    var result = strategy.resolveArtifacts();\n\n    assertThat(result.resolvedArtifactsByKey())\n      .containsKey(SonarPlugin.CSHARP_ENTERPRISE.getKey())\n      .doesNotContainKey(SonarPlugin.CS_OSS.getKey());\n    // binaries source is called with an empty set (pre-populate), but never with actual plugin keys\n    verify(binariesSource).load(Set.of());\n  }\n\n  // --- Enterprise priority override (same-key variants: GO, IAC, TEXT) ---\n\n  @Test\n  void resolvePlugins_should_prefer_server_enterprise_over_embedded_for_same_key_plugins() {\n    // Embedded has \"go\" (higher normal priority than server)\n    when(params.getConnectedModeEmbeddedPluginPathsByKey()).thenReturn(Map.of(SonarPlugin.GO.getKey(), Path.of(\"go-embedded.jar\")));\n    // Server also has \"go\", flagged as enterprise\n    when(serverSource.listAvailableArtifacts(any())).thenReturn(List.of(new AvailableArtifact(SonarPlugin.GO.getKey(), null, true, Optional.empty())));\n    var strategy = createStrategy();\n\n    var result = strategy.resolveArtifacts();\n\n    // Enterprise server must win over embedded\n    assertThat(result.resolvedArtifactsByKey())\n      .containsEntry(SonarPlugin.GO.getKey(), new ResolvedArtifact(ArtifactState.DOWNLOADING, null, null, null, null));\n    verify(serverSource).load(Set.of(SonarPlugin.GO.getKey()));\n  }\n\n  // --- Dependency removal (no dependent available) ---\n\n  @Test\n  void resolvePlugins_should_remove_dependency_when_dependent_plugin_is_not_available() {\n    when(binariesSource.listAvailableArtifacts(any())).thenReturn(List.of(\n      new AvailableArtifact(SonarPluginDependency.OMNISHARP_MONO.getKey(), null, false, Optional.of(SonarPluginDependency.OMNISHARP_MONO))));\n    var strategy = createStrategy();\n\n    var result = strategy.resolveArtifacts();\n\n    assertThat(result.resolvedArtifactsByKey()).doesNotContainKey(SonarPluginDependency.OMNISHARP_MONO.getKey());\n  }\n\n  // --- Plugin removal when required dependency is missing ---\n\n  @Test\n  void resolvePlugins_should_remove_plugin_when_a_required_dependency_is_not_available() {\n    when(serverSource.listAvailableArtifacts(any())).thenReturn(List.of(\n      new AvailableArtifact(SonarPlugin.SONARLINT_OMNISHARP.getKey(), null, false, Optional.of(SonarPlugin.SONARLINT_OMNISHARP))));\n    var strategy = createStrategy();\n\n    var result = strategy.resolveArtifacts();\n\n    assertThat(result.resolvedArtifactsByKey()).doesNotContainKey(SonarPlugin.SONARLINT_OMNISHARP.getKey());\n  }\n\n  @Test\n  void resolvePlugins_should_keep_plugin_when_all_required_dependencies_are_available() {\n    when(serverSource.listAvailableArtifacts(any())).thenReturn(List.of(\n      new AvailableArtifact(SonarPlugin.SONARLINT_OMNISHARP.getKey(), null, false, Optional.of(SonarPlugin.SONARLINT_OMNISHARP)),\n      new AvailableArtifact(SonarPlugin.CS_OSS.getKey(), null, false, Optional.of(SonarPlugin.CS_OSS))));\n    when(binariesSource.listAvailableArtifacts(any())).thenReturn(List.of(\n      new AvailableArtifact(SonarPluginDependency.OMNISHARP_MONO.getKey(), null, false, Optional.of(SonarPluginDependency.OMNISHARP_MONO)),\n      new AvailableArtifact(SonarPluginDependency.OMNISHARP_NET472.getKey(), null, false, Optional.of(SonarPluginDependency.OMNISHARP_NET472)),\n      new AvailableArtifact(SonarPluginDependency.OMNISHARP_NET6.getKey(), null, false, Optional.of(SonarPluginDependency.OMNISHARP_NET6))));\n    var strategy = createStrategy();\n\n    var result = strategy.resolveArtifacts();\n\n    assertThat(result.resolvedArtifactsByKey()).containsKey(SonarPlugin.SONARLINT_OMNISHARP.getKey());\n  }\n\n  @Test\n  void resolvePlugins_should_keep_dependency_when_dependent_plugin_is_available() {\n    when(binariesSource.listAvailableArtifacts(any())).thenReturn(List.of(\n      new AvailableArtifact(SonarPluginDependency.OMNISHARP_MONO.getKey(), null, false, Optional.of(SonarPluginDependency.OMNISHARP_MONO))));\n    when(serverSource.listAvailableArtifacts(any())).thenReturn(List.of(\n      new AvailableArtifact(SonarPlugin.SONARLINT_OMNISHARP.getKey(), null, false, Optional.of(SonarPlugin.SONARLINT_OMNISHARP))));\n    var strategy = createStrategy();\n\n    var result = strategy.resolveArtifacts();\n\n    assertThat(result.resolvedArtifactsByKey()).containsKey(SonarPluginDependency.OMNISHARP_MONO.getKey());\n  }\n\n  private ConnectedArtifactsLoadingStrategy createStrategy() {\n    return new ConnectedArtifactsLoadingStrategy(params, binariesSource, serverSource, languageSupportRepository);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/plugin/loading/strategy/StandaloneArtifactsLoadingStrategyTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.loading.strategy;\n\nimport java.util.EnumSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPluginDependency;\nimport org.sonarsource.sonarlint.core.languages.LanguageSupportRepository;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactOrigin;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactState;\nimport org.sonarsource.sonarlint.core.plugin.source.AvailableArtifact;\nimport org.sonarsource.sonarlint.core.plugin.source.LoadResult;\nimport org.sonarsource.sonarlint.core.plugin.source.ResolvedArtifact;\nimport org.sonarsource.sonarlint.core.plugin.source.binaries.BinariesArtifactSource;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass StandaloneArtifactsLoadingStrategyTest {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private BinariesArtifactSource binariesSource;\n  private LanguageSupportRepository languageSupportRepository;\n  private InitializeParams params;\n\n  @BeforeEach\n  void setUp() {\n    binariesSource = mock(BinariesArtifactSource.class);\n    languageSupportRepository = mock(LanguageSupportRepository.class);\n    params = mock(InitializeParams.class);\n    when(params.getEmbeddedPluginPaths()).thenReturn(java.util.Set.of());\n    when(params.getConnectedModeEmbeddedPluginPathsByKey()).thenReturn(Map.of());\n    when(binariesSource.listAvailableArtifacts(any())).thenReturn(List.of());\n    when(binariesSource.load(any())).thenAnswer(inv -> {\n      @SuppressWarnings(\"unchecked\") var keys = (Set<String>) inv.getArgument(0);\n      return new LoadResult(keys.stream().collect(Collectors.toMap(k -> k,\n        k -> new ResolvedArtifact(ArtifactState.ACTIVE, null, ArtifactOrigin.ON_DEMAND, null, null))));\n    });\n    when(languageSupportRepository.getEnabledLanguagesInStandaloneMode()).thenReturn(EnumSet.noneOf(SonarLanguage.class));\n    when(languageSupportRepository.isEnabledOnlyInConnectedMode(any())).thenReturn(false);\n  }\n\n  // --- Enterprise deduplication (different-key variants: CS, VBNET) ---\n\n  @Test\n  void resolvePlugins_should_remove_base_key_when_enterprise_variant_is_present() {\n    // Both the OSS base and the enterprise variant are available (e.g. base from binaries,\n    // enterprise from embedded). Only the enterprise variant should survive.\n    when(binariesSource.listAvailableArtifacts(any())).thenReturn(List.of(\n      new AvailableArtifact(SonarPlugin.CS_OSS.getKey(), null, false, Optional.of(SonarPlugin.CS_OSS)),\n      new AvailableArtifact(SonarPlugin.CSHARP_ENTERPRISE.getKey(), null, true, Optional.of(SonarPlugin.CSHARP_ENTERPRISE))));\n    var strategy = createStrategy();\n\n    var result = strategy.resolveArtifacts();\n\n    assertThat(result.resolvedArtifactsByKey())\n      .containsKey(SonarPlugin.CSHARP_ENTERPRISE.getKey())\n      .doesNotContainKey(SonarPlugin.CS_OSS.getKey());\n    // binaries must not be asked to load the non-enterprise key\n    verify(binariesSource).load(Set.of(SonarPlugin.CSHARP_ENTERPRISE.getKey()));\n  }\n\n  // --- Dependency removal (no dependent available) ---\n\n  @Test\n  void resolvePlugins_should_remove_dependency_when_dependent_plugin_is_not_available() {\n    when(binariesSource.listAvailableArtifacts(any())).thenReturn(List.of(\n      new AvailableArtifact(SonarPluginDependency.OMNISHARP_MONO.getKey(), null, false, Optional.of(SonarPluginDependency.OMNISHARP_MONO))));\n    var strategy = createStrategy();\n\n    var result = strategy.resolveArtifacts();\n\n    assertThat(result.resolvedArtifactsByKey()).doesNotContainKey(SonarPluginDependency.OMNISHARP_MONO.getKey());\n  }\n\n  @Test\n  void resolvePlugins_should_keep_dependency_when_dependent_plugin_is_available() {\n    when(binariesSource.listAvailableArtifacts(any())).thenReturn(List.of(\n      new AvailableArtifact(SonarPlugin.SONARLINT_OMNISHARP.getKey(), null, false, Optional.of(SonarPlugin.SONARLINT_OMNISHARP)),\n      new AvailableArtifact(SonarPlugin.CS_OSS.getKey(), null, false, Optional.of(SonarPlugin.CS_OSS)),\n      new AvailableArtifact(SonarPluginDependency.OMNISHARP_MONO.getKey(), null, false, Optional.of(SonarPluginDependency.OMNISHARP_MONO)),\n      new AvailableArtifact(SonarPluginDependency.OMNISHARP_NET472.getKey(), null, false, Optional.of(SonarPluginDependency.OMNISHARP_NET472)),\n      new AvailableArtifact(SonarPluginDependency.OMNISHARP_NET6.getKey(), null, false, Optional.of(SonarPluginDependency.OMNISHARP_NET6))));\n    var strategy = createStrategy();\n\n    var result = strategy.resolveArtifacts();\n\n    assertThat(result.resolvedArtifactsByKey()).containsKey(SonarPluginDependency.OMNISHARP_MONO.getKey());\n  }\n\n  // --- Plugin removal when required dependency is missing ---\n\n  @Test\n  void resolvePlugins_should_remove_plugin_when_a_required_dependency_is_not_available() {\n    when(binariesSource.listAvailableArtifacts(any())).thenReturn(List.of(\n      new AvailableArtifact(SonarPlugin.SONARLINT_OMNISHARP.getKey(), null, false, Optional.of(SonarPlugin.SONARLINT_OMNISHARP))));\n    var strategy = createStrategy();\n\n    var result = strategy.resolveArtifacts();\n\n    assertThat(result.resolvedArtifactsByKey()).doesNotContainKey(SonarPlugin.SONARLINT_OMNISHARP.getKey());\n  }\n\n  @Test\n  void resolvePlugins_should_keep_plugin_when_all_required_dependencies_are_available() {\n    when(binariesSource.listAvailableArtifacts(any())).thenReturn(List.of(\n      new AvailableArtifact(SonarPlugin.SONARLINT_OMNISHARP.getKey(), null, false, Optional.of(SonarPlugin.SONARLINT_OMNISHARP)),\n      new AvailableArtifact(SonarPlugin.CS_OSS.getKey(), null, false, Optional.of(SonarPlugin.CS_OSS)),\n      new AvailableArtifact(SonarPluginDependency.OMNISHARP_MONO.getKey(), null, false, Optional.of(SonarPluginDependency.OMNISHARP_MONO)),\n      new AvailableArtifact(SonarPluginDependency.OMNISHARP_NET472.getKey(), null, false, Optional.of(SonarPluginDependency.OMNISHARP_NET472)),\n      new AvailableArtifact(SonarPluginDependency.OMNISHARP_NET6.getKey(), null, false, Optional.of(SonarPluginDependency.OMNISHARP_NET6))));\n    var strategy = createStrategy();\n\n    var result = strategy.resolveArtifacts();\n\n    assertThat(result.resolvedArtifactsByKey()).containsKey(SonarPlugin.SONARLINT_OMNISHARP.getKey());\n  }\n\n  private StandaloneArtifactsLoadingStrategy createStrategy() {\n    return new StandaloneArtifactsLoadingStrategy(params, binariesSource, languageSupportRepository);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/plugin/source/BinariesArtifactTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.sonarsource.sonarlint.core.plugin.source.binaries.BinariesArtifact;\nimport uk.org.webcompere.systemstubs.jupiter.SystemStub;\nimport uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension;\nimport uk.org.webcompere.systemstubs.properties.SystemProperties;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@ExtendWith(SystemStubsExtension.class)\nclass BinariesArtifactTest {\n\n  @SystemStub\n  SystemProperties systemProperties;\n\n  @Test\n  void should_find_artifact_by_key() {\n    var actual = BinariesArtifact.findByKey(\"cpp\");\n\n    assertThat(actual).contains(BinariesArtifact.CFAMILY_PLUGIN);\n  }\n\n  @Test\n  void should_return_empty_for_unknown_key() {\n    var actual = BinariesArtifact.findByKey(\"unknown\");\n\n    assertThat(actual).isEmpty();\n  }\n\n  @Test\n  void should_return_empty_for_null_key() {\n    var actual = BinariesArtifact.findByKey(null);\n\n    assertThat(actual).isEmpty();\n  }\n\n  @Test\n  void should_return_version_from_properties() {\n    var actual = BinariesArtifact.CFAMILY_PLUGIN.version();\n\n    assertThat(actual).isEqualTo(\"6.80.0.98490\");\n  }\n\n  @Test\n  void should_use_default_base_url() {\n    var actual = BinariesArtifact.CFAMILY_PLUGIN.urlPattern();\n\n    assertThat(actual).isEqualTo(\"https://binaries.sonarsource.com/CommercialDistribution/sonar-cfamily-plugin/sonar-cfamily-plugin-%s.jar\");\n  }\n\n  @Test\n  void should_use_overridden_base_url_when_system_property_set() {\n    systemProperties.set(BinariesArtifact.PROPERTY_URL_PATTERN, \"http://mock-server\");\n\n    var actual = BinariesArtifact.CFAMILY_PLUGIN.urlPattern();\n\n    assertThat(actual).isEqualTo(\"http://mock-server/CommercialDistribution/sonar-cfamily-plugin/sonar-cfamily-plugin-%s.jar\");\n  }\n\n  @Test\n  void should_return_artifact_key() {\n    assertThat(BinariesArtifact.CFAMILY_PLUGIN.artifactKey()).isEqualTo(\"cpp\");\n  }\n\n  @Test\n  void should_return_correct_signature_resource_paths() {\n    assertThat(BinariesArtifact.CFAMILY_PLUGIN.signatureResourcePath()).isEqualTo(\"ondemand/sonar-cpp-plugin.jar.asc\");\n    assertThat(BinariesArtifact.CSHARP_OSS.signatureResourcePath()).isEqualTo(\"ondemand/sonar-cs-plugin.jar.asc\");\n    assertThat(BinariesArtifact.OMNISHARP_MONO.signatureResourcePath()).isEqualTo(\"ondemand/omnisharp-mono.tar.gz.asc\");\n    assertThat(BinariesArtifact.OMNISHARP_NET472.signatureResourcePath()).isEqualTo(\"ondemand/omnisharp-net472.tar.gz.asc\");\n    assertThat(BinariesArtifact.OMNISHARP_NET6.signatureResourcePath()).isEqualTo(\"ondemand/omnisharp-net6.0.tar.gz.asc\");\n  }\n\n  @Test\n  void should_return_omnisharp_version_from_properties() {\n    assertThat(BinariesArtifact.OMNISHARP_MONO.version()).isEqualTo(\"1.39.15\");\n    assertThat(BinariesArtifact.OMNISHARP_NET472.version()).isEqualTo(\"1.39.15\");\n    assertThat(BinariesArtifact.OMNISHARP_NET6.version()).isEqualTo(\"1.39.15\");\n  }\n\n  @Test\n  void should_return_correct_omnisharp_url_patterns() {\n    assertThat(BinariesArtifact.OMNISHARP_MONO.urlPattern())\n      .isEqualTo(\"https://binaries.sonarsource.com/OmniSharp-Roslyn/%s/omnisharp-mono.tar.gz\");\n    assertThat(BinariesArtifact.OMNISHARP_NET472.urlPattern())\n      .isEqualTo(\"https://binaries.sonarsource.com/OmniSharp-Roslyn/%s/omnisharp-net472.tar.gz\");\n    assertThat(BinariesArtifact.OMNISHARP_NET6.urlPattern())\n      .isEqualTo(\"https://binaries.sonarsource.com/OmniSharp-Roslyn/%s/omnisharp-net6.0.tar.gz\");\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/plugin/source/binaries/BinariesArtifactSourceTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source.binaries;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.EnumSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\nimport java.util.jar.Attributes;\nimport java.util.jar.JarOutputStream;\nimport java.util.jar.Manifest;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.UserPaths;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.event.PluginStatusUpdateEvent;\nimport org.sonarsource.sonarlint.core.http.HttpClient;\nimport org.sonarsource.sonarlint.core.http.HttpClientProvider;\nimport org.sonarsource.sonarlint.core.plugin.PluginStatus;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactOrigin;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactState;\nimport org.sonarsource.sonarlint.core.plugin.source.AvailableArtifact;\nimport org.sonarsource.sonarlint.core.plugin.source.ResolvedArtifact;\nimport org.springframework.context.ApplicationEventPublisher;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass BinariesArtifactSourceTest {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @TempDir\n  Path tempDir;\n\n  private HttpClientProvider httpClientProvider;\n  private ApplicationEventPublisher eventPublisher;\n  private List<PluginStatus> capturedStatuses;\n  private BinariesSignatureVerifier signatureVerifier;\n  private BinariesLocalCacheManager cacheManager;\n\n  @BeforeEach\n  void setUp() {\n    httpClientProvider = mock(HttpClientProvider.class);\n    eventPublisher = mock(ApplicationEventPublisher.class);\n    signatureVerifier = mock(BinariesSignatureVerifier.class);\n    cacheManager = mock(BinariesLocalCacheManager.class);\n    capturedStatuses = new CopyOnWriteArrayList<>();\n    doAnswer(inv -> {\n      capturedStatuses.addAll(inv.getArgument(0, PluginStatusUpdateEvent.class).newStatuses());\n      return null;\n    }).when(eventPublisher).publishEvent(any(PluginStatusUpdateEvent.class));\n  }\n\n  @Test\n  void load_should_return_empty_when_plugin_key_not_handled() {\n    var source = buildSource();\n\n    var result = source.load(Set.of(\"java\"));\n\n    assertThat(result.resolvedArtifactsByKey()).doesNotContainKey(\"java\");\n  }\n\n  @Test\n  void load_should_return_downloading_on_first_async_call_for_cfamily() {\n    var proceedLatch = new CountDownLatch(1);\n    mockBlockingHttpClient(proceedLatch);\n    var source = buildSource();\n    try {\n      var result = source.load(Set.of(\"cpp\"));\n\n      assertThat(result.resolvedArtifactsByKey().get(\"cpp\"))\n        .usingRecursiveComparison()\n        .ignoringFields(\"downloadFuture\")\n        .isEqualTo(downloading());\n    } finally {\n      proceedLatch.countDown();\n      await().atMost(5, TimeUnit.SECONDS).until(() -> !capturedStatuses.isEmpty());\n    }\n  }\n\n  @Test\n  void load_should_return_downloading_while_same_artifact_is_in_progress() {\n    var proceedLatch = new CountDownLatch(1);\n    mockBlockingHttpClient(proceedLatch);\n    var source = buildSource();\n    try {\n      source.load(Set.of(\"cpp\"));\n\n      var result = source.load(Set.of(\"cpp\"));\n\n      assertThat(result.resolvedArtifactsByKey().get(\"cpp\"))\n        .usingRecursiveComparison()\n        .ignoringFields(\"downloadFuture\")\n        .isEqualTo(downloading());\n    } finally {\n      proceedLatch.countDown();\n      await().atMost(5, TimeUnit.SECONDS).until(() -> !capturedStatuses.isEmpty());\n    }\n  }\n\n  @Test\n  void load_should_fire_failed_event_on_async_download_error() {\n    var httpClient = mock(HttpClient.class);\n    when(httpClient.get(anyString())).thenThrow(new RuntimeException(\"Connection refused\"));\n    when(httpClientProvider.getHttpClientWithoutAuth()).thenReturn(httpClient);\n    var source = buildSource();\n\n    source.load(Set.of(\"cpp\"));\n\n    await().atMost(5, TimeUnit.SECONDS).until(() -> capturedStatuses.size() == 3);\n    assertThat(capturedStatuses).containsExactlyInAnyOrder(\n      failedStatus(SonarLanguage.C),\n      failedStatus(SonarLanguage.CPP),\n      failedStatus(SonarLanguage.OBJC));\n  }\n\n  @Test\n  void load_should_fire_failed_event_when_signature_verification_fails() throws Exception {\n    mockSuccessfulHttpClient();\n    when(signatureVerifier.verify(any(Path.class), any(BinariesArtifact.class))).thenReturn(false);\n    var source = buildSource();\n\n    source.load(Set.of(\"cpp\"));\n\n    await().atMost(5, TimeUnit.SECONDS).until(() -> capturedStatuses.size() == 3);\n    assertThat(capturedStatuses).containsExactlyInAnyOrder(\n      failedStatus(SonarLanguage.C),\n      failedStatus(SonarLanguage.CPP),\n      failedStatus(SonarLanguage.OBJC));\n  }\n\n  @Test\n  void load_should_fire_active_event_covering_all_languages_on_successful_async_download() throws Exception {\n    mockSuccessfulHttpClient();\n    when(signatureVerifier.verify(any(Path.class), any(BinariesArtifact.class))).thenReturn(true);\n    var source = buildSource();\n\n    source.load(Set.of(\"cpp\"));\n\n    await().atMost(10, TimeUnit.SECONDS).until(() -> capturedStatuses.size() == 3);\n    var artifactVersion = BinariesArtifact.CFAMILY_PLUGIN.version();\n    var pluginPath = tempDir.resolve(\"ondemand-plugins\").resolve(\"cpp\").resolve(artifactVersion)\n      .resolve(\"sonar-cpp-plugin-\" + artifactVersion + \".jar\");\n    assertThat(capturedStatuses).containsExactlyInAnyOrder(\n      activeStatus(SonarLanguage.C, pluginPath),\n      activeStatus(SonarLanguage.CPP, pluginPath),\n      activeStatus(SonarLanguage.OBJC, pluginPath));\n    // cleanupOldVersions must receive the artifact-key directory (.../ondemand-plugins/cpp/),\n    // not the version directory or the JAR's parent\n    verify(cacheManager).cleanupOldVersions(\n      tempDir.resolve(\"ondemand-plugins\").resolve(\"cpp\"),\n      artifactVersion);\n  }\n\n  @Test\n  void load_should_return_active_on_warm_startup_when_omnisharp_directory_exists_and_is_non_empty() throws Exception {\n    var source = buildSource();\n    var artifactVersion = BinariesArtifact.OMNISHARP_MONO.version();\n    var omnisharpDir = tempDir.resolve(\"ondemand-plugins\").resolve(\"omnisharp-mono\").resolve(artifactVersion);\n    Files.createDirectories(omnisharpDir);\n    Files.createFile(omnisharpDir.resolve(\"OmniSharp.exe\"));\n\n    var result = source.load(Set.of(\"omnisharp-mono\"));\n\n    assertThat(result.resolvedArtifactsByKey().get(\"omnisharp-mono\"))\n      .usingRecursiveComparison()\n      .ignoringFields(\"downloadFuture\")\n      .isEqualTo(new ResolvedArtifact(ArtifactState.ACTIVE, omnisharpDir, ArtifactOrigin.ON_DEMAND, Version.create(artifactVersion), null));\n  }\n\n  @Test\n  void load_should_re_download_when_omnisharp_directory_is_empty() throws Exception {\n    var proceedLatch = new CountDownLatch(1);\n    mockBlockingHttpClient(proceedLatch);\n    var source = buildSource();\n    var artifactVersion = BinariesArtifact.OMNISHARP_MONO.version();\n    var omnisharpDir = tempDir.resolve(\"ondemand-plugins\").resolve(\"omnisharp-mono\").resolve(artifactVersion);\n    Files.createDirectories(omnisharpDir);\n\n    try {\n      var result = source.load(Set.of(\"omnisharp-mono\"));\n\n      assertThat(result.resolvedArtifactsByKey().get(\"omnisharp-mono\"))\n        .usingRecursiveComparison()\n        .ignoringFields(\"downloadFuture\")\n        .isEqualTo(downloading());\n    } finally {\n      proceedLatch.countDown();\n      await().atMost(5, TimeUnit.SECONDS).until(() -> !capturedStatuses.isEmpty());\n    }\n  }\n\n  @Test\n  void list_AvailablePlugins_should_return_entries_for_all_plugins() throws Exception {\n    mockSuccessfulHttpClient();\n    when(signatureVerifier.verify(any(Path.class), any(BinariesArtifact.class))).thenReturn(true);\n    var source = buildSource();\n\n    var listed = source.listAvailableArtifacts(EnumSet.allOf(SonarLanguage.class));\n    // Only one unique plugin key for C-family: \"cpp\"\n    assertThat(listed).hasSize(5);\n    assertThat(listed)\n      .extracting(AvailableArtifact::key)\n      .containsOnly(\"cpp\", \"csharp\", \"omnisharp-mono\", \"omnisharp-net472\", \"omnisharp-net6\");\n  }\n\n  private BinariesArtifactSource buildSource() {\n    var userPaths = mock(UserPaths.class);\n    when(userPaths.getStorageRoot()).thenReturn(tempDir);\n    return new BinariesArtifactSource(userPaths, httpClientProvider, eventPublisher, Executors.newCachedThreadPool(), signatureVerifier, cacheManager);\n  }\n\n  private void mockSuccessfulHttpClient() throws Exception {\n    var jarBytes = createMinimalPluginJarBytes(\"cpp\", \"1.0.0\");\n    var httpClient = mock(HttpClient.class);\n    var response = mock(HttpClient.Response.class);\n    when(response.code()).thenReturn(200);\n    when(response.isSuccessful()).thenReturn(true);\n    when(response.bodyAsStream()).thenReturn(new ByteArrayInputStream(jarBytes));\n    when(httpClient.get(anyString())).thenReturn(response);\n    when(httpClientProvider.getHttpClientWithoutAuth()).thenReturn(httpClient);\n  }\n\n  private void mockBlockingHttpClient(CountDownLatch proceedLatch) {\n    var httpClient = mock(HttpClient.class);\n    var response = mock(HttpClient.Response.class);\n    when(response.isSuccessful()).thenReturn(true);\n    when(response.code()).thenReturn(200);\n    when(response.bodyAsStream()).thenAnswer(inv -> awaitAndReturnEmpty(proceedLatch));\n    when(httpClient.get(anyString())).thenReturn(response);\n    when(httpClientProvider.getHttpClientWithoutAuth()).thenReturn(httpClient);\n  }\n\n  private static InputStream awaitAndReturnEmpty(CountDownLatch latch) throws InterruptedException {\n    latch.await();\n    return InputStream.nullInputStream();\n  }\n\n  private static byte[] createMinimalPluginJarBytes(String pluginKey, String pluginVersion) throws IOException {\n    var tempJar = Files.createTempFile(\"test-plugin\", \".jar\");\n    try {\n      var manifest = new Manifest();\n      manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, \"1.0\");\n      manifest.getMainAttributes().putValue(\"Plugin-Key\", pluginKey);\n      manifest.getMainAttributes().putValue(\"Plugin-Version\", pluginVersion);\n      try (var jos = new JarOutputStream(Files.newOutputStream(tempJar), manifest)) {\n        // minimal JAR with only the manifest\n      }\n      return Files.readAllBytes(tempJar);\n    } finally {\n      Files.deleteIfExists(tempJar);\n    }\n  }\n\n  private static ResolvedArtifact downloading() {\n    return new ResolvedArtifact(ArtifactState.DOWNLOADING, null, null, null, null);\n  }\n\n  private static PluginStatus activeStatus(SonarLanguage lang, Path path) {\n    return PluginStatus.forLanguage(lang, ArtifactState.ACTIVE, ArtifactOrigin.ON_DEMAND,\n      Version.create(BinariesArtifact.CFAMILY_PLUGIN.version()), null, path, null);\n  }\n\n  private static PluginStatus failedStatus(SonarLanguage lang) {\n    return PluginStatus.forLanguage(lang, ArtifactState.FAILED, null, null, null, null, null);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/plugin/source/binaries/BinariesLocalCacheManagerTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source.binaries;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.attribute.FileTime;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass BinariesLocalCacheManagerTest {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @TempDir\n  Path tempDir;\n\n  private final BinariesLocalCacheManager underTest = new BinariesLocalCacheManager();\n\n  @Test\n  void should_do_nothing_when_cache_directory_does_not_exist() {\n    var missing = tempDir.resolve(\"does-not-exist\");\n\n    Assertions.assertDoesNotThrow(() -> underTest.cleanupOldVersions(missing, \"1.0\"));\n  }\n\n  @Test\n  void should_not_delete_current_version_directory() throws IOException {\n    var cacheDir = tempDir.resolve(\"cpp\");\n    var currentVersionDir = cacheDir.resolve(\"1.0\");\n    Files.createDirectories(currentVersionDir);\n    setOldModificationTime(currentVersionDir);\n\n    underTest.cleanupOldVersions(cacheDir, \"1.0\");\n\n    assertThat(currentVersionDir).exists();\n  }\n\n  @Test\n  void should_delete_old_version_directory() throws IOException {\n    var cacheDir = tempDir.resolve(\"cpp\");\n    var oldVersionDir = cacheDir.resolve(\"0.9\");\n    Files.createDirectories(oldVersionDir);\n    setOldModificationTime(oldVersionDir);\n\n    underTest.cleanupOldVersions(cacheDir, \"1.0\");\n\n    assertThat(oldVersionDir).doesNotExist();\n  }\n\n  @Test\n  void should_not_delete_recently_modified_version_directory() throws IOException {\n    var cacheDir = tempDir.resolve(\"cpp\");\n    var recentVersionDir = cacheDir.resolve(\"0.9\");\n    Files.createDirectories(recentVersionDir);\n    // Modification time is \"now\" by default — well within retention period\n\n    underTest.cleanupOldVersions(cacheDir, \"1.0\");\n\n    assertThat(recentVersionDir).exists();\n  }\n\n  @Test\n  void should_delete_only_old_version_directories_in_mixed_scenario() throws IOException {\n    var cacheDir = tempDir.resolve(\"cpp\");\n    var currentDir = cacheDir.resolve(\"1.0\");\n    var oldDir = cacheDir.resolve(\"0.8\");\n    var recentDir = cacheDir.resolve(\"0.9\");\n    Files.createDirectories(currentDir);\n    Files.createDirectories(oldDir);\n    Files.createDirectories(recentDir);\n    setOldModificationTime(currentDir); // current is old but should be kept by name\n    setOldModificationTime(oldDir);\n    // recentDir stays with current modification time\n\n    underTest.cleanupOldVersions(cacheDir, \"1.0\");\n\n    assertThat(currentDir).exists();\n    assertThat(oldDir).doesNotExist();\n    assertThat(recentDir).exists();\n  }\n\n  @Test\n  void should_do_nothing_when_cache_directory_is_empty() throws IOException {\n    var cacheDir = tempDir.resolve(\"cpp\");\n    Files.createDirectories(cacheDir);\n\n    underTest.cleanupOldVersions(cacheDir, \"1.0\");\n\n    assertThat(cacheDir).exists();\n  }\n\n  private static void setOldModificationTime(Path path) throws IOException {\n    var oldTime = Instant.now().minus(61, ChronoUnit.DAYS);\n    Files.setLastModifiedTime(path, FileTime.from(oldTime));\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/plugin/source/binaries/BinariesSignatureVerifierTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source.binaries;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.jar.Attributes;\nimport java.util.jar.JarOutputStream;\nimport java.util.jar.Manifest;\nimport org.assertj.core.api.Assertions;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass BinariesSignatureVerifierTest {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @TempDir\n  Path tempDir;\n\n  private final BinariesSignatureVerifier underTest = new BinariesSignatureVerifier();\n\n  @Test\n  void should_return_false_when_jar_signature_not_found() throws IOException {\n    var jarPath = createMinimalPluginJar(\"nonexistent\", \"1.0.0\");\n\n    assertThat(underTest.verify(jarPath, \"nonexistent\")).isFalse();\n  }\n\n  @Test\n  void should_return_false_when_jar_is_tampered() throws IOException {\n    var tamperedJar = tempDir.resolve(\"sonar-cpp-plugin-tampered.jar\");\n    Files.write(tamperedJar, \"tampered content\".getBytes());\n\n    Assertions.assertThat(underTest.verify(tamperedJar, BinariesArtifact.CFAMILY_PLUGIN)).isFalse();\n  }\n\n  @ParameterizedTest\n  @ValueSource(strings = {\"cpp-unknownkey\", \"cpp-corrupt\", \"cpp-nosig\"})\n  void should_return_false_for_invalid_signatures(String pluginKey) throws IOException {\n    var jarPath = createMinimalPluginJar(pluginKey, \"1.0.0\");\n\n    // Verify it fails with a nonexistent signature path\n    assertThat(underTest.verify(jarPath, \"ondemand/sonar-cpp-plugin-nonexistent.jar.asc\")).isFalse();\n  }\n\n  private Path createMinimalPluginJar(String pluginKey, String pluginVersion) throws IOException {\n    var target = tempDir.resolve(\"sonar-\" + pluginKey + \"-plugin-test.jar\");\n    var manifest = new Manifest();\n    manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, \"1.0\");\n    manifest.getMainAttributes().putValue(\"Plugin-Key\", pluginKey);\n    manifest.getMainAttributes().putValue(\"Plugin-Version\", pluginVersion);\n    try (var jos = new JarOutputStream(Files.newOutputStream(target), manifest)) {\n      // minimal JAR with only the manifest\n    }\n    return target;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/plugin/source/embedded/EmbeddedPluginSourceTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source.embedded;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.jar.Attributes;\nimport java.util.jar.JarOutputStream;\nimport java.util.jar.Manifest;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\nimport org.sonarsource.sonarlint.core.plugin.source.AvailableArtifact;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass EmbeddedPluginSourceTest {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @TempDir\n  Path tempDir;\n\n  // --- forConnected() ---\n\n  @Test\n  void list_AvailablePlugins_should_return_active_embedded_in_connected_mode_when_plugin_key_is_in_map() throws IOException {\n    var javaJar = createJar(\"sonar-java-plugin.jar\");\n    var source = EmbeddedPluginSource.forConnected(mockParams(Set.of(), Map.of(SonarPlugin.JAVA.getKey(), javaJar)));\n\n    var result = source.listAvailableArtifacts(Set.of());\n\n    assertThat(result).hasSize(1);\n    assertThat(result.get(0).key()).isEqualTo(SonarPlugin.JAVA.getKey());\n  }\n\n  @Test\n  void list_AvailablePlugins_should_return_empty_in_connected_mode_when_plugin_key_is_absent() {\n    var source = EmbeddedPluginSource.forConnected(mockParams(Set.of(), Map.of()));\n\n    assertThat(source.listAvailableArtifacts(Set.of())).isEmpty();\n  }\n\n  @Test\n  void list_AvailablePlugins_should_include_companion_in_connected_mode_when_present_in_connected_mode_embedded_paths() throws IOException {\n    var omnisharpJar = createJar(\"sonarlint-omnisharp-plugin.jar\", \"omnisharp\");\n    var source = EmbeddedPluginSource.forConnected(mockParams(Set.of(), Map.of(\"omnisharp\", omnisharpJar)));\n\n    var result = source.listAvailableArtifacts(Set.of());\n\n    assertThat(result).hasSize(1);\n    assertThat(result.get(0).key()).isEqualTo(\"omnisharp\");\n  }\n\n  // --- forStandalone() ---\n\n  @Test\n  void list_AvailablePlugins_should_return_active_embedded_in_standalone_when_jar_contains_plugin_key_manifest() throws IOException {\n    var javaJar = createJar(\"sonar-java-plugin.jar\", \"java\");\n    var source = EmbeddedPluginSource.forStandalone(mockParams(Set.of(javaJar), Map.of()));\n\n    var result = source.listAvailableArtifacts(Set.of());\n\n    assertThat(result).hasSize(1);\n    assertThat(result.get(0).key()).isEqualTo(\"java\");\n  }\n\n  @Test\n  void list_AvailablePlugins_should_return_empty_in_standalone_when_embedded_paths_are_empty() {\n    var source = EmbeddedPluginSource.forStandalone(mockParams(Set.of(), Map.of()));\n\n    assertThat(source.listAvailableArtifacts(Set.of())).isEmpty();\n  }\n\n  @Test\n  void list_AvailablePlugins_should_throw_when_duplicate_plugin_keys_are_found() throws IOException {\n    var javaJar1 = createJar(\"sonar-java-plugin.jar\", \"java\");\n    var javaJar2 = createJar(\"sonar-java-plugin-2.jar\", \"java\");\n    var params = mockParams(Set.of(javaJar1, javaJar2), Map.of());\n\n    assertThatThrownBy(() -> EmbeddedPluginSource.forStandalone(params))\n      .isInstanceOf(IllegalArgumentException.class)\n      .hasMessageContaining(\"Multiple embedded plugins found with the same key for paths\");\n  }\n\n  // --- Companion plugins are included naturally in list() ---\n\n  @Test\n  void list_AvailablePlugins_should_include_companion_plugins_in_standalone_along_with_language_plugins() throws IOException {\n    var omnisharpJar = createJar(\"sonarlint-omnisharp-plugin.jar\", \"omnisharp\");\n    var javaJar = createJar(\"sonar-java-plugin.jar\", \"java\");\n    var source = EmbeddedPluginSource.forStandalone(mockParams(Set.of(omnisharpJar, javaJar), Map.of()));\n\n    var result = source.listAvailableArtifacts(Set.of());\n\n    assertThat(result).hasSize(2);\n    assertThat(result.stream().map(AvailableArtifact::key))\n      .containsExactlyInAnyOrder(\"omnisharp\", \"java\");\n  }\n\n  @Test\n  void list_AvailablePlugins_should_not_include_html_plugin_key_when_manifest_says_web() throws IOException {\n    var htmlJar = createJar(\"sonar-html-plugin.jar\", \"web\");\n    var source = EmbeddedPluginSource.forStandalone(mockParams(Set.of(htmlJar), Map.of()));\n\n    var result = source.listAvailableArtifacts(Set.of());\n\n    assertThat(result).hasSize(1);\n    assertThat(result.get(0).key()).isEqualTo(\"web\");\n  }\n\n  private static InitializeParams mockParams(Set<Path> embeddedPaths, Map<String, Path> connectedPaths) {\n    var params = mock(InitializeParams.class);\n    when(params.getEmbeddedPluginPaths()).thenReturn(embeddedPaths);\n    when(params.getConnectedModeEmbeddedPluginPathsByKey()).thenReturn(connectedPaths);\n    return params;\n  }\n\n  private Path createJar(String name) throws IOException {\n    return createJar(name, name.replace(\"sonar-\", \"\").replaceAll(\"-plugin\\\\.jar|-oss\\\\.jar|-standalone\\\\.jar\", \"\"));\n  }\n\n  private Path createJar(String name, String pluginKey) throws IOException {\n    var path = tempDir.resolve(name);\n    var manifest = new Manifest();\n    manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, \"1.0\");\n    manifest.getMainAttributes().putValue(\"Plugin-Key\", pluginKey);\n    try (var jos = new JarOutputStream(Files.newOutputStream(path), manifest)) {\n      // empty JAR with manifest\n    }\n    return path;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/plugin/source/server/ServerPluginDownloaderTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source.server;\n\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.commons.ConnectionKind;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\nimport org.sonarsource.sonarlint.core.event.PluginStatusUpdateEvent;\nimport org.sonarsource.sonarlint.core.plugin.PluginStatus;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactOrigin;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactState;\nimport org.sonarsource.sonarlint.core.repository.connection.AbstractConnectionConfiguration;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.serverapi.plugins.ServerPlugin;\nimport org.sonarsource.sonarlint.core.serverconnection.ConnectionStorage;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.PluginsStorage;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.springframework.context.ApplicationEventPublisher;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass ServerPluginDownloaderTest {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private StorageService storageService;\n  private ConnectionConfigurationRepository connectionRepo;\n  private SonarQubeClientManager sonarQubeClientManager;\n  private ApplicationEventPublisher eventPublisher;\n  private ExecutorService downloadExecutor;\n  private PluginsStorage pluginsStorage;\n  private Path javaJar;\n\n  @BeforeEach\n  void setUp() {\n    storageService = mock(StorageService.class);\n    connectionRepo = mock(ConnectionConfigurationRepository.class);\n    sonarQubeClientManager = mock(SonarQubeClientManager.class);\n    eventPublisher = mock(ApplicationEventPublisher.class);\n    var connectionStorage = mock(ConnectionStorage.class);\n    pluginsStorage = mock(PluginsStorage.class);\n\n    javaJar = Path.of(\"sonar-java-plugin.jar\");\n\n    when(storageService.connection(\"conn\")).thenReturn(connectionStorage);\n    when(connectionStorage.plugins()).thenReturn(pluginsStorage);\n\n    var connection = mock(AbstractConnectionConfiguration.class);\n    when(connection.getKind()).thenReturn(ConnectionKind.SONARQUBE);\n    when(connectionRepo.getConnectionById(\"conn\")).thenReturn(connection);\n  }\n\n  @Test\n  void should_publish_synced_event_after_language_plugin_download_succeeds() {\n    downloadExecutor = Executors.newSingleThreadExecutor();\n    try {\n      var downloader = new ServerPluginDownloader(storageService, sonarQubeClientManager, connectionRepo, eventPublisher, downloadExecutor);\n      when(pluginsStorage.getStoredPluginPathsByKey()).thenReturn(Map.of(SonarPlugin.JAVA.getKey(), javaJar));\n\n      var serverPlugin = mockServerPlugin(SonarPlugin.JAVA.getKey());\n      downloader.schedulePluginDownload(\"conn\", serverPlugin);\n\n      var expectedEvent = new PluginStatusUpdateEvent(\"conn\",\n        List.of(PluginStatus.forLanguage(SonarLanguage.JAVA, ArtifactState.SYNCED, ArtifactOrigin.SONARQUBE_SERVER, null, null, javaJar, null)));\n\n      await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> verify(eventPublisher).publishEvent(expectedEvent));\n    } finally {\n      downloadExecutor.shutdownNow();\n    }\n  }\n\n  @Test\n  void should_publish_failed_event_when_language_plugin_download_fails() {\n    downloadExecutor = Executors.newSingleThreadExecutor();\n    try {\n      var downloader = new ServerPluginDownloader(storageService, sonarQubeClientManager, connectionRepo, eventPublisher, downloadExecutor);\n      var serverPlugin = mockServerPlugin(SonarPlugin.JAVA.getKey());\n\n      doThrow(new RuntimeException(\"Download failed\")).when(sonarQubeClientManager).withActiveClient(any(), any());\n\n      downloader.schedulePluginDownload(\"conn\", serverPlugin);\n\n      var expectedEvent = new PluginStatusUpdateEvent(\"conn\", List.of(PluginStatus.failed(SonarLanguage.JAVA)));\n      await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> verify(eventPublisher).publishEvent(expectedEvent));\n    } finally {\n      downloadExecutor.shutdownNow();\n    }\n  }\n\n  @Test\n  void should_deduplicate_concurrent_plugin_downloads() {\n    var mockedExecutor = mock(ExecutorService.class);\n    var downloader = new ServerPluginDownloader(storageService, sonarQubeClientManager, connectionRepo, eventPublisher, mockedExecutor);\n    var serverPlugin = mockServerPlugin(SonarPlugin.JAVA.getKey());\n\n    downloader.schedulePluginDownload(\"conn\", serverPlugin);\n    downloader.schedulePluginDownload(\"conn\", serverPlugin);\n\n    verify(mockedExecutor, times(1)).execute(any(Runnable.class));\n  }\n\n  @Test\n  void should_perform_synchronous_download_and_return_state() {\n    downloadExecutor = mock(ExecutorService.class);\n    var downloader = new ServerPluginDownloader(storageService, sonarQubeClientManager, connectionRepo, eventPublisher, downloadExecutor);\n    var serverPlugin = mockServerPlugin(\"custom-plugin\");\n\n    var state = downloader.downloadPluginSync(\"conn\", serverPlugin);\n\n    assertThat(state).isEqualTo(ArtifactState.SYNCED);\n    verify(sonarQubeClientManager).withActiveClient(any(), any());\n  }\n\n  private static ServerPlugin mockServerPlugin(String pluginKey) {\n    var plugin = mock(ServerPlugin.class);\n    when(plugin.getKey()).thenReturn(pluginKey);\n    return plugin;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/plugin/source/server/ServerPluginSourceTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source.server;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactOrigin;\nimport org.sonarsource.sonarlint.core.plugin.source.ArtifactState;\nimport org.sonarsource.sonarlint.core.plugin.source.LoadResult;\nimport org.sonarsource.sonarlint.core.plugin.source.ResolvedArtifact;\nimport org.sonarsource.sonarlint.core.serverapi.exception.ServerRequestException;\nimport org.sonarsource.sonarlint.core.serverapi.plugins.ServerPlugin;\nimport org.sonarsource.sonarlint.core.serverconnection.ConnectionStorage;\nimport org.sonarsource.sonarlint.core.serverconnection.StoredPlugin;\nimport org.sonarsource.sonarlint.core.serverconnection.StoredServerInfo;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.PluginsStorage;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ServerInfoStorage;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass ServerPluginSourceTest {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @TempDir\n  Path tempDir;\n\n  private Path javaJar;\n  private Path iacJar;\n\n  private StorageService storageService;\n  private ConnectionStorage connectionStorage;\n  private PluginsStorage pluginsStorage;\n  private ServerPluginsCache serverPluginsCache;\n  private ServerPluginDownloader downloader;\n\n  @BeforeEach\n  void setUp() throws IOException {\n    javaJar = Files.createFile(tempDir.resolve(\"sonar-java-plugin.jar\"));\n    iacJar = Files.createFile(tempDir.resolve(\"sonar-iac-plugin.jar\"));\n\n    storageService = mock(StorageService.class);\n    connectionStorage = mock(ConnectionStorage.class);\n    pluginsStorage = mock(PluginsStorage.class);\n    serverPluginsCache = mock(ServerPluginsCache.class);\n    downloader = mock(ServerPluginDownloader.class);\n    when(downloader.schedulePluginDownload(any(), any())).thenReturn(null);\n    when(connectionStorage.plugins()).thenReturn(pluginsStorage);\n    when(pluginsStorage.getStoredPluginsByKey()).thenReturn(Map.of());\n  }\n\n  // --- load() — connected mode ---\n\n  @Test\n  void load_should_return_synced_from_storage_when_plugin_is_in_storage_but_not_on_server() {\n    mockStorage(\"conn\");\n    mockStoredPlugin(SonarPlugin.JAVA.getKey(), javaJar, \"hash\");\n    mockServerPlugins(\"conn\", List.of());\n    when(downloader.sourceFor(\"conn\")).thenReturn(ArtifactOrigin.SONARQUBE_SERVER);\n    var source = createSource(\"conn\");\n    var expected = resolved(ArtifactState.SYNCED, javaJar, ArtifactOrigin.SONARQUBE_SERVER);\n\n    var result = source.load(Set.of(SonarPlugin.JAVA.getKey()));\n\n    assertThat(result.resolvedArtifactsByKey()).containsEntry(SonarPlugin.JAVA.getKey(), expected);\n  }\n\n  @Test\n  void load_should_return_empty_when_plugin_is_not_in_storage_and_not_on_server() {\n    mockStorage(\"conn\");\n    when(pluginsStorage.getStoredPluginPathsByKey()).thenReturn(Map.of());\n    mockServerPlugins(\"conn\", List.of());\n    var source = createSource(\"conn\");\n\n    var result = source.load(Set.of(SonarPlugin.JAVA.getKey()));\n\n    assertThat(result.resolvedArtifactsByKey()).doesNotContainKey(SonarPlugin.JAVA.getKey());\n  }\n\n  @Test\n  void load_should_trigger_download_when_stored_jar_does_not_exist_on_disk() {\n    mockStorage(\"conn\");\n    mockStoredPlugin(SonarPlugin.JAVA.getKey(), tempDir.resolve(\"missing.jar\"), \"hash\");\n    var serverPlugin = mockServerPlugin(SonarPlugin.JAVA.getKey(), \"hash\");\n    mockServerPlugins(\"conn\", List.of(serverPlugin));\n    var source = createSource(\"conn\");\n\n    var result = source.load(Set.of(SonarPlugin.JAVA.getKey()));\n\n    assertThat(result.resolvedArtifactsByKey()).containsEntry(SonarPlugin.JAVA.getKey(), new ResolvedArtifact(ArtifactState.DOWNLOADING, null, null, null, null));\n    verify(downloader).schedulePluginDownload(\"conn\", serverPlugin);\n  }\n\n  @Test\n  void load_should_return_downloading_when_plugin_is_not_in_storage_but_on_server() {\n    mockStorage(\"conn\");\n    when(pluginsStorage.getStoredPluginPathsByKey()).thenReturn(Map.of());\n    var serverPlugin = mockServerPlugin(SonarPlugin.JAVA.getKey());\n    mockServerPlugins(\"conn\", List.of(serverPlugin));\n    var source = createSource(\"conn\");\n\n    var result = source.load(Set.of(SonarPlugin.JAVA.getKey()));\n\n    assertThat(result.resolvedArtifactsByKey()).containsEntry(SonarPlugin.JAVA.getKey(), new ResolvedArtifact(ArtifactState.DOWNLOADING, null, null, null, null));\n    verify(downloader).schedulePluginDownload(\"conn\", serverPlugin);\n  }\n\n  @Test\n  void load_should_call_cleanUpUnknownPlugins_with_empty_list_when_no_server_plugins_win() {\n    mockStorage(\"conn\");\n    mockServerPlugins(\"conn\", List.of());\n    var source = createSource(\"conn\");\n\n    source.load(Set.of());\n\n    verify(pluginsStorage).cleanUpUnknownPlugins(List.of());\n  }\n\n  @Test\n  void load_should_call_cleanUpUnknownPlugins_with_server_plugins_that_won() {\n    mockStorage(\"conn\");\n    var javaPlugin = mockServerPlugin(SonarPlugin.JAVA.getKey(), \"hash\");\n    mockServerPlugins(\"conn\", List.of(javaPlugin));\n    var source = createSource(\"conn\");\n\n    source.load(Set.of(SonarPlugin.JAVA.getKey()));\n\n    verify(pluginsStorage).cleanUpUnknownPlugins(List.of(javaPlugin));\n  }\n\n  @Test\n  void load_should_not_call_cleanUpUnknownPlugins_when_server_is_not_accessible() {\n    mockStorage(\"conn\");\n    when(serverPluginsCache.getPlugins(\"conn\")).thenThrow(new ServerRequestException(\"Connection refused\"));\n    var source = createSource(\"conn\");\n\n    source.load(Set.of(SonarPlugin.JAVA.getKey()));\n\n    verify(pluginsStorage, never()).cleanUpUnknownPlugins(any());\n  }\n\n  @Test\n  void load_should_return_empty_when_server_plugin_list_AvailablePlugins_request_fails_and_no_storage() {\n    mockStorage(\"conn\");\n    when(serverPluginsCache.getPlugins(\"conn\")).thenThrow(new ServerRequestException(\"Connection refused\"));\n    var source = createSource(\"conn\");\n\n    var result = source.load(Set.of(SonarPlugin.JAVA.getKey()));\n\n    assertThat(result.resolvedArtifactsByKey()).doesNotContainKey(SonarPlugin.JAVA.getKey());\n  }\n\n  @Test\n  void load_should_return_synced_from_storage_when_server_plugin_list_AvailablePlugins_request_fails() {\n    mockStorage(\"conn\");\n    mockStoredPlugin(SonarPlugin.JAVA.getKey(), javaJar, \"hash\");\n    when(serverPluginsCache.getPlugins(\"conn\")).thenThrow(new ServerRequestException(\"Connection refused\"));\n    when(downloader.sourceFor(\"conn\")).thenReturn(ArtifactOrigin.SONARQUBE_SERVER);\n    var source = createSource(\"conn\");\n\n    var result = source.load(Set.of(SonarPlugin.JAVA.getKey()));\n\n    assertThat(result.resolvedArtifactsByKey()).containsEntry(SonarPlugin.JAVA.getKey(), new ResolvedArtifact(ArtifactState.SYNCED, javaJar, ArtifactOrigin.SONARQUBE_SERVER, null, null));\n  }\n\n  @Test\n  void load_should_return_synced_with_sonarqube_server_source() {\n    mockStorage(\"conn\");\n    mockStoredPlugin(SonarPlugin.JAVA.getKey(), javaJar, \"hash\");\n    mockServerPlugins(\"conn\", List.of(mockServerPlugin(SonarPlugin.JAVA.getKey(), \"hash\")));\n    when(downloader.sourceFor(\"conn\")).thenReturn(ArtifactOrigin.SONARQUBE_SERVER);\n    var source = createSource(\"conn\");\n    var expected = resolved(ArtifactState.SYNCED, javaJar, ArtifactOrigin.SONARQUBE_SERVER);\n\n    var result = source.load(Set.of(SonarPlugin.JAVA.getKey()));\n\n    assertThat(result.resolvedArtifactsByKey()).containsEntry(SonarPlugin.JAVA.getKey(), expected);\n  }\n\n  @Test\n  void load_should_return_synced_with_sonarqube_cloud_source() {\n    mockStorage(\"cloud\");\n    mockStoredPlugin(SonarPlugin.JAVA.getKey(), javaJar, \"hash\");\n    mockServerPlugins(\"cloud\", List.of(mockServerPlugin(SonarPlugin.JAVA.getKey(), \"hash\")));\n    when(downloader.sourceFor(\"cloud\")).thenReturn(ArtifactOrigin.SONARQUBE_CLOUD);\n    var source = createSource(\"cloud\");\n    var expected = resolved(ArtifactState.SYNCED, javaJar, ArtifactOrigin.SONARQUBE_CLOUD);\n\n    var result = source.load(Set.of(SonarPlugin.JAVA.getKey()));\n\n    assertThat(result.resolvedArtifactsByKey()).containsEntry(SonarPlugin.JAVA.getKey(), expected);\n  }\n\n  // --- ANSIBLE/GITHUBACTIONS use \"iac\" plugin key ---\n\n  @Test\n  void load_should_resolve_iac_plugin_for_iac_language() {\n    mockStorage(\"conn\");\n    mockStoredPlugin(\"iac\", iacJar, \"hash\");\n    mockServerPlugins(\"conn\", List.of(mockServerPlugin(\"iac\", \"hash\")));\n    when(downloader.sourceFor(\"conn\")).thenReturn(ArtifactOrigin.SONARQUBE_SERVER);\n    var source = createSource(\"conn\");\n    var expected = resolved(ArtifactState.SYNCED, iacJar, ArtifactOrigin.SONARQUBE_SERVER);\n\n    var result = source.load(Set.of(SonarPlugin.IAC.getKey()));\n\n    assertThat(result.resolvedArtifactsByKey()).containsEntry(SonarPlugin.IAC.getKey(), expected);\n  }\n\n  // --- listAvailableArtifacts() ---\n\n  @Test\n  void listAvailableArtifacts_should_include_language_plugin_when_language_is_enabled() {\n    mockServerPlugins(\"conn\", List.of(mockServerPlugin(SonarPlugin.JAVA.getKey())));\n    var source = createSource(\"conn\");\n\n    var result = source.listAvailableArtifacts(Set.of(SonarLanguage.JAVA));\n\n    assertThat(result).hasSize(1);\n    assertThat(result.get(0).key()).isEqualTo(SonarPlugin.JAVA.getKey());\n  }\n\n  @Test\n  void listAvailableArtifacts_should_exclude_language_plugin_when_language_not_enabled() {\n    mockServerPlugins(\"conn\", List.of(mockServerPlugin(SonarPlugin.JAVA.getKey())));\n    var source = createSource(\"conn\");\n\n    var result = source.listAvailableArtifacts(Set.of());\n\n    assertThat(result).isEmpty();\n  }\n\n  @Test\n  void listAvailableArtifacts_should_include_csharpenterprise_when_csharp_is_enabled() {\n    var csEnterprise = mockServerPlugin(\"csharpenterprise\");\n    when(csEnterprise.isSonarLintSupported()).thenReturn(false);\n    mockServerPlugins(\"conn\", List.of(csEnterprise));\n    var source = createSource(\"conn\");\n\n    var result = source.listAvailableArtifacts(Set.of(SonarLanguage.CS));\n\n    assertThat(result).hasSize(1);\n    assertThat(result.get(0).key()).isEqualTo(\"csharpenterprise\");\n  }\n\n  @Test\n  void listAvailableArtifacts_should_exclude_csharpenterprise_when_csharp_not_enabled() {\n    var csEnterprise = mockServerPlugin(\"csharpenterprise\");\n    when(csEnterprise.isSonarLintSupported()).thenReturn(false);\n    mockServerPlugins(\"conn\", List.of(csEnterprise));\n    var source = createSource(\"conn\");\n\n    var result = source.listAvailableArtifacts(Set.of());\n\n    assertThat(result).isEmpty();\n  }\n\n  @Test\n  void listAvailableArtifacts_should_mark_csharpenterprise_as_enterprise() {\n    var csEnterprise = mockServerPlugin(\"csharpenterprise\");\n    mockServerPlugins(\"conn\", List.of(csEnterprise));\n    var source = createSource(\"conn\");\n\n    var result = source.listAvailableArtifacts(Set.of(SonarLanguage.CS));\n\n    assertThat(result).hasSize(1);\n    assertThat(result.get(0).isEnterprise()).isTrue();\n  }\n\n  @Test\n  void listAvailableArtifacts_should_include_companion_plugin_when_sonar_lint_supported() {\n    var companion = mockServerPlugin(\"my-companion-plugin\");\n    when(companion.isSonarLintSupported()).thenReturn(true);\n    mockServerPlugins(\"conn\", List.of(companion));\n    var source = createSource(\"conn\");\n\n    var result = source.listAvailableArtifacts(Set.of());\n\n    assertThat(result).hasSize(1);\n    assertThat(result.get(0).key()).isEqualTo(\"my-companion-plugin\");\n  }\n\n  @Test\n  void listAvailableArtifacts_should_exclude_companion_plugin_when_not_sonar_lint_supported() {\n    var companion = mockServerPlugin(\"some-unknown-plugin\");\n    when(companion.isSonarLintSupported()).thenReturn(false);\n    mockServerPlugins(\"conn\", List.of(companion));\n    var source = createSource(\"conn\");\n\n    var result = source.listAvailableArtifacts(Set.of());\n\n    assertThat(result).isEmpty();\n  }\n\n  @Test\n  void listAvailableArtifacts_should_return_empty_when_server_request_fails_and_nothing_stored() {\n    mockStorage(\"conn\");\n    when(serverPluginsCache.getPlugins(\"conn\")).thenThrow(new RuntimeException(\"Connection refused\"));\n    var source = createSource(\"conn\");\n\n    assertThat(source.listAvailableArtifacts(Set.of(SonarLanguage.JAVA))).isEmpty();\n  }\n\n  @Test\n  void listAvailableArtifacts_should_fall_back_to_stored_plugins_when_server_request_fails() {\n    mockStorage(\"conn\");\n    mockStoredPlugin(SonarPlugin.JAVA.getKey(), javaJar, \"hash\");\n    when(serverPluginsCache.getPlugins(\"conn\")).thenThrow(new RuntimeException(\"Connection refused\"));\n    var source = createSource(\"conn\");\n\n    var result = source.listAvailableArtifacts(Set.of(SonarLanguage.JAVA));\n\n    assertThat(result).hasSize(1);\n    assertThat(result.get(0).key()).isEqualTo(SonarPlugin.JAVA.getKey());\n  }\n\n  // --- Enterprise detection for same-key plugins (GO, IAC, TEXT) ---\n\n  @Test\n  void listAvailableArtifacts_should_mark_go_as_enterprise_when_server_version_qualifies() {\n    mockStorage(\"conn\");\n    mockServerVersion(\"2025.2\");\n    mockServerPlugins(\"conn\", List.of(mockServerPlugin(SonarPlugin.GO.getKey())));\n    when(downloader.sourceFor(\"conn\")).thenReturn(ArtifactOrigin.SONARQUBE_SERVER);\n    var source = createSource(\"conn\");\n\n    var result = source.listAvailableArtifacts(Set.of(SonarLanguage.GO));\n\n    assertThat(result).hasSize(1);\n    assertThat(result.get(0).key()).isEqualTo(SonarPlugin.GO.getKey());\n    assertThat(result.get(0).isEnterprise()).isTrue();\n  }\n\n  @Test\n  void listAvailableArtifacts_should_not_mark_go_as_enterprise_when_server_version_too_old() {\n    mockStorage(\"conn\");\n    mockServerVersion(\"2025.1\");\n    mockServerPlugins(\"conn\", List.of(mockServerPlugin(SonarPlugin.GO.getKey())));\n    when(downloader.sourceFor(\"conn\")).thenReturn(ArtifactOrigin.SONARQUBE_SERVER);\n    var source = createSource(\"conn\");\n\n    var result = source.listAvailableArtifacts(Set.of(SonarLanguage.GO));\n\n    assertThat(result).hasSize(1);\n    assertThat(result.get(0).isEnterprise()).isFalse();\n  }\n\n  @Test\n  void listAvailableArtifacts_should_mark_go_as_enterprise_on_sonarqube_cloud() {\n    mockServerPlugins(\"conn\", List.of(mockServerPlugin(SonarPlugin.GO.getKey())));\n    when(downloader.sourceFor(\"conn\")).thenReturn(ArtifactOrigin.SONARQUBE_CLOUD);\n    var source = createSource(\"conn\");\n\n    var result = source.listAvailableArtifacts(Set.of(SonarLanguage.GO));\n\n    assertThat(result).hasSize(1);\n    assertThat(result.get(0).isEnterprise()).isTrue();\n  }\n\n  @Test\n  void listAvailableArtifacts_should_not_mark_java_as_enterprise() {\n    mockServerPlugins(\"conn\", List.of(mockServerPlugin(SonarPlugin.JAVA.getKey())));\n    when(downloader.sourceFor(\"conn\")).thenReturn(ArtifactOrigin.SONARQUBE_CLOUD);\n    var source = createSource(\"conn\");\n\n    var result = source.listAvailableArtifacts(Set.of(SonarLanguage.JAVA));\n\n    assertThat(result).hasSize(1);\n    assertThat(result.get(0).isEnterprise()).isFalse();\n  }\n\n  private ServerPluginSource createSource(String connectionId) {\n    return new ServerPluginSource(connectionId, storageService, serverPluginsCache, downloader);\n  }\n\n  private void mockStorage(String connectionId) {\n    when(storageService.connection(connectionId)).thenReturn(connectionStorage);\n  }\n\n  private void mockServerVersion(String version) {\n    var serverInfoStorage = mock(ServerInfoStorage.class);\n    var storedServerInfo = mock(StoredServerInfo.class);\n    when(connectionStorage.serverInfo()).thenReturn(serverInfoStorage);\n    when(serverInfoStorage.read()).thenReturn(Optional.of(storedServerInfo));\n    when(storedServerInfo.version()).thenReturn(Version.create(version));\n  }\n\n  private void mockServerPlugins(String connectionId, List<ServerPlugin> plugins) {\n    when(serverPluginsCache.getPlugins(connectionId)).thenReturn(Optional.of(plugins));\n  }\n\n  private static ServerPlugin mockServerPlugin(String pluginKey) {\n    var plugin = mock(ServerPlugin.class);\n    when(plugin.getKey()).thenReturn(pluginKey);\n    return plugin;\n  }\n\n  private static ServerPlugin mockServerPlugin(String pluginKey, String hash) {\n    var plugin = mock(ServerPlugin.class);\n    when(plugin.getKey()).thenReturn(pluginKey);\n    when(plugin.getHash()).thenReturn(hash);\n    return plugin;\n  }\n\n  private void mockStoredPlugin(String pluginKey, Path jarPath, String hash) {\n    when(pluginsStorage.getStoredPluginsByKey()).thenReturn(Map.of(pluginKey, new StoredPlugin(pluginKey, hash, jarPath)));\n  }\n\n  private static ResolvedArtifact resolved(ArtifactState state, Path path, ArtifactOrigin source) {\n    return new ResolvedArtifact(state, path, source, null, null);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/plugin/source/server/ServerPluginsCacheTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.source.server;\n\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.function.Function;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationRemovedEvent;\nimport org.sonarsource.sonarlint.core.event.ConnectionConfigurationUpdatedEvent;\nimport org.sonarsource.sonarlint.core.serverapi.plugins.ServerPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass ServerPluginsCacheTest {\n\n  private SonarQubeClientManager sonarQubeClientManager;\n  private ServerPluginsCache cache;\n\n  @BeforeEach\n  void setUp() {\n    sonarQubeClientManager = mock(SonarQubeClientManager.class);\n    cache = new ServerPluginsCache(sonarQubeClientManager);\n  }\n\n  @Test\n  void should_return_cached_plugins_on_second_call() {\n    var plugins = List.of(mockPlugin(\"java\"));\n    mockApiResponse(\"conn\", plugins);\n\n    cache.getPlugins(\"conn\");\n    cache.getPlugins(\"conn\");\n\n    verifyApiCalledOnce(\"conn\");\n  }\n\n  @Test\n  void should_invalidate_on_connection_removed() {\n    var plugins = List.of(mockPlugin(\"java\"));\n    mockApiResponse(\"conn\", plugins);\n\n    cache.getPlugins(\"conn\");\n    cache.connectionRemoved(new ConnectionConfigurationRemovedEvent(\"conn\"));\n    cache.getPlugins(\"conn\");\n\n    verifyApiCalledTimes(\"conn\", 2);\n  }\n\n  @Test\n  void should_invalidate_on_connection_updated() {\n    var plugins = List.of(mockPlugin(\"java\"));\n    mockApiResponse(\"conn\", plugins);\n\n    cache.getPlugins(\"conn\");\n    cache.connectionUpdated(new ConnectionConfigurationUpdatedEvent(\"conn\"));\n    cache.getPlugins(\"conn\");\n\n    verifyApiCalledTimes(\"conn\", 2);\n  }\n\n\n  @Test\n  void should_cache_per_connection_id() {\n    mockApiResponse(\"conn1\", List.of(mockPlugin(\"java\")));\n    mockApiResponse(\"conn2\", List.of(mockPlugin(\"python\")));\n\n    var result1 = cache.getPlugins(\"conn1\");\n    var result2 = cache.getPlugins(\"conn2\");\n\n    assertThat(result1).isPresent();\n    assertThat(result2).isPresent();\n    assertThat(result1.get()).extracting(ServerPlugin::getKey).containsExactly(\"java\");\n    assertThat(result2.get()).extracting(ServerPlugin::getKey).containsExactly(\"python\");\n  }\n\n  @Test\n  void should_return_empty_when_connection_not_found() {\n    when(sonarQubeClientManager.withActiveClientAndReturn(eq(\"unknown\"), any(Function.class)))\n      .thenReturn(Optional.empty());\n\n    var result = cache.getPlugins(\"unknown\");\n\n    assertThat(result).isEmpty();\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private void mockApiResponse(String connectionId, List<ServerPlugin> plugins) {\n    when(sonarQubeClientManager.withActiveClientAndReturn(eq(connectionId), any(Function.class)))\n      .thenAnswer(invocation -> {\n        Function<Object, Object> fn = invocation.getArgument(1);\n        var api = mock(org.sonarsource.sonarlint.core.serverapi.ServerApi.class);\n        var pluginsApi = mock(org.sonarsource.sonarlint.core.serverapi.plugins.PluginsApi.class);\n        when(api.plugins()).thenReturn(pluginsApi);\n        when(pluginsApi.getInstalled(any())).thenReturn(plugins);\n        return Optional.of(fn.apply(api));\n      });\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private void verifyApiCalledOnce(String connectionId) {\n    verify(sonarQubeClientManager, times(1)).withActiveClientAndReturn(eq(connectionId), any(Function.class));\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private void verifyApiCalledTimes(String connectionId, int times) {\n    verify(sonarQubeClientManager, times(times)).withActiveClientAndReturn(eq(connectionId), any(Function.class));\n  }\n\n  private static ServerPlugin mockPlugin(String key) {\n    var plugin = mock(ServerPlugin.class);\n    when(plugin.getKey()).thenReturn(key);\n    return plugin;\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/progress/ClientAwareTaskManagerTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.progress;\n\nimport java.util.UUID;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.atomic.AtomicReference;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.api.progress.CanceledException;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass ClientAwareTaskManagerTest {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @Test\n  void it_should_throw_when_interrupted() throws InterruptedException {\n    var client = mock(SonarLintRpcClient.class);\n    when(client.startProgress(any())).thenReturn(new CompletableFuture<>());\n    var taskManager = new ClientAwareTaskManager(client);\n    var caughtException = new AtomicReference<Exception>();\n    var thread = new Thread(() -> {\n      try {\n        taskManager.createAndRunTask(\"configScopeId\", UUID.randomUUID(), \"Title\", null, true, true, progressIndicator -> {\n        }, new SonarLintCancelMonitor());\n      } catch (Exception e) {\n        caughtException.set(e);\n      }\n    });\n    thread.start();\n    Thread.sleep(500);\n\n    thread.interrupt();\n\n    await().untilAsserted(() -> assertThat(caughtException.get()).isInstanceOf(CanceledException.class));\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/remediation/aicodefix/AiCodeFixServiceTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.remediation.aicodefix;\n\nimport java.nio.file.Path;\nimport java.util.Optional;\nimport java.util.Set;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.SonarQubeClientManager;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabase;\nimport org.sonarsource.sonarlint.core.fs.ClientFileSystemService;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;\nimport org.sonarsource.sonarlint.core.repository.reporting.PreviouslyRaisedFindingsRepository;\nimport org.sonarsource.sonarlint.core.serverconnection.aicodefix.AiCodeFix;\nimport org.sonarsource.sonarlint.core.serverconnection.aicodefix.AiCodeFixRepository;\nimport org.sonarsource.sonarlint.core.tracking.TaintVulnerabilityTrackingService;\nimport org.springframework.context.ApplicationEventPublisher;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\n\n/**\n * This test verifies that AiCodeFixService.getFeature() reads settings\n * from the H2-backed AiCodeFixRepository (and does not rely on file-based StorageService).\n */\nclass AiCodeFixServiceTest {\n\n  @RegisterExtension\n  static SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @TempDir\n  Path tempDir;\n\n  private SonarLintDatabase db;\n\n  @AfterEach\n  void tearDown() {\n    if (db != null) {\n      db.shutdown();\n    }\n  }\n\n  @Test\n  void getFeature_reads_from_h2_repository() {\n    db = new SonarLintDatabase(tempDir);\n    var aiCodeFixRepo = new AiCodeFixRepository(db.dsl());\n\n    var connectionId = \"conn-1\";\n    var projectKey = \"project-A\";\n    aiCodeFixRepo.upsert(new AiCodeFix(\n      connectionId,\n      Set.of(\"xml:S3421\"),\n      true,\n      AiCodeFix.Enablement.ENABLED_FOR_ALL_PROJECTS,\n      Set.of(projectKey)));\n\n    var connectionRepository = mock(ConnectionConfigurationRepository.class);\n    var configurationRepository = mock(ConfigurationRepository.class);\n    var sonarQubeClientManager = mock(SonarQubeClientManager.class);\n    var previouslyRaisedFindingsRepository = mock(PreviouslyRaisedFindingsRepository.class);\n    var clientFileSystemService = mock(ClientFileSystemService.class);\n    var eventPublisher = mock(ApplicationEventPublisher.class);\n    var taintService = mock(TaintVulnerabilityTrackingService.class);\n\n    var service = new AiCodeFixService(connectionRepository, configurationRepository, sonarQubeClientManager, previouslyRaisedFindingsRepository, clientFileSystemService,\n      eventPublisher, taintService, aiCodeFixRepo);\n\n    var binding = new Binding(connectionId, projectKey);\n\n    Optional<AiCodeFixFeature> featureOpt = service.getFeature(binding);\n\n    assertThat(featureOpt).isPresent();\n    var feature = featureOpt.get();\n    assertThat(feature.settings().supportedRules()).contains(\"xml:S3421\");\n    assertThat(feature.settings().isFeatureEnabled(projectKey)).isTrue();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/repository/config/BindingConfigurationTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.repository.config;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass BindingConfigurationTest {\n\n  @Test\n  void test_isBound() {\n    BindingConfiguration noBinding = BindingConfiguration.noBinding();\n    assertThat(noBinding.isBound()).isFalse();\n\n    BindingConfiguration noProjectKey = new BindingConfiguration(\"connection\", null, true);\n    assertThat(noProjectKey.isBound()).isFalse();\n\n    BindingConfiguration noConnection = new BindingConfiguration(null, \"projectKey\", true);\n    assertThat(noConnection.isBound()).isFalse();\n\n    BindingConfiguration valid = new BindingConfiguration(\"connection\", \"projectKey\", true);\n    assertThat(valid.isBound()).isTrue();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/repository/config/ConfigurationRepositoryTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.repository.config;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ConfigurationRepositoryTest {\n\n  private ConfigurationRepository configurationRepository;\n\n  @BeforeEach\n  void prepare() {\n    configurationRepository = new ConfigurationRepository();\n  }\n\n  @Test\n  void it_should_not_find_any_binding_on_an_unknown_scope() {\n    var binding = configurationRepository.getEffectiveBinding(\"id\");\n\n    assertThat(binding).isEmpty();\n  }\n\n  @Test\n  void it_should_not_find_any_binding_on_an_unbound_scope() {\n    configurationRepository.addOrReplace(new ConfigurationScope(\"id\", null, true, \"name\"), BindingConfiguration.noBinding(true));\n\n    var binding = configurationRepository.getEffectiveBinding(\"id\");\n\n    assertThat(binding).isEmpty();\n  }\n\n  @Test\n  void it_should_consider_the_binding_configured_on_a_scope_as_effective() {\n    configurationRepository.addOrReplace(new ConfigurationScope(\"id\", null, true, \"name\"), new BindingConfiguration(\"connectionId\", \"projectKey\", true));\n\n    var binding = configurationRepository.getEffectiveBinding(\"id\");\n\n    assertThat(binding)\n      .hasValueSatisfying(b -> {\n        assertThat(b.connectionId()).isEqualTo(\"connectionId\");\n        assertThat(b.sonarProjectKey()).isEqualTo(\"projectKey\");\n      });\n  }\n\n  @Test\n  void it_should_get_the_effective_binding_from_parent_if_child_is_unbound() {\n    configurationRepository.addOrReplace(new ConfigurationScope(\"parentId\", null, true, \"name\"), new BindingConfiguration(\"connectionId\", \"projectKey\", true));\n    configurationRepository.addOrReplace(new ConfigurationScope(\"id\", \"parentId\", true, \"name\"), new BindingConfiguration(null, null, true));\n\n    var binding = configurationRepository.getEffectiveBinding(\"id\");\n\n    assertThat(binding)\n      .hasValueSatisfying(b -> {\n        assertThat(b.connectionId()).isEqualTo(\"connectionId\");\n        assertThat(b.sonarProjectKey()).isEqualTo(\"projectKey\");\n      });\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/repository/connection/SonarCloudConnectionConfigurationTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.repository.connection;\n\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.SonarCloudRegion;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SonarCloudConnectionConfigurationTest {\n\n  @Test\n  void testEqualsAndHashCode() {\n    var underTest = new SonarCloudConnectionConfiguration(SonarCloudRegion.EU.getProductionUri(), SonarCloudRegion.EU.getApiProductionUri(), \"id1\", \"org1\", SonarCloudRegion.EU, true);\n    assertThat(underTest)\n      .isEqualTo(new SonarCloudConnectionConfiguration(SonarCloudRegion.EU.getProductionUri(), SonarCloudRegion.EU.getApiProductionUri(), \"id1\", \"org1\", SonarCloudRegion.EU, true))\n      .isNotEqualTo(new SonarCloudConnectionConfiguration(SonarCloudRegion.EU.getProductionUri(), SonarCloudRegion.EU.getApiProductionUri(), \"id2\", \"org1\", SonarCloudRegion.EU, true))\n      .isNotEqualTo(new SonarCloudConnectionConfiguration(SonarCloudRegion.EU.getProductionUri(), SonarCloudRegion.EU.getApiProductionUri(), \"id1\", \"org2\", SonarCloudRegion.EU, true))\n      .isNotEqualTo(new SonarQubeConnectionConfiguration(\"id1\", \"http://server1\", true))\n      .hasSameHashCodeAs(new SonarCloudConnectionConfiguration(SonarCloudRegion.EU.getProductionUri(), SonarCloudRegion.EU.getApiProductionUri(), \"id1\", \"org1\", SonarCloudRegion.EU, true));\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/repository/connection/SonarQubeConnectionConfigurationTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.repository.connection;\n\nimport java.net.URI;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.SonarCloudRegion;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SonarQubeConnectionConfigurationTest {\n\n  @Test\n  void test_isSameServerUrl() {\n    var underTest = new SonarQubeConnectionConfiguration(\"id\", \"https://mycompany.org\", true);\n    assertThat(underTest.isSameServerUrl(\"https://mycompany.org\")).isTrue();\n    // URL are case insensitive\n    assertThat(underTest.isSameServerUrl(\"https://Mycompany.Org\")).isTrue();\n    // We can ignore trailing slash difference, as we are looking for a base URL\n    assertThat(underTest.isSameServerUrl(\"https://mycompany.org/\")).isTrue();\n    // Protocol difference, let's play it safe and not assume it is the same server\n    assertThat(underTest.isSameServerUrl(\"http://mycompany.org\")).isFalse();\n    // Different path\n    assertThat(underTest.isSameServerUrl(\"https://mycompany.org/sonarqube\")).isFalse();\n    // Different domain\n    assertThat(underTest.isSameServerUrl(\"https://sq.mycompany.org\")).isFalse();\n  }\n\n  @Test\n  void testEqualsAndHashCode() {\n    var underTest = new SonarQubeConnectionConfiguration(\"id1\", \"http://server1\", true);\n\n    assertThat(underTest)\n      .isEqualTo(new SonarQubeConnectionConfiguration(\"id1\", \"http://server1\", true))\n      .isNotEqualTo(new SonarQubeConnectionConfiguration(\"id2\", \"http://server1\", true))\n      .isNotEqualTo(new SonarQubeConnectionConfiguration(\"id1\", \"http://server2\", true))\n      .isNotEqualTo(new SonarCloudConnectionConfiguration(URI.create(\"http://server1\"), URI.create(\"http://server1\"), \"id1\", \"org1\", SonarCloudRegion.EU, true))\n      .hasSameHashCodeAs(new SonarQubeConnectionConfiguration(\"id1\", \"http://server1\", true));\n  }\n\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/repository/rules/RulesRepositoryTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.repository.rules;\n\nimport java.util.Optional;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.rules.RulesExtractionHelper;\nimport org.sonarsource.sonarlint.core.serverconnection.ConnectionStorage;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ServerInfoStorage;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\n\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.reset;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.when;\n\nclass RulesRepositoryTest {\n\n  @Test\n  void it_should_not_touch_storage_after_rules_are_lazily_loaded_in_connected_mode() {\n    var storageService = mock(StorageService.class);\n    var rulesRepository = new RulesRepository(mock(RulesExtractionHelper.class), storageService);\n    var connectionStorage = mock(ConnectionStorage.class);\n    when(storageService.connection(\"connection\")).thenReturn(connectionStorage);\n    var serverInfoStorage = mock(ServerInfoStorage.class);\n    when(connectionStorage.serverInfo()).thenReturn(serverInfoStorage);\n    when(serverInfoStorage.read()).thenReturn(Optional.empty());\n    rulesRepository.getRule(\"connection\", \"rule\");\n    reset(storageService);\n\n    rulesRepository.getRule(\"connection\", \"rule\");\n\n    verifyNoInteractions(storageService);\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/rules/RuleDetailsAdapterTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rules;\n\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.commons.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.commons.CleanCodeAttributeCategory;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.sonarsource.sonarlint.core.rules.RuleDetailsAdapter.adapt;\n\nclass RuleDetailsAdapterTests {\n\n  @Test\n  void it_should_adapt_all_cca_enum_values() {\n    for (var cca : CleanCodeAttribute.values()) {\n      var adapted = adapt(cca);\n      assertThat(adapted.name()).isEqualTo(cca.name());\n    }\n  }\n\n  @Test\n  void it_should_adapt_all_ccac_enum_values() {\n    for (var ccac : CleanCodeAttributeCategory.values()) {\n      var adapted = adapt(ccac);\n      assertThat(adapted.name()).isEqualTo(ccac.name());\n    }\n  }\n\n  @Test\n  void it_should_adapt_all_severity_enum_values() {\n    for (var s : IssueSeverity.values()) {\n      var adapted = adapt(s);\n      assertThat(adapted.name()).isEqualTo(s.name());\n    }\n  }\n\n  @Test\n  void it_should_adapt_all_ruletype_enum_values() {\n    for (var t : RuleType.values()) {\n      var adapted = adapt(t);\n      assertThat(adapted.name()).isEqualTo(t.name());\n    }\n  }\n\n  @Test\n  void it_should_adapt_all_language_enum_values() {\n    for (var l : SonarLanguage.values()) {\n      var adapted = adapt(l);\n      assertThat(adapted.name()).isEqualTo(l.name());\n    }\n  }\n\n  @Test\n  void it_should_adapt_all_impact_severity_enum_values() {\n    for (var is : ImpactSeverity.values()) {\n      var adapted = adapt(is);\n      assertThat(adapted.name()).isEqualTo(is.name());\n    }\n  }\n\n  @Test\n  void it_should_adapt_all_software_quality_enum_values() {\n    for (var sq : SoftwareQuality.values()) {\n      var adapted = adapt(sq);\n      assertThat(adapted.name()).isEqualTo(sq.name());\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/rules/RulesFixtures.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rules;\n\nimport org.sonar.api.rules.CleanCodeAttribute;\nimport org.sonar.api.rules.RuleType;\nimport org.sonar.api.server.rule.RuleParamType;\nimport org.sonar.api.server.rule.RulesDefinition;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.rule.extractor.SonarLintRuleDefinition;\n\npublic class RulesFixtures {\n  public static SonarLintRuleDefinition aRule() {\n    RulesDefinition.Context c = new RulesDefinition.Context();\n    var repository = c.createRepository(\"repo\", SonarLanguage.JAVA.getSonarLanguageKey());\n    repository.createRule(\"ruleKey\")\n      .setName(\"ruleName\")\n      .setType(RuleType.BUG)\n      .setCleanCodeAttribute(CleanCodeAttribute.TRUSTWORTHY)\n      .setHtmlDescription(\"Hello, world!\")\n        .createParam(\"paramKey\")\n          .setName(\"paramName\")\n          .setType(RuleParamType.TEXT)\n          .setDescription(\"paramDesc\")\n          .setDefaultValue(\"defaultValue\");\n    repository.done();\n    var rule = c.repositories().get(0).rule(\"ruleKey\");\n    return new SonarLintRuleDefinition(rule);\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/rules/RulesServiceTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rules;\n\nimport java.util.List;\nimport java.util.Map;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.repository.rules.RulesRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleDefinitionDto;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.common.ImpactPayload;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\nimport static org.sonarsource.sonarlint.core.rules.RulesFixtures.aRule;\n\nclass RulesServiceTests {\n\n  private RulesRepository rulesRepository;\n  private RulesExtractionHelper extractionHelper;\n\n  @BeforeEach\n  void prepare() {\n    extractionHelper = mock(RulesExtractionHelper.class);\n    var storageService = mock(StorageService.class);\n    rulesRepository = new RulesRepository(extractionHelper, storageService);\n  }\n\n  @Test\n  void it_should_return_all_embedded_rules_from_the_repository() {\n    when(extractionHelper.extractEmbeddedRules()).thenReturn(List.of(aRule()));\n    var rulesService = new RulesService(rulesRepository);\n\n    var embeddedRules = rulesService.listAllStandaloneRulesDefinitions().values();\n\n    assertThat(embeddedRules)\n      .extracting(RuleDefinitionDto::getKey, RuleDefinitionDto::getName)\n      .containsExactly(tuple(\"repo:ruleKey\", \"ruleName\"));\n  }\n\n  @Test\n  void it_should_only_override_overridden_impact_quality() {\n    Map<SoftwareQuality, ImpactSeverity> defaultImpacts = Map.of(\n      SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW,\n      SoftwareQuality.RELIABILITY, ImpactSeverity.MEDIUM);\n\n    List<ImpactPayload> overriddenImpacts = List.of(\n      new ImpactPayload(\"MAINTAINABILITY\", \"HIGH\"));\n\n    Map<SoftwareQuality, ImpactSeverity> result = RuleDetails.mergeImpacts(defaultImpacts, overriddenImpacts);\n    assertThat(result)\n      .containsEntry(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.HIGH)\n      .containsEntry(SoftwareQuality.RELIABILITY, ImpactSeverity.MEDIUM);\n  }\n\n  @Test\n  void it_should_work_when_no_overridden_impacts() {\n    Map<SoftwareQuality, ImpactSeverity> defaultImpacts = Map.of(\n      SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW,\n      SoftwareQuality.RELIABILITY, ImpactSeverity.MEDIUM);\n\n    Map<SoftwareQuality, ImpactSeverity> result = RuleDetails.mergeImpacts(defaultImpacts, List.of());\n\n    assertThat(result).isEqualTo(defaultImpacts);\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/sca/DependencyRiskServiceTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.sca;\n\nimport java.util.UUID;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.serverapi.EndpointParams;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass DependencyRiskServiceTests {\n\n  @Test\n  void testBuildSonarQubeServerScaUrl() {\n    var dependencyKey = UUID.randomUUID();\n    assertThat(DependencyRiskService.buildDependencyRiskBrowseUrl(\"myProject\", \"myBranch\", dependencyKey, new EndpointParams(\"http://foo.com\", \"\", false, null)))\n      .isEqualTo(String.format(\"http://foo.com/dependency-risks/%s/what?id=myProject&branch=myBranch\", dependencyKey));\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/smartnotifications/LastEventPollingTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.smartnotifications;\n\nimport java.nio.file.Path;\nimport java.time.ZonedDateTime;\nimport java.time.temporal.ChronoUnit;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.UserPaths;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.serverconnection.FileUtils;\nimport org.sonarsource.sonarlint.core.serverconnection.proto.Sonarlint;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ProtobufFileUtil;\nimport org.sonarsource.sonarlint.core.storage.SonarLintDatabaseService;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ProjectStoragePaths.encodeForFs;\n\nclass LastEventPollingTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private static final ZonedDateTime STORED_DATE = ZonedDateTime.now().minusDays(5);\n  private static final String PROJECT_KEY = \"projectKey\";\n  private static final String CONNECTION_ID = \"connectionId\";\n  private static final String FILE_NAME = \"last_event_polling.pb\";\n\n  @Test\n  void should_retrieve_stored_last_event_polling(@TempDir Path tmpDir) {\n    var storageFile = tmpDir.resolve(encodeForFs(CONNECTION_ID)).resolve(\"projects\").resolve(encodeForFs(PROJECT_KEY)).resolve(FILE_NAME);\n    FileUtils.mkdirs(storageFile.getParent());\n    ProtobufFileUtil.writeToFile(Sonarlint.LastEventPolling.newBuilder()\n      .setLastEventPolling(STORED_DATE.toInstant().toEpochMilli())\n      .build(), storageFile);\n    var databaseService = mock(SonarLintDatabaseService.class);\n    var storage = new StorageService(userPathsFrom(tmpDir), databaseService);\n    var lastEventPolling = new LastEventPolling(storage);\n\n    var result = lastEventPolling.getLastEventPolling(CONNECTION_ID, PROJECT_KEY);\n\n    assertThat(result).isEqualTo(STORED_DATE.truncatedTo(ChronoUnit.MILLIS));\n  }\n\n  @Test\n  void should_store_last_event_polling(@TempDir Path tmpDir) {\n    var databaseService = mock(SonarLintDatabaseService.class);\n    var storage = new StorageService(userPathsFrom(tmpDir), databaseService);\n    var lastEventPolling = new LastEventPolling(storage);\n    lastEventPolling.setLastEventPolling(STORED_DATE, CONNECTION_ID, PROJECT_KEY);\n\n    var result = lastEventPolling.getLastEventPolling(CONNECTION_ID, PROJECT_KEY);\n\n    assertThat(result).isEqualTo(STORED_DATE.truncatedTo(ChronoUnit.MILLIS));\n  }\n\n  @Test\n  void should_not_retrieve_stored_last_event_polling(@TempDir Path tmpDir) {\n    var databaseService = mock(SonarLintDatabaseService.class);\n    var storage = new StorageService(userPathsFrom(tmpDir), databaseService);\n    var lastEventPolling = new LastEventPolling(storage);\n\n    var result = lastEventPolling.getLastEventPolling(CONNECTION_ID, PROJECT_KEY);\n\n    assertThat(result).isBeforeOrEqualTo(ZonedDateTime.now()).isAfter(ZonedDateTime.now().minusSeconds(3));\n  }\n\n  private static UserPaths userPathsFrom(Path tmpDir) {\n    var mock = mock(UserPaths.class);\n    when(mock.getStorageRoot()).thenReturn(tmpDir);\n    when(mock.getWorkDir()).thenReturn(tmpDir);\n    return mock;\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/sync/BranchBindingTest.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.sync;\n\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.commons.Binding;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass BranchBindingTest {\n\n  @Test\n  void testEquals() {\n    var branchBinding = new BranchBinding(new Binding(\"connection\", \"projectKey\"), \"branch\");\n\n    assertThat(branchBinding.equals(branchBinding)).isTrue();\n    assertThat(branchBinding.equals(null)).isFalse();\n    assertThat(branchBinding.equals(new Object())).isFalse();\n    assertThat(branchBinding.equals(new BranchBinding(new Binding(\"connection2\", \"projectKey\"), \"branch\"))).isFalse();\n    assertThat(branchBinding.equals(new BranchBinding(new Binding(\"connection\", \"projectKey2\"), \"branch\"))).isFalse();\n    assertThat(branchBinding.equals(new BranchBinding(new Binding(\"connection\", \"projectKey\"), \"branch2\"))).isFalse();\n    assertThat(branchBinding.equals(new BranchBinding(new Binding(\"connection\", \"projectKey\"), \"branch\"))).isTrue();\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/tracking/KnownFindingMatchingAttributesMapperTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking;\n\nimport java.util.UUID;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.commons.KnownFinding;\nimport org.sonarsource.sonarlint.core.commons.LineWithHash;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.tracking.matching.KnownIssueMatchingAttributesMapper;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass KnownFindingMatchingAttributesMapperTests {\n\n  private final KnownFinding knownFinding = mock(KnownFinding.class);\n  private final KnownIssueMatchingAttributesMapper underTest = new KnownIssueMatchingAttributesMapper();\n\n  @BeforeEach\n  void prepare() {\n    when(knownFinding.getId()).thenReturn(UUID.randomUUID());\n    when(knownFinding.getMessage()).thenReturn(\"msg\");\n    when(knownFinding.getRuleKey()).thenReturn(\"ruleKey\");\n    when(knownFinding.getTextRangeWithHash()).thenReturn(new TextRangeWithHash(1, 2, 3, 4, \"rangehash\"));\n    when(knownFinding.getLineWithHash()).thenReturn(new LineWithHash(1, \"linehash\"));\n  }\n\n  @Test\n  void should_delegate_fields_to_server_issue() {\n    assertThat(underTest.getMessage(knownFinding)).isEqualTo(knownFinding.getMessage());\n    assertThat(underTest.getRuleKey(knownFinding)).isEqualTo(knownFinding.getRuleKey());\n    assertThat(underTest.getLine(knownFinding)).contains(knownFinding.getLineWithHash().getNumber());\n    assertThat(underTest.getLineHash(knownFinding)).contains(knownFinding.getLineWithHash().getHash());\n    assertThat(underTest.getTextRangeHash(knownFinding)).contains(knownFinding.getTextRangeWithHash().getHash());\n    assertThat(underTest.getServerIssueKey(knownFinding)).isEmpty();\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/tracking/LocalOnlyIssueMatchingAttributesMapperTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.UUID;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.LineWithHash;\nimport org.sonarsource.sonarlint.core.commons.LocalOnlyIssue;\nimport org.sonarsource.sonarlint.core.commons.LocalOnlyIssueResolution;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.tracking.matching.LocalOnlyIssueMatchingAttributesMapper;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass LocalOnlyIssueMatchingAttributesMapperTests {\n\n  private final LocalOnlyIssue localOnlyIssue = mock(LocalOnlyIssue.class);\n  private final LocalOnlyIssueMatchingAttributesMapper underTest = new LocalOnlyIssueMatchingAttributesMapper();\n\n  @BeforeEach\n  void prepare() {\n    when(localOnlyIssue.getId()).thenReturn(UUID.randomUUID());\n    when(localOnlyIssue.getMessage()).thenReturn(\"msg\");\n    when(localOnlyIssue.getResolution()).thenReturn(new LocalOnlyIssueResolution(IssueStatus.WONT_FIX, Instant.now(), null));\n    when(localOnlyIssue.getRuleKey()).thenReturn(\"ruleKey\");\n    when(localOnlyIssue.getServerRelativePath()).thenReturn(Path.of(\"file/path\"));\n    when(localOnlyIssue.getTextRangeWithHash()).thenReturn(new TextRangeWithHash(1, 2, 3, 4, \"rangehash\"));\n    when(localOnlyIssue.getLineWithHash()).thenReturn(new LineWithHash(1, \"linehash\"));\n  }\n\n  @Test\n  void should_delegate_fields_to_server_issue() {\n    assertThat(underTest.getMessage(localOnlyIssue)).isEqualTo(localOnlyIssue.getMessage());\n    assertThat(underTest.getRuleKey(localOnlyIssue)).isEqualTo(localOnlyIssue.getRuleKey());\n    assertThat(underTest.getLine(localOnlyIssue)).contains(localOnlyIssue.getLineWithHash().getNumber());\n    assertThat(underTest.getLineHash(localOnlyIssue)).contains(localOnlyIssue.getLineWithHash().getHash());\n    assertThat(underTest.getTextRangeHash(localOnlyIssue)).contains(localOnlyIssue.getTextRangeWithHash().getHash());\n    assertThat(underTest.getServerIssueKey(localOnlyIssue)).isEmpty();\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/tracking/ServerHotspotMatchingAttributesMapperTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.serverapi.hotspot.ServerHotspot;\nimport org.sonarsource.sonarlint.core.tracking.matching.ServerHotspotMatchingAttributesMapper;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ServerHotspotMatchingAttributesMapperTests {\n\n  @Test\n  void should_delegate_fields_to_server_issue() {\n    var creationDate = Instant.now();\n    var textRange = new TextRangeWithHash(1, 2, 3, 4, \"realHash\");\n    var serverHotspot = new ServerHotspot(\"key\", \"ruleKey\", \"message\", Path.of(\"filePath\"), textRange, creationDate, HotspotReviewStatus.SAFE, VulnerabilityProbability.LOW, null);\n\n    var underTest = new ServerHotspotMatchingAttributesMapper();\n\n    assertThat(underTest.getServerIssueKey(serverHotspot)).contains(\"key\");\n    assertThat(underTest.getMessage(serverHotspot)).isEqualTo(\"message\");\n    assertThat(underTest.getLineHash(serverHotspot)).isEmpty();\n    assertThat(underTest.getRuleKey(serverHotspot)).isEqualTo(\"ruleKey\");\n    assertThat(underTest.getLine(serverHotspot)).contains(1);\n    assertThat(underTest.getTextRangeHash(serverHotspot)).contains(textRange.getHash());\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/tracking/ServerIssueMatchingAttributesMapperTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.tracking;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.LineLevelServerIssue;\nimport org.sonarsource.sonarlint.core.tracking.matching.ServerIssueMatchingAttributesMapper;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass ServerIssueMatchingAttributesMapperTests {\n\n  private final LineLevelServerIssue serverIssue = mock(LineLevelServerIssue.class);\n  private final ServerIssueMatchingAttributesMapper underTest = new ServerIssueMatchingAttributesMapper();\n\n  @BeforeEach\n  void prepare() {\n    when(serverIssue.getLineHash()).thenReturn(\"blah\");\n    when(serverIssue.isResolved()).thenReturn(true);\n    when(serverIssue.getLine()).thenReturn(22);\n  }\n\n  @Test\n  void should_delegate_fields_to_server_issue() {\n    assertThat(underTest.getMessage(serverIssue)).isEqualTo(serverIssue.getMessage());\n    assertThat(underTest.getLineHash(serverIssue)).contains(serverIssue.getLineHash());\n    assertThat(underTest.getRuleKey(serverIssue)).isEqualTo(serverIssue.getRuleKey());\n    assertThat(underTest.getLine(serverIssue)).contains(serverIssue.getLine());\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/websocket/SonarCloudWebSocketTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.websocket;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.http.WebSocket;\nimport java.nio.channels.UnresolvedAddressException;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.function.Consumer;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.mockito.ArgumentCaptor;\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.http.WebSocketClient;\nimport org.sonarsource.sonarlint.core.serverapi.push.SonarServerEvent;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass SonarCloudWebSocketTests {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private WebSocketClient webSocketClient;\n  private WebSocket mockWebSocket;\n  private CompletableFuture<WebSocket> wsFuture;\n  private Consumer<SonarServerEvent> serverEventConsumer;\n  private Runnable connectionEndedRunnable;\n  private SonarCloudWebSocket sonarCloudWebSocket;\n  private URI testUri;\n\n  @BeforeEach\n  void setUp() {\n    webSocketClient = mock(WebSocketClient.class);\n    mockWebSocket = mock(WebSocket.class);\n    wsFuture = new CompletableFuture<>();\n    serverEventConsumer = mock(Consumer.class);\n    connectionEndedRunnable = mock(Runnable.class);\n    testUri = URI.create(\"wss://test.example.com/websocket\");\n  }\n\n  @Test\n  void should_create_websocket_connection_successfully() {\n    when(webSocketClient.createWebSocketConnection(any(URI.class), any(Consumer.class), any(Runnable.class))).thenReturn(wsFuture);\n    when(mockWebSocket.sendText(anyString(), eq(true))).thenReturn(CompletableFuture.completedFuture(null));\n\n    sonarCloudWebSocket = SonarCloudWebSocket.create(testUri, webSocketClient, serverEventConsumer, connectionEndedRunnable);\n    wsFuture.complete(mockWebSocket);\n\n    assertThat(sonarCloudWebSocket).isNotNull();\n    verify(webSocketClient).createWebSocketConnection(eq(testUri), any(Consumer.class), any(Runnable.class));\n    assertThat(logTester.logs()).anyMatch(log -> log.contains(\"Creating WebSocket connection to \" + testUri));\n  }\n\n  @Test\n  void should_handle_connection_failure_with_generic_exception() {\n    when(webSocketClient.createWebSocketConnection(any(URI.class), any(Consumer.class), any(Runnable.class))).thenReturn(wsFuture);\n\n    sonarCloudWebSocket = SonarCloudWebSocket.create(testUri, webSocketClient, serverEventConsumer, connectionEndedRunnable);\n    wsFuture.completeExceptionally(new RuntimeException(\"Generic error\"));\n\n    assertThat(sonarCloudWebSocket).isNotNull();\n    assertThat(logTester.logs(LogOutput.Level.ERROR)).anyMatch(log -> log.contains(\"Error while trying to create WebSocket connection for \" + testUri));\n  }\n\n  @Test\n  void should_close_websocket_connection_with_proper_completion() {\n    when(webSocketClient.createWebSocketConnection(any(URI.class), any(Consumer.class), any(Runnable.class))).thenReturn(wsFuture);\n    when(mockWebSocket.sendText(anyString(), eq(true))).thenReturn(CompletableFuture.completedFuture(null));\n    when(mockWebSocket.isOutputClosed()).thenReturn(false);\n    when(mockWebSocket.sendClose(anyInt(), anyString())).thenReturn(CompletableFuture.completedFuture(null));\n    sonarCloudWebSocket = SonarCloudWebSocket.create(testUri, webSocketClient, serverEventConsumer, connectionEndedRunnable);\n    wsFuture.complete(mockWebSocket);\n    var onClosedCaptor = ArgumentCaptor.forClass(Runnable.class);\n    verify(webSocketClient).createWebSocketConnection(any(URI.class), any(Consumer.class), onClosedCaptor.capture());\n\n    // Simulate the WebSocket input being closed by the server BEFORE calling close\n    onClosedCaptor.getValue().run();\n    // Now call close - it should complete immediately since webSocketInputClosed is already completed\n    sonarCloudWebSocket.close(\"Test reason\");\n\n    verify(mockWebSocket).sendClose(WebSocket.NORMAL_CLOSURE, \"\");\n    assertThat(logTester.logs()).anyMatch(log -> log.contains(\"Closing SonarCloud WebSocket connection, reason=Test reason\"));\n    assertThat(logTester.logs()).anyMatch(log -> log.contains(\"Waiting for SonarCloud WebSocket input to be closed...\"));\n    assertThat(logTester.logs()).anyMatch(log -> log.contains(\"SonarCloud WebSocket closed\"));\n  }\n\n  @Test\n  void should_handle_close_execution_exception() {\n    when(webSocketClient.createWebSocketConnection(any(URI.class), any(Consumer.class), any(Runnable.class))).thenReturn(wsFuture);\n    when(mockWebSocket.sendText(anyString(), eq(true))).thenReturn(CompletableFuture.completedFuture(null));\n    when(mockWebSocket.isOutputClosed()).thenReturn(false);\n    when(mockWebSocket.sendClose(anyInt(), anyString())).thenReturn(CompletableFuture.failedFuture(new RuntimeException(\"Close failed\")));\n\n    sonarCloudWebSocket = SonarCloudWebSocket.create(testUri, webSocketClient, serverEventConsumer, connectionEndedRunnable);\n    wsFuture.complete(mockWebSocket);\n\n    sonarCloudWebSocket.close(\"Test reason\");\n\n    verify(mockWebSocket).sendClose(WebSocket.NORMAL_CLOSURE, \"\");\n    assertThat(logTester.logs(LogOutput.Level.ERROR)).anyMatch(log -> log.contains(\"Cannot close the WebSocket output\"));\n  }\n\n  @Test\n  void should_handle_unresolved_address_exception_during_close() {\n    when(webSocketClient.createWebSocketConnection(any(URI.class), any(Consumer.class), any(Runnable.class))).thenReturn(wsFuture);\n    when(mockWebSocket.sendText(anyString(), eq(true))).thenReturn(CompletableFuture.completedFuture(null));\n    when(mockWebSocket.isOutputClosed()).thenReturn(false);\n    when(mockWebSocket.sendClose(anyInt(), anyString())).thenReturn(CompletableFuture.failedFuture(new UnresolvedAddressException()));\n\n    sonarCloudWebSocket = SonarCloudWebSocket.create(testUri, webSocketClient, serverEventConsumer, connectionEndedRunnable);\n    wsFuture.complete(mockWebSocket);\n\n    // Capture the onClosedRunnable callback and complete it to avoid timeout\n    var onClosedCaptor = ArgumentCaptor.forClass(Runnable.class);\n    verify(webSocketClient).createWebSocketConnection(any(URI.class), any(Consumer.class), onClosedCaptor.capture());\n    onClosedCaptor.getValue().run();\n\n    sonarCloudWebSocket.close(\"Test reason\");\n\n    verify(mockWebSocket).sendClose(WebSocket.NORMAL_CLOSURE, \"\");\n    assertThat(logTester.logs(LogOutput.Level.DEBUG)).anyMatch(log -> log.contains(\"WebSocket could not be closed gracefully\"));\n  }\n\n  @Test\n  void should_handle_ioexception_with_output_closed_message() {\n    when(webSocketClient.createWebSocketConnection(any(URI.class), any(Consumer.class), any(Runnable.class))).thenReturn(wsFuture);\n    when(mockWebSocket.sendText(anyString(), eq(true))).thenReturn(CompletableFuture.completedFuture(null));\n    when(mockWebSocket.isOutputClosed()).thenReturn(false);\n    when(mockWebSocket.sendClose(anyInt(), anyString())).thenReturn(CompletableFuture.failedFuture(new IOException(\"Output closed\")));\n\n    sonarCloudWebSocket = SonarCloudWebSocket.create(testUri, webSocketClient, serverEventConsumer, connectionEndedRunnable);\n    wsFuture.complete(mockWebSocket);\n\n    // Capture the onClosedRunnable callback and complete it to avoid timeout\n    var onClosedCaptor = ArgumentCaptor.forClass(Runnable.class);\n    verify(webSocketClient).createWebSocketConnection(any(URI.class), any(Consumer.class), onClosedCaptor.capture());\n    onClosedCaptor.getValue().run();\n\n    sonarCloudWebSocket.close(\"Test reason\");\n\n    verify(mockWebSocket).sendClose(WebSocket.NORMAL_CLOSURE, \"\");\n    assertThat(logTester.logs(LogOutput.Level.DEBUG)).anyMatch(log -> log.contains(\"WebSocket could not be closed gracefully\"));\n  }\n\n  @Test\n  void should_handle_ioexception_with_closed_output_message() {\n    when(webSocketClient.createWebSocketConnection(any(URI.class), any(Consumer.class), any(Runnable.class))).thenReturn(wsFuture);\n    when(mockWebSocket.sendText(anyString(), eq(true))).thenReturn(CompletableFuture.completedFuture(null));\n    when(mockWebSocket.isOutputClosed()).thenReturn(false);\n    when(mockWebSocket.sendClose(anyInt(), anyString())).thenReturn(CompletableFuture.failedFuture(new IOException(\"closed output\")));\n\n    sonarCloudWebSocket = SonarCloudWebSocket.create(testUri, webSocketClient, serverEventConsumer, connectionEndedRunnable);\n    wsFuture.complete(mockWebSocket);\n\n    // Capture the onClosedRunnable callback and complete it to avoid timeout\n    var onClosedCaptor = ArgumentCaptor.forClass(Runnable.class);\n    verify(webSocketClient).createWebSocketConnection(any(URI.class), any(Consumer.class), onClosedCaptor.capture());\n    onClosedCaptor.getValue().run();\n\n    sonarCloudWebSocket.close(\"Test reason\");\n\n    verify(mockWebSocket).sendClose(WebSocket.NORMAL_CLOSURE, \"\");\n    assertThat(logTester.logs(LogOutput.Level.DEBUG)).anyMatch(log -> log.contains(\"WebSocket could not be closed gracefully\"));\n  }\n\n  @Test\n  void should_handle_ioexception_with_different_message() {\n    when(webSocketClient.createWebSocketConnection(any(URI.class), any(Consumer.class), any(Runnable.class))).thenReturn(wsFuture);\n    when(mockWebSocket.sendText(anyString(), eq(true))).thenReturn(CompletableFuture.completedFuture(null));\n    when(mockWebSocket.isOutputClosed()).thenReturn(false);\n    when(mockWebSocket.sendClose(anyInt(), anyString())).thenReturn(CompletableFuture.failedFuture(new IOException(\"Connection reset\")));\n\n    sonarCloudWebSocket = SonarCloudWebSocket.create(testUri, webSocketClient, serverEventConsumer, connectionEndedRunnable);\n    wsFuture.complete(mockWebSocket);\n\n    // Capture the onClosedRunnable callback and complete it to avoid timeout\n    var onClosedCaptor = ArgumentCaptor.forClass(Runnable.class);\n    verify(webSocketClient).createWebSocketConnection(any(URI.class), any(Consumer.class), onClosedCaptor.capture());\n    onClosedCaptor.getValue().run();\n\n    sonarCloudWebSocket.close(\"Test reason\");\n\n    verify(mockWebSocket).sendClose(WebSocket.NORMAL_CLOSURE, \"\");\n    assertThat(logTester.logs(LogOutput.Level.ERROR)).anyMatch(log -> log.contains(\"Cannot close the WebSocket output\"));\n  }\n\n  @Test\n  void should_handle_already_closed_websocket() {\n    when(webSocketClient.createWebSocketConnection(any(URI.class), any(Consumer.class), any(Runnable.class))).thenReturn(wsFuture);\n    when(mockWebSocket.sendText(anyString(), eq(true))).thenReturn(CompletableFuture.completedFuture(null));\n    when(mockWebSocket.isOutputClosed()).thenReturn(true);\n\n    sonarCloudWebSocket = SonarCloudWebSocket.create(testUri, webSocketClient, serverEventConsumer, connectionEndedRunnable);\n    wsFuture.complete(mockWebSocket);\n\n    sonarCloudWebSocket.close(\"Test reason\");\n\n    verify(mockWebSocket, never()).sendClose(anyInt(), anyString());\n  }\n\n  @Test\n  void should_handle_failed_websocket_future() {\n    when(webSocketClient.createWebSocketConnection(any(URI.class), any(Consumer.class), any(Runnable.class))).thenReturn(wsFuture);\n\n    sonarCloudWebSocket = SonarCloudWebSocket.create(testUri, webSocketClient, serverEventConsumer, connectionEndedRunnable);\n    wsFuture.completeExceptionally(new RuntimeException(\"Connection failed\"));\n\n    sonarCloudWebSocket.close(\"Test reason\");\n\n    assertThat(logTester.logs()).anyMatch(log -> log.contains(\"WebSocket connection was already closed, skipping close operation\"));\n  }\n\n  @Test\n  void should_handle_pending_websocket_future() {\n    when(webSocketClient.createWebSocketConnection(any(URI.class), any(Consumer.class), any(Runnable.class)))\n      .thenReturn(wsFuture);\n\n    sonarCloudWebSocket = SonarCloudWebSocket.create(testUri, webSocketClient, serverEventConsumer, connectionEndedRunnable);\n\n    sonarCloudWebSocket.close(\"Test reason\");\n\n    assertThat(logTester.logs()).anyMatch(log -> log.contains(\"WebSocket connection was still pending, cancelled\"));\n  }\n\n  @Test\n  void should_check_if_websocket_is_open() {\n    when(webSocketClient.createWebSocketConnection(any(URI.class), any(Consumer.class), any(Runnable.class))).thenReturn(wsFuture);\n    when(mockWebSocket.sendText(anyString(), eq(true))).thenReturn(CompletableFuture.completedFuture(null));\n    when(mockWebSocket.isInputClosed()).thenReturn(false);\n    when(mockWebSocket.isOutputClosed()).thenReturn(false);\n\n    sonarCloudWebSocket = SonarCloudWebSocket.create(testUri, webSocketClient, serverEventConsumer, connectionEndedRunnable);\n    wsFuture.complete(mockWebSocket);\n\n    assertThat(sonarCloudWebSocket.isOpen()).isTrue();\n  }\n\n  @Test\n  void should_return_false_when_websocket_is_closed() {\n    when(webSocketClient.createWebSocketConnection(any(URI.class), any(Consumer.class), any(Runnable.class))).thenReturn(wsFuture);\n    when(mockWebSocket.sendText(anyString(), eq(true))).thenReturn(CompletableFuture.completedFuture(null));\n    when(mockWebSocket.isInputClosed()).thenReturn(true);\n    when(mockWebSocket.isOutputClosed()).thenReturn(false);\n\n    sonarCloudWebSocket = SonarCloudWebSocket.create(testUri, webSocketClient, serverEventConsumer, connectionEndedRunnable);\n    wsFuture.complete(mockWebSocket);\n\n    assertThat(sonarCloudWebSocket.isOpen()).isFalse();\n  }\n\n  @Test\n  void should_return_false_when_websocket_future_is_not_done() {\n    when(webSocketClient.createWebSocketConnection(any(URI.class), any(Consumer.class), any(Runnable.class))).thenReturn(wsFuture);\n\n    sonarCloudWebSocket = SonarCloudWebSocket.create(testUri, webSocketClient, serverEventConsumer, connectionEndedRunnable);\n\n    assertThat(sonarCloudWebSocket.isOpen()).isFalse();\n  }\n\n  @Test\n  void should_return_false_when_websocket_future_failed() {\n    when(webSocketClient.createWebSocketConnection(any(URI.class), any(Consumer.class), any(Runnable.class))).thenReturn(wsFuture);\n\n    sonarCloudWebSocket = SonarCloudWebSocket.create(testUri, webSocketClient, serverEventConsumer, connectionEndedRunnable);\n    wsFuture.completeExceptionally(new RuntimeException(\"Connection failed\"));\n\n    assertThat(sonarCloudWebSocket.isOpen()).isFalse();\n  }\n\n  @Test\n  void should_return_false_when_websocket_future_is_cancelled() {\n    when(webSocketClient.createWebSocketConnection(any(URI.class), any(Consumer.class), any(Runnable.class))).thenReturn(wsFuture);\n\n    sonarCloudWebSocket = SonarCloudWebSocket.create(testUri, webSocketClient, serverEventConsumer, connectionEndedRunnable);\n    wsFuture.cancel(true);\n\n    assertThat(sonarCloudWebSocket.isOpen()).isFalse();\n  }\n\n  @Test\n  void should_handle_connection_ended_callback() {\n    when(webSocketClient.createWebSocketConnection(any(URI.class), any(Consumer.class), any(Runnable.class))).thenReturn(wsFuture);\n    when(mockWebSocket.sendText(anyString(), eq(true))).thenReturn(CompletableFuture.completedFuture(null));\n\n    sonarCloudWebSocket = SonarCloudWebSocket.create(testUri, webSocketClient, serverEventConsumer, connectionEndedRunnable);\n    wsFuture.complete(mockWebSocket);\n\n    var onClosedCaptor = ArgumentCaptor.forClass(Runnable.class);\n    verify(webSocketClient).createWebSocketConnection(any(URI.class), any(Consumer.class), onClosedCaptor.capture());\n\n    // Simulate connection ended\n    onClosedCaptor.getValue().run();\n\n    verify(connectionEndedRunnable).run();\n  }\n\n  @Test\n  void should_not_call_connection_ended_callback_when_closing_initiated() {\n    when(webSocketClient.createWebSocketConnection(any(URI.class), any(Consumer.class), any(Runnable.class))).thenReturn(wsFuture);\n    when(mockWebSocket.sendText(anyString(), eq(true))).thenReturn(CompletableFuture.completedFuture(null));\n\n    sonarCloudWebSocket = SonarCloudWebSocket.create(testUri, webSocketClient, serverEventConsumer, connectionEndedRunnable);\n    wsFuture.complete(mockWebSocket);\n\n    // Close the connection first\n    sonarCloudWebSocket.close(\"Test reason\");\n\n    var onClosedCaptor = ArgumentCaptor.forClass(Runnable.class);\n    verify(webSocketClient).createWebSocketConnection(any(URI.class), any(Consumer.class), onClosedCaptor.capture());\n\n    // Simulate connection ended after closing was initiated\n    onClosedCaptor.getValue().run();\n\n    // Should not call the callback since closing was initiated\n    verify(connectionEndedRunnable, never()).run();\n  }\n\n} \n"
  },
  {
    "path": "backend/core/src/test/java/org/sonarsource/sonarlint/core/websocket/parsing/SmartNotificationEventParserTests.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.websocket.parsing;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SmartNotificationEventParserTests {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private SmartNotificationEventParser smartNotificationEventParser;\n\n  @Test\n  void should_parse_valid_json_date() {\n    smartNotificationEventParser = new SmartNotificationEventParser(\"QA\");\n    var jsonData = \"{\\\"message\\\": \\\"msg\\\", \\\"link\\\": \\\"lnk\\\", \\\"project\\\": \\\"projectKey\\\", \\\"date\\\": \\\"2023-07-19T15:08:01+0000\\\"}\";\n\n    var optionalEvent = smartNotificationEventParser.parse(jsonData);\n\n    assertThat(optionalEvent).isPresent();\n    var event = optionalEvent.get();\n    assertThat(event.category()).isEqualTo(\"QA\");\n    assertThat(event.date()).isEqualTo(\"2023-07-19T15:08:01+0000\");\n    assertThat(event.message()).isEqualTo(\"msg\");\n    assertThat(event.project()).isEqualTo(\"projectKey\");\n    assertThat(event.link()).isEqualTo(\"lnk\");\n  }\n\n  @Test\n  void should_not_parse_invalid_json_date() {\n    smartNotificationEventParser = new SmartNotificationEventParser(\"QA\");\n    var jsonData = \"{\\\"invalid\\\": \\\"msg\\\", \\\"link\\\": \\\"lnk\\\", \\\"project\\\": \\\"projectKey\\\", \\\"date\\\": \\\"2023-07-19T15:08:01+0000\\\"}\";\n\n    var optionalEvent = smartNotificationEventParser.parse(jsonData);\n\n    assertThat(optionalEvent).isEmpty();\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/testutils/LocalOnlyIssueFixtures.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage testutils;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.UUID;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.LineWithHash;\nimport org.sonarsource.sonarlint.core.commons.LocalOnlyIssue;\nimport org.sonarsource.sonarlint.core.commons.LocalOnlyIssueResolution;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\n\npublic class LocalOnlyIssueFixtures {\n\n  public static LocalOnlyIssue aLocalOnlyIssueResolvedWithoutTextAndLineRange() {\n    return new LocalOnlyIssue(\n      UUID.randomUUID(),\n      Path.of(\"file/path\"),\n      null,\n      null,\n      \"ruleKey\",\n      \"message\",\n      new LocalOnlyIssueResolution(IssueStatus.WONT_FIX, Instant.now().truncatedTo(ChronoUnit.MILLIS), \"comment\")\n    );\n  }\n\n  public static LocalOnlyIssue aLocalOnlyIssueResolved() {\n    return aLocalOnlyIssueResolved(UUID.randomUUID());\n  }\n\n  public static LocalOnlyIssue aLocalOnlyIssueResolved(Instant resolutionDate) {\n    return aLocalOnlyIssueResolved(UUID.randomUUID(), resolutionDate);\n  }\n\n  public static LocalOnlyIssue aLocalOnlyIssueResolved(UUID id) {\n    return aLocalOnlyIssueResolved(id, Instant.now());\n  }\n\n  public static LocalOnlyIssue aLocalOnlyIssueResolved(UUID id, Instant resolutionDate) {\n    return new LocalOnlyIssue(\n      id,\n      Path.of(\"file/path\"),\n      new TextRangeWithHash(1, 2, 3, 4, \"ab12\"),\n      new LineWithHash(1, \"linehash\"),\n      \"ruleKey\",\n      \"message\",\n      new LocalOnlyIssueResolution(IssueStatus.WONT_FIX, resolutionDate.truncatedTo(ChronoUnit.MILLIS), \"comment\")\n    );\n  }\n\n}\n"
  },
  {
    "path": "backend/core/src/test/java/testutils/TakeThreadDumpAfter.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage testutils;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Retention(RetentionPolicy.RUNTIME)\n@Target({ElementType.METHOD, ElementType.TYPE})\npublic @interface TakeThreadDumpAfter {\n  /** Set to zero to disable timeout **/\n  int seconds();\n\n  /** Set to true if the test is expected to timeout and this is OK with you. \n      This is mostly for self-testing the extension.\n  **/\n  boolean expectTimeout() default false;\n}\n"
  },
  {
    "path": "backend/core/src/test/java/testutils/TestUtils.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage testutils;\n\nimport java.lang.management.ManagementFactory;\n\npublic class TestUtils {\n\n  private static String generateThreadDump() {\n    final var dump = new StringBuilder();\n    final var threadMXBean = ManagementFactory.getThreadMXBean();\n    final var threadInfos = threadMXBean.getThreadInfo(threadMXBean.getAllThreadIds(), 100);\n    for (var threadInfo : threadInfos) {\n      dump.append('\"');\n      dump.append(threadInfo.getThreadName());\n      dump.append(\"\\\" \");\n      final var state = threadInfo.getThreadState();\n      dump.append(\"\\n   java.lang.Thread.State: \");\n      dump.append(state);\n      final var stackTraceElements = threadInfo.getStackTrace();\n      for (final var stackTraceElement : stackTraceElements) {\n        dump.append(\"\\n        at \");\n        dump.append(stackTraceElement);\n      }\n      dump.append(\"\\n\\n\");\n    }\n    return dump.toString();\n  }\n\n  public static void printThreadDump() {\n    System.out.println(generateThreadDump());\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/java/testutils/ThreadDumpExtension.java",
    "content": "/*\n * SonarLint Core - Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage testutils;\n\nimport java.lang.reflect.Method;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.api.extension.InvocationInterceptor;\nimport org.junit.jupiter.api.extension.ReflectiveInvocationContext;\n\nimport static testutils.TestUtils.printThreadDump;\n\npublic class ThreadDumpExtension implements InvocationInterceptor {\n  private static final ScheduledExecutorService exec = Executors.newScheduledThreadPool(1);\n\n  @Override\n  public void interceptTestMethod(Invocation<Void> invocation, ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable {\n    var timeout = invocationContext.getExecutable().getAnnotation(TakeThreadDumpAfter.class);\n    var clazz = invocationContext.getExecutable().getDeclaringClass();\n    while (timeout == null && clazz != Object.class) {\n      timeout = clazz.getAnnotation(TakeThreadDumpAfter.class);\n      clazz = clazz.getSuperclass();\n    }\n    if (timeout == null || timeout.seconds() <= 0) {\n      invocation.proceed();\n      return;\n    }\n    var seconds = timeout.seconds();\n    var caller = Thread.currentThread();\n    var timedOut = new AtomicBoolean();\n    Future<Void> future = exec.schedule(() -> {\n      System.out.println(\"**** TIMEOUT ERROR: TEST EXCEEDED \" + seconds + \" SECONDS ****\");\n      printThreadDump();\n      timedOut.set(true);\n      caller.interrupt();\n      return null;\n    }, seconds, TimeUnit.SECONDS);\n    Exception caught = null;\n    try {\n      invocation.proceed();\n    } catch (Exception ex) {\n      caught = ex;\n    } finally {\n      future.cancel(true);\n      if (timedOut.get()) {\n        if (!timeout.expectTimeout()) {\n          Exception ex = new TimeoutException(\"Test exceeded timeout of \" + seconds + \" seconds\");\n          if (caught != null) {\n            ex.addSuppressed(caught);\n          }\n          throw ex;\n        }\n      } else if (caught != null) {\n        throw caught;\n      } else if (timeout.expectTimeout()) {\n        throw new RuntimeException(\"Test expected to timeout at \" + seconds + \" but didn't\");\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "backend/core/src/test/resources/logback-test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE configuration>\n\n<configuration>\n  <include resource=\"logback-shared.xml\"/>\n</configuration>"
  },
  {
    "path": "backend/core/src/test/resources/ondemand/sonar-cpp-corrupt-plugin.jar.asc",
    "content": "-----BEG"
  },
  {
    "path": "backend/core/src/test/resources/ondemand/sonar-cpp-plugin.jar.asc",
    "content": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEE5uYX+edGKHKP3xYibMoJTbbStoAFAmmljsUACgkQbMoJTbbS\ntoDAtQ//ZLTHCRJ1EImtlEnYybFcd+a1Al42FCI7XwKGamwPxX1W5URp8z2SY8CC\nP4Z1CDsh2U8rCohGqaLcd6xaoGDdMJZUxU2D/64VbBnIc0fiYzyDVt8tc5QybYGI\nT3n3t5JfZG/BEXKGg/c3Rzk95H2ArmV6i7S/6IW2TjuBd8foROonln+5SpQRDFWN\nfkP3Av6JK/xK3FoDTnhidbJhh3irWTRScbXVx7GRT+HyNKlIZzNz6OEfrpMu0mGk\nj1xeuoa+082sqfq9vc7crF+7mEhSLX7N1iEqgBFtbUj7rTJAN90HXw7TvzP6MxOz\nIag29dh3NO8tAjtykSMWtLBd7xilWNQA3v4gQSdg0u14vU6JVuwO3UXrXg4QZr6K\nVBzXujSxeII2kIHLeK9PyKvcqsMBdCq9Fal+/JO8Ev6YERuFT1qGdSWqSAW7CdFl\nLVDNM+PVZ7cpZhAuSMrbtERhfoADLODeFCBbAorpp/00RsD8uPOgrsLVqspk19uT\nzLPfpT1P8B8vWZlAqfx4nVpJ1QCD6HcrsFko1jqBATQB0C5wdvLpAntvLOOw2wy2\n8OwmisNNl19i0Hm48ohkTm8eFYQUcv/uC9sUPdckTaZOG5ughYqUZNJzASWoz2Ma\n8ngjnG64IBA0jvnY6y5AKA/dIlNc+MMwAJ2RYh0A8IowAz8DyQI=\n=kU4O\n-----END PGP SIGNATURE-----\n"
  },
  {
    "path": "backend/core/src/test/resources/ondemand/sonar-cpp-unknownkey-plugin.jar.asc",
    "content": "-----BEGIN PGP SIGNATURE-----\n\niQFFBAABCgAvFiEEXvC7OWK1m82TnVrOUsDmg8MZOC0FAmm7/X8RHHRlc3RAZXhh\nbXBsZS5jb20ACgkQUsDmg8MZOC3f/Qf5AWtIKjebbkio1QBHO1z7CrjOpHyjtHzO\nZpf7EjicM4Yfq7zUzEOeRy9DAttBtkSat4cxUgs4IHg2FxFCwvq49/+/f4AJT9/S\nCWQnaCI9tA+3aXJ8Vz5VDLNbZFblmaiWcnDye/39Sqv9saxxyf4iOzg/uKFrQ25B\nebOrV2wFp+GRp1pzPhZEQv9/pUwzgC5vfZIfOe+d+tTCLzZ4IgIKeSmVUA+XYogi\nmP+rKeOzj3vicxl6+8eyTEoShY1Y08isLrqhsWbUxFXl0LM17uTc9VMw/jYBDFp2\nhJw6ASpJtFjfm3NmNffqRgtX/k81YYz4HzqfhaKzkVh1bR5vUM6MBg==\n=ugwf\n-----END PGP SIGNATURE-----\n"
  },
  {
    "path": "backend/core/src/test/resources/ondemand/sonarsource-public.key",
    "content": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBGmTDYEBEADBL3EBQ1xSS+omyRmmCcE+IgRz/i2aSP4ZD0W5xawminV0EfNf\nN/AEV0aBoi2yzQqPm5rZODqvOpdP4t3rJKAvygvkiMHornY97cwQiJ6T4iiz04yk\nl41oO513kZV2BvBclU4WrwDGDoTBH6KPn52r2k8eGtnDl1QKfGbK9ey8/Hs8ySCT\nzOaWZ+mRqF6SrEmG8OzrP+u1O2MwqC+KNnywpl1OFeBcpBAhzDgMd3ZKe3B69w1w\nwXr9KnmpYkQ9pe90wjWI2xL8JFePTymq26nd9hMhiuPXbIEWcJs6gGACEJOgdOJM\n/f5LTu7Vm3eOj8wmmCeXIc9QJT8LNm29ie+PDH1qnQLpW5edEbV4wJY9WdmQ7LeV\nbKJU29vGsE/I5GkB6PE+0RdNx0hggKzV+1opeoZw4Fl4RsF35fUtpv5d2sb86u8g\nMUY38dSgvW8cHbqkXAdusN3ys9PiTRdZd6lcZX7U8TicQ2sVkyXJF1GjPSFG6TeF\nXaTN/jh6P4EoLAlO5x6KC9Q43O2oYuvFQtDSkqjqG4JwVPEWOOo51LNi2cJrEJDM\nnTKCh3hsh3dPEHcHJwF4v9vA1kiNXVVBHw7yNi/HScqgNoSGbOMDg8OPyPuLNUw3\nhelyJOfStvAk7mdllhF1g+8mGAfwezXFTnQG84LU5bpUMBmirUst6rElpQARAQAB\ntBxUZXN0IFVzZXIgPHRlc3RAZXhhbXBsZS5jb20+iQJOBBMBCgA4FiEE5uYX+edG\nKHKP3xYibMoJTbbStoAFAmmTDYECGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AA\nCgkQbMoJTbbStoBRfhAAtQYbgRGZnpBjtF/U0ZZYNwlX0vaDU2vddL8Bh0pvHlJH\n6KfQZPUzW6KsxoNQzU2VI4d4GgrA/pAcWIs3yczg5ZxPWQBilTD1s8Djbjl5iGTW\nBISGoafYaUSyRKc10rtKKJXxYdxzGFlkgrPta7F6zEHFxKJZm0O9RpWpRG4nOGEN\nOD3DmrF17qNkdKebsHU0eq0Zf0vNEl848Ja9KfUGGv4lrf5mM4n4FY8LwxL9cCZR\nvCZlHkQMDFH8d6sgDYK4jP3ebJI+83ckk6L9z05l04AgJuLoIL19UkWFgLCcSUjW\nLo6QXIvmTgEI81wfihfrn57aGD/d057OKri2wg717G6jX7bnfc5/fFYmvRJGtTNo\nQ9DSEyVePzb0ytCNNL6mgMQpZCA7NIOdBQK7TiKwFEDxHCpoij2cCGJtijUlZNS0\nv9fFG5obVG5PUGcwj78gfaMys+rlU0P1+4pT45bQprz/DXaCQJbgP2qoIlVxf4T/\nM3/VHkYZ2bLFUHsIpP3zH+k6zHOZSquf0zHl1e10Lz+4bPTGpwaMpJ84b6qmJ3en\nWqzbSvw0RgVmX3A8/3vKNd0w+fDY5GAk6oh4R5aEig14fhkO1FE+40w9M4L9jhsK\nFYm2pEQxniUUMAbYQfEjRDonK71ZGXCgAY+lHNEtRelucFPHwOE12cezUHQ6TYk=\n=buVr\n-----END PGP PUBLIC KEY BLOCK-----\n"
  },
  {
    "path": "backend/http/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-backend-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../pom.xml</relativePath>\n  </parent>\n  <artifactId>sonarlint-http</artifactId>\n  <name>SonarLint Core - HTTP</name>\n  <description>HTTP related code used by SonarLint</description>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.code.findbugs</groupId>\n      <artifactId>jsr305</artifactId>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>jakarta.inject</groupId>\n      <artifactId>jakarta.inject-api</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>jakarta.annotation</groupId>\n      <artifactId>jakarta.annotation-api</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>javax.annotation</groupId>\n      <artifactId>javax.annotation-api</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-commons</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.httpcomponents.client5</groupId>\n      <artifactId>httpclient5</artifactId>\n      <exclusions>\n        <exclusion>\n          <groupId>org.slf4j</groupId>\n          <artifactId>slf4j-api</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>io.github.hakky54</groupId>\n      <artifactId>ayza</artifactId>\n      <exclusions>\n        <exclusion>\n          <groupId>org.slf4j</groupId>\n          <artifactId>slf4j-api</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>com.google.guava</groupId>\n      <artifactId>guava</artifactId>\n    </dependency>\n  <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-lang3</artifactId>\n  </dependency>\n    <!-- unit tests -->\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-engine</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.assertj</groupId>\n      <artifactId>assertj-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.wiremock</groupId>\n      <artifactId>wiremock-jetty12</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>ch.qos.logback</groupId>\n      <artifactId>logback-classic</artifactId>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <profiles>\n    <!-- Workaround for https://issues.apache.org/jira/projects/MJAR/issues/MJAR-138 -->\n    <profile>\n      <id>conditionally-add-commons-tests-if-tests-not-skipped</id>\n      <activation>\n        <property>\n          <name>maven.test.skip</name>\n          <value>!true</value>\n        </property>\n      </activation>\n      <dependencies>\n        <dependency>\n          <groupId>${project.groupId}</groupId>\n          <artifactId>sonarlint-commons</artifactId>\n          <version>${project.version}</version>\n          <classifier>tests</classifier>\n          <type>test-jar</type>\n          <scope>test</scope>\n        </dependency>\n      </dependencies>\n    </profile>\n  </profiles>\n\n</project>\n"
  },
  {
    "path": "backend/http/src/main/java/org/sonarsource/sonarlint/core/http/ApacheHttpClientAdapter.java",
    "content": "/*\n * SonarLint Core - HTTP\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.http;\n\nimport java.io.InterruptedIOException;\nimport java.net.URISyntaxException;\nimport java.nio.CharBuffer;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\nimport java.util.Objects;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Consumer;\nimport javax.annotation.Nullable;\nimport org.apache.hc.client5.http.async.methods.AbstractCharResponseConsumer;\nimport org.apache.hc.client5.http.async.methods.SimpleHttpRequest;\nimport org.apache.hc.client5.http.async.methods.SimpleHttpResponse;\nimport org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;\nimport org.apache.hc.client5.http.config.RequestConfig;\nimport org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;\nimport org.apache.hc.client5.http.protocol.HttpClientContext;\nimport org.apache.hc.core5.concurrent.FutureCallback;\nimport org.apache.hc.core5.http.ContentType;\nimport org.apache.hc.core5.http.HttpResponse;\nimport org.apache.hc.core5.http.nio.support.BasicRequestProducer;\nimport org.apache.hc.core5.util.Timeout;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\nclass ApacheHttpClientAdapter implements HttpClient {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final String AUTHORIZATION_HEADER = \"Authorization\";\n  private static final String X_API_KEY_HEADER = \"x-api-key\";\n  private static final Timeout STREAM_CONNECTION_REQUEST_TIMEOUT = Timeout.ofSeconds(10);\n  private static final Timeout STREAM_CONNECTION_TIMEOUT = Timeout.ofMinutes(1);\n\n  private final CloseableHttpAsyncClient apacheClient;\n  @Nullable\n  private final String usernameOrToken;\n  @Nullable\n  private final String password;\n  private final boolean shouldUseBearer;\n  @Nullable\n  private final String xApiKey;\n  private final boolean withRetries;\n  private boolean connected = false;\n\n  private ApacheHttpClientAdapter(CloseableHttpAsyncClient apacheClient, @Nullable String usernameOrToken, @Nullable String password, boolean shouldUseBearer,\n    @Nullable String xApiKey, boolean withRetries) {\n    this.apacheClient = apacheClient;\n    this.usernameOrToken = usernameOrToken;\n    this.password = password;\n    this.shouldUseBearer = shouldUseBearer;\n    this.xApiKey = xApiKey;\n    this.withRetries = withRetries;\n  }\n\n  @Override\n  public Response post(String url, String contentType, String bodyContent) {\n    return waitFor(postAsync(url, contentType, bodyContent));\n  }\n\n  @Override\n  public CompletableFuture<Response> postAsync(String url, String contentType, String body) {\n    var request = SimpleRequestBuilder.post(url)\n      .setBody(body, ContentType.parse(contentType))\n      .build();\n    return executeAsync(request);\n  }\n\n  @Override\n  public Response get(String url) {\n    return waitFor(getAsync(url));\n  }\n\n  @Override\n  public CompletableFuture<Response> getAsync(String url) {\n    return executeAsync(SimpleRequestBuilder.get(url).build());\n  }\n\n  @Override\n  public CompletableFuture<Response> getAsyncAnonymous(String url) {\n    return executeAsyncAnonymous(SimpleRequestBuilder.get(url).build());\n  }\n\n  @Override\n  public CompletableFuture<Response> deleteAsync(String url, String contentType, String body) {\n    var httpRequest = SimpleRequestBuilder\n      .delete(url)\n      .setBody(body, ContentType.parse(contentType))\n      .build();\n    return executeAsync(httpRequest);\n  }\n\n  private static Response waitFor(CompletableFuture<Response> f) {\n    return f.join();\n  }\n\n  @Override\n  public AsyncRequest getEventStream(String url, HttpConnectionListener connectionListener, Consumer<String> messageConsumer) {\n    var request = SimpleRequestBuilder.get(url).build();\n    request.setConfig(RequestConfig.custom()\n      .setConnectionRequestTimeout(STREAM_CONNECTION_REQUEST_TIMEOUT)\n      .setConnectTimeout(STREAM_CONNECTION_TIMEOUT)\n      .setResponseTimeout(Timeout.ZERO_MILLISECONDS)\n      .build());\n\n    setAuthHeader(request);\n    request.setHeader(\"Accept\", \"text/event-stream\");\n    connected = false;\n    var cancelled = new AtomicBoolean();\n    var httpFuture = apacheClient.execute(new BasicRequestProducer(request, null),\n      new AbstractCharResponseConsumer<>() {\n        @Override\n        public void releaseResources() {\n          // should we close something ?\n        }\n\n        @Override\n        protected int capacityIncrement() {\n          return Integer.MAX_VALUE;\n        }\n\n        @Override\n        protected void data(CharBuffer src, boolean endOfStream) {\n          if (cancelled.get()) {\n            throw new CancellationException();\n          }\n          if (connected) {\n            messageConsumer.accept(src.toString());\n          } else {\n            var possiblyErrorMessage = src.toString();\n            if (!possiblyErrorMessage.isEmpty()) {\n              LOG.debug(\"Received event-stream data while not connected: \" + possiblyErrorMessage);\n            }\n          }\n        }\n\n        @Override\n        protected void start(HttpResponse httpResponse, ContentType contentType) {\n          if (httpResponse.getCode() < 200 || httpResponse.getCode() >= 300) {\n            connectionListener.onError(httpResponse.getCode());\n          } else {\n            connected = true;\n            connectionListener.onConnected();\n          }\n        }\n\n        @Override\n        protected Object buildResult() {\n          return null;\n        }\n\n        @Override\n        public void failed(Exception cause) {\n          if (cause instanceof CancellationException || cause instanceof InterruptedIOException) {\n            return;\n          }\n          LOG.error(\"Stream failed\", cause);\n        }\n      }, new FutureCallback<>() {\n\n        @Override\n        public void completed(Object result) {\n          if (connected) {\n            connectionListener.onClosed();\n          }\n        }\n\n        @Override\n        public void failed(Exception ex) {\n          if (connected) {\n            // called when disconnected from server\n            connectionListener.onClosed();\n          } else {\n            connectionListener.onError(null);\n          }\n        }\n\n        @Override\n        public void cancelled() {\n          cancelled.set(true);\n          LOG.debug(\"Stream has been cancelled\");\n        }\n      });\n\n    return new HttpAsyncRequest(httpFuture);\n  }\n\n  private void setAuthHeader(SimpleHttpRequest request) {\n    if (usernameOrToken != null) {\n      if (shouldUseBearer) {\n        request.setHeader(AUTHORIZATION_HEADER, bearer(usernameOrToken));\n      } else {\n        request.setHeader(AUTHORIZATION_HEADER, basic(usernameOrToken, Objects.requireNonNullElse(password, \"\")));\n      }\n    } else if (xApiKey != null) {\n      request.setHeader(X_API_KEY_HEADER, xApiKey);\n    }\n  }\n\n  private class CompletableFutureWrappingFuture extends CompletableFuture<HttpClient.Response> {\n\n    private final Future<SimpleHttpResponse> wrapped;\n\n    private CompletableFutureWrappingFuture(SimpleHttpRequest httpRequest) {\n      var callingThreadLogOutput = SonarLintLogger.get().getTargetForCopy();\n      var context = new HttpClientContext();\n      context.setAttribute(ContextAttributes.RETRIES_ENABLED, withRetries);\n      this.wrapped = apacheClient.execute(httpRequest, context, new FutureCallback<>() {\n        @Override\n        public void completed(SimpleHttpResponse result) {\n          SonarLintLogger.get().setTarget(callingThreadLogOutput);\n          // getRequestUri may be relative, so we prefer getUri\n          try {\n            var uri = httpRequest.getUri().toString();\n            CompletableFutureWrappingFuture.this.completeAsync(() -> {\n              SonarLintLogger.get().setTarget(callingThreadLogOutput);\n              return new ApacheHttpResponse(uri, result);\n            });\n          } catch (URISyntaxException e) {\n            CompletableFutureWrappingFuture.this.completeAsync(() -> {\n              SonarLintLogger.get().setTarget(callingThreadLogOutput);\n              return new ApacheHttpResponse(httpRequest.getRequestUri(), result);\n            });\n          }\n        }\n\n        @Override\n        public void failed(Exception ex) {\n          SonarLintLogger.get().setTarget(callingThreadLogOutput);\n          LOG.debug(\"Request failed\", ex);\n          CompletableFutureWrappingFuture.this.completeExceptionally(ex);\n        }\n\n        @Override\n        public void cancelled() {\n          SonarLintLogger.get().setTarget(callingThreadLogOutput);\n          LOG.debug(\"Request cancelled\");\n          CompletableFutureWrappingFuture.this.cancel();\n        }\n      });\n    }\n\n    private void cancel() {\n      super.cancel(true);\n    }\n\n    @Override\n    public boolean cancel(boolean mayInterruptIfRunning) {\n      return wrapped.cancel(mayInterruptIfRunning);\n    }\n  }\n\n  private CompletableFuture<Response> executeAsync(SimpleHttpRequest httpRequest) {\n    try {\n      setAuthHeader(httpRequest);\n      return new CompletableFutureWrappingFuture(httpRequest);\n    } catch (Exception e) {\n      throw new IllegalStateException(\"Unable to execute request: \" + e.getMessage(), e);\n    }\n  }\n\n  private CompletableFuture<Response> executeAsyncAnonymous(SimpleHttpRequest httpRequest) {\n    try {\n      return new CompletableFutureWrappingFuture(httpRequest);\n    } catch (Exception e) {\n      throw new IllegalStateException(\"Unable to execute request: \" + e.getMessage(), e);\n    }\n  }\n\n  private static String basic(String username, String password) {\n    var usernameAndPassword = String.format(\"%s:%s\", username, password);\n    var encoded = Base64.getEncoder().encodeToString(usernameAndPassword.getBytes(StandardCharsets.UTF_8));\n    return String.format(\"Basic %s\", encoded);\n  }\n\n  private static String bearer(String token) {\n    return String.format(\"Bearer %s\", token);\n  }\n\n  public static class HttpAsyncRequest implements AsyncRequest {\n    private final Future<?> response;\n\n    private HttpAsyncRequest(Future<?> response) {\n      this.response = response;\n    }\n\n    @Override\n    public void cancel() {\n      try {\n        response.cancel(true);\n      } catch (Exception e) {\n        // ignore errors\n      }\n    }\n  }\n\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  public static final class Builder {\n\n    private CloseableHttpAsyncClient apacheClient;\n    @Nullable\n    private String usernameOrToken;\n    @Nullable\n    private String password;\n    private boolean shouldUseBearer = false;\n    @Nullable\n    private String xApiKey;\n    private boolean withRetries = false;\n\n    public Builder withInnerClient(CloseableHttpAsyncClient apacheClient) {\n      this.apacheClient = apacheClient;\n      return this;\n    }\n\n    public Builder withUserNamePassword(String username, @Nullable String password) {\n      this.usernameOrToken = username;\n      this.password = password;\n      return this;\n    }\n\n    public Builder withToken(String token) {\n      this.usernameOrToken = token;\n      return this;\n    }\n\n    public Builder useBearer(boolean shouldUseBearer) {\n      this.shouldUseBearer = shouldUseBearer;\n      return this;\n    }\n\n    public Builder withXApiKey(String xApiKey) {\n      this.xApiKey = xApiKey;\n      return this;\n    }\n\n    public Builder withRetries() {\n      this.withRetries = true;\n      return this;\n    }\n\n    ApacheHttpClientAdapter build() {\n      if (apacheClient == null) {\n        throw new IllegalStateException(\"Required an Apache HTTP client to wrap.\");\n      }\n\n      return new ApacheHttpClientAdapter(apacheClient, usernameOrToken, password, shouldUseBearer, xApiKey, withRetries);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/http/src/main/java/org/sonarsource/sonarlint/core/http/ApacheHttpResponse.java",
    "content": "/*\n * SonarLint Core - HTTP\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.http;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.InputStream;\nimport org.apache.hc.client5.http.async.methods.SimpleHttpResponse;\n\nclass ApacheHttpResponse implements HttpClient.Response {\n\n  private final String requestUrl;\n  private final SimpleHttpResponse response;\n\n  public ApacheHttpResponse(String requestUrl, SimpleHttpResponse response) {\n    this.requestUrl = requestUrl;\n    this.response = response;\n  }\n\n  @Override\n  public int code() {\n    return response.getCode();\n  }\n\n  @Override\n  public String bodyAsString() {\n    return response.getBodyText();\n  }\n\n  @Override\n  public InputStream bodyAsStream() {\n    if (response.getBodyBytes() == null) {\n      return new ByteArrayInputStream(new byte[0]);\n    }\n    return new ByteArrayInputStream(response.getBodyBytes());\n  }\n\n  @Override\n  public void close() {\n    // nothing to do\n  }\n\n  @Override\n  public String url() {\n    return requestUrl;\n  }\n\n  @Override\n  public String toString() {\n    return response.toString();\n  }\n}\n"
  },
  {
    "path": "backend/http/src/main/java/org/sonarsource/sonarlint/core/http/ContextAttributes.java",
    "content": "/*\n * SonarLint Core - HTTP\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.http;\n\npublic class ContextAttributes {\n\n  public static final String RETRIES_ENABLED = \"retries.enabled\";\n\n  private ContextAttributes() { }\n}\n"
  },
  {
    "path": "backend/http/src/main/java/org/sonarsource/sonarlint/core/http/HttpClient.java",
    "content": "/*\n * SonarLint Core - HTTP\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.http;\n\nimport java.io.Closeable;\nimport java.io.InputStream;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.function.Consumer;\n\npublic interface HttpClient {\n\n  String JSON_CONTENT_TYPE = \"application/json; charset=utf-8\";\n  String FORM_URL_ENCODED_CONTENT_TYPE = \"application/x-www-form-urlencoded\";\n\n  interface Response extends Closeable {\n\n    int code();\n\n    default boolean isSuccessful() {\n      return code() >= 200 && code() < 300;\n    }\n\n    String bodyAsString();\n\n    InputStream bodyAsStream();\n\n    /**\n     * Only runtime exception\n     */\n    @Override\n    void close();\n\n    String url();\n  }\n\n  Response get(String url);\n\n  CompletableFuture<Response> getAsync(String url);\n\n  CompletableFuture<Response> getAsyncAnonymous(String url);\n\n  AsyncRequest getEventStream(String url, HttpConnectionListener connectionListener, Consumer<String> messageConsumer);\n\n  Response post(String url, String contentType, String body);\n\n  CompletableFuture<Response> postAsync(String url, String contentType, String body);\n\n  CompletableFuture<Response> deleteAsync(String url, String contentType, String body);\n\n  interface AsyncRequest {\n    void cancel();\n  }\n\n}\n"
  },
  {
    "path": "backend/http/src/main/java/org/sonarsource/sonarlint/core/http/HttpClientProvider.java",
    "content": "/*\n * SonarLint Core - HTTP\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.http;\n\nimport com.google.common.util.concurrent.MoreExecutors;\nimport jakarta.annotation.PreDestroy;\nimport java.net.ProxySelector;\nimport java.nio.file.Files;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Predicate;\nimport javax.annotation.Nullable;\nimport javax.net.ssl.SSLContext;\nimport nl.altindag.ssl.SSLFactory;\nimport nl.altindag.ssl.model.TrustManagerParameters;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.apache.hc.client5.http.auth.CredentialsProvider;\nimport org.apache.hc.client5.http.config.ConnectionConfig;\nimport org.apache.hc.client5.http.config.RequestConfig;\nimport org.apache.hc.client5.http.config.TlsConfig;\nimport org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;\nimport org.apache.hc.client5.http.impl.async.HttpAsyncClients;\nimport org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;\nimport org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;\nimport org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner;\nimport org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;\nimport org.apache.hc.core5.http2.HttpVersionPolicy;\nimport org.apache.hc.core5.io.CloseMode;\nimport org.apache.hc.core5.util.TimeValue;\nimport org.apache.hc.core5.util.Timeout;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.util.FailSafeExecutors;\nimport org.sonarsource.sonarlint.core.http.ssl.SslConfig;\n\nimport static org.sonarsource.sonarlint.core.http.ThreadFactories.threadWithNamePrefix;\n\npublic class HttpClientProvider {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final int DEFAULT_MAX_RETRIES = 2;\n  private static final int DEFAULT_RETRY_INTERVAL = 3;\n\n  private final CloseableHttpAsyncClient sharedClient;\n  private final ExecutorService webSocketThreadPool;\n  private final String userAgent;\n\n  /**\n   * Return an {@link HttpClientProvider} made for testing, with a dummy user agent, and basic configuration regarding proxy/SSL\n   */\n  public static HttpClientProvider forTesting() {\n    return new HttpClientProvider(\"SonarLint tests\", new HttpConfig(new SslConfig(null, null), null, null, null, null), null, ProxySelector.getDefault(),\n      new BasicCredentialsProvider());\n  }\n\n  public HttpClientProvider(String userAgent, HttpConfig httpConfig, @Nullable Predicate<TrustManagerParameters> trustManagerParametersPredicate, ProxySelector proxySelector,\n    CredentialsProvider proxyCredentialsProvider) {\n    this.userAgent = userAgent;\n    this.webSocketThreadPool = FailSafeExecutors.newCachedThreadPool(threadWithNamePrefix(\"sonarcloud-websocket-\"));\n    var maxRetries = Integer.parseInt(System.getProperty(\"sonarlint.http.max.retries\", String.valueOf(DEFAULT_MAX_RETRIES)));\n    var retryInterval = Integer.parseInt(System.getProperty(\"sonarlint.http.retry.interval.seconds\", String.valueOf(DEFAULT_RETRY_INTERVAL)));\n    sharedClient = buildSharedClient(userAgent, httpConfig, trustManagerParametersPredicate, proxySelector, proxyCredentialsProvider, maxRetries, retryInterval);\n    sharedClient.start();\n  }\n\n  private static CloseableHttpAsyncClient buildSharedClient(String userAgent, HttpConfig httpConfig, @Nullable Predicate<TrustManagerParameters> trustManagerParametersPredicate,\n    ProxySelector proxySelector, CredentialsProvider proxyCredentialsProvider, int maxRetries, int retryInterval) {\n    var asyncConnectionManager = PoolingAsyncClientConnectionManagerBuilder.create()\n      .setTlsStrategy(new DefaultClientTlsStrategy(configureSsl(httpConfig.sslConfig(), trustManagerParametersPredicate)))\n      .setDefaultTlsConfig(TlsConfig.custom()\n        // Force HTTP/1 since we know SQ/SC don't support HTTP/2 ATM\n        .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1)\n        .build())\n      .setDefaultConnectionConfig(buildConnectionConfig(httpConfig.connectTimeout(), httpConfig.socketTimeout()))\n      .build();\n    var routePlanner = new SystemDefaultRoutePlanner(proxySelector);\n    return HttpAsyncClients.custom()\n      .setConnectionManager(asyncConnectionManager)\n      .addResponseInterceptorFirst(new RedirectInterceptor())\n      .setUserAgent(userAgent)\n      // proxy settings\n      .setRoutePlanner(routePlanner)\n      .setDefaultCredentialsProvider(proxyCredentialsProvider)\n      .setDefaultRequestConfig(buildRequestConfig(httpConfig.connectionRequestTimeout(), httpConfig.responseTimeout()))\n      .setRetryStrategy(new RetryOnDemandStrategy(maxRetries, TimeValue.ofSeconds(retryInterval)))\n      .build();\n  }\n\n  private static SSLContext configureSsl(SslConfig sslConfig, @Nullable Predicate<TrustManagerParameters> trustManagerParametersPredicate) {\n    var sslFactoryBuilder = SSLFactory.builder()\n      .withDefaultTrustMaterial();\n    // SLCORE-686; SLCORE-669\n    if (isNotWindows()) {\n      sslFactoryBuilder.withSystemTrustMaterial();\n    }\n    var keyStore = sslConfig.getKeyStore();\n    if (keyStore != null && Files.exists(keyStore.getPath())) {\n      try {\n        sslFactoryBuilder.withIdentityMaterial(keyStore.getPath(), keyStore.getKeyStorePassword().toCharArray(), keyStore.getKeyStoreType());\n      } catch (Exception e) {\n        LOG.warn(\"Unable to load key store from '{}', it will be ignored: {}\", keyStore.getPath(), e.getMessage());\n      }\n    }\n    var trustStore = sslConfig.getTrustStore();\n    if (trustStore != null) {\n      try {\n        sslFactoryBuilder.withInflatableTrustMaterial(trustStore.getPath(), trustStore.getKeyStorePassword().toCharArray(),\n          trustStore.getKeyStoreType(), trustManagerParametersPredicate);\n      } catch (Exception e) {\n        LOG.warn(\"Unable to load trust store from '{}', it will be ignored: {}\", trustStore.getPath(), e.getMessage());\n      }\n    }\n    return sslFactoryBuilder.build().getSslContext();\n  }\n\n  private static boolean isNotWindows() {\n    return !SystemUtils.IS_OS_WINDOWS;\n  }\n\n  private static ConnectionConfig buildConnectionConfig(@Nullable Timeout connectTimeout, @Nullable Timeout socketTimeout) {\n    var connectionConfig = ConnectionConfig.custom();\n    if (connectTimeout != null) {\n      connectionConfig.setConnectTimeout(connectTimeout);\n    }\n    if (socketTimeout != null) {\n      connectionConfig.setSocketTimeout(socketTimeout);\n    }\n    return connectionConfig.build();\n  }\n\n  private static RequestConfig buildRequestConfig(@Nullable Timeout connectionRequestTimeout, @Nullable Timeout responseTimeout) {\n    var requestConfig = RequestConfig.custom()\n      .setContentCompressionEnabled(false);\n    if (connectionRequestTimeout != null) {\n      requestConfig.setConnectionRequestTimeout(connectionRequestTimeout);\n    }\n    if (responseTimeout != null) {\n      requestConfig.setResponseTimeout(responseTimeout);\n    }\n    return requestConfig.build();\n  }\n\n  public HttpClient getHttpClientWithoutAuth() {\n    return ApacheHttpClientAdapter.builder()\n      .withInnerClient(sharedClient)\n      .build();\n  }\n\n  public HttpClient getHttpClientWithPreemptiveAuth(String username, @Nullable String password) {\n    return ApacheHttpClientAdapter.builder()\n      .withInnerClient(sharedClient)\n      .withUserNamePassword(username, password)\n      .build();\n  }\n\n  public HttpClient getHttpClientWithPreemptiveAuth(String token, boolean shouldUseBearer) {\n    return ApacheHttpClientAdapter.builder()\n      .withInnerClient(sharedClient)\n      .withToken(token)\n      .useBearer(shouldUseBearer)\n      .build();\n  }\n\n  public HttpClient getHttpClientWithXApiKeyAndRetries(String xApiKey) {\n    return ApacheHttpClientAdapter.builder()\n      .withInnerClient(sharedClient)\n      .withXApiKey(xApiKey)\n      .withRetries()\n      .build();\n  }\n\n  public WebSocketClient getWebSocketClient(String token) {\n    return new WebSocketClient(userAgent, token, webSocketThreadPool);\n  }\n\n  @PreDestroy\n  public void close() {\n    sharedClient.close(CloseMode.IMMEDIATE);\n    if (!MoreExecutors.shutdownAndAwaitTermination(webSocketThreadPool, 1, TimeUnit.SECONDS)) {\n      LOG.warn(\"Unable to stop web socket executor service in a timely manner\");\n    }\n  }\n}\n"
  },
  {
    "path": "backend/http/src/main/java/org/sonarsource/sonarlint/core/http/HttpConfig.java",
    "content": "/*\n * SonarLint Core - HTTP\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.http;\n\nimport javax.annotation.Nullable;\nimport org.apache.hc.core5.util.Timeout;\nimport org.sonarsource.sonarlint.core.http.ssl.SslConfig;\n\npublic record HttpConfig(SslConfig sslConfig, @Nullable Timeout connectTimeout, @Nullable Timeout socketTimeout,\n                         @Nullable Timeout connectionRequestTimeout, @Nullable Timeout responseTimeout) {\n\n  private static final Timeout DEFAULT_CONNECT_TIMEOUT = Timeout.ofSeconds(60);\n  private static final Timeout DEFAULT_RESPONSE_TIMEOUT = Timeout.ofMinutes(10);\n\n  @Override\n  public Timeout connectionRequestTimeout() {\n    if (connectionRequestTimeout == null) {\n      return DEFAULT_CONNECT_TIMEOUT;\n    }\n    return connectionRequestTimeout;\n  }\n\n  @Override\n  public Timeout responseTimeout() {\n    if (responseTimeout == null) {\n      return DEFAULT_RESPONSE_TIMEOUT;\n    }\n    return responseTimeout;\n  }\n\n  @Override\n  public Timeout connectTimeout() {\n    if (connectTimeout == null) {\n      return DEFAULT_CONNECT_TIMEOUT;\n    }\n    return connectTimeout;\n  }\n\n}\n"
  },
  {
    "path": "backend/http/src/main/java/org/sonarsource/sonarlint/core/http/HttpConnectionListener.java",
    "content": "/*\n * SonarLint Core - HTTP\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.http;\n\nimport javax.annotation.Nullable;\n\npublic interface HttpConnectionListener {\n  /**\n   * Should be called when the request returns status >= 200 and < 300.\n   */\n  void onConnected();\n\n  /**\n   * Should be called when the request returns status < 200 or >= 300, or another error occurs. No need to call {@link #onClosed()} after that.\n   * @param responseCode the HTTP status response, or null for other error types (e.g. timeout)\n   */\n  void onError(@Nullable Integer responseCode);\n\n  /**\n   * Should be called when the connection is closed, only after it was successfully established (ie after {@link #onConnected()} was called)\n   */\n  void onClosed();\n}\n"
  },
  {
    "path": "backend/http/src/main/java/org/sonarsource/sonarlint/core/http/RedirectInterceptor.java",
    "content": "/*\n * SonarLint Core - HTTP\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.http;\n\nimport org.apache.hc.core5.http.EntityDetails;\nimport org.apache.hc.core5.http.HttpResponse;\nimport org.apache.hc.core5.http.HttpResponseInterceptor;\nimport org.apache.hc.core5.http.HttpStatus;\nimport org.apache.hc.core5.http.Method;\nimport org.apache.hc.core5.http.protocol.HttpContext;\nimport org.apache.hc.core5.http.protocol.HttpCoreContext;\n\nclass RedirectInterceptor implements HttpResponseInterceptor {\n\n  @Override\n  public void process(HttpResponse response, EntityDetails entity, HttpContext context) {\n    alterResponseCodeIfNeeded(context, response);\n  }\n\n  private static void alterResponseCodeIfNeeded(HttpContext context, HttpResponse response) {\n    if (isPost(context)) {\n      // Apache handles some redirect statuses by transforming the POST into a GET\n      // we force a different status to keep the request a POST\n      var code = response.getCode();\n      if (code == HttpStatus.SC_MOVED_PERMANENTLY) {\n        response.setCode(HttpStatus.SC_PERMANENT_REDIRECT);\n      } else if (code == HttpStatus.SC_MOVED_TEMPORARILY || code == HttpStatus.SC_SEE_OTHER) {\n        response.setCode(HttpStatus.SC_TEMPORARY_REDIRECT);\n      }\n    }\n  }\n\n  private static boolean isPost(HttpContext context) {\n    var httpCoreContext = HttpCoreContext.cast(context);\n    var request = httpCoreContext.getRequest();\n    return request != null && Method.POST.isSame(request.getMethod());\n  }\n}\n"
  },
  {
    "path": "backend/http/src/main/java/org/sonarsource/sonarlint/core/http/RetryOnDemandStrategy.java",
    "content": "/*\n * SonarLint Core - HTTP\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.http;\n\nimport java.io.IOException;\nimport java.io.InterruptedIOException;\nimport java.net.ConnectException;\nimport java.net.NoRouteToHostException;\nimport java.net.UnknownHostException;\nimport java.util.Arrays;\nimport java.util.List;\nimport javax.net.ssl.SSLException;\nimport org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy;\nimport org.apache.hc.core5.http.ConnectionClosedException;\nimport org.apache.hc.core5.http.HttpResponse;\nimport org.apache.hc.core5.http.HttpStatus;\nimport org.apache.hc.core5.http.protocol.HttpContext;\nimport org.apache.hc.core5.util.TimeValue;\n\npublic class RetryOnDemandStrategy extends DefaultHttpRequestRetryStrategy {\n\n  private static final List<Class<? extends IOException>> SAME_AS_DEFAULT = Arrays.asList(\n    InterruptedIOException.class,\n    UnknownHostException.class,\n    ConnectException.class,\n    ConnectionClosedException.class,\n    NoRouteToHostException.class,\n    SSLException.class);\n\n  public RetryOnDemandStrategy(int maxRetries, TimeValue defaultRetryInterval) {\n    super(maxRetries,\n      defaultRetryInterval,\n      SAME_AS_DEFAULT,\n      Arrays.asList(\n        HttpStatus.SC_TOO_MANY_REQUESTS,\n        HttpStatus.SC_INTERNAL_SERVER_ERROR,\n        HttpStatus.SC_SERVICE_UNAVAILABLE));\n  }\n\n  @Override\n  public boolean retryRequest(HttpResponse response, int execCount, HttpContext context) {\n    return areRetriesEnabled(context)\n      && super.retryRequest(response, execCount, context);\n  }\n\n  private static boolean areRetriesEnabled(HttpContext context) {\n    var retriesEnabledFlag = context.getAttribute(ContextAttributes.RETRIES_ENABLED);\n    return Boolean.TRUE.equals(retriesEnabledFlag);\n  }\n}\n"
  },
  {
    "path": "backend/http/src/main/java/org/sonarsource/sonarlint/core/http/ThreadFactories.java",
    "content": "/*\n * SonarLint Core - HTTP\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.http;\n\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.atomic.AtomicInteger;\n\npublic class ThreadFactories {\n\n  private ThreadFactories() {\n  }\n\n  public static ThreadFactory threadWithNamePrefix(String namePrefix) {\n    return new ThreadFactoryWithNamePrefix(namePrefix);\n  }\n\n  private static final class ThreadFactoryWithNamePrefix implements ThreadFactory {\n    private final String namePrefix;\n    private final AtomicInteger nextId = new AtomicInteger();\n\n    ThreadFactoryWithNamePrefix(String prefix) {\n      this.namePrefix = prefix;\n    }\n\n    @Override\n    public Thread newThread(Runnable r) {\n      String name = namePrefix + nextId.getAndIncrement();\n      return new Thread(null, r, name, 0, false);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/http/src/main/java/org/sonarsource/sonarlint/core/http/WebSocketClient.java",
    "content": "/*\n * SonarLint Core - HTTP\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.http;\n\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.WebSocket;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.CompletionStage;\nimport java.util.concurrent.ExecutorService;\nimport java.util.function.Consumer;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic class WebSocketClient {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final String AUTHORIZATION_HEADER_NAME = \"Authorization\";\n  private static final String USER_AGENT_HEADER_NAME = \"User-Agent\";\n\n  private final String userAgent;\n  @Nullable\n  private final String token;\n  private final HttpClient httpClient;\n\n  WebSocketClient(String userAgent, @Nullable String token, ExecutorService executor) {\n    this.userAgent = userAgent;\n    this.token = token;\n    this.httpClient = HttpClient\n      .newBuilder()\n      // Don't use the default thread pool as it won't allow inheriting thread local variables\n      .executor(executor)\n      .build();\n  }\n\n  public CompletableFuture<WebSocket> createWebSocketConnection(@Nullable URI uri, Consumer<String> messageConsumer,\n    Runnable onClosedRunnable) {\n    // Validate URI before attempting connection\n    if (uri == null || (!\"ws\".equals(uri.getScheme()) && !\"wss\".equals(uri.getScheme()))) {\n      var future = new CompletableFuture<WebSocket>();\n      future.completeExceptionally(new IllegalArgumentException(\"WebSocket URI must use 'ws' or 'wss' scheme: \" + uri));\n      return future;\n    }\n\n    // TODO handle handshake or other errors\n    var currentThreadOutput = SonarLintLogger.get().getTargetForCopy();\n    return httpClient\n      .newWebSocketBuilder()\n      .header(AUTHORIZATION_HEADER_NAME, \"Bearer \" + token)\n      .header(USER_AGENT_HEADER_NAME, userAgent)\n      .buildAsync(uri, new MessageConsumerWrapper(messageConsumer, onClosedRunnable, currentThreadOutput));\n  }\n\n  private record MessageConsumerWrapper(Consumer<String> messageConsumer, Runnable onWebSocketInputClosedRunnable,\n                                        @Nullable LogOutput currentThreadOutput) implements WebSocket.Listener {\n\n    @Override\n    public void onOpen(WebSocket webSocket) {\n      // HttpClient is calling downstream completablefutures on the CF common pool so the thread local variables are\n      // not necessarily inherited\n      // See\n      // https://github.com/openjdk/jdk/blob/744e0893100d402b2b51762d57bcc2e99ab7fdcc/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java#L1069\n      SonarLintLogger.get().setTarget(currentThreadOutput);\n      LOG.debug(\"WebSocket opened\");\n      WebSocket.Listener.super.onOpen(webSocket);\n    }\n\n    @Override\n    public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {\n      SonarLintLogger.get().setTarget(currentThreadOutput);\n      messageConsumer.accept(data.toString());\n      return WebSocket.Listener.super.onText(webSocket, data, last);\n    }\n\n    @Override\n    public void onError(WebSocket webSocket, Throwable error) {\n      SonarLintLogger.get().setTarget(currentThreadOutput);\n      LOG.error(\"Error occurred on the WebSocket\", error);\n      onWebSocketInputClosedRunnable.run();\n    }\n\n    @Override\n    public CompletionStage<?> onClose(WebSocket webSocket, int statusCode, String reason) {\n      SonarLintLogger.get().setTarget(currentThreadOutput);\n      LOG.debug(\"WebSocket closed, status=\" + statusCode + \", reason=\" + reason);\n      onWebSocketInputClosedRunnable.run();\n      return null;\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/http/src/main/java/org/sonarsource/sonarlint/core/http/package-info.java",
    "content": "/*\n * SonarLint Core - HTTP\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.http;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/http/src/main/java/org/sonarsource/sonarlint/core/http/ssl/CertificateStore.java",
    "content": "/*\n * SonarLint Core - HTTP\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.http.ssl;\n\nimport java.nio.file.Path;\n\npublic class CertificateStore {\n  public static final String DEFAULT_PASSWORD = \"sonarlint\";\n  public static final String DEFAULT_STORE_TYPE = \"PKCS12\";\n  private final Path path;\n  private final String keyStorePassword;\n  private final String keyStoreType;\n\n  public CertificateStore(Path path, String keyStorePassword, String keyStoreType) {\n    this.path = path;\n    this.keyStorePassword = keyStorePassword;\n    this.keyStoreType = keyStoreType;\n  }\n\n  public Path getPath() {\n    return path;\n  }\n\n  public String getKeyStorePassword() {\n    return keyStorePassword;\n  }\n\n  public String getKeyStoreType() {\n    return keyStoreType;\n  }\n}\n"
  },
  {
    "path": "backend/http/src/main/java/org/sonarsource/sonarlint/core/http/ssl/SslConfig.java",
    "content": "/*\n * SonarLint Core - HTTP\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.http.ssl;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class SslConfig {\n  private final CertificateStore keyStore;\n  private final CertificateStore trustStore;\n\n  public SslConfig(@Nullable CertificateStore keyStore, @Nullable CertificateStore trustStore) {\n    this.keyStore = keyStore;\n    this.trustStore = trustStore;\n  }\n\n  @CheckForNull\n  public CertificateStore getKeyStore() {\n    return keyStore;\n  }\n\n  @CheckForNull\n  public CertificateStore getTrustStore() {\n    return trustStore;\n  }\n}\n"
  },
  {
    "path": "backend/http/src/main/java/org/sonarsource/sonarlint/core/http/ssl/package-info.java",
    "content": "/*\n * SonarLint Core - HTTP\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.http.ssl;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/http/src/test/java/org/sonarsource/sonarlint/core/http/HttpClientProviderTests.java",
    "content": "/*\n * SonarLint Core - HTTP\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.http;\n\nimport com.github.tomakehurst.wiremock.junit5.WireMockExtension;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport org.apache.hc.core5.http.ContentType;\nimport org.apache.hc.core5.http.HttpStatus;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.equalTo;\nimport static com.github.tomakehurst.wiremock.client.WireMock.get;\nimport static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.post;\nimport static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass HttpClientProviderTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @RegisterExtension\n  static WireMockExtension sonarqubeMock = WireMockExtension.newInstance()\n    .options(wireMockConfig().dynamicPort())\n    .build();\n\n  @Test\n  void it_should_use_user_agent() {\n    var underTest = HttpClientProvider.forTesting();\n\n    underTest.getHttpClientWithoutAuth().get(sonarqubeMock.url(\"/test\"));\n\n    sonarqubeMock.verify(getRequestedFor(urlEqualTo(\"/test\"))\n      .withHeader(\"User-Agent\", equalTo(\"SonarLint tests\")));\n  }\n\n  @Test\n  void it_should_support_cancellation() {\n    sonarqubeMock.stubFor(get(\"/delayed\")\n      .willReturn(aResponse()\n        .withFixedDelay(20000)));\n\n    var underTest = HttpClientProvider.forTesting();\n\n    var future = underTest.getHttpClientWithoutAuth().getAsync(sonarqubeMock.url(\"/delayed\"));\n    assertThrows(TimeoutException.class, () -> future.get(100, TimeUnit.MILLISECONDS));\n    assertThat(future.cancel(true)).isTrue();\n    assertThat(future).isCancelled();\n\n    assertThat(logTester.logs()).containsExactly(\"Request cancelled\");\n  }\n\n  @Test\n  void it_should_preserve_post_on_permanent_moved_status() {\n    sonarqubeMock.stubFor(post(\"/afterMove\").willReturn(aResponse()));\n    sonarqubeMock.stubFor(post(\"/permanentMoved\")\n      .willReturn(aResponse()\n        .withStatus(HttpStatus.SC_MOVED_PERMANENTLY)\n        .withHeader(\"Location\", sonarqubeMock.url(\"/afterMove\"))));\n\n    HttpClientProvider.forTesting().getHttpClientWithoutAuth().post(sonarqubeMock.url(\"/permanentMoved\"), \"text/html\", \"Foo\");\n\n    sonarqubeMock.verify(postRequestedFor(urlEqualTo(\"/afterMove\")));\n  }\n\n  @Test\n  void it_should_preserve_post_on_temporarily_moved_status() {\n    sonarqubeMock.stubFor(post(\"/afterMove\").willReturn(aResponse()));\n    sonarqubeMock.stubFor(post(\"/tempMoved\")\n      .willReturn(aResponse()\n        .withStatus(HttpStatus.SC_MOVED_TEMPORARILY)\n        .withHeader(\"Location\", sonarqubeMock.url(\"/afterMove\"))));\n\n    HttpClientProvider.forTesting().getHttpClientWithoutAuth().post(sonarqubeMock.url(\"/tempMoved\"), \"text/html\", \"Foo\");\n\n    sonarqubeMock.verify(postRequestedFor(urlEqualTo(\"/afterMove\")));\n  }\n\n  @Test\n  void it_should_preserve_post_on_see_other_status() {\n    sonarqubeMock.stubFor(post(\"/afterMove\").willReturn(aResponse()));\n    sonarqubeMock.stubFor(post(\"/seeOther\")\n      .willReturn(aResponse()\n        .withStatus(HttpStatus.SC_SEE_OTHER)\n        .withHeader(\"Location\", sonarqubeMock.url(\"/afterMove\"))));\n\n    HttpClientProvider.forTesting().getHttpClientWithoutAuth().post(sonarqubeMock.url(\"/seeOther\"), \"text/html\", \"Foo\");\n\n    sonarqubeMock.verify(postRequestedFor(urlEqualTo(\"/afterMove\")));\n  }\n\n  @Test\n  void it_should_not_retry_non_idempotent_by_default() {\n    sonarqubeMock.stubFor(post(\"/error\").willReturn(aResponse().withStatus(HttpStatus.SC_SERVICE_UNAVAILABLE)));\n\n    var underTest = HttpClientProvider.forTesting();\n\n    underTest.getHttpClientWithoutAuth().post(sonarqubeMock.url(\"/error\"), ContentType.TEXT_PLAIN.getMimeType(), \"body\");\n\n    sonarqubeMock.verify(1, postRequestedFor(urlEqualTo(\"/error\"))\n      .withHeader(\"User-Agent\", equalTo(\"SonarLint tests\")));\n  }\n}\n"
  },
  {
    "path": "backend/http/src/test/java/org/sonarsource/sonarlint/core/http/WebSocketClientTest.java",
    "content": "/*\n * SonarLint Core - HTTP\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.http;\n\nimport java.net.URI;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass WebSocketClientTest {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private static ExecutorService executor;\n\n  @BeforeAll\n  static void setUp() {\n    executor = Executors.newSingleThreadExecutor();\n  }\n\n  @AfterAll\n  static void tearDown() {\n    if (executor != null) {\n      executor.shutdown();\n      try {\n        if (!executor.awaitTermination(1, TimeUnit.SECONDS)) {\n          executor.shutdownNow();\n        }\n      } catch (InterruptedException e) {\n        executor.shutdownNow();\n        Thread.currentThread().interrupt();\n      }\n    }\n  }\n\n  @Test\n  void should_validate_null_uri() {\n    var client = new WebSocketClient(\"test-agent\", \"token\", executor);\n    \n    var future = client.createWebSocketConnection(null, message -> {}, () -> {});\n    \n    assertThat(future).isCompletedExceptionally();\n    assertThatThrownBy(future::get)\n      .hasCauseInstanceOf(IllegalArgumentException.class)\n      .hasMessageContaining(\"WebSocket URI must use 'ws' or 'wss' scheme\");\n  }\n\n  @Test\n  void should_validate_invalid_scheme() {\n    var client = new WebSocketClient(\"test-agent\", \"token\", executor);\n    \n    var future = client.createWebSocketConnection(URI.create(\"http://example.com\"), message -> {}, () -> {});\n    \n    assertThat(future).isCompletedExceptionally();\n    assertThatThrownBy(future::get)\n      .hasCauseInstanceOf(IllegalArgumentException.class)\n      .hasMessageContaining(\"WebSocket URI must use 'ws' or 'wss' scheme\");\n  }\n\n  @Test\n  void should_accept_valid_ws_uri() {\n    var client = new WebSocketClient(\"test-agent\", \"token\", executor);\n    \n    var future = client.createWebSocketConnection(URI.create(\"ws://example.com\"), message -> {}, () -> {});\n\n    assertThat(future).isNotCompletedExceptionally();\n  }\n\n  @Test\n  void should_accept_valid_wss_uri() {\n    var client = new WebSocketClient(\"test-agent\", \"token\", executor);\n    \n    var future = client.createWebSocketConnection(URI.create(\"wss://example.com\"), message -> {}, () -> {});\n\n    assertThat(future).isNotCompletedExceptionally();\n  }\n\n}\n"
  },
  {
    "path": "backend/http/src/test/resources/logback-test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE configuration>\n\n<configuration>\n  <include resource=\"logback-shared.xml\"/>\n</configuration>"
  },
  {
    "path": "backend/plugin-api/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-backend-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../pom.xml</relativePath>\n  </parent>\n  <artifactId>sonarlint-plugin-api</artifactId>\n  <packaging>jar</packaging>\n  <name>SonarLint Plugin API</name>\n  <description>API used between SonarLint and analyzers</description>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.code.findbugs</groupId>\n      <artifactId>jsr305</artifactId>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.api.plugin</groupId>\n      <artifactId>sonar-plugin-api</artifactId>\n      <version>${sonar-plugin-api.version}</version>\n      <exclusions>\n        <exclusion>\n          <groupId>org.slf4j</groupId>\n          <artifactId>slf4j-api</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <resources>\n    <resource>\n      <directory>src/main/resources</directory>\n      <filtering>true</filtering>\n      <includes>\n        <include>sonarlint-api-version.txt</include>\n      </includes>\n    </resource>\n  </resources>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-source-plugin</artifactId>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "backend/plugin-api/src/main/java/org/sonarsource/sonarlint/plugin/api/SonarLintRuntime.java",
    "content": "/*\n * SonarLint Plugin API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.plugin.api;\n\nimport org.sonar.api.Plugin;\nimport org.sonar.api.SonarRuntime;\nimport org.sonar.api.utils.Version;\n\n/**\n * Provides extra runtime related information when in the context of SonarLint.\n *\n * An instance of this class can be accessed through the context passed in {@link org.sonar.api.Plugin.Context#define(Plugin.Context)}.\n * @since 6.0\n */\npublic interface SonarLintRuntime extends SonarRuntime {\n  /**\n   * @since 6.0\n   * @return the version of the sonarlint-plugin-api\n   */\n  Version getSonarLintPluginApiVersion();\n\n  /**\n   * @since 6.2\n   * @return the PID of the client (IDE)\n   */\n  long getClientPid();\n}\n"
  },
  {
    "path": "backend/plugin-api/src/main/java/org/sonarsource/sonarlint/plugin/api/issue/NewInputFileEdit.java",
    "content": "/*\n * SonarLint Plugin API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.plugin.api.issue;\n\nimport org.sonar.api.batch.fs.InputFile;\n\n/**\n * Describe a file edit for a {@link NewQuickFix} as a collection of {@link NewTextEdit}s on a given {@link InputFile}.\n * Text edits are applied in the order they are added, insofar that their ranges do not overlap.\n * @since 6.3\n * @deprecated use org.sonar.api.batch.sensor.issue.fix.NewInputFileEdit from the sonar-plugin-api instead\n */\n@Deprecated(since = \"8.12\")\npublic interface NewInputFileEdit {\n\n  /**\n   * @param inputFile the input file on which to apply this edit\n   * @return the modified edit\n   */\n  NewInputFileEdit on(InputFile inputFile);\n\n  /**\n   * Create a new text edit\n   * @return a new uninitialized instance of a text edit for a given file edit\n   */\n  NewTextEdit newTextEdit();\n\n  /**\n   * Add a text edit to this input file edit\n   * @param newTextEdit the text edit to add\n   * @return this instance\n   */\n  NewInputFileEdit addTextEdit(NewTextEdit newTextEdit);\n}\n"
  },
  {
    "path": "backend/plugin-api/src/main/java/org/sonarsource/sonarlint/plugin/api/issue/NewQuickFix.java",
    "content": "/*\n * SonarLint Plugin API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.plugin.api.issue;\n\n/**\n * Describe a quick fix for a {@link NewSonarLintIssue}, with a description and a collection of {@link NewInputFileEdit}.\n * Input file edits will be applied in the order they are added, insofar that they are compatible with one another.\n * @since 6.3\n * @deprecated use org.sonar.api.batch.sensor.issue.fix.NewQuickFix from the sonar-plugin-api instead\n */\n@Deprecated(since = \"8.12\")\npublic interface NewQuickFix {\n\n  /**\n   * Define the message for this quick fix, which will be shown to the user as an action item.\n   * The fix message may be inspired by the issue message, but the context into which they appear is different,\n   * so it might be better to adapt it. A good message should:\n   * <ul>\n   *   <li>Be short (ideally, not more than 50 characters)</li>\n   *   <li>Use sentence capitalization</li>\n   *   <li><em>Not</em> end with a full stop (<code>.</code>)</li>\n   *   <li>Describe the expected outcome of the change, e.g. <i>Make the constructor explicit</i> instead of <i>Add the \"explicit\" keyword</i>.\n   *   It tells the user how to fix the issue</li>\n   *   <li>Focus on the target more than the current situation. For instance, <i>Replace \"AAA\" with \"BBB\"</i> would be better phrased <i>Replace with \"BBB\"</i></li>\n   *   <li>Avoid the use of a demonstrative, e.g. <i>this</i>. Prefer the more neutral <i>the</i>.\n   *   The message may be used in several contexts, some of which would not work very well with a demonstrative</li>\n   * </ul>\n   * @param message a description for this quick fix\n   * @return the updated quickfix\n   */\n  NewQuickFix message(String message);\n\n  /**\n   * Create a new input file edit\n   * @return a new uninitialized instance of a file edit for a given fix\n   */\n  NewInputFileEdit newInputFileEdit();\n\n  /**\n   * Add a new input file edit to this quick fix\n   * @param newInputFileEdit the input file edit to add\n   * @return this instance\n   */\n  NewQuickFix addInputFileEdit(NewInputFileEdit newInputFileEdit);\n}\n"
  },
  {
    "path": "backend/plugin-api/src/main/java/org/sonarsource/sonarlint/plugin/api/issue/NewSonarLintIssue.java",
    "content": "/*\n * SonarLint Plugin API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.plugin.api.issue;\n\n/**\n * Extension interface to add {@link NewQuickFix}es to a {@link org.sonar.api.batch.sensor.issue.NewIssue}\n * @since 6.3\n * @deprecated use org.sonar.api.batch.sensor.issue.NewIssue from the sonar-plugin-api instead\n */\n@Deprecated(since = \"8.12\")\npublic interface NewSonarLintIssue {\n\n  /**\n   * Create a new quick fix\n   * @return a new uninitialized instance of a quick fix for a given issue\n   */\n  NewQuickFix newQuickFix();\n\n  /**\n   * Add a new quick fix to this issue\n   * @param newQuickFix the quick fix to add\n   * @return this object\n   */\n  NewSonarLintIssue addQuickFix(NewQuickFix newQuickFix);\n}\n"
  },
  {
    "path": "backend/plugin-api/src/main/java/org/sonarsource/sonarlint/plugin/api/issue/NewTextEdit.java",
    "content": "/*\n * SonarLint Plugin API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.plugin.api.issue;\n\nimport org.sonar.api.batch.fs.TextRange;\n\n/**\n * Describe a text edit for a {@link NewInputFileEdit} as a replacement text for a given {@link TextRange}\n * @since 6.3\n * @deprecated use org.sonar.api.batch.sensor.issue.fix.NewTextEdit from the sonar-plugin-api instead\n */\n@Deprecated(since = \"8.12\")\npublic interface NewTextEdit {\n\n  /**\n   * @param range the range on which to apply this edit\n   * @return the modified edit\n   */\n  NewTextEdit at(TextRange range);\n\n  /**\n   * Prior to 6.4, line returns had to be represented with the '\\n' character.\n   * From 6.4 on, analyzers can use any EOL character they see fit, SonarLint takes care of adapting this to the one\n   * expected by the IDE.\n   * To remove code, use the empty string (\"\").\n   * When removing some code from the source file, make sure that no lines consisting only of whitespaces remain.\n   * If after the code is removed a non-whitespace character remains, place it at the same indentation level as the removed code.\n   * @param newText the replacement text.\n   * @return the modified edit\n   */\n  NewTextEdit withNewText(String newText);\n}\n"
  },
  {
    "path": "backend/plugin-api/src/main/java/org/sonarsource/sonarlint/plugin/api/issue/package-info.java",
    "content": "/*\n * SonarLint Plugin API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.plugin.api.issue;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/plugin-api/src/main/java/org/sonarsource/sonarlint/plugin/api/module/file/ModuleFileEvent.java",
    "content": "/*\n * SonarLint Plugin API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.plugin.api.module.file;\n\nimport org.sonar.api.batch.fs.InputFile;\n\n/**\n * @since 6.0\n */\npublic interface ModuleFileEvent {\n  /**\n   * @return the InputFile concerned by the event\n   * @since 6.0\n   */\n  InputFile getTarget();\n\n  /**\n   * @return the event Type\n   * @since 6.0\n   */\n  Type getType();\n\n  /**\n   * @since 6.0\n   */\n  enum Type {\n    CREATED, MODIFIED, DELETED\n  }\n}\n"
  },
  {
    "path": "backend/plugin-api/src/main/java/org/sonarsource/sonarlint/plugin/api/module/file/ModuleFileListener.java",
    "content": "/*\n * SonarLint Plugin API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.plugin.api.module.file;\n\n/**\n * Implement this interface and annotate the class with {@code @SonarLintSide(MODULE)} to receive events related to the module file system.\n * @since 6.0\n */\npublic interface ModuleFileListener {\n  /**\n   * React to a file creation, deletion or modification event\n   *\n   * @param event an event that concerns a file in the module file system\n   * @since 6.0\n   */\n  void process(ModuleFileEvent event);\n}\n"
  },
  {
    "path": "backend/plugin-api/src/main/java/org/sonarsource/sonarlint/plugin/api/module/file/ModuleFileSystem.java",
    "content": "/*\n * SonarLint Plugin API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.plugin.api.module.file;\n\nimport java.util.stream.Stream;\nimport org.sonar.api.batch.fs.InputFile;\n\n/**\n * This class is made available to components annotated with {@code @SonarLintSide(MODULE)}.\n * @since 6.0\n */\npublic interface ModuleFileSystem {\n  /**\n   * Returns all the files within the module that end with {@code suffix} and match {@code type}.\n   *\n   * @param suffix a suffix to filter the files\n   * @param type   the type of file\n   * @return a stream of files that match the given suffix and type in the module\n   * @since 6.0\n   */\n  Stream<InputFile> files(String suffix, InputFile.Type type);\n\n\n  /**\n   * Returns all the files within the module.\n   *\n   * @return a stream of module files\n   * @since 6.0\n   */\n  Stream<InputFile> files();\n}\n"
  },
  {
    "path": "backend/plugin-api/src/main/java/org/sonarsource/sonarlint/plugin/api/module/file/package-info.java",
    "content": "/*\n * SonarLint Plugin API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.plugin.api.module.file;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/plugin-api/src/main/java/org/sonarsource/sonarlint/plugin/api/package-info.java",
    "content": "/*\n * SonarLint Plugin API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.plugin.api;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/plugin-api/src/main/resources/sonarlint-api-version.txt",
    "content": "${project.version}\n"
  },
  {
    "path": "backend/plugin-commons/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-backend-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../pom.xml</relativePath>\n  </parent>\n  <artifactId>sonarlint-plugin-commons</artifactId>\n  <name>SonarLint Core - Plugin Commons</name>\n  <description>Common code used to load/execute SonarQube plugins</description>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.code.findbugs</groupId>\n      <artifactId>jsr305</artifactId>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-commons</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-plugin-api</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.api.plugin</groupId>\n      <artifactId>sonar-plugin-api</artifactId>\n      <version>${sonar-plugin-api.version}</version>\n      <exclusions>\n        <exclusion>\n          <groupId>org.slf4j</groupId>\n          <artifactId>slf4j-api</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>org.springframework</groupId>\n      <artifactId>spring-context</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-lang3</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>commons-io</groupId>\n      <artifactId>commons-io</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.classloader</groupId>\n      <artifactId>sonar-classloader</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-csv</artifactId>\n      <version>1.14.1</version>\n    </dependency>\n\n    <!-- Tests -->\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-engine</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-params</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.assertj</groupId>\n      <artifactId>assertj-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>commons-codec</groupId>\n      <artifactId>commons-codec</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>jakarta.annotation</groupId>\n      <artifactId>jakarta.annotation-api</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>javax.annotation</groupId>\n      <artifactId>javax.annotation-api</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>ch.qos.logback</groupId>\n      <artifactId>logback-classic</artifactId>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <profiles>\n    <!-- Workaround for https://issues.apache.org/jira/projects/MJAR/issues/MJAR-138 -->\n    <profile>\n      <id>conditionally-add-commons-tests-if-tests-not-skipped</id>\n      <activation>\n        <property>\n          <name>maven.test.skip</name>\n          <value>!true</value>\n        </property>\n      </activation>\n      <dependencies>\n        <dependency>\n          <groupId>${project.groupId}</groupId>\n          <artifactId>sonarlint-commons</artifactId>\n          <version>${project.version}</version>\n          <classifier>tests</classifier>\n          <type>test-jar</type>\n          <scope>test</scope>\n        </dependency>\n      </dependencies>\n    </profile>\n  </profiles>\n</project>\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/com/sonarsource/plugins/license/api/LicensedPluginRegistration.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage com.sonarsource.plugins.license.api;\n\npublic class LicensedPluginRegistration {\n\n  private final String pluginKey;\n\n  private LicensedPluginRegistration(Builder builder) {\n    this.pluginKey = builder.pluginKey;\n  }\n\n  public String getPluginKey() {\n    return pluginKey;\n  }\n\n  public static LicensedPluginRegistration forPlugin(String pluginKey) {\n    return new Builder().setPluginKey(pluginKey).build();\n  }\n\n  public static final class Builder {\n    private String pluginKey;\n\n    public Builder setPluginKey(String s) {\n      this.pluginKey = s;\n      return this;\n    }\n\n    public LicensedPluginRegistration build() {\n      return new LicensedPluginRegistration(this);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/com/sonarsource/plugins/license/api/package-info.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage com.sonarsource.plugins.license.api;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonar/api/SonarQubeVersion.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonar.api;\n\nimport javax.annotation.concurrent.Immutable;\nimport org.sonar.api.ce.ComputeEngineSide;\nimport org.sonar.api.scanner.ScannerSide;\nimport org.sonar.api.server.ServerSide;\nimport org.sonar.api.utils.Version;\n\nimport static java.util.Objects.requireNonNull;\n\n/**\n * This class was removed from the plugin API but still used in older CFamily analyzer\n */\n@ScannerSide\n@ServerSide\n@ComputeEngineSide\n@Immutable\n@Deprecated\npublic class SonarQubeVersion {\n\n  private final Version version;\n\n  public SonarQubeVersion(Version version) {\n    requireNonNull(version);\n    this.version = version;\n  }\n\n  public Version get() {\n    return this.version;\n  }\n\n  public boolean isGreaterThanOrEqual(Version than) {\n    return this.version.isGreaterThanOrEqual(than);\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonar/api/package-info.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonar.api;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/ApiVersions.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons;\n\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Scanner;\nimport org.sonar.api.utils.Version;\n\npublic class ApiVersions {\n\n  static final String SONAR_PLUGIN_API_VERSION_FILE_PATH = \"/sonar-api-version.txt\";\n  private static final String SONARLINT_PLUGIN_API_VERSION_FILE_PATH = \"/sonarlint-api-version.txt\";\n\n  private ApiVersions() {\n    // only static methods\n  }\n\n  public static Version loadSonarPluginApiVersion() {\n    return loadVersion(SONAR_PLUGIN_API_VERSION_FILE_PATH);\n  }\n\n  public static Version loadSonarLintPluginApiVersion() {\n    return loadVersion(SONARLINT_PLUGIN_API_VERSION_FILE_PATH);\n  }\n\n  private static Version loadVersion(String versionFilePath) {\n    return loadVersion(ApiVersions.class.getResource(versionFilePath), versionFilePath);\n  }\n\n  static Version loadVersion(URL versionFileURL, String versionFilePath) {\n    try (var scanner = new Scanner(versionFileURL.openStream(), StandardCharsets.UTF_8)) {\n      var versionInFile = scanner.nextLine();\n      return Version.parse(versionInFile);\n    } catch (Exception e) {\n      throw new IllegalStateException(\"Can not load \" + versionFilePath + \" from classpath\", e);\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/DataflowBugDetection.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons;\n\nimport java.util.Set;\n\npublic class DataflowBugDetection {\n\n  private DataflowBugDetection() {\n    // Static stuff only\n  }\n\n  public static final Set<String> PLUGIN_ALLOW_LIST = Set.of(\"dbd\", \"dbdpythonfrontend\", \"dbdjavafrontend\");\n\n  static Set<String> getPluginAllowList(boolean isDataflowBugDetectionEnabled) {\n    return isDataflowBugDetectionEnabled ? PLUGIN_ALLOW_LIST : Set.of();\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/ExtensionInstaller.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons;\n\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.function.BiPredicate;\nimport org.sonar.api.Plugin;\nimport org.sonar.api.config.Configuration;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.plugin.commons.container.ExtensionContainer;\nimport org.sonarsource.sonarlint.core.plugin.commons.sonarapi.PluginContextImpl;\nimport org.sonarsource.sonarlint.plugin.api.SonarLintRuntime;\n\npublic class ExtensionInstaller {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final SonarLintRuntime sonarRuntime;\n  private final Configuration bootConfiguration;\n\n  public ExtensionInstaller(SonarLintRuntime sonarRuntime, Configuration bootConfiguration) {\n    this.sonarRuntime = sonarRuntime;\n    this.bootConfiguration = bootConfiguration;\n  }\n\n  public void install(ExtensionContainer container, Map<String, Plugin> pluginInstancesByKey, BiPredicate<String, Object> extensionFilter) {\n    for (Entry<String, Plugin> pluginInstanceEntry : pluginInstancesByKey.entrySet()) {\n      var plugin = pluginInstanceEntry.getValue();\n      var context = new PluginContextImpl.Builder()\n        .setSonarRuntime(sonarRuntime)\n        .setBootConfiguration(bootConfiguration)\n        .build();\n      var pluginKey = pluginInstanceEntry.getKey();\n      try {\n        plugin.define(context);\n        loadExtensions(container, pluginKey, context, extensionFilter);\n      } catch (Throwable t) {\n        LOG.error(\"Error loading components for plugin '{}'\", pluginKey, t);\n      }\n    }\n  }\n\n  private static void loadExtensions(ExtensionContainer container, String pluginKey, Plugin.Context context, BiPredicate<String, Object> extensionFilter) {\n    for (Object extension : context.getExtensions()) {\n      if (extensionFilter.test(pluginKey, extension)) {\n        container.addExtension(pluginKey, extension);\n      }\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/ExtensionUtils.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons;\n\nimport org.sonar.api.batch.InstantiationStrategy;\nimport org.sonar.api.batch.ScannerSide;\nimport org.sonar.api.utils.AnnotationUtils;\nimport org.sonarsource.api.sonarlint.SonarLintSide;\n\npublic class ExtensionUtils {\n\n  private ExtensionUtils() {\n    // only static methods\n  }\n\n  public static boolean isInstantiationStrategy(Object extension, String strategy) {\n    var annotation = AnnotationUtils.getAnnotation(extension, InstantiationStrategy.class);\n    if (annotation != null) {\n      return strategy.equals(annotation.value());\n    }\n    return InstantiationStrategy.PER_PROJECT.equals(strategy);\n  }\n\n  public static boolean isSonarLintSide(Object extension) {\n    return AnnotationUtils.getAnnotation(extension, SonarLintSide.class) != null;\n  }\n\n  public static boolean isScannerSide(Object extension) {\n    return AnnotationUtils.getAnnotation(extension, ScannerSide.class) != null ||\n      AnnotationUtils.getAnnotation(extension, SonarLintSide.class) != null;\n  }\n\n  public static boolean isType(Object extension, Class<?> extensionClass) {\n    var clazz = extension instanceof Class ? (Class<?>) extension : extension.getClass();\n    return extensionClass.isAssignableFrom(clazz);\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/LoadedPlugins.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons;\n\nimport java.io.IOException;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport org.sonar.api.Plugin;\nimport org.sonarsource.sonarlint.core.plugin.commons.loading.PluginInstancesLoader;\n\npublic class LoadedPlugins {\n  private final Map<String, Plugin> pluginInstancesByKeys;\n  private final PluginInstancesLoader pluginInstancesLoader;\n  private final Set<String> additionalAllowedPlugins;\n  private final Set<String> disabledPluginKeys;\n\n  public LoadedPlugins(Map<String, Plugin> pluginInstancesByKeys, PluginInstancesLoader pluginInstancesLoader,\n    Set<String> additionalAllowedPlugins, Set<String> disabledPluginKeys) {\n    this.pluginInstancesByKeys = pluginInstancesByKeys;\n    this.pluginInstancesLoader = pluginInstancesLoader;\n    this.additionalAllowedPlugins = additionalAllowedPlugins;\n    this.disabledPluginKeys = disabledPluginKeys;\n  }\n\n  public Map<String, Plugin> getAllPluginInstancesByKeys() {\n    return pluginInstancesByKeys;\n  }\n\n  public Map<String, Plugin> getAnalysisPluginInstancesByKeys() {\n    return pluginInstancesByKeys.entrySet().stream()\n      .filter(entry -> !disabledPluginKeys.contains(entry.getKey()))\n      .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\n  }\n\n  public Set<String> getAdditionalAllowedPlugins() {\n    return additionalAllowedPlugins;\n  }\n\n  public void close() throws IOException {\n    // close plugins classloaders\n    pluginInstancesByKeys.clear();\n    pluginInstancesLoader.close();\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/MultivalueProperty.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons;\n\nimport java.io.IOException;\nimport java.io.StringReader;\nimport java.io.UncheckedIOException;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.function.UnaryOperator;\nimport org.apache.commons.csv.CSVFormat;\nimport org.apache.commons.csv.CSVRecord;\nimport org.apache.commons.lang3.ArrayUtils;\n\nimport static java.util.function.UnaryOperator.identity;\n\npublic class MultivalueProperty {\n  private MultivalueProperty() {\n    // prevents instantiation\n  }\n\n  public static String[] parseAsCsv(String key, String value) {\n    return parseAsCsv(key, value, identity());\n  }\n\n  public static String[] parseAsCsv(String key, String value, UnaryOperator<String> valueProcessor) {\n    String cleanValue = MultivalueProperty.trimFieldsAndRemoveEmptyFields(value);\n    List<String> result = new ArrayList<>();\n    try (var csvParser = CSVFormat.RFC4180.builder()\n      .setSkipHeaderRecord(true)\n      .setIgnoreEmptyLines(true)\n      .setIgnoreSurroundingSpaces(true)\n      .get()\n      .parse(new StringReader(cleanValue))) {\n      List<CSVRecord> records = csvParser.getRecords();\n      if (records.isEmpty()) {\n        return ArrayUtils.EMPTY_STRING_ARRAY;\n      }\n      processRecords(result, records, valueProcessor);\n      return result.toArray(new String[0]);\n    } catch (IOException | UncheckedIOException e) {\n      throw new IllegalStateException(\"Property: '\" + key + \"' doesn't contain a valid CSV value: '\" + value + \"'\", e);\n    }\n  }\n\n  /**\n   * In most cases we expect a single record. <br>Having multiple records means the input value was splitted over multiple lines (this is common in Maven).\n   * For example:\n   * <pre>\n   *   &lt;sonar.exclusions&gt;\n   *     src/foo,\n   *     src/bar,\n   *     src/biz\n   *   &lt;sonar.exclusions&gt;\n   * </pre>\n   * In this case records will be merged to form a single list of items. Last item of a record is appended to first item of next record.\n   * <p>\n   * This is a very curious case, but we try to preserve line break in the middle of an item:\n   * <pre>\n   *   &lt;sonar.exclusions&gt;\n   *     a\n   *     b,\n   *     c\n   *   &lt;sonar.exclusions&gt;\n   * </pre>\n   * will produce ['a\\nb', 'c']\n   */\n  private static void processRecords(List<String> result, List<CSVRecord> records, UnaryOperator<String> valueProcessor) {\n    for (CSVRecord csvRecord : records) {\n      Iterator<String> it = csvRecord.iterator();\n      if (!result.isEmpty()) {\n        String next = it.next();\n        if (!next.isEmpty()) {\n          int lastItemIdx = result.size() - 1;\n          String previous = result.get(lastItemIdx);\n          if (previous.isEmpty()) {\n            result.set(lastItemIdx, valueProcessor.apply(next));\n          } else {\n            result.set(lastItemIdx, valueProcessor.apply(previous + \"\\n\" + next));\n          }\n        }\n      }\n      it.forEachRemaining(s -> {\n        String apply = valueProcessor.apply(s);\n        result.add(apply);\n      });\n    }\n  }\n\n  /**\n   * Removes the empty fields from the value of a multi-value property from empty fields, including trimming each field.\n   * <p>\n   * Quotes can be used to prevent an empty field to be removed (as it is used to preserve empty spaces).\n   * <ul>\n   *    <li>{@code \"\" => \"\"}</li>\n   *    <li>{@code \" \" => \"\"}</li>\n   *    <li>{@code \",\" => \"\"}</li>\n   *    <li>{@code \",,\" => \"\"}</li>\n   *    <li>{@code \",,,\" => \"\"}</li>\n   *    <li>{@code \",a\" => \"a\"}</li>\n   *    <li>{@code \"a,\" => \"a\"}</li>\n   *    <li>{@code \",a,\" => \"a\"}</li>\n   *    <li>{@code \"a,,b\" => \"a,b\"}</li>\n   *    <li>{@code \"a,   ,b\" => \"a,b\"}</li>\n   *    <li>{@code \"a,\\\"\\\",b\" => \"a,b\"}</li>\n   *    <li>{@code \"\\\"a\\\",\\\"b\\\"\" => \"\\\"a\\\",\\\"b\\\"\"}</li>\n   *    <li>{@code \"\\\"  a  \\\",\\\"b \\\"\" => \"\\\"  a  \\\",\\\"b \\\"\"}</li>\n   *    <li>{@code \"\\\"a\\\",\\\"\\\",\\\"b\\\"\" => \"\\\"a\\\",\\\"\\\",\\\"b\\\"\"}</li>\n   *    <li>{@code \"\\\"a\\\",\\\"  \\\",\\\"b\\\"\" => \"\\\"a\\\",\\\"  \\\",\\\"b\\\"\"}</li>\n   *    <li>{@code \"\\\"  a,,b,c  \\\",\\\"d \\\"\" => \"\\\"  a,,b,c  \\\",\\\"d \\\"\"}</li>\n   *    <li>{@code \"a,\\\"  \\\",b\" => \"ab\"]}</li>\n   * </ul>\n   */\n  static String trimFieldsAndRemoveEmptyFields(String str) {\n    char[] chars = str.toCharArray();\n    var res = new char[chars.length];\n    /*\n     * set when reading the first non trimmable char after a separator char (or the beginning of the string)\n     * unset when reading a separator\n     */\n    var inField = false;\n    var inQuotes = false;\n    var i = 0;\n    var resI = 0;\n    for (; i < chars.length; i++) {\n      boolean isSeparator = chars[i] == ',';\n      if (!inQuotes && isSeparator) {\n        // exiting field (may already be unset)\n        inField = false;\n        if (resI > 0) {\n          resI = retroTrim(res, resI);\n        }\n      } else {\n        boolean isTrimmed = !inQuotes && istrimmable(chars[i]);\n        if (isTrimmed && !inField) {\n          // we haven't met any non trimmable char since the last separator yet\n          continue;\n        }\n\n        boolean isEscape = isEscapeChar(chars[i]);\n        if (isEscape) {\n          inQuotes = !inQuotes;\n        }\n\n        // add separator as we already had one field\n        if (!inField && resI > 0) {\n          res[resI] = ',';\n          resI++;\n        }\n\n        // register in field (may already be set)\n        inField = true;\n        // copy current char\n        res[resI] = chars[i];\n        resI++;\n      }\n    }\n    // inQuotes can only be true at this point if quotes are unbalanced\n    if (!inQuotes) {\n      // trim end of str\n      resI = retroTrim(res, resI);\n    }\n    return new String(res, 0, resI);\n  }\n\n  private static boolean isEscapeChar(char aChar) {\n    return aChar == '\"';\n  }\n\n  private static boolean istrimmable(char aChar) {\n    return aChar <= ' ';\n  }\n\n  /**\n   * Reads from index {@code resI} to the beginning into {@code res} looking up the location of the trimmable char with\n   * the lowest index before encountering a non-trimmable char.\n   * <p>\n   * This basically trims {@code res} from any trimmable char at its end.\n   *\n   * @return index of next location to put new char in res\n   */\n  private static int retroTrim(char[] res, int resI) {\n    int i = resI;\n    while (i >= 1) {\n      if (!istrimmable(res[i - 1])) {\n        return i;\n      }\n      i--;\n    }\n    return i;\n  }\n\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/PluginsLoadResult.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons;\n\nimport java.util.Map;\nimport org.sonarsource.sonarlint.core.plugin.commons.loading.PluginRequirementsCheckResult;\n\npublic class PluginsLoadResult {\n  private final LoadedPlugins loadedPlugins;\n  private final Map<String, PluginRequirementsCheckResult> pluginCheckResultByKeys;\n\n  PluginsLoadResult(LoadedPlugins loadedPlugins, Map<String, PluginRequirementsCheckResult> pluginCheckResultByKeys) {\n    this.loadedPlugins = loadedPlugins;\n    this.pluginCheckResultByKeys = pluginCheckResultByKeys;\n  }\n\n  public LoadedPlugins getLoadedPlugins() {\n    return loadedPlugins;\n  }\n\n  public Map<String, PluginRequirementsCheckResult> getPluginCheckResultByKeys() {\n    return pluginCheckResultByKeys;\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/PluginsLoader.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons;\n\nimport java.nio.file.Path;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.Set;\nimport org.sonar.api.utils.System2;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.plugin.commons.loading.PluginInfo;\nimport org.sonarsource.sonarlint.core.plugin.commons.loading.PluginInstancesLoader;\nimport org.sonarsource.sonarlint.core.plugin.commons.loading.PluginRequirementsCheckResult;\nimport org.sonarsource.sonarlint.core.plugin.commons.loading.SonarPluginRequirementsChecker;\n\nimport static java.util.function.Predicate.not;\n\n/**\n * Orchestrates the loading and instantiation of plugins\n */\npublic class PluginsLoader {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final SonarPluginRequirementsChecker requirementsChecker = new SonarPluginRequirementsChecker();\n\n  public static class Configuration {\n    private final Set<Path> pluginJarLocations;\n    private final Set<SonarLanguage> enabledLanguages;\n    private final Optional<Version> nodeCurrentVersion;\n    private final boolean enableDataflowBugDetection;\n\n    public Configuration(Set<Path> pluginJarLocations, Set<SonarLanguage> enabledLanguages, boolean enableDataflowBugDetection, Optional<Version> nodeCurrentVersion) {\n      this.pluginJarLocations = pluginJarLocations;\n      this.enabledLanguages = enabledLanguages;\n      this.nodeCurrentVersion = nodeCurrentVersion;\n      this.enableDataflowBugDetection = enableDataflowBugDetection;\n    }\n  }\n\n  public PluginsLoadResult load(Configuration configuration, Set<String> disabledPluginsForAnalysis) {\n    var javaSpecVersion = Objects.requireNonNull(System2.INSTANCE.property(\"java.specification.version\"), \"Missing Java property 'java.specification.version'\");\n    var pluginCheckResultByKeys = requirementsChecker.checkRequirements(configuration.pluginJarLocations, configuration.enabledLanguages, Version.create(javaSpecVersion),\n      configuration.nodeCurrentVersion, configuration.enableDataflowBugDetection);\n\n    var nonSkippedPlugins = getNonSkippedPlugins(pluginCheckResultByKeys);\n    logPlugins(nonSkippedPlugins);\n\n    var instancesLoader = new PluginInstancesLoader();\n    var pluginInstancesByKeys = instancesLoader.instantiatePluginClasses(nonSkippedPlugins);\n\n    return new PluginsLoadResult(new LoadedPlugins(pluginInstancesByKeys, instancesLoader, additionalAllowedPlugins(configuration), disabledPluginsForAnalysis),\n      pluginCheckResultByKeys);\n  }\n\n  private static Set<String> additionalAllowedPlugins(Configuration configuration) {\n    var allowedPluginsIds = new HashSet<String>();\n    allowedPluginsIds.add(\"textdeveloper\");\n    allowedPluginsIds.add(\"textenterprise\");\n    allowedPluginsIds.add(\"omnisharp\");\n    allowedPluginsIds.add(\"sqvsroslyn\");\n    allowedPluginsIds.add(\"iacenterprise\");\n    allowedPluginsIds.add(\"goenterprise\");\n    allowedPluginsIds.addAll(maybeDbdAllowedPlugins(configuration.enableDataflowBugDetection));\n    return Collections.unmodifiableSet(allowedPluginsIds);\n  }\n\n  private static Set<String> maybeDbdAllowedPlugins(boolean enableDataflowBugDetection) {\n    return DataflowBugDetection.getPluginAllowList(enableDataflowBugDetection);\n  }\n\n  private static void logPlugins(Collection<PluginInfo> nonSkippedPlugins) {\n    LOG.debug(\"Loaded {} plugins\", nonSkippedPlugins.size());\n    for (PluginInfo p : nonSkippedPlugins) {\n      LOG.debug(\"  * {} {} ({})\", p.getName(), p.getVersion(), p.getKey());\n    }\n  }\n\n  private static Collection<PluginInfo> getNonSkippedPlugins(Map<String, PluginRequirementsCheckResult> pluginCheckResultByKeys) {\n    return pluginCheckResultByKeys.values().stream()\n      .filter(not(PluginRequirementsCheckResult::isSkipped))\n      .map(PluginRequirementsCheckResult::getPlugin)\n      .toList();\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/api/SkipReason.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.api;\n\nimport java.util.Collection;\nimport java.util.LinkedHashSet;\nimport java.util.Objects;\nimport java.util.Set;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\n\npublic interface SkipReason {\n\n  class UnsupportedFeature implements SkipReason {\n\n    public static final UnsupportedFeature INSTANCE = new UnsupportedFeature();\n\n    private UnsupportedFeature() {\n      // Singleton\n    }\n\n  }\n\n  class IncompatiblePluginApi implements SkipReason {\n\n    public static final IncompatiblePluginApi INSTANCE = new IncompatiblePluginApi();\n\n    private IncompatiblePluginApi() {\n      // Singleton\n    }\n\n  }\n\n  class LanguagesNotEnabled implements SkipReason {\n    private final Set<SonarLanguage> languages;\n\n    public LanguagesNotEnabled(Collection<SonarLanguage> languages) {\n      this.languages = new LinkedHashSet<>(languages);\n    }\n\n    public Set<SonarLanguage> getNotEnabledLanguages() {\n      return languages;\n    }\n\n    @Override\n    public int hashCode() {\n      return Objects.hash(languages);\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n      if (this == obj) {\n        return true;\n      }\n      if (!(obj instanceof LanguagesNotEnabled other)) {\n        return false;\n      }\n      return Objects.equals(languages, other.languages);\n    }\n\n    @Override\n    public String toString() {\n      return \"LanguagesNotEnabled [languages=\" + languages + \"]\";\n    }\n\n  }\n\n  class UnsatisfiedDependency implements SkipReason {\n    private final String dependencyKey;\n\n    public UnsatisfiedDependency(String dependencyKey) {\n      this.dependencyKey = dependencyKey;\n    }\n\n    public String getDependencyKey() {\n      return dependencyKey;\n    }\n\n    @Override\n    public int hashCode() {\n      return Objects.hash(dependencyKey);\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n      if (this == obj) {\n        return true;\n      }\n      if (!(obj instanceof UnsatisfiedDependency other)) {\n        return false;\n      }\n      return Objects.equals(dependencyKey, other.dependencyKey);\n    }\n\n    @Override\n    public String toString() {\n      return \"UnsatisfiedDependency [dependencyKey=\" + dependencyKey + \"]\";\n    }\n\n  }\n\n  class UnsatisfiedRuntimeRequirement implements SkipReason {\n    public enum RuntimeRequirement {\n      JRE,\n      NODEJS\n    }\n\n    private final RuntimeRequirement runtime;\n    private final String currentVersion;\n    private final String minVersion;\n\n    public UnsatisfiedRuntimeRequirement(RuntimeRequirement runtime, @Nullable String currentVersion, String minVersion) {\n      this.runtime = runtime;\n      this.currentVersion = currentVersion;\n      this.minVersion = minVersion;\n    }\n\n    public RuntimeRequirement getRuntime() {\n      return runtime;\n    }\n\n    @CheckForNull\n    public String getCurrentVersion() {\n      return currentVersion;\n    }\n\n    public String getMinVersion() {\n      return minVersion;\n    }\n\n    @Override\n    public int hashCode() {\n      return Objects.hash(runtime, currentVersion, minVersion);\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n      if (this == obj) {\n        return true;\n      }\n      if (!(obj instanceof UnsatisfiedRuntimeRequirement other)) {\n        return false;\n      }\n      return runtime == other.runtime && Objects.equals(currentVersion, other.currentVersion) && Objects.equals(minVersion, other.minVersion);\n    }\n\n    @Override\n    public String toString() {\n      return \"UnsatisfiedRuntimeRequirement [runtime=\" + runtime + \", currentVersion=\" + currentVersion + \", minVersion=\" + minVersion + \"]\";\n    }\n\n  }\n\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/api/package-info.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.plugin.commons.api;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/container/ClassDerivedBeanDefinition.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.container;\n\nimport java.lang.reflect.Constructor;\nimport org.springframework.beans.BeanUtils;\nimport org.springframework.beans.factory.support.RootBeanDefinition;\nimport org.springframework.lang.Nullable;\n\n/**\n * Taken from Spring's GenericApplicationContext.ClassDerivedBeanDefinition.\n * The goal is to support multiple constructors when adding extensions for plugins when no annotations are present.\n * Spring will pick the constructor with the highest number of arguments that it can inject.\n */\npublic class ClassDerivedBeanDefinition extends RootBeanDefinition {\n  public ClassDerivedBeanDefinition(Class<?> beanClass) {\n    super(beanClass);\n  }\n\n  public ClassDerivedBeanDefinition(ClassDerivedBeanDefinition original) {\n    super(original);\n  }\n\n  /**\n   * This method gets called from AbstractAutowireCapableBeanFactory#createBeanInstance when a bean is instantiated.\n   * It first tries to look at annotations or any other methods provided by bean post processors. If a constructor can't be determined, it will fallback to this method.\n   */\n  @Override\n  @Nullable\n  public Constructor<?>[] getPreferredConstructors() {\n    Class<?> clazz = getBeanClass();\n    Constructor<?> primaryCtor = BeanUtils.findPrimaryConstructor(clazz);\n    if (primaryCtor != null) {\n      return new Constructor<?>[] {primaryCtor};\n    }\n    Constructor<?>[] publicCtors = clazz.getConstructors();\n    if (publicCtors.length > 0) {\n      return publicCtors;\n    }\n    return null;\n  }\n\n  @Override\n  public RootBeanDefinition cloneBeanDefinition() {\n    return new ClassDerivedBeanDefinition(this);\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/container/ComponentKeys.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.container;\n\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.regex.Pattern;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\nclass ComponentKeys {\n\n  private static final Pattern IDENTITY_HASH_PATTERN = Pattern.compile(\".+@[a-f0-9]+\");\n  private final Set<Class<?>> objectsWithoutToString = new HashSet<>();\n\n  Object of(Object component) {\n    return of(component, SonarLintLogger.get());\n  }\n\n  Object of(Object component, SonarLintLogger log) {\n    if (component instanceof Class) {\n      return component;\n    }\n    return ofInstance(component, log);\n  }\n\n  public String ofInstance(Object component) {\n    return ofInstance(component, SonarLintLogger.get());\n  }\n\n  public String ofClass(Class<?> clazz) {\n    return clazz.getClassLoader() + \"-\" + clazz.getCanonicalName();\n  }\n\n  String ofInstance(Object component, SonarLintLogger log) {\n    var key = component.toString();\n    if (IDENTITY_HASH_PATTERN.matcher(key).matches()) {\n      if (!objectsWithoutToString.add(component.getClass())) {\n        log.warn(String.format(\"Bad component key: %s. Please implement toString() method on class %s\", key, component.getClass().getName()));\n      }\n      key += UUID.randomUUID().toString();\n    }\n    return ofClass(component.getClass()) + \"-\" + key;\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/container/Container.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.container;\n\nimport java.util.List;\nimport java.util.Optional;\n\npublic interface Container {\n  Container add(Object... objects);\n\n  <T> T getComponentByType(Class<T> type);\n\n  <T> Optional<T> getOptionalComponentByType(Class<T> type);\n\n  <T> List<T> getComponentsByType(Class<T> type);\n\n  Container getParent();\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/container/ExtensionContainer.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.container;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic interface ExtensionContainer extends Container {\n  ExtensionContainer addExtension(@Nullable String pluginKey, Object extension);\n\n  ExtensionContainer declareProperties(Object extension);\n\n  @Override\n  @CheckForNull\n  ExtensionContainer getParent();\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/container/LazyUnlessStartableStrategy.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.container;\n\nimport javax.annotation.Nullable;\nimport org.sonar.api.Startable;\nimport org.springframework.beans.factory.config.BeanDefinition;\n\npublic class LazyUnlessStartableStrategy extends SpringInitStrategy {\n  @Override\n  protected boolean isLazyInit(BeanDefinition beanDefinition, @Nullable Class<?> clazz) {\n    return clazz == null || !Startable.class.isAssignableFrom(clazz);\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/container/PriorityBeanFactory.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.container;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Function;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.springframework.beans.BeanWrapper;\nimport org.springframework.beans.factory.support.DefaultListableBeanFactory;\nimport org.springframework.beans.factory.support.RootBeanDefinition;\n\npublic class PriorityBeanFactory extends DefaultListableBeanFactory {\n  /**\n   * Determines highest priority of the bean candidates.\n   * Does not take into account the @Primary annotations.\n   * This gets called from {@link DefaultListableBeanFactory#determineAutowireCandidate} when the bean factory is finding the beans to autowire. That method\n   * checks for @Primary before calling this method.\n   *\n   * The strategy is to look at the @Priority annotations. If there are ties, we give priority to components that were added to child containers over their parents.\n   * If there are still ties, null is returned, which will ultimately cause Spring to throw a NoUniqueBeanDefinitionException.\n   */\n  @Override\n  @Nullable\n  protected String determineHighestPriorityCandidate(Map<String, Object> candidates, Class<?> requiredType) {\n    List<Bean> candidateBeans = candidates.entrySet().stream()\n      .filter(e -> e.getValue() != null)\n      .map(e -> new Bean(e.getKey(), e.getValue()))\n      .toList();\n\n    List<Bean> beansAfterPriority = highestPriority(candidateBeans, b -> getPriority(b.getInstance()));\n    if (beansAfterPriority.isEmpty()) {\n      return null;\n    } else if (beansAfterPriority.size() == 1) {\n      return beansAfterPriority.get(0).getName();\n    }\n\n    List<Bean> beansAfterHierarchy = highestPriority(beansAfterPriority, b -> getHierarchyPriority(b.getName()));\n    if (beansAfterHierarchy.size() == 1) {\n      return beansAfterHierarchy.get(0).getName();\n    }\n\n    return null;\n  }\n\n  private static List<Bean> highestPriority(List<Bean> candidates, Function<PriorityBeanFactory.Bean, Integer> priorityFunction) {\n    List<Bean> highestPriorityBeans = new ArrayList<>();\n    Integer highestPriority = null;\n\n    for (Bean candidate : candidates) {\n      Integer candidatePriority = priorityFunction.apply(candidate);\n      if (candidatePriority == null) {\n        candidatePriority = Integer.MAX_VALUE;\n      }\n      if (highestPriority == null) {\n        highestPriority = candidatePriority;\n        highestPriorityBeans.add(candidate);\n      } else if (candidatePriority < highestPriority) {\n        highestPriorityBeans.clear();\n        highestPriority = candidatePriority;\n        highestPriorityBeans.add(candidate);\n      } else if (candidatePriority.equals(highestPriority)) {\n        highestPriorityBeans.add(candidate);\n      }\n    }\n    return highestPriorityBeans;\n  }\n\n  @CheckForNull\n  private Integer getHierarchyPriority(String beanName) {\n    DefaultListableBeanFactory factory = this;\n    var i = 1;\n    while (factory != null) {\n      if (factory.containsBeanDefinition(beanName)) {\n        return i;\n      }\n      factory = (DefaultListableBeanFactory) factory.getParentBeanFactory();\n      i++;\n    }\n    return null;\n  }\n\n  /**\n   * A common mistake when migrating from Pico Container to Spring is to forget to add @Inject or @Autowire annotations to classes that have multiple constructors.\n   * Spring will fail if there is no default no-arg constructor, but it will silently use the no-arg constructor if there is one, never calling the other constructors.\n   * We override this method to fail fast if a class has multiple constructors.\n   */\n  @Override\n  protected BeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd) {\n    if (mbd.hasBeanClass() && mbd.getBeanClass().getConstructors().length > 1) {\n      throw new IllegalStateException(\"Constructor annotations missing in: \" + mbd.getBeanClass());\n    }\n    return super.instantiateBean(beanName, mbd);\n  }\n\n  private static class Bean {\n    private final String name;\n    private final Object instance;\n\n    public Bean(String name, Object instance) {\n      this.name = name;\n      this.instance = instance;\n    }\n\n    public String getName() {\n      return name;\n    }\n\n    public Object getInstance() {\n      return instance;\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/container/SpringComponentContainer.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.container;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.function.Supplier;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonar.api.config.PropertyDefinitions;\nimport org.sonar.api.utils.System2;\nimport org.sonarsource.sonarlint.core.commons.tracing.Step;\nimport org.sonarsource.sonarlint.core.commons.tracing.Trace;\nimport org.springframework.beans.factory.NoSuchBeanDefinitionException;\nimport org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;\nimport org.springframework.context.annotation.AnnotationConfigApplicationContext;\n\nimport static java.util.Collections.emptyList;\nimport static org.sonarsource.sonarlint.core.commons.tracing.Trace.startChildren;\n\npublic class SpringComponentContainer implements StartableContainer {\n\n  protected final AnnotationConfigApplicationContext context;\n  @Nullable\n  protected final SpringComponentContainer parent;\n  protected final List<SpringComponentContainer> children = new ArrayList<>();\n\n  private final PropertyDefinitions propertyDefinitions;\n  private final ComponentKeys componentKeys = new ComponentKeys();\n  @Nullable\n  private Trace trace;\n\n  public SpringComponentContainer() {\n    this(null, new PropertyDefinitions(System2.INSTANCE), emptyList(), new LazyUnlessStartableStrategy());\n  }\n\n  protected SpringComponentContainer(List<?> externalExtensions) {\n    this(null, new PropertyDefinitions(System2.INSTANCE), externalExtensions, new LazyUnlessStartableStrategy());\n  }\n\n  protected SpringComponentContainer(SpringComponentContainer parent) {\n    this(parent, parent.propertyDefinitions, emptyList(), new LazyUnlessStartableStrategy());\n  }\n\n  protected SpringComponentContainer(SpringComponentContainer parent, SpringInitStrategy initStrategy) {\n    this(parent, parent.propertyDefinitions, emptyList(), initStrategy);\n  }\n\n  protected SpringComponentContainer(@Nullable SpringComponentContainer parent, PropertyDefinitions propertyDefs, List<?> externalExtensions, SpringInitStrategy initStrategy) {\n    this.parent = parent;\n    this.propertyDefinitions = propertyDefs;\n    this.context = new AnnotationConfigApplicationContext(new PriorityBeanFactory());\n    this.context.setAllowBeanDefinitionOverriding(false);\n    ((AbstractAutowireCapableBeanFactory) context.getBeanFactory()).setParameterNameDiscoverer(null);\n    if (parent != null) {\n      context.setParent(parent.context);\n      parent.children.add(this);\n    }\n    add(initStrategy);\n    add(this);\n    add(new StartableBeanPostProcessor());\n    add(externalExtensions);\n    add(propertyDefs);\n  }\n\n  /**\n   * Beans need to have a unique name, otherwise they'll override each other.\n   * The strategy is:\n   * - For classes, use the classloader + fully qualified class name as the name of the bean\n   * - For instances, use the Classloader + FQCN + toString()\n   * - If the object is a collection, iterate through the elements and apply the same strategy for each of them\n   */\n  @Override\n  public Container add(Object... objects) {\n    for (Object o : objects) {\n      if (o instanceof Class<?> clazz) {\n        context.registerBean(componentKeys.ofClass(clazz), clazz);\n        declareProperties(o);\n      } else if (o instanceof Iterable) {\n        ((Iterable<?>) o).forEach(this::add);\n      } else {\n        registerInstance(o);\n        declareProperties(o);\n      }\n    }\n    return this;\n  }\n\n  private <T> void registerInstance(T instance) {\n    Supplier<T> supplier = () -> instance;\n    Class<T> clazz = (Class<T>) instance.getClass();\n    context.registerBean(componentKeys.ofInstance(instance), clazz, supplier);\n  }\n\n  /**\n   * Extensions are usually added by plugins and we assume they don't support any injection-related annotations.\n   * Spring contexts supporting annotations will fail if multiple constructors are present without any annotations indicating which one to use for injection.\n   * For that reason, we need to create the beans ourselves, using ClassDerivedBeanDefinition, which will declare that all constructors can be used for injection.\n   */\n  private void addExtension(Object o) {\n    if (o instanceof Class<?> clazz) {\n      var bd = new ClassDerivedBeanDefinition(clazz);\n      context.registerBeanDefinition(componentKeys.ofClass(clazz), bd);\n    } else if (o instanceof Iterable) {\n      ((Iterable<?>) o).forEach(this::addExtension);\n    } else {\n      registerInstance(o);\n    }\n  }\n\n  @Override\n  public <T> T getComponentByType(Class<T> type) {\n    try {\n      return context.getBean(type);\n    } catch (Exception t) {\n      throw new IllegalStateException(\"Unable to load component \" + type, t);\n    }\n  }\n\n  @Override\n  public <T> Optional<T> getOptionalComponentByType(Class<T> type) {\n    try {\n      return Optional.of(context.getBean(type));\n    } catch (NoSuchBeanDefinitionException t) {\n      return Optional.empty();\n    }\n  }\n\n  @Override\n  public <T> List<T> getComponentsByType(Class<T> type) {\n    try {\n      return new ArrayList<>(context.getBeansOfType(type).values());\n    } catch (Exception t) {\n      throw new IllegalStateException(\"Unable to load components \" + type, t);\n    }\n  }\n\n  public AnnotationConfigApplicationContext context() {\n    return context;\n  }\n\n  public void execute(@Nullable Trace trace) {\n    this.trace = trace;\n    RuntimeException r = null;\n    try {\n      startComponents();\n    } catch (RuntimeException e) {\n      r = e;\n    } finally {\n      try {\n        stopComponents();\n      } catch (RuntimeException e) {\n        if (r == null) {\n          r = e;\n        }\n      }\n    }\n    if (r != null) {\n      throw r;\n    }\n  }\n\n  @Override\n  public SpringComponentContainer startComponents() {\n    startChildren(trace, \"startComponents\",\n      new Step(\"doBeforeStart\", this::doBeforeStart),\n      new Step(\"refresh\", context::refresh),\n      new Step(\"doAfterStart\", this::doAfterStart)\n    );\n    return this;\n  }\n\n  public SpringComponentContainer stopComponents() {\n    try {\n      stopChildren();\n      if (context.isActive()) {\n        context.close();\n      }\n    } finally {\n      if (parent != null) {\n        parent.children.remove(this);\n      }\n    }\n    return this;\n  }\n\n  private void stopChildren() {\n    // loop over a copy of list of children in reverse order\n    var childrenCopy = new ArrayList<>(this.children);\n    childrenCopy.reversed().forEach(SpringComponentContainer::stopComponents);\n  }\n\n  public SpringComponentContainer createChild() {\n    return new SpringComponentContainer(this);\n  }\n\n  @Override\n  @CheckForNull\n  public SpringComponentContainer getParent() {\n    return parent;\n  }\n\n  @Override\n  public SpringComponentContainer addExtension(@Nullable String pluginKey, Object extension) {\n    try {\n      addExtension(extension);\n    } catch (Throwable t) {\n      throw new IllegalStateException(\"Unable to register extension \" + getName(extension) + (pluginKey != null ? (\" from plugin '\" + pluginKey + \"'\") : \"\"), t);\n    }\n    declareProperties(extension);\n    return this;\n  }\n\n  private static String getName(Object extension) {\n    if (extension instanceof Class) {\n      return ((Class<?>) extension).getName();\n    }\n    return getName(extension.getClass());\n  }\n\n  @Override\n  public SpringComponentContainer declareProperties(Object extension) {\n    this.propertyDefinitions.addComponent(extension, \"\");\n    return this;\n  }\n\n  /**\n   * This method aims to be overridden\n   */\n  protected void doBeforeStart() {\n    // nothing\n  }\n\n  /**\n   * This method aims to be overridden\n   */\n  protected void doAfterStart() {\n    // nothing\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/container/SpringInitStrategy.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.container;\n\nimport javax.annotation.Nullable;\nimport org.springframework.beans.BeansException;\nimport org.springframework.beans.factory.config.BeanDefinition;\nimport org.springframework.beans.factory.config.BeanFactoryPostProcessor;\nimport org.springframework.beans.factory.config.ConfigurableListableBeanFactory;\n\npublic abstract class SpringInitStrategy implements BeanFactoryPostProcessor {\n  @Override\n  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {\n    for (String beanName : beanFactory.getBeanDefinitionNames()) {\n      var beanDefinition = beanFactory.getBeanDefinition(beanName);\n      Class<?> rawClass = beanDefinition.getResolvableType().getRawClass();\n      beanDefinition.setLazyInit(isLazyInit(beanDefinition, rawClass));\n    }\n  }\n\n  protected abstract boolean isLazyInit(BeanDefinition beanDefinition, @Nullable Class<?> clazz);\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/container/StartableBeanPostProcessor.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.container;\n\nimport org.sonar.api.Startable;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.springframework.beans.BeansException;\nimport org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;\nimport org.springframework.lang.Nullable;\n\npublic class StartableBeanPostProcessor implements DestructionAwareBeanPostProcessor {\n  @Override\n  @Nullable\n  public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {\n    if (bean instanceof Startable startable) {\n      startable.start();\n    }\n    return bean;\n  }\n\n  @Override\n  public boolean requiresDestruction(Object bean) {\n    return bean instanceof Startable;\n  }\n\n  @Override\n  public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {\n    try {\n      // note: Spring will call close() on AutoCloseable beans.\n      if (bean instanceof Startable startable) {\n        startable.stop();\n      }\n    } catch (Exception e) {\n      SonarLintLogger.get()\n        .warn(\"Dispose of component {} failed\", bean.getClass().getCanonicalName(), e);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/container/StartableContainer.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.container;\n\npublic interface StartableContainer extends ExtensionContainer {\n  StartableContainer startComponents();\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/container/package-info.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.plugin.commons.container;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/loading/PluginClassLoaderDef.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.loading;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport javax.annotation.Nullable;\nimport org.sonar.classloader.Mask;\n\nimport static org.apache.commons.lang3.StringUtils.isNotEmpty;\n\n/**\n * Temporary information about the classLoader to be created for a plugin (or a group of plugins).\n */\nclass PluginClassLoaderDef {\n\n  private final String basePluginKey;\n  private final Map<String, String> mainClassesByPluginKey = new HashMap<>();\n  private final List<File> files = new ArrayList<>();\n  private final Mask.Builder mask = Mask.builder();\n\n  PluginClassLoaderDef(String basePluginKey) {\n    this.basePluginKey = basePluginKey;\n  }\n\n  String getBasePluginKey() {\n    return basePluginKey;\n  }\n\n  List<File> getFiles() {\n    return files;\n  }\n\n  void addFiles(Collection<File> f) {\n    this.files.addAll(f);\n  }\n\n  Mask.Builder getExportMaskBuilder() {\n    return mask;\n  }\n\n  Map<String, String> getMainClassesByPluginKey() {\n    return mainClassesByPluginKey;\n  }\n\n  void addMainClass(String pluginKey, @Nullable String mainClass) {\n    if (isNotEmpty(mainClass)) {\n      mainClassesByPluginKey.put(pluginKey, mainClass);\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/loading/PluginClassloaderFactory.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.loading;\n\nimport java.io.File;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.Collection;\nimport java.util.IdentityHashMap;\nimport java.util.Map;\nimport org.sonar.classloader.ClassloaderBuilder;\nimport org.sonar.classloader.Mask;\nimport org.sonarsource.api.sonarlint.SonarLintSide;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\nimport static org.sonar.classloader.ClassloaderBuilder.LoadingOrder.PARENT_FIRST;\n\n/**\n * Builds the graph of classloaders to be used to instantiate plugins. It deals with:\n * <ul>\n *   <li>isolation of plugins against core classes (except api)</li>\n *   <li>backward-compatibility with plugins built for versions of SQ lower than 5.2. At that time\n *   API declared transitive dependencies that were automatically available to plugins</li>\n *   <li>sharing of some packages between plugins</li>\n *   <li>loading of the libraries embedded in plugin JAR files (directory META-INF/libs)</li>\n * </ul>\n */\n@SonarLintSide\npublic class PluginClassloaderFactory {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  // underscores are used to not conflict with plugin keys (if someday a plugin key is \"api\")\n  private static final String API_CLASSLOADER_KEY = \"_api_\";\n\n  /**\n   * Creates as many classloaders as requested by the input parameter.\n   */\n  Map<PluginClassLoaderDef, ClassLoader> create(ClassLoader baseClassLoader, Collection<PluginClassLoaderDef> defs) {\n    var builder = new ClassloaderBuilder();\n    builder.newClassloader(API_CLASSLOADER_KEY, baseClassLoader);\n    builder.setMask(API_CLASSLOADER_KEY, apiMask());\n\n    for (var def : defs) {\n      builder.newClassloader(def.getBasePluginKey());\n      builder.setParent(def.getBasePluginKey(), API_CLASSLOADER_KEY, Mask.ALL);\n      builder.setLoadingOrder(def.getBasePluginKey(), PARENT_FIRST);\n      for (var jar : def.getFiles()) {\n        builder.addURL(def.getBasePluginKey(), fileToUrl(jar));\n      }\n      exportResources(def, builder, defs);\n    }\n\n    return build(defs, builder);\n  }\n\n  /**\n   * A plugin can export some resources to other plugins\n   */\n  private static void exportResources(PluginClassLoaderDef def, ClassloaderBuilder builder, Collection<PluginClassLoaderDef> allPlugins) {\n    // export the resources to all other plugins\n    builder.setExportMask(def.getBasePluginKey(), def.getExportMaskBuilder().build());\n    for (var other : allPlugins) {\n      if (!other.getBasePluginKey().equals(def.getBasePluginKey())) {\n        builder.addSibling(def.getBasePluginKey(), other.getBasePluginKey(), Mask.ALL);\n      }\n    }\n  }\n\n  /**\n   * Builds classloaders and verifies that all of them are correctly defined\n   */\n  private static Map<PluginClassLoaderDef, ClassLoader> build(Collection<PluginClassLoaderDef> defs, ClassloaderBuilder builder) {\n    Map<PluginClassLoaderDef, ClassLoader> result = new IdentityHashMap<>(defs.size());\n    var classloadersByBasePluginKey = builder.build();\n    for (var def : defs) {\n      var classloader = classloadersByBasePluginKey.get(def.getBasePluginKey());\n      if (classloader == null) {\n        LOG.error(\"Fail to create classloader for plugin '{}'\", def.getBasePluginKey());\n      } else {\n        result.put(def, classloader);\n      }\n    }\n    return result;\n  }\n\n  private static URL fileToUrl(File file) {\n    try {\n      return file.toURI().toURL();\n    } catch (MalformedURLException e) {\n      throw new IllegalArgumentException(e);\n    }\n  }\n\n  /**\n   * The resources (packages) that API exposes to plugins. Other core classes (SonarQube, MyBatis, ...)\n   * can't be accessed.\n   * <p>To sum-up, these are the classes packaged in sonar-plugin-api.jar or available as\n   * a transitive dependency of sonar-plugin-api</p>\n   */\n  private static Mask apiMask() {\n    return Mask.builder()\n      .include(\"org/sonar/api/\")\n      .include(\"org/sonarsource/api/sonarlint/\")\n      .include(\"org/sonar/check/\")\n      .include(\"net/sourceforge/pmd/\")\n      .include(\"com/sonarsource/plugins/license/api/\")\n      .include(\"org/sonarsource/sonarlint/plugin/api/\")\n      .include(\"org/slf4j/\")\n\n      // API exclusions\n      .exclude(\"org/sonar/api/internal/\")\n      .build();\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/loading/PluginInfo.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.loading;\n\nimport java.io.File;\nimport java.nio.file.Path;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.Set;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.apache.commons.lang3.StringUtils;\nimport org.sonar.api.utils.MessageException;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.plugin.commons.loading.SonarPluginManifest.RequiredPlugin;\n\nimport static java.util.Objects.requireNonNull;\n\npublic class PluginInfo {\n\n  private final String key;\n  private String name;\n\n  private File jarFile;\n\n  @CheckForNull\n  private String mainClass;\n\n  @CheckForNull\n  private Version version;\n\n  @CheckForNull\n  private Version minimalSqVersion;\n\n  @CheckForNull\n  private String basePlugin;\n\n  private final Set<RequiredPlugin> requiredPlugins = new HashSet<>();\n\n  @CheckForNull\n  private Version jreMinVersion;\n\n  @CheckForNull\n  private Version nodeJsMinVersion;\n\n  private List<String> dependencies = List.of();\n\n  public PluginInfo(String key) {\n    requireNonNull(key, \"Plugin key is missing from manifest\");\n    this.key = key;\n    this.name = key;\n  }\n\n  public PluginInfo setJarFile(File f) {\n    this.jarFile = f;\n    return this;\n  }\n\n  public File getJarFile() {\n    return jarFile;\n  }\n\n  public String getKey() {\n    return key;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  @CheckForNull\n  public Version getVersion() {\n    return version;\n  }\n\n  @CheckForNull\n  public Version getMinimalSqVersion() {\n    return minimalSqVersion;\n  }\n\n  @CheckForNull\n  public String getMainClass() {\n    return mainClass;\n  }\n\n  @CheckForNull\n  public String getBasePlugin() {\n    return basePlugin;\n  }\n\n  public Set<RequiredPlugin> getRequiredPlugins() {\n    return requiredPlugins;\n  }\n\n  @CheckForNull\n  public Version getJreMinVersion() {\n    return jreMinVersion;\n  }\n\n  @CheckForNull\n  public Version getNodeJsMinVersion() {\n    return nodeJsMinVersion;\n  }\n\n  public List<String> getDependencies() {\n    return dependencies;\n  }\n\n  public PluginInfo setName(@Nullable String name) {\n    this.name = Optional.ofNullable(name).orElse(this.key);\n    return this;\n  }\n\n  public PluginInfo setVersion(Version version) {\n    this.version = version;\n    return this;\n  }\n\n  public PluginInfo setMinimalSqVersion(@Nullable Version v) {\n    this.minimalSqVersion = v;\n    return this;\n  }\n\n  /**\n   * Required\n   */\n  public PluginInfo setMainClass(String mainClass) {\n    this.mainClass = mainClass;\n    return this;\n  }\n\n  public PluginInfo setBasePlugin(@Nullable String s) {\n    this.basePlugin = s;\n    return this;\n  }\n\n  public PluginInfo addRequiredPlugin(RequiredPlugin p) {\n    this.requiredPlugins.add(p);\n    return this;\n  }\n\n  private PluginInfo setMinimalJreVersion(@Nullable Version jreMinVersion) {\n    this.jreMinVersion = jreMinVersion;\n    return this;\n  }\n\n  private PluginInfo setMinimalNodeJsVersion(@Nullable Version nodeJsMinVersion) {\n    this.nodeJsMinVersion = nodeJsMinVersion;\n    return this;\n  }\n\n  public PluginInfo setDependencies(List<String> dependencies) {\n    this.dependencies = dependencies;\n    return this;\n  }\n\n  /**\n   * Find out if this plugin is compatible with a given version of SonarQube.\n   * The version of SQ must be greater than or equal to the minimal version\n   * needed by the plugin.\n   */\n  public boolean isCompatibleWith(String implementedApi) {\n    if (null == this.minimalSqVersion) {\n      // no constraint defined on the plugin\n      return true;\n    }\n\n    // Ignore patch and build numbers since this should not change API compatibility\n    var requestedApi = Version.create(minimalSqVersion.getMajor() + \".\" + minimalSqVersion.getMinor());\n    var implementedApiVersion = Version.create(implementedApi);\n    return implementedApiVersion.compareToIgnoreQualifier(requestedApi) >= 0;\n  }\n\n  @Override\n  public String toString() {\n    return \"[\" + key + \" / \" + version + \"]\";\n  }\n\n  @Override\n  public boolean equals(@Nullable Object o) {\n    if (this == o) {\n      return true;\n    }\n    if (o == null || getClass() != o.getClass()) {\n      return false;\n    }\n    var info = (PluginInfo) o;\n    if (!key.equals(info.key)) {\n      return false;\n    }\n    return !(version != null ? !version.equals(info.version) : (info.version != null));\n\n  }\n\n  @Override\n  public int hashCode() {\n    var result = key.hashCode();\n    result = 31 * result + (version != null ? version.hashCode() : 0);\n    return result;\n  }\n\n  public static PluginInfo create(Path jarFile) {\n    var manifest = SonarPluginManifest.fromJar(jarFile);\n    return create(jarFile, manifest);\n  }\n\n  static PluginInfo create(Path jarPath, SonarPluginManifest manifest) {\n    if (StringUtils.isBlank(manifest.getKey())) {\n      throw MessageException.of(String.format(\"File is not a plugin. Please delete it and restart: %s\", jarPath.toAbsolutePath()));\n    }\n    var info = new PluginInfo(manifest.getKey());\n\n    info.setJarFile(jarPath.toFile());\n    info.setName(manifest.getName());\n    info.setMainClass(manifest.getMainClass());\n    var version = manifest.getVersion();\n    if (version != null) {\n      info.setVersion(Version.create(version));\n    }\n\n    info.setMinimalSqVersion(manifest.getSonarMinVersion().orElse(null));\n    info.setBasePlugin(manifest.getBasePluginKey());\n    manifest.getRequiredPlugins().forEach(info::addRequiredPlugin);\n    info.setMinimalJreVersion(manifest.getJreMinVersion().orElse(null));\n    info.setMinimalNodeJsVersion(manifest.getNodeJsMinVersion().orElse(null));\n    info.setDependencies(manifest.getDependencies());\n    return info;\n  }\n\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/loading/PluginInstancesLoader.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.loading;\n\nimport java.io.Closeable;\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.JarURLConnection;\nimport java.net.URI;\nimport java.net.URL;\nimport java.nio.file.FileSystems;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Queue;\nimport java.util.jar.JarFile;\nimport java.util.stream.Collectors;\nimport java.util.zip.ZipException;\nimport javax.annotation.CheckForNull;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.sonar.api.Plugin;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\nimport static org.apache.commons.lang3.StringUtils.isNotEmpty;\nimport static org.sonarsource.sonarlint.core.commons.IOExceptionUtils.throwFirstWithOtherSuppressed;\nimport static org.sonarsource.sonarlint.core.commons.IOExceptionUtils.tryAndCollectIOException;\n\n/**\n * Loads the plugin JAR files by creating the appropriate classloaders and by instantiating\n * the entry point classes as defined in manifests. It assumes that JAR files are compatible with current\n * environment (minimal sonarqube version, compatibility between plugins, ...):\n * <ul>\n *   <li>server verifies compatibility of JARs before deploying them at startup (see ServerPluginRepository)</li>\n *   <li>batch loads only the plugins deployed on server (see BatchPluginRepository)</li>\n * </ul>\n * <p>\n * Plugins have their own isolated classloader, inheriting only from API classes.\n * Some plugins can extend a \"base\" plugin, sharing the same classloader.\n */\npublic class PluginInstancesLoader implements Closeable {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private static final String[] DEFAULT_SHARED_RESOURCES = {\"org/sonar/plugins\", \"com/sonar/plugins\", \"com/sonarsource/plugins\"};\n\n  private final PluginClassloaderFactory classloaderFactory;\n  private final ClassLoader baseClassLoader;\n  private final Collection<ClassLoader> classloadersToClose = new ArrayList<>();\n  private final List<JarFile> jarFilesToClose = new ArrayList<>();\n  private final List<Path> filesToDelete = new ArrayList<>();\n\n  public PluginInstancesLoader() {\n    this(new PluginClassloaderFactory());\n  }\n\n  PluginInstancesLoader(PluginClassloaderFactory classloaderFactory) {\n    this.classloaderFactory = classloaderFactory;\n    this.baseClassLoader = getClass().getClassLoader();\n  }\n\n  public Map<String, Plugin> instantiatePluginClasses(Collection<PluginInfo> plugins) {\n    var defs = defineClassloaders(plugins.stream().collect(Collectors.toMap(PluginInfo::getKey, p -> p)));\n    var classloaders = classloaderFactory.create(baseClassLoader, defs);\n    this.classloadersToClose.addAll(classloaders.values());\n    return instantiatePluginClasses(classloaders);\n  }\n\n  /**\n   * Defines the different classloaders to be created. Number of classloaders can be\n   * different than number of plugins.\n   */\n  Collection<PluginClassLoaderDef> defineClassloaders(Map<String, PluginInfo> pluginsByKey) {\n    Map<String, PluginClassLoaderDef> classloadersByBasePlugin = new HashMap<>();\n\n    for (var info : pluginsByKey.values()) {\n      var baseKey = basePluginKey(info, pluginsByKey);\n      if (baseKey == null) {\n        continue;\n      }\n      var def = classloadersByBasePlugin.computeIfAbsent(baseKey, PluginClassLoaderDef::new);\n      def.addFiles(List.of(info.getJarFile()));\n      getJarFile(info.getJarFile().toPath()).ifPresent(jarFilesToClose::add);\n      if (!info.getDependencies().isEmpty()) {\n        LOG.warn(\"Plugin '{}' embeds dependencies. This will be deprecated soon. Plugin should be updated.\", info.getKey());\n        var tmpFolderForDeps = createTmpFolderForPluginDeps(info);\n        for (var dependency : info.getDependencies()) {\n          var tmpDepFile = extractDependencyInTempFolder(info, dependency, tmpFolderForDeps);\n          def.addFiles(List.of(tmpDepFile.toFile()));\n          filesToDelete.add(tmpDepFile);\n          getJarFile(tmpDepFile).ifPresent(jarFilesToClose::add);\n        }\n      }\n      def.addMainClass(info.getKey(), info.getMainClass());\n\n      for (var defaultSharedResource : DEFAULT_SHARED_RESOURCES) {\n        def.getExportMaskBuilder().include(String.format(\"%s/%s/api/\", defaultSharedResource, info.getKey()));\n      }\n    }\n    return classloadersByBasePlugin.values();\n  }\n\n  /**\n   * SLCORE-557 Because of bug <a href=\"https://bugs.java.com/bugdatabase/view_bug?bug_id=JDK-8315993\">JDK-8315993</a> we have to somehow get access\n   * to the underlying cached JarFile that will be also opened by the URLClassloader, and close it ourselves.\n   */\n  private static Optional<JarFile> getJarFile(Path tmpDepFile) {\n    try {\n      return Optional.of(((JarURLConnection) URI.create(\"jar:\" + tmpDepFile.toUri().toURL() + \"!/\").toURL().openConnection()).getJarFile());\n    } catch (ZipException ignore) {\n      // For tests, we are using fake JARs, so ignore ZipException: zip file is empty\n      return Optional.empty();\n    } catch (IOException e) {\n      throw new IllegalArgumentException(e);\n    }\n  }\n\n  private static Path createTmpFolderForPluginDeps(PluginInfo info) {\n    try {\n      var prefix = \"sonarlint_\" + info.getKey();\n      return Files.createTempDirectory(prefix);\n    } catch (IOException e) {\n      throw new IllegalStateException(\"Unable to create temporary directory\", e);\n    }\n  }\n\n  private static Path extractDependencyInTempFolder(PluginInfo info, String dependency, Path tempFolder) {\n    try {\n      var tmpDepFile = tempFolder.resolve(dependency);\n      if (!tmpDepFile.startsWith(tempFolder + File.separator)) {\n        throw new IOException(\"Entry is outside of the target dir: \" + dependency);\n      }\n      Files.createDirectories(tmpDepFile.getParent());\n      extractFile(info.getJarFile().toPath(), dependency, tmpDepFile);\n      return tmpDepFile;\n    } catch (IOException e) {\n      throw new IllegalStateException(\"Unable to extract plugin dependency: \" + dependency, e);\n    }\n  }\n\n  private static void extractFile(Path zipFile, String fileName, Path outputFile) throws IOException {\n    try (var fileSystem = FileSystems.newFileSystem(zipFile, (ClassLoader) null)) {\n      var fileToExtract = fileSystem.getPath(fileName);\n      Files.copy(fileToExtract, outputFile);\n    }\n  }\n\n  /**\n   * Instantiates collection of {@link org.sonar.api.Plugin} according to given metadata and classloaders\n   *\n   * @return the instances grouped by plugin key\n   * @throws IllegalStateException if at least one plugin can't be correctly loaded\n   */\n  Map<String, Plugin> instantiatePluginClasses(Map<PluginClassLoaderDef, ClassLoader> classloaders) {\n    // instantiate plugins\n    Map<String, Plugin> instancesByPluginKey = new HashMap<>();\n    for (var entry : classloaders.entrySet()) {\n      var def = entry.getKey();\n      var classLoader = entry.getValue();\n\n      // the same classloader can be used by multiple plugins\n      for (var mainClassEntry : def.getMainClassesByPluginKey().entrySet()) {\n        var pluginKey = mainClassEntry.getKey();\n        var mainClass = mainClassEntry.getValue();\n        try {\n          instancesByPluginKey.put(pluginKey, (Plugin) classLoader.loadClass(mainClass).getDeclaredConstructor().newInstance());\n        } catch (UnsupportedClassVersionError e) {\n          LOG.error(\"The plugin [{}] does not support Java {}\", pluginKey, SystemUtils.JAVA_RUNTIME_VERSION, e);\n        } catch (Throwable e) {\n          LOG.error(\"Fail to instantiate class [{}] of plugin [{}]\", mainClass, pluginKey, e);\n        }\n      }\n    }\n    return instancesByPluginKey;\n  }\n\n  @Override\n  public void close() throws IOException {\n    Queue<IOException> exceptions = new LinkedList<>();\n    synchronized (classloadersToClose) {\n      for (var classLoader : classloadersToClose) {\n        if (classLoader instanceof Closeable closeableClassloader) {\n          tryAndCollectIOException(closeableClassloader::close, exceptions);\n        }\n      }\n      classloadersToClose.clear();\n    }\n    synchronized (jarFilesToClose) {\n      for (var jarFile : jarFilesToClose) {\n        tryAndCollectIOException(jarFile::close, exceptions);\n      }\n      jarFilesToClose.clear();\n    }\n    synchronized (filesToDelete) {\n      for (var fileToDelete : filesToDelete) {\n        tryAndCollectIOException(() -> FileUtils.forceDelete(fileToDelete.toFile()), exceptions);\n      }\n      filesToDelete.clear();\n    }\n    throwFirstWithOtherSuppressed(exceptions);\n  }\n\n  /**\n   * Get the root key of a tree of plugins. For example if plugin C depends on B, which depends on A, then\n   * B and C must be attached to the classloader of A. The method returns A in the three cases.\n   */\n  @CheckForNull\n  static String basePluginKey(PluginInfo plugin, Map<String, PluginInfo> allPluginsPerKey) {\n    var base = plugin.getKey();\n    var parentKey = plugin.getBasePlugin();\n    while (isNotEmpty(parentKey)) {\n      var parentPlugin = allPluginsPerKey.get(parentKey);\n      if (parentPlugin == null) {\n        LOG.warn(\"Unable to find base plugin '{}' referenced by plugin '{}'\", parentKey, base);\n        return null;\n      }\n      base = parentPlugin.getKey();\n      parentKey = parentPlugin.getBasePlugin();\n    }\n    return base;\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/loading/PluginRequirementsCheckResult.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.loading;\n\nimport java.util.Optional;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.plugin.commons.api.SkipReason;\n\npublic class PluginRequirementsCheckResult {\n\n  private final PluginInfo plugin;\n\n  @CheckForNull\n  private final SkipReason skipReason;\n\n  public PluginRequirementsCheckResult(PluginInfo plugin, @Nullable SkipReason skipReason) {\n    this.plugin = plugin;\n    this.skipReason = skipReason;\n  }\n\n  public PluginInfo getPlugin() {\n    return plugin;\n  }\n\n  public Optional<SkipReason> getSkipReason() {\n    return Optional.ofNullable(skipReason);\n  }\n\n  public boolean isSkipped() {\n    return skipReason != null;\n  }\n\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/loading/SonarPluginManifest.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.loading;\n\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.jar.JarFile;\nimport java.util.jar.Manifest;\nimport java.util.regex.Pattern;\nimport java.util.stream.Stream;\nimport javax.annotation.CheckForNull;\nimport org.apache.commons.lang3.StringUtils;\nimport org.sonarsource.sonarlint.core.commons.Version;\n\nimport static java.util.Objects.requireNonNull;\n\n/**\n * This class loads Sonar plugin metadata from JAR manifest.\n */\npublic class SonarPluginManifest {\n\n  public static final String KEY_ATTRIBUTE = \"Plugin-Key\";\n  public static final String MAIN_CLASS_ATTRIBUTE = \"Plugin-Class\";\n  public static final String NAME_ATTRIBUTE = \"Plugin-Name\";\n  public static final String VERSION_ATTRIBUTE = \"Plugin-Version\";\n  public static final String SONAR_VERSION_ATTRIBUTE = \"Sonar-Version\";\n  public static final String DEPENDENCIES_ATTRIBUTE = \"Plugin-Dependencies\";\n  public static final String REQUIRE_PLUGINS_ATTRIBUTE = \"Plugin-RequirePlugins\";\n  public static final String BASE_PLUGIN = \"Plugin-Base\";\n  public static final String JRE_MIN_VERSION = \"Jre-Min-Version\";\n  public static final String NODEJS_MIN_VERSION = \"NodeJs-Min-Version\";\n\n  private final String key;\n  private final String name;\n  private final String mainClass;\n  private final String version;\n  private final Optional<Version> sonarMinVersion;\n  private final List<String> dependencies;\n  private final String basePluginKey;\n  private final List<RequiredPlugin> requiredPlugins;\n  private final Optional<Version> jreMinVersion;\n  private final Optional<Version> nodeJsMinVersion;\n\n  public static class RequiredPlugin {\n\n    private static final Pattern PARSER = Pattern.compile(\"\\\\w+:.+\");\n\n    private final String key;\n    private final Version minimalVersion;\n\n    public RequiredPlugin(String key, Version minimalVersion) {\n      this.key = key;\n      this.minimalVersion = minimalVersion;\n    }\n\n    public String getKey() {\n      return key;\n    }\n\n    public Version getMinimalVersion() {\n      return minimalVersion;\n    }\n\n    public static RequiredPlugin parse(String s) {\n      if (!PARSER.matcher(s).matches()) {\n        throw new IllegalArgumentException(\"Manifest field does not have correct format: \" + s);\n      }\n      var fields = StringUtils.split(s, ':');\n      return new RequiredPlugin(fields[0], Version.create(fields[1]).removeQualifier());\n    }\n\n  }\n\n  /**\n   * Load the manifest from a JAR file.\n   */\n  public static SonarPluginManifest fromJar(Path jarPath) {\n    try (var jar = new JarFile(jarPath.toFile())) {\n      var manifest = jar.getManifest();\n      if (manifest != null) {\n        return new SonarPluginManifest(manifest);\n      } else {\n        throw new IllegalStateException(\"No manifest in jar: \" + jarPath.toAbsolutePath());\n      }\n    } catch (Exception e) {\n      throw new IllegalStateException(\"Error while reading plugin manifest from jar: \" + jarPath.toAbsolutePath(), e);\n    }\n  }\n\n  public SonarPluginManifest(Manifest manifest) {\n    var attributes = manifest.getMainAttributes();\n    this.key = requireNonNull(attributes.getValue(KEY_ATTRIBUTE), \"Plugin key is mandatory\");\n    this.mainClass = attributes.getValue(MAIN_CLASS_ATTRIBUTE);\n    this.name = attributes.getValue(NAME_ATTRIBUTE);\n    this.version = attributes.getValue(VERSION_ATTRIBUTE);\n    this.sonarMinVersion = Optional.ofNullable(attributes.getValue(SONAR_VERSION_ATTRIBUTE)).map(Version::create);\n    this.basePluginKey = attributes.getValue(BASE_PLUGIN);\n\n    var deps = attributes.getValue(DEPENDENCIES_ATTRIBUTE);\n    this.dependencies = List.of(StringUtils.split(StringUtils.defaultString(deps), ' '));\n\n    var requires = attributes.getValue(REQUIRE_PLUGINS_ATTRIBUTE);\n    this.requiredPlugins = Stream.of(StringUtils.split(StringUtils.defaultString(requires), ',')).map(RequiredPlugin::parse).toList();\n    this.jreMinVersion = Optional.ofNullable(attributes.getValue(JRE_MIN_VERSION)).map(Version::create);\n    this.nodeJsMinVersion = Optional.ofNullable(attributes.getValue(NODEJS_MIN_VERSION)).map(Version::create);\n  }\n\n  public String getKey() {\n    return key;\n  }\n\n  @CheckForNull\n  public String getName() {\n    return name;\n  }\n\n  public List<RequiredPlugin> getRequiredPlugins() {\n    return requiredPlugins;\n  }\n\n  @CheckForNull\n  public String getVersion() {\n    return version;\n  }\n\n  public Optional<Version> getSonarMinVersion() {\n    return sonarMinVersion;\n  }\n\n  public String getMainClass() {\n    return mainClass;\n  }\n\n  public List<String> getDependencies() {\n    return dependencies;\n  }\n\n  @CheckForNull\n  public String getBasePluginKey() {\n    return basePluginKey;\n  }\n\n  public Optional<Version> getJreMinVersion() {\n    return jreMinVersion;\n  }\n\n  public Optional<Version> getNodeJsMinVersion() {\n    return nodeJsMinVersion;\n  }\n\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/loading/SonarPluginRequirementsChecker.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.loading;\n\nimport java.nio.file.Path;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\nimport org.sonarsource.sonarlint.core.plugin.commons.ApiVersions;\nimport org.sonarsource.sonarlint.core.plugin.commons.DataflowBugDetection;\nimport org.sonarsource.sonarlint.core.plugin.commons.api.SkipReason;\nimport org.sonarsource.sonarlint.core.plugin.commons.api.SkipReason.UnsatisfiedRuntimeRequirement.RuntimeRequirement;\nimport org.sonarsource.sonarlint.core.plugin.commons.loading.SonarPluginManifest.RequiredPlugin;\n\npublic class SonarPluginRequirementsChecker {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final Version implementedPluginApiVersion;\n\n  public SonarPluginRequirementsChecker() {\n    this(ApiVersions.loadSonarPluginApiVersion());\n  }\n\n  SonarPluginRequirementsChecker(org.sonar.api.utils.Version pluginApiVersion) {\n    this.implementedPluginApiVersion = Version.create(pluginApiVersion.toString());\n  }\n\n  /**\n   * Attempt to read JAR manifests, load metadata, and check all requirements to ensure the plugin can be instantiated.\n   */\n  public Map<String, PluginRequirementsCheckResult> checkRequirements(Set<Path> pluginJarLocations, Set<SonarLanguage> enabledLanguages, Version jreCurrentVersion,\n    Optional<Version> nodeCurrentVersion, boolean enableDataflowBugDetection) {\n    Map<String, PluginRequirementsCheckResult> resultsByKey = new HashMap<>();\n\n    for (Path jarLocation : pluginJarLocations) {\n      PluginInfo plugin;\n\n      try {\n        plugin = PluginInfo.create(jarLocation);\n      } catch (Exception e) {\n        LOG.error(\"Unable to load plugin \" + jarLocation, e);\n        continue;\n      }\n      if (resultsByKey.containsKey(plugin.getKey())) {\n        throw new IllegalStateException(\n          \"Duplicate plugin key '\" + plugin.getKey() + \"' from '\" + plugin.getJarFile() + \"' and '\" + resultsByKey.get(plugin.getKey()).getPlugin().getJarFile() + \"'\");\n      }\n      resultsByKey.put(plugin.getKey(), checkIfSkippedAndPopulateReason(plugin, enabledLanguages, jreCurrentVersion, nodeCurrentVersion));\n    }\n    // Second pass of checks\n    for (PluginRequirementsCheckResult result : resultsByKey.values()) {\n      if (!result.isSkipped()) {\n        resultsByKey.put(result.getPlugin().getKey(), checkUnsatisfiedPluginDependency(result, resultsByKey, enableDataflowBugDetection));\n      }\n    }\n    return resultsByKey;\n  }\n\n  private PluginRequirementsCheckResult checkIfSkippedAndPopulateReason(PluginInfo plugin, Set<SonarLanguage> enabledLanguages, Version jreCurrentVersion,\n    Optional<Version> nodeCurrentVersion) {\n    var pluginKey = plugin.getKey();\n    var languages = SonarPlugin.findByKey(pluginKey).map(SonarPlugin::getLanguages).orElseGet(Set::of);\n    if (!languages.isEmpty() && enabledLanguages.stream().noneMatch(languages::contains)) {\n      if (languages.size() > 1) {\n        LOG.debug(\"Plugin '{}' is excluded because none of languages '{}' are enabled. Skip loading it.\", plugin.getName(),\n          languages.stream().map(SonarLanguage::toString).collect(Collectors.joining(\",\")));\n      } else {\n        LOG.debug(\"Plugin '{}' is excluded because language '{}' is not enabled. Skip loading it.\", plugin.getName(),\n          languages.iterator().next());\n      }\n      return new PluginRequirementsCheckResult(plugin, new SkipReason.LanguagesNotEnabled(languages));\n    }\n\n    if (!isCompatibleWith(plugin, implementedPluginApiVersion)) {\n      LOG.debug(\"Plugin '{}' requires plugin API {} while SonarLint supports only up to {}. Skip loading it.\", plugin.getName(),\n        plugin.getMinimalSqVersion(), implementedPluginApiVersion.removeQualifier().toString());\n      return new PluginRequirementsCheckResult(plugin, SkipReason.IncompatiblePluginApi.INSTANCE);\n    }\n    var jreMinVersion = plugin.getJreMinVersion();\n    if (jreMinVersion != null && !jreCurrentVersion.satisfiesMinRequirement(jreMinVersion)) {\n      LOG.debug(\"Plugin '{}' requires JRE {} while current is {}. Skip loading it.\", plugin.getName(), jreMinVersion, jreCurrentVersion);\n      return new PluginRequirementsCheckResult(plugin,\n        new SkipReason.UnsatisfiedRuntimeRequirement(RuntimeRequirement.JRE, jreCurrentVersion.toString(), jreMinVersion.toString()));\n    }\n    var nodeMinVersion = plugin.getNodeJsMinVersion();\n    if (nodeMinVersion != null) {\n      if (nodeCurrentVersion.isEmpty()) {\n        LOG.debug(\"Plugin '{}' requires Node.js {}. Skip loading it.\", plugin.getName(), nodeMinVersion);\n        return new PluginRequirementsCheckResult(plugin, new SkipReason.UnsatisfiedRuntimeRequirement(RuntimeRequirement.NODEJS, null, nodeMinVersion.toString()));\n      } else if (!nodeCurrentVersion.get().satisfiesMinRequirement(nodeMinVersion)) {\n        LOG.debug(\"Plugin '{}' requires Node.js {} while current is {}. Skip loading it.\", plugin.getName(), nodeMinVersion, nodeCurrentVersion.get());\n        return new PluginRequirementsCheckResult(plugin,\n          new SkipReason.UnsatisfiedRuntimeRequirement(RuntimeRequirement.NODEJS, nodeCurrentVersion.get().toString(), nodeMinVersion.toString()));\n      }\n    }\n\n    return new PluginRequirementsCheckResult(plugin, null);\n  }\n\n  /**\n   * Find out if this plugin is compatible with a given version of the sonar-plugin-api.\n   * The version of the API must be greater than or equal to the minimal version\n   * needed by the plugin.\n   */\n  static boolean isCompatibleWith(PluginInfo plugin, Version implementedApiVersion) {\n    var sonarMinVersion = plugin.getMinimalSqVersion();\n    if (sonarMinVersion == null) {\n      // no constraint defined on the plugin\n      return true;\n    }\n\n    // Ignore patch and build numbers since this should not change API compatibility\n    var requestedApi = Version.create(sonarMinVersion.getMajor() + \".\" + sonarMinVersion.getMinor());\n    return implementedApiVersion.satisfiesMinRequirement(requestedApi);\n  }\n\n  private static PluginRequirementsCheckResult checkUnsatisfiedPluginDependency(PluginRequirementsCheckResult currentResult,\n    Map<String, PluginRequirementsCheckResult> currentResultsByKey, boolean enableDataflowBugDetection) {\n    var plugin = currentResult.getPlugin();\n    for (RequiredPlugin required : plugin.getRequiredPlugins()) {\n      if (\"license\".equals(required.getKey())) {\n        continue;\n      }\n      var depInfo = currentResultsByKey.get(required.getKey());\n      // We could possibly have a problem with transitive dependencies, since we evaluate in no specific order.\n      // A -> B -> C\n      // If C is skipped, then B should be skipped, then A should be skipped\n      // If we evaluate A before B, then A might be wrongly included\n      // But I'm not aware of such case in real life.\n      if (depInfo == null || depInfo.isSkipped()) {\n        return processUnsatisfiedDependency(currentResult.getPlugin(), required.getKey());\n      }\n    }\n    var basePluginKey = plugin.getBasePlugin();\n    if (basePluginKey != null && checkForPluginSkipped(currentResultsByKey.get(basePluginKey))) {\n      return processUnsatisfiedDependency(currentResult.getPlugin(), basePluginKey);\n    }\n    if (DataflowBugDetection.PLUGIN_ALLOW_LIST.contains(plugin.getKey())) {\n      // Workaround for SLCORE-667\n      // dbd and dbdpythonfrontend require Python to be working\n      if (!enableDataflowBugDetection) {\n        LOG.debug(\"DBD feature disabled. Skip loading plugin '{}'.\", plugin.getName());\n        return new PluginRequirementsCheckResult(plugin, SkipReason.UnsupportedFeature.INSTANCE);\n      }\n      var pythonPluginResult = currentResultsByKey.get(SonarPlugin.PYTHON.getKey());\n      if (checkForPluginSkipped(pythonPluginResult)) {\n        return processUnsatisfiedDependency(currentResult.getPlugin(), SonarPlugin.PYTHON.getKey());\n      }\n    }\n    return currentResult;\n  }\n\n  private static boolean checkForPluginSkipped(@Nullable PluginRequirementsCheckResult plugin) {\n    return plugin == null || plugin.isSkipped();\n  }\n\n  private static PluginRequirementsCheckResult processUnsatisfiedDependency(PluginInfo plugin, String pluginKeyDependency) {\n    LOG.debug(\"Plugin '{}' dependency on '{}' is unsatisfied. Skip loading it.\", plugin.getName(), pluginKeyDependency);\n    return new PluginRequirementsCheckResult(plugin, new SkipReason.UnsatisfiedDependency(pluginKeyDependency));\n  }\n\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/loading/package-info.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.plugin.commons.loading;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/package-info.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.plugin.commons;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/sonarapi/ConfigurationBridge.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.sonarapi;\n\nimport java.util.Optional;\nimport org.sonar.api.config.Configuration;\nimport org.sonar.api.config.Settings;\n\n/**\n * Used to help migration from {@link Settings} to {@link Configuration}\n */\npublic class ConfigurationBridge implements Configuration {\n\n  private final Settings settings;\n\n  public ConfigurationBridge(Settings settings) {\n    this.settings = settings;\n  }\n\n  @Override\n  public Optional<String> get(String key) {\n    return Optional.ofNullable(settings.getString(key));\n  }\n\n  @Override\n  public boolean hasKey(String key) {\n    return settings.hasKey(key);\n  }\n\n  @Override\n  public String[] getStringArray(String key) {\n    return settings.getStringArray(key);\n  }\n\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/sonarapi/MapSettings.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.sonarapi;\n\nimport java.util.Date;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport javax.annotation.CheckForNull;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.Strings;\nimport org.sonar.api.config.Configuration;\nimport org.sonar.api.config.PropertyDefinition;\nimport org.sonar.api.config.PropertyDefinitions;\nimport org.sonar.api.config.Settings;\nimport org.sonar.api.utils.DateUtils;\nimport org.sonar.api.utils.System2;\n\nimport static java.util.Objects.requireNonNull;\nimport static java.util.stream.Collectors.toUnmodifiableMap;\nimport static org.apache.commons.lang3.StringUtils.trim;\nimport static org.sonarsource.sonarlint.core.plugin.commons.MultivalueProperty.parseAsCsv;\n\npublic class MapSettings extends Settings {\n\n  private final Map<String, String> props;\n  private final ConfigurationBridge configurationBridge;\n  private final PropertyDefinitions definitions;\n\n  // For testing\n  public MapSettings(Map<String, String> props) {\n    this(new PropertyDefinitions(System2.INSTANCE), props);\n  }\n\n  public MapSettings(PropertyDefinitions definitions, Map<String, String> props) {\n    this.props = props.entrySet().stream()\n      .collect(\n        toUnmodifiableMap(e -> definitions.validKey(e.getKey()), e -> trim(e.getValue())));\n    this.definitions = definitions;\n    configurationBridge = new ConfigurationBridge(this);\n  }\n\n  protected Optional<String> get(String key) {\n    return Optional.ofNullable(props.get(key));\n  }\n\n  public Map<String, String> getProperties() {\n    return props;\n  }\n\n  /**\n   * The value that overrides the default value. It\n   * may be encrypted with a secret key. Use {@link #getString(String)} to get\n   * the effective and decrypted value.\n   *\n   * @since 6.1\n   */\n  public Optional<String> getRawString(String key) {\n    return get(definitions.validKey(requireNonNull(key)));\n  }\n\n  /**\n   * All the property definitions declared by core and plugins.\n   */\n  public PropertyDefinitions getDefinitions() {\n    return definitions;\n  }\n\n  /**\n   * The definition related to the specified property. It may\n   * be empty.\n   *\n   * @since 6.1\n   */\n  public Optional<PropertyDefinition> getDefinition(String key) {\n    return Optional.ofNullable(definitions.get(key));\n  }\n\n  /**\n   * @return {@code true} if the property has a non-default value, else {@code false}.\n   */\n  @Override\n  public boolean hasKey(String key) {\n    return getRawString(key).isPresent();\n  }\n\n  @CheckForNull\n  public String getDefaultValue(String key) {\n    return definitions.getDefaultValue(key);\n  }\n\n  public boolean hasDefaultValue(String key) {\n    return StringUtils.isNotEmpty(getDefaultValue(key));\n  }\n\n  /**\n   * The effective value of the specified property. Can return\n   * {@code null} if the property is not set and has no\n   * defined default value.\n   * <p>\n   * If the property is encrypted with a secret key,\n   * then the returned value is decrypted.\n   * </p>\n   *\n   * @throws IllegalStateException if value is encrypted but fails to be decrypted.\n   */\n  @CheckForNull\n  @Override\n  public String getString(String key) {\n    var effectiveKey = definitions.validKey(key);\n    // default values cannot be encrypted, so return value as-is.\n    return getRawString(effectiveKey)\n      .orElseGet(() -> getDefaultValue(effectiveKey));\n  }\n\n  /**\n   * Effective value as boolean. It is {@code false} if {@link #getString(String)}\n   * does not return {@code \"true\"}, even if it's not a boolean representation.\n   *\n   * @return {@code true} if the effective value is {@code \"true\"}, else {@code false}.\n   */\n  @Override\n  public boolean getBoolean(String key) {\n    var value = getString(key);\n    return StringUtils.isNotEmpty(value) && Boolean.parseBoolean(value);\n  }\n\n  /**\n   * Effective value as {@code int}.\n   *\n   * @return the value as {@code int}. If the property does not have value nor default value, then {@code 0} is returned.\n   * @throws NumberFormatException if value is not empty and is not a parsable integer\n   */\n  @Override\n  public int getInt(String key) {\n    var value = getString(key);\n    if (StringUtils.isNotEmpty(value)) {\n      return Integer.parseInt(value);\n    }\n    return 0;\n  }\n\n  /**\n   * Effective value as {@code long}.\n   *\n   * @return the value as {@code long}. If the property does not have value nor default value, then {@code 0L} is returned.\n   * @throws NumberFormatException if value is not empty and is not a parsable {@code long}\n   */\n  @Override\n  public long getLong(String key) {\n    var value = getString(key);\n    if (StringUtils.isNotEmpty(value)) {\n      return Long.parseLong(value);\n    }\n    return 0L;\n  }\n\n  /**\n   * Effective value as {@link Date}, without time fields. Format is {@link DateUtils#DATE_FORMAT}.\n   *\n   * @return the value as a {@link Date}. If the property does not have value nor default value, then {@code null} is returned.\n   * @throws RuntimeException if value is not empty and is not in accordance with {@link DateUtils#DATE_FORMAT}.\n   */\n  @CheckForNull\n  @Override\n  public Date getDate(String key) {\n    var value = getString(key);\n    if (StringUtils.isNotEmpty(value)) {\n      return DateUtils.parseDate(value);\n    }\n    return null;\n  }\n\n  /**\n   * Effective value as {@link Date}, with time fields. Format is {@link DateUtils#DATETIME_FORMAT}.\n   *\n   * @return the value as a {@link Date}. If the property does not have value nor default value, then {@code null} is returned.\n   * @throws RuntimeException if value is not empty and is not in accordance with {@link DateUtils#DATETIME_FORMAT}.\n   */\n  @CheckForNull\n  @Override\n  public Date getDateTime(String key) {\n    var value = getString(key);\n    if (StringUtils.isNotEmpty(value)) {\n      return DateUtils.parseDateTime(value);\n    }\n    return null;\n  }\n\n  /**\n   * Effective value as {@code Float}.\n   *\n   * @return the value as {@code Float}. If the property does not have value nor default value, then {@code null} is returned.\n   * @throws NumberFormatException if value is not empty and is not a parsable number\n   */\n  @CheckForNull\n  @Override\n  public Float getFloat(String key) {\n    var value = getString(key);\n    if (StringUtils.isNotEmpty(value)) {\n      try {\n        return Float.valueOf(value);\n      } catch (NumberFormatException e) {\n        throw new IllegalStateException(String.format(\"The property '%s' is not a float value\", key));\n      }\n    }\n    return null;\n  }\n\n  /**\n   * Effective value as {@code Double}.\n   *\n   * @return the value as {@code Double}. If the property does not have value nor default value, then {@code null} is returned.\n   * @throws NumberFormatException if value is not empty and is not a parsable number\n   */\n  @CheckForNull\n  @Override\n  public Double getDouble(String key) {\n    var value = getString(key);\n    if (StringUtils.isNotEmpty(value)) {\n      try {\n        return Double.valueOf(value);\n      } catch (NumberFormatException e) {\n        throw new IllegalStateException(String.format(\"The property '%s' is not a double value\", key));\n      }\n    }\n    return null;\n  }\n\n  /**\n   * Value is split by comma and trimmed. Never returns null.\n   * <br>\n   * Examples :\n   * <ul>\n   * <li>\"one,two,three \" -&gt; [\"one\", \"two\", \"three\"]</li>\n   * <li>\"  one, two, three \" -&gt; [\"one\", \"two\", \"three\"]</li>\n   * <li>\"one, , three\" -&gt; [\"one\", \"\", \"three\"]</li>\n   * </ul>\n   */\n  @Override\n  public String[] getStringArray(String key) {\n    var effectiveKey = definitions.validKey(key);\n    var def = getDefinition(effectiveKey);\n    if ((def.isPresent()) && (def.get().multiValues())) {\n      var value = getString(key);\n      if (value == null) {\n        return ArrayUtils.EMPTY_STRING_ARRAY;\n      }\n\n      return parseAsCsv(effectiveKey, value);\n    }\n\n    return getStringArrayBySeparator(key, \",\");\n  }\n\n  /**\n   * Value is split by carriage returns.\n   *\n   * @return non-null array of lines. The line termination characters are excluded.\n   * @since 3.2\n   */\n  @Override\n  public String[] getStringLines(String key) {\n    var value = getString(key);\n    if (StringUtils.isEmpty(value)) {\n      return new String[0];\n    }\n    return value.split(\"\\r?\\n|\\r\", -1);\n  }\n\n  /**\n   * Value is split and trimmed.\n   */\n  @Override\n  public String[] getStringArrayBySeparator(String key, String separator) {\n    var value = getString(key);\n    if (value != null) {\n      var strings = StringUtils.splitByWholeSeparator(value, separator);\n      var result = new String[strings.length];\n      for (var index = 0; index < strings.length; index++) {\n        result[index] = trim(strings[index]);\n      }\n      return result;\n    }\n    return ArrayUtils.EMPTY_STRING_ARRAY;\n  }\n\n  @Override\n  public List<String> getKeysStartingWith(String prefix) {\n    return getProperties().keySet().stream()\n      .filter(key -> Strings.CS.startsWith(key, prefix))\n      .toList();\n  }\n\n  /**\n   * @return a {@link Configuration} proxy on top of this existing {@link Settings} implementation. Changes are reflected in the {@link Configuration} object.\n   * @since 6.5\n   */\n  public Configuration asConfig() {\n    return configurationBridge;\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/sonarapi/PluginContextImpl.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.sonarapi;\n\nimport java.util.Objects;\nimport org.sonar.api.Plugin;\nimport org.sonar.api.config.Configuration;\nimport org.sonarsource.sonarlint.plugin.api.SonarLintRuntime;\n\npublic class PluginContextImpl extends Plugin.Context {\n\n  private final Configuration bootConfiguration;\n\n  private PluginContextImpl(Builder builder) {\n    super(builder.sonarRuntime);\n    this.bootConfiguration = builder.bootConfiguration;\n  }\n\n  @Override\n  public Configuration getBootConfiguration() {\n    return bootConfiguration;\n  }\n\n  @Override\n  public SonarLintRuntime getRuntime() {\n    return (SonarLintRuntime) super.getRuntime();\n  }\n\n  public static class Builder {\n    private SonarLintRuntime sonarRuntime;\n    private Configuration bootConfiguration;\n\n    /**\n     * Required.\n     * @see SonarLintRuntimeImpl\n     * @return this\n     */\n    public Builder setSonarRuntime(SonarLintRuntime r) {\n      this.sonarRuntime = r;\n      return this;\n    }\n\n    /**\n     * Required.\n     * @return this\n     */\n    public Builder setBootConfiguration(Configuration c) {\n      this.bootConfiguration = c;\n      return this;\n    }\n\n    public Plugin.Context build() {\n      Objects.requireNonNull(bootConfiguration, \"bootConfiguration is mandatory\");\n      return new PluginContextImpl(this);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/sonarapi/SonarLintRuntimeImpl.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.sonarapi;\n\nimport org.sonar.api.SonarEdition;\nimport org.sonar.api.SonarProduct;\nimport org.sonar.api.SonarQubeSide;\nimport org.sonar.api.utils.Version;\nimport org.sonarsource.sonarlint.plugin.api.SonarLintRuntime;\n\nimport static java.util.Objects.requireNonNull;\n\npublic class SonarLintRuntimeImpl implements SonarLintRuntime {\n\n  private final Version sonarPluginApiVersion;\n  private final Version sonarLintPluginApiVersion;\n  private final long clientPid;\n\n  public SonarLintRuntimeImpl(Version sonarPluginApiVersion, Version sonarLintPluginApiVersion, long clientPid) {\n    this.clientPid = clientPid;\n    this.sonarPluginApiVersion = requireNonNull(sonarPluginApiVersion);\n    this.sonarLintPluginApiVersion = sonarLintPluginApiVersion;\n  }\n\n  @Override\n  public Version getApiVersion() {\n    return sonarPluginApiVersion;\n  }\n\n  @Override\n  public Version getSonarLintPluginApiVersion() {\n    return sonarLintPluginApiVersion;\n  }\n\n  @Override\n  public SonarProduct getProduct() {\n    return SonarProduct.SONARLINT;\n  }\n\n  @Override\n  public SonarQubeSide getSonarQubeSide() {\n    throw new UnsupportedOperationException(\"Can only be called in SonarQube\");\n  }\n\n  @Override\n  public SonarEdition getEdition() {\n    throw new UnsupportedOperationException(\"Can only be called in SonarQube\");\n  }\n\n  @Override\n  public long getClientPid() {\n    return clientPid;\n  }\n\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/sonarapi/package-info.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.plugin.commons.sonarapi;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/plugin-commons/src/test/java/com/sonarsource/plugins/license/api/LicensedPluginRegistrationTests.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage com.sonarsource.plugins.license.api;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass LicensedPluginRegistrationTests {\n\n  @Test\n  void testBuilder() {\n    var underTest = LicensedPluginRegistration.forPlugin(\"xoo\");\n    assertThat(underTest.getPluginKey()).isEqualTo(\"xoo\");\n  }\n\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/test/java/org/sonarsource/sonarlint/core/plugin/commons/ApiVersionsTests.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons;\n\nimport java.net.URI;\nimport org.junit.jupiter.api.Test;\nimport org.sonar.api.utils.Version;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\n\nclass ApiVersionsTests {\n  @Test\n  void can_load_sonar_plugin_api_version_from_embedded_resource() {\n    var version = ApiVersions.loadSonarPluginApiVersion();\n\n    assertThat(version).isNotNull();\n    assertThat(version.isGreaterThanOrEqual(Version.create(8, 5))).isTrue();\n  }\n\n  @Test\n  void can_load_sonarlint_plugin_api_version_from_embedded_resource() {\n    var version = ApiVersions.loadSonarLintPluginApiVersion();\n\n    assertThat(version).isNotNull();\n    assertThat(version.isGreaterThanOrEqual(Version.create(5, 4))).isTrue();\n  }\n\n  @Test\n  void should_throw_an_exception_if_resource_does_not_exist() {\n    var throwable = catchThrowable(() -> ApiVersions.loadVersion(null, \"wrongPath\"));\n\n    assertThat(throwable).hasMessage(\"Can not load wrongPath from classpath\");\n  }\n\n  @Test\n  void should_throw_an_exception_if_resource_can_not_be_loaded() {\n    var throwable = catchThrowable(() -> ApiVersions.loadVersion(URI.create(\"file://wrong\").toURL(), \"wrongPath\"));\n\n    assertThat(throwable).hasMessage(\"Can not load wrongPath from classpath\");\n  }\n\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/test/java/org/sonarsource/sonarlint/core/plugin/commons/ExtensionUtilsTests.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons;\n\nimport org.junit.jupiter.api.Test;\nimport org.sonar.api.batch.InstantiationStrategy;\nimport org.sonar.api.batch.ScannerSide;\nimport org.sonar.api.ce.ComputeEngineSide;\nimport org.sonar.api.server.ServerSide;\nimport org.sonarsource.api.sonarlint.SonarLintSide;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ExtensionUtilsTests {\n\n  @Test\n  void shouldBeBatchInstantiationStrategy() {\n    assertThat(ExtensionUtils.isInstantiationStrategy(DefaultScannerService.class, InstantiationStrategy.PER_BATCH)).isFalse();\n    assertThat(ExtensionUtils.isInstantiationStrategy(new DefaultScannerService(), InstantiationStrategy.PER_BATCH)).isFalse();\n\n  }\n\n  @Test\n  void shouldBeProjectInstantiationStrategy() {\n    assertThat(ExtensionUtils.isInstantiationStrategy(DefaultScannerService.class, InstantiationStrategy.PER_PROJECT)).isTrue();\n    assertThat(ExtensionUtils.isInstantiationStrategy(new DefaultScannerService(), InstantiationStrategy.PER_PROJECT)).isTrue();\n\n  }\n\n  @Test\n  void testIsSonarLintSide() {\n    assertThat(ExtensionUtils.isSonarLintSide(ScannerService.class)).isFalse();\n\n    assertThat(ExtensionUtils.isSonarLintSide(ServerService.class)).isFalse();\n    assertThat(ExtensionUtils.isSonarLintSide(new ServerService())).isFalse();\n    assertThat(ExtensionUtils.isSonarLintSide(new WebServerService())).isFalse();\n    assertThat(ExtensionUtils.isSonarLintSide(new ComputeEngineService())).isFalse();\n    assertThat(ExtensionUtils.isSonarLintSide(new DefaultSonarLintService())).isTrue();\n  }\n\n  @ScannerSide\n  @InstantiationStrategy(InstantiationStrategy.PER_BATCH)\n  public static class ScannerService {\n\n  }\n\n  @ScannerSide\n  public static class DefaultScannerService {\n\n  }\n\n  @SonarLintSide\n  public static class DefaultSonarLintService {\n\n  }\n\n  @ServerSide\n  public static class ServerService {\n\n  }\n\n  @ServerSide\n  public static class WebServerService {\n\n  }\n\n  @ComputeEngineSide\n  public static class ComputeEngineService {\n\n  }\n\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/test/java/org/sonarsource/sonarlint/core/plugin/commons/MultivaluePropertyTests.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons;\n\nimport java.util.Random;\nimport java.util.stream.Stream;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport static java.util.function.UnaryOperator.identity;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.sonarsource.sonarlint.core.plugin.commons.MultivalueProperty.parseAsCsv;\nimport static org.sonarsource.sonarlint.core.plugin.commons.MultivalueProperty.trimFieldsAndRemoveEmptyFields;\nimport static org.sonarsource.sonarlint.core.plugin.commons.Utils.randomAlphanumeric;\n\nclass MultivaluePropertyTests {\n  private static final String[] EMPTY_STRING_ARRAY = {};\n\n  @ParameterizedTest\n  @MethodSource(\"testParseAsCsv\")\n  void parseAsCsv_for_coverage(String value, String[] expected) {\n    assertThat(parseAsCsv(\"key\", value))\n      .isEqualTo(parseAsCsv(\"key\", value, identity()))\n      .isEqualTo(expected);\n  }\n\n  @Test\n  void parseAsCsv_fails_with_ISE_if_value_can_not_be_parsed() {\n    assertThatThrownBy(() -> parseAsCsv(\"multi\", \"\\\"a ,b\"))\n      .isInstanceOf(IllegalStateException.class)\n      .hasMessage(\"Property: 'multi' doesn't contain a valid CSV value: '\\\"a ,b'\");\n  }\n\n  public static Stream<Arguments> testParseAsCsv() {\n    return Stream.of(\n      Arguments.of(\"\", EMPTY_STRING_ARRAY),\n      Arguments.of(\"a\", arrayOf(\"a\")),\n      Arguments.of(\" a\", arrayOf(\"a\")),\n      Arguments.of(\"a \", arrayOf(\"a\")),\n      Arguments.of(\" a, b\", arrayOf(\"a\", \"b\")),\n      Arguments.of(\"a,b \", arrayOf(\"a\", \"b\")),\n      Arguments.of(\"a,,,b,c,,d\", arrayOf(\"a\", \"b\", \"c\", \"d\")),\n      Arguments.of(\"a,\\n\\tb,\\n   c,\\n   d\\n\", arrayOf(\"a\", \"b\", \"c\", \"d\")),\n      Arguments.of(\"a\\n\\tb\\n   c,\\n   d\\n\", arrayOf(\"a\\nb\\nc\", \"d\")),\n      Arguments.of(\"\\na\\n\\tb\\n   c,\\n   d\\n\", arrayOf(\"a\\nb\\nc\", \"d\")),\n      Arguments.of(\"a,\\n,\\nb\", arrayOf(\"a\", \"b\")),\n      Arguments.of(\" , \\n ,, \\t\", EMPTY_STRING_ARRAY),\n      Arguments.of(\"\\\" a\\\"\", arrayOf(\" a\")),\n      Arguments.of(\"\\\",\\\"\", arrayOf(\",\")),\n      // escaped quote in quoted field\n      Arguments.of(\"\\\"\\\"\\\"\\\"\", arrayOf(\"\\\"\")));\n  }\n\n  private static String[] arrayOf(String... strs) {\n    return strs;\n  }\n\n  @Test\n  void trimFieldsAndRemoveEmptyFields_throws_NPE_if_arg_is_null() {\n    assertThatThrownBy(() -> trimFieldsAndRemoveEmptyFields(null))\n      .isInstanceOf(NullPointerException.class);\n  }\n\n  @ParameterizedTest\n  @MethodSource(\"plains\")\n  void trimFieldsAndRemoveEmptyFields_ignores_EmptyFields(String str) {\n    assertThat(trimFieldsAndRemoveEmptyFields(\"\")).isEmpty();\n    assertThat(trimFieldsAndRemoveEmptyFields(str)).isEqualTo(str);\n\n    assertThat(trimFieldsAndRemoveEmptyFields(',' + str)).isEqualTo(str);\n    assertThat(trimFieldsAndRemoveEmptyFields(str + ',')).isEqualTo(str);\n    assertThat(trimFieldsAndRemoveEmptyFields(\",,,\" + str)).isEqualTo(str);\n    assertThat(trimFieldsAndRemoveEmptyFields(str + \",,,\")).isEqualTo(str);\n\n    assertThat(trimFieldsAndRemoveEmptyFields(str + ',' + str)).isEqualTo(str + ',' + str);\n    assertThat(trimFieldsAndRemoveEmptyFields(str + \",,,\" + str)).isEqualTo(str + ',' + str);\n    assertThat(trimFieldsAndRemoveEmptyFields(',' + str + ',' + str)).isEqualTo(str + ',' + str);\n    assertThat(trimFieldsAndRemoveEmptyFields(\",\" + str + \",,,\" + str)).isEqualTo(str + ',' + str);\n    assertThat(trimFieldsAndRemoveEmptyFields(\",,,\" + str + \",,,\" + str)).isEqualTo(str + ',' + str);\n\n    assertThat(trimFieldsAndRemoveEmptyFields(str + ',' + str + ',')).isEqualTo(str + ',' + str);\n    assertThat(trimFieldsAndRemoveEmptyFields(str + \",,,\" + str + \",\")).isEqualTo(str + ',' + str);\n    assertThat(trimFieldsAndRemoveEmptyFields(str + \",,,\" + str + \",,\")).isEqualTo(str + ',' + str);\n\n    assertThat(trimFieldsAndRemoveEmptyFields(',' + str + ',' + str + ',')).isEqualTo(str + ',' + str);\n    assertThat(trimFieldsAndRemoveEmptyFields(\",,\" + str + ',' + str + ',')).isEqualTo(str + ',' + str);\n    assertThat(trimFieldsAndRemoveEmptyFields(',' + str + \",,\" + str + ',')).isEqualTo(str + ',' + str);\n    assertThat(trimFieldsAndRemoveEmptyFields(',' + str + ',' + str + \",,\")).isEqualTo(str + ',' + str);\n    assertThat(trimFieldsAndRemoveEmptyFields(\",,,\" + str + \",,,\" + str + \",,\")).isEqualTo(str + ',' + str);\n\n    assertThat(trimFieldsAndRemoveEmptyFields(str + ',' + str + ',' + str)).isEqualTo(str + ',' + str + ',' + str);\n    assertThat(trimFieldsAndRemoveEmptyFields(str + ',' + str + ',' + str)).isEqualTo(str + ',' + str + ',' + str);\n  }\n\n  public static Object[][] plains() {\n    return new Object[][] {\n      {randomAlphanumeric(1)},\n      {randomAlphanumeric(2)},\n      {randomAlphanumeric(3 + new Random().nextInt(5))}\n    };\n  }\n\n  @ParameterizedTest\n  @MethodSource(\"emptyAndtrimmable\")\n  void trimFieldsAndRemoveEmptyFields_ignores_empty_fields_and_trims_fields(String empty, String trimmable) {\n    String expected = trimmable.trim();\n    assertThat(empty.trim()).isEmpty();\n\n    assertThat(trimFieldsAndRemoveEmptyFields(trimmable)).isEqualTo(expected);\n    assertThat(trimFieldsAndRemoveEmptyFields(trimmable + ',' + empty)).isEqualTo(expected);\n    assertThat(trimFieldsAndRemoveEmptyFields(trimmable + \",,\" + empty)).isEqualTo(expected);\n    assertThat(trimFieldsAndRemoveEmptyFields(empty + ',' + trimmable)).isEqualTo(expected);\n    assertThat(trimFieldsAndRemoveEmptyFields(empty + \",,\" + trimmable)).isEqualTo(expected);\n    assertThat(trimFieldsAndRemoveEmptyFields(empty + ',' + trimmable + ',' + empty)).isEqualTo(expected);\n    assertThat(trimFieldsAndRemoveEmptyFields(empty + \",,\" + trimmable + \",,,\" + empty)).isEqualTo(expected);\n\n    assertThat(trimFieldsAndRemoveEmptyFields(trimmable + ',' + empty + ',' + empty)).isEqualTo(expected);\n    assertThat(trimFieldsAndRemoveEmptyFields(trimmable + \",,\" + empty + \",,,\" + empty)).isEqualTo(expected);\n\n    assertThat(trimFieldsAndRemoveEmptyFields(empty + ',' + empty + ',' + trimmable)).isEqualTo(expected);\n    assertThat(trimFieldsAndRemoveEmptyFields(empty + \",,,,\" + empty + \",,\" + trimmable)).isEqualTo(expected);\n\n    assertThat(trimFieldsAndRemoveEmptyFields(trimmable + ',' + trimmable)).isEqualTo(expected + ',' + expected);\n    assertThat(trimFieldsAndRemoveEmptyFields(trimmable + ',' + trimmable + ',' + trimmable)).isEqualTo(expected + ',' + expected + ',' + expected);\n    assertThat(trimFieldsAndRemoveEmptyFields(trimmable + \",\" + trimmable + ',' + trimmable)).isEqualTo(expected + ',' + expected + ',' + expected);\n  }\n\n  @Test\n  void trimAccordingToStringTrim() {\n    String str = randomAlphanumeric(4);\n    for (int i = 0; i <= ' '; i++) {\n      String prefixed = (char) i + str;\n      String suffixed = (char) i + str;\n      String both = (char) i + str + (char) i;\n      assertThat(trimFieldsAndRemoveEmptyFields(prefixed)).isEqualTo(prefixed.trim());\n      assertThat(trimFieldsAndRemoveEmptyFields(suffixed)).isEqualTo(suffixed.trim());\n      assertThat(trimFieldsAndRemoveEmptyFields(both)).isEqualTo(both.trim());\n    }\n  }\n\n  public static Object[][] emptyAndtrimmable() {\n    Random random = new Random();\n    String oneEmpty = randomTrimmedChars(1, random);\n    String twoEmpty = randomTrimmedChars(2, random);\n    String threePlusEmpty = randomTrimmedChars(3 + random.nextInt(5), random);\n    String onePlusEmpty = randomTrimmedChars(1 + random.nextInt(5), random);\n\n    String plain = randomAlphanumeric(1);\n    String plainWithtrimmable = randomAlphanumeric(2) + onePlusEmpty + randomAlphanumeric(3);\n    String quotedWithSeparator = '\"' + randomAlphanumeric(3) + ',' + randomAlphanumeric(2) + '\"';\n    String quotedWithDoubleSeparator = '\"' + randomAlphanumeric(3) + \",,\" + randomAlphanumeric(2) + '\"';\n    String quotedWithtrimmable = '\"' + randomAlphanumeric(3) + onePlusEmpty + randomAlphanumeric(2) + '\"';\n\n    String[] empties = {oneEmpty, twoEmpty, threePlusEmpty};\n    String[] strings = {plain, plainWithtrimmable,\n      onePlusEmpty + plain, plain + onePlusEmpty, onePlusEmpty + plain + onePlusEmpty,\n      onePlusEmpty + plainWithtrimmable, plainWithtrimmable + onePlusEmpty, onePlusEmpty + plainWithtrimmable + onePlusEmpty,\n      onePlusEmpty + quotedWithSeparator, quotedWithSeparator + onePlusEmpty, onePlusEmpty + quotedWithSeparator + onePlusEmpty,\n      onePlusEmpty + quotedWithDoubleSeparator, quotedWithDoubleSeparator + onePlusEmpty, onePlusEmpty + quotedWithDoubleSeparator + onePlusEmpty,\n      onePlusEmpty + quotedWithtrimmable, quotedWithtrimmable + onePlusEmpty, onePlusEmpty + quotedWithtrimmable + onePlusEmpty\n    };\n\n    Object[][] res = new Object[empties.length * strings.length][2];\n    int i = 0;\n    for (String empty : empties) {\n      for (String string : strings) {\n        res[i][0] = empty;\n        res[i][1] = string;\n        i++;\n      }\n    }\n    return res;\n  }\n\n  @ParameterizedTest\n  @MethodSource(\"emptys\")\n  void trimFieldsAndRemoveEmptyFields_quotes_allow_to_preserve_fields(String empty) {\n    String quotedEmpty = '\"' + empty + '\"';\n\n    assertThat(trimFieldsAndRemoveEmptyFields(quotedEmpty)).isEqualTo(quotedEmpty);\n    assertThat(trimFieldsAndRemoveEmptyFields(',' + quotedEmpty)).isEqualTo(quotedEmpty);\n    assertThat(trimFieldsAndRemoveEmptyFields(quotedEmpty + ',')).isEqualTo(quotedEmpty);\n    assertThat(trimFieldsAndRemoveEmptyFields(',' + quotedEmpty + ',')).isEqualTo(quotedEmpty);\n\n    assertThat(trimFieldsAndRemoveEmptyFields(quotedEmpty + ',' + quotedEmpty)).isEqualTo(quotedEmpty + ',' + quotedEmpty);\n    assertThat(trimFieldsAndRemoveEmptyFields(quotedEmpty + \",,\" + quotedEmpty)).isEqualTo(quotedEmpty + ',' + quotedEmpty);\n\n    assertThat(trimFieldsAndRemoveEmptyFields(quotedEmpty + ',' + quotedEmpty + ',' + quotedEmpty)).isEqualTo(quotedEmpty + ',' + quotedEmpty + ',' + quotedEmpty);\n  }\n\n  public static Object[][] emptys() {\n    Random random = new Random();\n    return new Object[][] {\n      {randomTrimmedChars(1, random)},\n      {randomTrimmedChars(2, random)},\n      {randomTrimmedChars(3 + random.nextInt(5), random)}\n    };\n  }\n\n  @Test\n  void trimFieldsAndRemoveEmptyFields_supports_escaped_quote_in_quotes() {\n    assertThat(trimFieldsAndRemoveEmptyFields(\"\\\"f\\\"\\\"oo\\\"\")).isEqualTo(\"\\\"f\\\"\\\"oo\\\"\");\n    assertThat(trimFieldsAndRemoveEmptyFields(\"\\\"f\\\"\\\"oo\\\",\\\"bar\\\"\\\"\\\"\")).isEqualTo(\"\\\"f\\\"\\\"oo\\\",\\\"bar\\\"\\\"\\\"\");\n  }\n\n  @Test\n  void trimFieldsAndRemoveEmptyFields_does_not_fail_on_unbalanced_quotes() {\n    assertThat(trimFieldsAndRemoveEmptyFields(\"\\\"\")).isEqualTo(\"\\\"\");\n    assertThat(trimFieldsAndRemoveEmptyFields(\"\\\"foo\")).isEqualTo(\"\\\"foo\");\n    assertThat(trimFieldsAndRemoveEmptyFields(\"foo\\\"\")).isEqualTo(\"foo\\\"\");\n\n    assertThat(trimFieldsAndRemoveEmptyFields(\"\\\"foo\\\",\\\"\")).isEqualTo(\"\\\"foo\\\",\\\"\");\n    assertThat(trimFieldsAndRemoveEmptyFields(\"\\\",\\\"foo\\\"\")).isEqualTo(\"\\\",\\\"foo\\\"\");\n\n    assertThat(trimFieldsAndRemoveEmptyFields(\"\\\"foo\\\",\\\",  \")).isEqualTo(\"\\\"foo\\\",\\\",  \");\n\n    assertThat(trimFieldsAndRemoveEmptyFields(\" a ,,b , c,  \\\"foo\\\",\\\"  \")).isEqualTo(\"a,b,c,\\\"foo\\\",\\\"  \");\n    assertThat(trimFieldsAndRemoveEmptyFields(\"\\\" a ,,b , c,  \")).isEqualTo(\"\\\" a ,,b , c,  \");\n  }\n\n  private static final char[] SOME_PRINTABLE_TRIMMABLE_CHARS = {\n    ' ', '\\t', '\\n', '\\r'\n  };\n\n  /**\n   * Result of randomTrimmedChars being used as arguments to JUnit test method through the DataProvider feature, they\n   * are printed to surefire report. Some of those chars breaks the parsing of the surefire report during sonar analysis.\n   * Therefor, we only use a subset of the trimmable chars.\n   */\n  private static String randomTrimmedChars(int length, Random random) {\n    char[] chars = new char[length];\n    for (int i = 0; i < chars.length; i++) {\n      chars[i] = SOME_PRINTABLE_TRIMMABLE_CHARS[random.nextInt(SOME_PRINTABLE_TRIMMABLE_CHARS.length)];\n    }\n    return new String(chars);\n  }\n\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/test/java/org/sonarsource/sonarlint/core/plugin/commons/SkipReasonTests.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons;\n\nimport java.util.List;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.plugin.commons.api.SkipReason.IncompatiblePluginApi;\nimport org.sonarsource.sonarlint.core.plugin.commons.api.SkipReason.LanguagesNotEnabled;\nimport org.sonarsource.sonarlint.core.plugin.commons.api.SkipReason.UnsatisfiedDependency;\nimport org.sonarsource.sonarlint.core.plugin.commons.api.SkipReason.UnsatisfiedRuntimeRequirement;\nimport org.sonarsource.sonarlint.core.plugin.commons.api.SkipReason.UnsatisfiedRuntimeRequirement.RuntimeRequirement;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SkipReasonTests {\n\n  @Test\n  void testLanguageNotEnabled_getters_equals_hashcode_tostring() {\n    var underTest = new LanguagesNotEnabled(List.of(SonarLanguage.JAVA));\n    // Getters\n    assertThat(underTest.getNotEnabledLanguages())\n      .containsExactly(SonarLanguage.JAVA);\n    assertThat(underTest)\n      // Equals\n      .isEqualTo(underTest)\n      .isNotEqualTo(IncompatiblePluginApi.INSTANCE)\n      .isNotEqualTo(new LanguagesNotEnabled(List.of(SonarLanguage.JS)))\n      .isEqualTo(new LanguagesNotEnabled(List.of(SonarLanguage.JAVA)))\n      // HashCode\n      .hasSameHashCodeAs(underTest)\n      .hasSameHashCodeAs(new LanguagesNotEnabled(List.of(SonarLanguage.JAVA)))\n      // To String\n      .hasToString(\"LanguagesNotEnabled [languages=[JAVA]]\");\n  }\n\n  @Test\n  void testUnsatisfiedDependency_getters_equals_hashcode_tostring() {\n    var underTest = new UnsatisfiedDependency(\"foo\");\n    // Getters\n    assertThat(underTest.getDependencyKey()).isEqualTo(\"foo\");\n    assertThat(underTest)\n      // Equals\n      .isEqualTo(underTest)\n      .isNotEqualTo(IncompatiblePluginApi.INSTANCE)\n      .isNotEqualTo(new UnsatisfiedDependency(\"bar\"))\n      .isEqualTo(new UnsatisfiedDependency(\"foo\"))\n      // HashCode\n      .hasSameHashCodeAs(underTest)\n      .hasSameHashCodeAs(new UnsatisfiedDependency(\"foo\"))\n      // To String\n      .hasToString(\"UnsatisfiedDependency [dependencyKey=foo]\");\n  }\n\n  @Test\n  void testUnsatisfiedRuntimeRequirement_getters_equals_hashcode_tostring() {\n    var underTest = new UnsatisfiedRuntimeRequirement(RuntimeRequirement.JRE, \"1.0\", \"2.0\");\n    // Getters\n    assertThat(underTest.getMinVersion()).isEqualTo(\"2.0\");\n    assertThat(underTest.getCurrentVersion()).isEqualTo(\"1.0\");\n    assertThat(underTest)\n      // Equals\n      .isEqualTo(underTest)\n      .isNotEqualTo(IncompatiblePluginApi.INSTANCE)\n      .isNotEqualTo(new UnsatisfiedRuntimeRequirement(RuntimeRequirement.NODEJS, \"1.0\", \"2.0\"))\n      .isNotEqualTo(new UnsatisfiedRuntimeRequirement(RuntimeRequirement.JRE, \"1.0\", \"1.0\"))\n      .isNotEqualTo(new UnsatisfiedRuntimeRequirement(RuntimeRequirement.JRE, \"2.0\", \"1.0\"))\n      .isEqualTo(new UnsatisfiedRuntimeRequirement(RuntimeRequirement.JRE, \"1.0\", \"2.0\"))\n      // HashCode\n      .hasSameHashCodeAs(underTest)\n      .hasSameHashCodeAs(new UnsatisfiedRuntimeRequirement(RuntimeRequirement.JRE, \"1.0\", \"2.0\"))\n      // To String\n      .hasToString(\"UnsatisfiedRuntimeRequirement [runtime=JRE, currentVersion=1.0, minVersion=2.0]\");\n  }\n\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/test/java/org/sonarsource/sonarlint/core/plugin/commons/Utils.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons;\n\nimport static org.apache.commons.lang3.RandomStringUtils.insecure;\n\npublic class Utils {\n  private Utils() {\n    // utility class\n  }\n\n  public static String randomAlphanumeric(int count) {\n    return insecure().nextAlphanumeric(count);\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/test/java/org/sonarsource/sonarlint/core/plugin/commons/container/ComponentKeysTests.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.container;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.startsWith;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\n\nclass ComponentKeysTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  ComponentKeys keys = new ComponentKeys();\n\n  @Test\n  void generate_key_of_object() {\n    assertThat(keys.of(FakeComponent.class)).isEqualTo(FakeComponent.class);\n  }\n\n  @Test\n  void generate_key_of_instance() {\n    assertThat((String) keys.of(new FakeComponent())).endsWith(\"-org.sonarsource.sonarlint.core.plugin.commons.container.ComponentKeysTests.FakeComponent-fake\");\n  }\n\n  @Test\n  void generate_key_of_class() {\n    assertThat(keys.ofClass(FakeComponent.class)).endsWith(\"-org.sonarsource.sonarlint.core.plugin.commons.container.ComponentKeysTests.FakeComponent\");\n  }\n\n  @Test\n  void should_log_warning_if_toString_is_not_overridden() {\n    SonarLintLogger log = mock(SonarLintLogger.class);\n    keys.of(new Object(), log);\n    verifyNoInteractions(log);\n\n    // only on non-first runs, to avoid false-positives on singletons\n    keys.of(new Object(), log);\n    verify(log).warn(startsWith(\"Bad component key\"));\n  }\n\n  @Test\n  void should_generate_unique_key_when_toString_is_not_overridden() {\n    Object key = keys.of(new WrongToStringImpl());\n    assertThat(key).isNotEqualTo(WrongToStringImpl.KEY);\n\n    Object key2 = keys.of(new WrongToStringImpl());\n    assertThat(key2).isNotEqualTo(key);\n  }\n\n  static class FakeComponent {\n    @Override\n    public String toString() {\n      return \"fake\";\n    }\n  }\n\n  static class WrongToStringImpl {\n    static final String KEY = \"my.Component@123a\";\n\n    @Override\n    public String toString() {\n      return KEY;\n    }\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/test/java/org/sonarsource/sonarlint/core/plugin/commons/container/LazyUnlessStartableStrategyTests.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.container;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.support.DefaultListableBeanFactory;\nimport org.springframework.beans.factory.support.RootBeanDefinition;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass LazyUnlessStartableStrategyTests {\n  private final LazyUnlessStartableStrategy postProcessor = new LazyUnlessStartableStrategy();\n\n  @Test\n  void sets_all_beans_lazy() {\n    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();\n    beanFactory.registerBeanDefinition(\"bean1\", new RootBeanDefinition());\n    assertThat(beanFactory.getBeanDefinition(\"bean1\").isLazyInit()).isFalse();\n\n    postProcessor.postProcessBeanFactory(beanFactory);\n    assertThat(beanFactory.getBeanDefinition(\"bean1\").isLazyInit()).isTrue();\n  }\n\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/test/java/org/sonarsource/sonarlint/core/plugin/commons/container/PriorityBeanFactoryTests.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.container;\n\nimport jakarta.annotation.Priority;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.springframework.beans.factory.NoUniqueBeanDefinitionException;\nimport org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;\nimport org.springframework.beans.factory.support.DefaultListableBeanFactory;\nimport org.springframework.beans.factory.support.RootBeanDefinition;\nimport org.springframework.core.annotation.AnnotationAwareOrderComparator;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass PriorityBeanFactoryTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  private final DefaultListableBeanFactory parentBeanFactory = new PriorityBeanFactory();\n  private final DefaultListableBeanFactory beanFactory = new PriorityBeanFactory();\n\n  @BeforeEach\n  public void setUp() {\n    // needed to support autowiring with @Inject\n    beanFactory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());\n    // needed to read @Priority\n    beanFactory.setDependencyComparator(new AnnotationAwareOrderComparator());\n    beanFactory.setParentBeanFactory(parentBeanFactory);\n  }\n\n  @Test\n  void give_priority_to_child_container() {\n    parentBeanFactory.registerBeanDefinition(\"A1\", new RootBeanDefinition(A1.class));\n\n    beanFactory.registerBeanDefinition(\"A2\", new RootBeanDefinition(A2.class));\n    beanFactory.registerBeanDefinition(\"B\", new RootBeanDefinition(B.class));\n\n    assertThat(beanFactory.getBean(B.class).dep.getClass()).isEqualTo(A2.class);\n  }\n\n  @Test\n  void follow_priority_annotations() {\n    parentBeanFactory.registerBeanDefinition(\"A3\", new RootBeanDefinition(A3.class));\n\n    beanFactory.registerBeanDefinition(\"A1\", new RootBeanDefinition(A1.class));\n    beanFactory.registerBeanDefinition(\"A2\", new RootBeanDefinition(A2.class));\n    beanFactory.registerBeanDefinition(\"B\", new RootBeanDefinition(B.class));\n\n    assertThat(beanFactory.getBean(B.class).dep.getClass()).isEqualTo(A3.class);\n  }\n\n  @Test\n  void throw_NoUniqueBeanDefinitionException_if_cant_find_single_bean_with_higher_priority() {\n    beanFactory.registerBeanDefinition(\"A1\", new RootBeanDefinition(A1.class));\n    beanFactory.registerBeanDefinition(\"A2\", new RootBeanDefinition(A2.class));\n    beanFactory.registerBeanDefinition(\"B\", new RootBeanDefinition(B.class));\n\n    assertThatThrownBy(() -> beanFactory.getBean(B.class))\n      .hasRootCauseInstanceOf(NoUniqueBeanDefinitionException.class);\n  }\n\n  private static class B {\n    private final A dep;\n\n    public B(A dep) {\n      this.dep = dep;\n    }\n  }\n\n  private interface A {\n\n  }\n\n  private static class A1 implements A {\n\n  }\n\n  private static class A2 implements A {\n\n  }\n\n  @Priority(1)\n  private static class A3 implements A {\n\n  }\n\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/test/java/org/sonarsource/sonarlint/core/plugin/commons/container/SpringComponentContainerTests.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.container;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport jakarta.annotation.PostConstruct;\nimport jakarta.annotation.PreDestroy;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonar.api.Property;\nimport org.sonar.api.Startable;\nimport org.sonar.api.config.PropertyDefinition;\nimport org.sonar.api.config.PropertyDefinitions;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass SpringComponentContainerTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  @Test\n  void should_stop_after_failing() {\n    ApiStartable startStop = new ApiStartable();\n    SpringComponentContainer container = new SpringComponentContainer() {\n      @Override\n      public void doBeforeStart() {\n        add(startStop);\n      }\n\n      @Override\n      public void doAfterStart() {\n        getComponentByType(ApiStartable.class);\n        throw new IllegalStateException(\"doBeforeStart\");\n      }\n    };\n\n    assertThrows(IllegalStateException.class, () -> container.execute(null));\n    assertThat(startStop.start).isOne();\n    assertThat(startStop.stop).isOne();\n  }\n\n  @Test\n  void add_registers_instance_with_toString() {\n    SpringComponentContainer container = new SimpleContainer(new ToString(\"a\"), new ToString(\"b\"));\n    container.startComponents();\n    assertThat(container.context.getBeanDefinitionNames())\n      .contains(\n        this.getClass().getClassLoader() + \"-org.sonarsource.sonarlint.core.plugin.commons.container.SpringComponentContainerTests.ToString-a\",\n        this.getClass().getClassLoader() + \"-org.sonarsource.sonarlint.core.plugin.commons.container.SpringComponentContainerTests.ToString-b\");\n    assertThat(container.getComponentsByType(ToString.class)).hasSize(2);\n  }\n\n  @Test\n  void add_registers_class_with_classloader_and_fqcn() {\n    SpringComponentContainer container = new SimpleContainer(A.class, B.class);\n    container.startComponents();\n    assertThat(container.context.getBeanDefinitionNames())\n      .contains(\n        this.getClass().getClassLoader() + \"-org.sonarsource.sonarlint.core.plugin.commons.container.SpringComponentContainerTests.A\",\n        this.getClass().getClassLoader() + \"-org.sonarsource.sonarlint.core.plugin.commons.container.SpringComponentContainerTests.B\");\n    assertThat(container.getComponentByType(A.class)).isNotNull();\n    assertThat(container.getComponentByType(B.class)).isNotNull();\n  }\n\n  @Test\n  void get_optional_component_by_type_should_return_correctly() {\n    SpringComponentContainer container = new SpringComponentContainer();\n    container.add(A.class);\n    container.startComponents();\n    assertThat(container.getOptionalComponentByType(A.class)).containsInstanceOf(A.class);\n    assertThat(container.getOptionalComponentByType(B.class)).isEmpty();\n  }\n\n  @Test\n  void createChild_method_should_spawn_a_child_container() {\n    SpringComponentContainer parent = new SpringComponentContainer();\n    SpringComponentContainer child = parent.createChild();\n    assertThat(child).isNotEqualTo(parent);\n    assertThat(child.parent).isEqualTo(parent);\n    assertThat(parent.children).contains(child);\n  }\n\n  @Test\n  void get_component_by_type_should_throw_exception_when_type_does_not_exist() {\n    SpringComponentContainer container = new SpringComponentContainer();\n    container.add(A.class);\n    container.startComponents();\n    assertThatThrownBy(() -> container.getComponentByType(B.class))\n      .isInstanceOf(IllegalStateException.class)\n      .hasMessage(\"Unable to load component class org.sonarsource.sonarlint.core.plugin.commons.container.SpringComponentContainerTests$B\");\n  }\n\n  @Test\n  void should_throw_start_exception_if_stop_also_throws_exception() {\n    ErrorStopClass errorStopClass = new ErrorStopClass();\n    SpringComponentContainer container = new SpringComponentContainer() {\n      @Override\n      public void doBeforeStart() {\n        add(errorStopClass);\n      }\n\n      @Override\n      public void doAfterStart() {\n        getComponentByType(ErrorStopClass.class);\n        throw new IllegalStateException(\"doBeforeStart\");\n      }\n    };\n    assertThrows(IllegalStateException.class, () -> container.execute(null));\n    assertThat(errorStopClass.stopped).isTrue();\n  }\n\n  @Test\n  void addExtension_supports_extensions_without_annotations() {\n    SpringComponentContainer container = new SimpleContainer(A.class, B.class);\n    container.addExtension(\"\", ExtensionWithMultipleConstructorsAndNoAnnotations.class);\n    container.startComponents();\n    assertThat(container.getComponentByType(ExtensionWithMultipleConstructorsAndNoAnnotations.class).gotBothArgs).isTrue();\n  }\n\n  @Test\n  void addExtension_supports_extension_instances_without_annotations() {\n    SpringComponentContainer container = new SpringComponentContainer();\n    container.addExtension(\"\", new ExtensionWithMultipleConstructorsAndNoAnnotations(new A()));\n    container.startComponents();\n    assertThat(container.getComponentByType(ExtensionWithMultipleConstructorsAndNoAnnotations.class)).isNotNull();\n  }\n\n  @Test\n  void addExtension_resolves_iterables() {\n    List<Class<?>> classes = Arrays.asList(A.class, B.class);\n    SpringComponentContainer container = new SpringComponentContainer();\n    container.addExtension(\"\", classes);\n    container.startComponents();\n    assertThat(container.getComponentByType(A.class)).isNotNull();\n    assertThat(container.getComponentByType(B.class)).isNotNull();\n  }\n\n  @Test\n  void declareExtension_adds_property() {\n    SpringComponentContainer container = new SpringComponentContainer();\n    container.addExtension(\"myPlugin\", A.class);\n\n    container.startComponents();\n    PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class);\n    PropertyDefinition propertyDefinition = propertyDefinitions.get(\"k\");\n    assertThat(propertyDefinition.key()).isEqualTo(\"k\");\n    assertThat(propertyDefinitions.getCategory(\"k\")).isEmpty();\n  }\n\n  @Test\n  void stop_should_stop_children() {\n    SpringComponentContainer parent = new SpringComponentContainer();\n    ApiStartable s1 = new ApiStartable();\n    parent.add(s1);\n    parent.startComponents();\n    SpringComponentContainer child = new SpringComponentContainer(parent);\n    assertThat(child.getParent()).isEqualTo(parent);\n    assertThat(parent.children).containsOnly(child);\n    ApiStartable s2 = new ApiStartable();\n    child.add(s2);\n    child.startComponents();\n\n    parent.stopComponents();\n    assertThat(s1.stop).isOne();\n    assertThat(s2.stop).isOne();\n  }\n\n  @Test\n  void stop_should_remove_container_from_parent() {\n    SpringComponentContainer parent = new SpringComponentContainer();\n    SpringComponentContainer child = new SpringComponentContainer(parent);\n    assertThat(parent.children).containsOnly(child);\n    child.stopComponents();\n    assertThat(parent.children).isEmpty();\n  }\n\n  @Test\n  void bean_create_fails_if_class_has_default_constructor_and_other_constructors() {\n    SpringComponentContainer container = new SpringComponentContainer();\n    container.add(ClassWithMultipleConstructorsIncNoArg.class);\n    container.startComponents();\n    assertThatThrownBy(() -> container.getComponentByType(ClassWithMultipleConstructorsIncNoArg.class))\n      .hasRootCauseMessage(\n        \"Constructor annotations missing in: class org.sonarsource.sonarlint.core.plugin.commons.container.SpringComponentContainerTests$ClassWithMultipleConstructorsIncNoArg\");\n  }\n\n  @Test\n  void support_start_stop_callbacks() {\n    JsrLifecycleCallbacks jsr = new JsrLifecycleCallbacks();\n    ApiStartable api = new ApiStartable();\n    AutoClose closeable = new AutoClose();\n\n    SpringComponentContainer container = new SimpleContainer(jsr, api, closeable) {\n      @Override\n      public void doAfterStart() {\n        // force lazy instantiation\n        getComponentByType(JsrLifecycleCallbacks.class);\n        getComponentByType(ApiStartable.class);\n        getComponentByType(AutoClose.class);\n      }\n    };\n    container.execute(null);\n\n    assertThat(closeable.closed).isOne();\n    assertThat(jsr.postConstruct).isOne();\n    assertThat(jsr.preDestroy).isOne();\n    assertThat(api.start).isOne();\n    assertThat(api.stop).isOne();\n  }\n\n  private static class JsrLifecycleCallbacks {\n    private int postConstruct = 0;\n    private int preDestroy = 0;\n\n    @PostConstruct\n    public void postConstruct() {\n      postConstruct++;\n    }\n\n    @PreDestroy\n    public void preDestroy() {\n      preDestroy++;\n    }\n  }\n\n  private static class AutoClose implements AutoCloseable {\n    private int closed = 0;\n\n    @Override\n    public void close() {\n      closed++;\n    }\n  }\n\n  private static class ApiStartable implements Startable {\n    private int start = 0;\n    private int stop = 0;\n\n    @Override\n    public void start() {\n      start++;\n    }\n\n    @Override\n    public void stop() {\n      stop++;\n    }\n  }\n\n  private static class ToString {\n    private final String toString;\n\n    public ToString(String toString) {\n      this.toString = toString;\n    }\n\n    @Override\n    public String toString() {\n      return toString;\n    }\n  }\n\n  @Property(key = \"k\", name = \"name\")\n  private static class A {\n  }\n\n  private static class B {\n  }\n\n  private static class ClassWithMultipleConstructorsIncNoArg {\n    public ClassWithMultipleConstructorsIncNoArg() {\n    }\n\n    public ClassWithMultipleConstructorsIncNoArg(A a) {\n    }\n  }\n\n  private static class ExtensionWithMultipleConstructorsAndNoAnnotations {\n    private boolean gotBothArgs = false;\n\n    public ExtensionWithMultipleConstructorsAndNoAnnotations(A a) {\n    }\n\n    public ExtensionWithMultipleConstructorsAndNoAnnotations(A a, B b) {\n      gotBothArgs = true;\n    }\n  }\n\n  private static class ErrorStopClass implements Startable {\n    private boolean stopped = false;\n\n    @Override\n    public void start() {\n    }\n\n    @Override\n    public void stop() {\n      stopped = true;\n      throw new IllegalStateException(\"stop\");\n    }\n  }\n\n  private static class SimpleContainer extends SpringComponentContainer {\n    public SimpleContainer(Object... objects) {\n      add(objects);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/test/java/org/sonarsource/sonarlint/core/plugin/commons/container/StartableBeanPostProcessorTests.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.container;\n\nimport org.junit.jupiter.api.Test;\nimport org.sonar.api.Startable;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\n\n class StartableBeanPostProcessorTests {\n  private final StartableBeanPostProcessor underTest = new StartableBeanPostProcessor();\n\n  @Test\n   void starts_api_startable() {\n    Startable startable = mock(Startable.class);\n    underTest.postProcessBeforeInitialization(startable, \"startable\");\n    verify(startable).start();\n    verifyNoMoreInteractions(startable);\n  }\n\n  @Test\n   void stops_api_startable() {\n    Startable startable = mock(Startable.class);\n    underTest.postProcessBeforeDestruction(startable, \"startable\");\n    verify(startable).stop();\n    verifyNoMoreInteractions(startable);\n  }\n\n  @Test\n   void startable_and_autoCloseable_should_require_destruction() {\n    assertThat(underTest.requiresDestruction(mock(Startable.class))).isTrue();\n    assertThat(underTest.requiresDestruction(mock(org.sonar.api.Startable.class))).isTrue();\n    assertThat(underTest.requiresDestruction(mock(Object.class))).isFalse();\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/test/java/org/sonarsource/sonarlint/core/plugin/commons/loading/PluginClassloaderFactoryTests.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.loading;\n\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport org.apache.commons.lang3.StringUtils;\nimport org.junit.jupiter.api.Test;\nimport org.sonar.api.server.rule.RulesDefinition;\nimport org.sonarsource.sonarlint.plugin.api.module.file.ModuleFileListener;\n\nimport static java.util.Arrays.asList;\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass PluginClassloaderFactoryTests {\n\n  private static final String BASE_PLUGIN_CLASSNAME = \"org.sonar.plugins.base.BasePlugin\";\n  private static final String DEPENDENT_PLUGIN_CLASSNAME = \"org.sonar.plugins.dependent.DependentPlugin\";\n  private static final String BASE_PLUGIN_KEY = \"base\";\n  private static final String DEPENDENT_PLUGIN_KEY = \"dependent\";\n\n  private final PluginClassloaderFactory factory = new PluginClassloaderFactory();\n\n  @Test\n  void create_isolated_classloader() {\n    var def = basePluginDef();\n    var map = factory.create(getClass().getClassLoader(), List.of(def));\n\n    assertThat(map).containsOnlyKeys(def);\n    var classLoader = map.get(def);\n\n    // plugin can access to sonar-plugin-api classes...\n    assertThat(canLoadClass(classLoader, RulesDefinition.class.getCanonicalName())).isTrue();\n    // ... to sonarlint-plugin-api classes...\n    assertThat(canLoadClass(classLoader, ModuleFileListener.class.getCanonicalName())).isTrue();\n    // ... and of course to its own classes !\n    assertThat(canLoadClass(classLoader, BASE_PLUGIN_CLASSNAME)).isTrue();\n\n    // plugin can not access to core classes\n    assertThat(canLoadClass(classLoader, PluginClassloaderFactory.class.getCanonicalName())).isFalse();\n    assertThat(canLoadClass(classLoader, Test.class.getCanonicalName())).isFalse();\n    assertThat(canLoadClass(classLoader, StringUtils.class.getCanonicalName())).isFalse();\n  }\n\n  @Test\n  void classloader_exports_resources_to_other_classloaders() {\n    var baseDef = basePluginDef();\n    var dependentDef = dependentPluginDef();\n    var map = factory.create(getClass().getClassLoader(), asList(baseDef, dependentDef));\n    var baseClassloader = map.get(baseDef);\n    var dependentClassloader = map.get(dependentDef);\n\n    // base-plugin exports its API package to other plugins\n    assertThat(canLoadClass(dependentClassloader, \"org.sonar.plugins.base.api.BaseApi\")).isTrue();\n    assertThat(canLoadClass(dependentClassloader, BASE_PLUGIN_CLASSNAME)).isFalse();\n    assertThat(canLoadClass(dependentClassloader, DEPENDENT_PLUGIN_CLASSNAME)).isTrue();\n\n    // dependent-plugin does not export its classes\n    assertThat(canLoadClass(baseClassloader, DEPENDENT_PLUGIN_CLASSNAME)).isFalse();\n    assertThat(canLoadClass(baseClassloader, BASE_PLUGIN_CLASSNAME)).isTrue();\n  }\n\n  private static PluginClassLoaderDef basePluginDef() {\n    var def = new PluginClassLoaderDef(BASE_PLUGIN_KEY);\n    def.addMainClass(BASE_PLUGIN_KEY, BASE_PLUGIN_CLASSNAME);\n    def.getExportMaskBuilder().include(\"org/sonar/plugins/base/api/\");\n    def.addFiles(List.of(testPluginJar(\"base-plugin/target/base-plugin-0.1-SNAPSHOT.jar\")));\n    return def;\n  }\n\n  private static PluginClassLoaderDef dependentPluginDef() {\n    var def = new PluginClassLoaderDef(DEPENDENT_PLUGIN_KEY);\n    def.addMainClass(DEPENDENT_PLUGIN_KEY, DEPENDENT_PLUGIN_CLASSNAME);\n    def.getExportMaskBuilder().include(\"org/sonar/plugins/dependent/api/\");\n    def.addFiles(List.of(testPluginJar(\"dependent-plugin/target/dependent-plugin-0.1-SNAPSHOT.jar\")));\n    return def;\n  }\n\n  static File testPluginJar(String path) {\n    var file = Paths.get(\"src/test/projects/\" + path);\n    if (!Files.exists(file)) {\n        throw new IllegalArgumentException(\"Test projects are not built: \" + path);\n    }\n    return file.toFile();\n  }\n\n  private static boolean canLoadClass(ClassLoader classloader, String classname) {\n    try {\n      classloader.loadClass(classname);\n      return true;\n    } catch (ClassNotFoundException e) {\n      return false;\n    }\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/test/java/org/sonarsource/sonarlint/core/plugin/commons/loading/PluginInfoTests.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.loading;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonar.api.internal.apachecommons.io.FileUtils;\nimport org.sonar.api.utils.ZipUtils;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.plugin.commons.loading.SonarPluginManifest.RequiredPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass PluginInfoTests {\n\n  @Test\n  void test_equals() {\n    var java1 = new PluginInfo(\"java\").setVersion(Version.create(\"1.0\"));\n    var java2 = new PluginInfo(\"java\").setVersion(Version.create(\"2.0\"));\n    var javaNoVersion = new PluginInfo(\"java\");\n    var cobol = new PluginInfo(\"cobol\").setVersion(Version.create(\"1.0\"));\n\n    assertThat(java1.equals(java1)).isTrue();\n    assertThat(java1.equals(java2)).isFalse();\n    assertThat(java1.equals(javaNoVersion)).isFalse();\n    assertThat(java1.equals(cobol)).isFalse();\n    assertThat(java1.equals(\"java:1.0\")).isFalse();\n    assertThat(java1.equals(null)).isFalse();\n    assertThat(javaNoVersion.equals(javaNoVersion)).isTrue();\n\n    assertThat(java1).hasSameHashCodeAs(java1);\n    assertThat(javaNoVersion).hasSameHashCodeAs(javaNoVersion);\n  }\n\n  @Test\n  void test_compatibility_with_sq_version() {\n    assertThat(withMinSqVersion(\"1.1\").isCompatibleWith(\"1.1\")).isTrue();\n    assertThat(withMinSqVersion(\"1.1\").isCompatibleWith(\"1.1.0\")).isTrue();\n    assertThat(withMinSqVersion(\"1.0\").isCompatibleWith(\"1.0.0\")).isTrue();\n\n    assertThat(withMinSqVersion(\"1.0\").isCompatibleWith(\"1.1\")).isTrue();\n    assertThat(withMinSqVersion(\"1.1.1\").isCompatibleWith(\"1.1.2\")).isTrue();\n    assertThat(withMinSqVersion(\"2.0\").isCompatibleWith(\"2.1.0\")).isTrue();\n    assertThat(withMinSqVersion(\"3.2\").isCompatibleWith(\"3.2-RC1\")).isTrue();\n    assertThat(withMinSqVersion(\"3.2\").isCompatibleWith(\"3.2-RC2\")).isTrue();\n    assertThat(withMinSqVersion(\"3.2\").isCompatibleWith(\"3.1-RC2\")).isFalse();\n\n    assertThat(withMinSqVersion(\"1.1\").isCompatibleWith(\"1.0\")).isFalse();\n    assertThat(withMinSqVersion(\"2.0.1\").isCompatibleWith(\"2.0.0\")).isTrue();\n    assertThat(withMinSqVersion(\"2.10\").isCompatibleWith(\"2.1\")).isFalse();\n    assertThat(withMinSqVersion(\"10.10\").isCompatibleWith(\"2.2\")).isFalse();\n\n    assertThat(withMinSqVersion(\"1.1-SNAPSHOT\").isCompatibleWith(\"1.0\")).isFalse();\n    assertThat(withMinSqVersion(\"1.1-SNAPSHOT\").isCompatibleWith(\"1.1\")).isTrue();\n    assertThat(withMinSqVersion(\"1.1-SNAPSHOT\").isCompatibleWith(\"1.2\")).isTrue();\n    assertThat(withMinSqVersion(\"1.0.1-SNAPSHOT\").isCompatibleWith(\"1.0\")).isTrue();\n\n    assertThat(withMinSqVersion(\"3.1-RC2\").isCompatibleWith(\"3.2-SNAPSHOT\")).isTrue();\n    assertThat(withMinSqVersion(\"3.1-RC1\").isCompatibleWith(\"3.2-RC2\")).isTrue();\n    assertThat(withMinSqVersion(\"3.1-RC1\").isCompatibleWith(\"3.1-RC2\")).isTrue();\n\n    assertThat(withMinSqVersion(null).isCompatibleWith(\"0\")).isTrue();\n    assertThat(withMinSqVersion(null).isCompatibleWith(\"3.1\")).isTrue();\n\n    assertThat(withMinSqVersion(\"7.0.0.12345\").isCompatibleWith(\"7.0\")).isTrue();\n  }\n\n  @Test\n  void create_from_minimal_manifest(@TempDir Path temp) throws Exception {\n    var manifest = mock(SonarPluginManifest.class);\n    when(manifest.getKey()).thenReturn(\"java\");\n    when(manifest.getVersion()).thenReturn(\"1.0\");\n    when(manifest.getName()).thenReturn(\"Java\");\n    when(manifest.getMainClass()).thenReturn(\"org.foo.FooPlugin\");\n\n    var jarFile = temp.resolve(\"myPlugin.jar\");\n    var pluginInfo = PluginInfo.create(jarFile, manifest);\n\n    assertThat(pluginInfo.getKey()).isEqualTo(\"java\");\n    assertThat(pluginInfo.getName()).isEqualTo(\"Java\");\n    assertThat(pluginInfo.getVersion().getName()).isEqualTo(\"1.0\");\n    assertThat(pluginInfo.getJarFile()).isEqualTo(jarFile.toFile());\n    assertThat(pluginInfo.getMainClass()).isEqualTo(\"org.foo.FooPlugin\");\n    assertThat(pluginInfo.getBasePlugin()).isNull();\n    assertThat(pluginInfo.getMinimalSqVersion()).isNull();\n    assertThat(pluginInfo.getRequiredPlugins()).isEmpty();\n    assertThat(pluginInfo.getJreMinVersion()).isNull();\n    assertThat(pluginInfo.getNodeJsMinVersion()).isNull();\n  }\n\n  @Test\n  void create_from_complete_manifest(@TempDir Path temp) throws Exception {\n    var manifest = mock(SonarPluginManifest.class);\n    when(manifest.getKey()).thenReturn(\"fbcontrib\");\n    when(manifest.getVersion()).thenReturn(\"2.0\");\n    when(manifest.getName()).thenReturn(\"Java\");\n    when(manifest.getMainClass()).thenReturn(\"org.fb.FindbugsPlugin\");\n    when(manifest.getBasePluginKey()).thenReturn(\"findbugs\");\n    when(manifest.getSonarMinVersion()).thenReturn(Optional.of(Version.create(\"4.5.1\")));\n    when(manifest.getRequiredPlugins()).thenReturn(List.of(new RequiredPlugin(\"java\", Version.create(\"2.0\")), new RequiredPlugin(\"pmd\", Version.create(\"1.3\"))));\n    when(manifest.getJreMinVersion()).thenReturn(Optional.of(Version.create(\"11\")));\n    when(manifest.getNodeJsMinVersion()).thenReturn(Optional.of(Version.create(\"12.18.3\")));\n\n    var jarFile = temp.resolve(\"myPlugin.jar\");\n    var pluginInfo = PluginInfo.create(jarFile, manifest);\n\n    assertThat(pluginInfo.getBasePlugin()).isEqualTo(\"findbugs\");\n    assertThat(pluginInfo.getMinimalSqVersion().getName()).isEqualTo(\"4.5.1\");\n    assertThat(pluginInfo.getRequiredPlugins()).extracting(\"key\").containsOnly(\"java\", \"pmd\");\n    assertThat(pluginInfo.getJreMinVersion().getName()).isEqualTo(\"11\");\n    assertThat(pluginInfo.getNodeJsMinVersion().getName()).isEqualTo(\"12.18.3\");\n  }\n\n  @Test\n  void create_from_file() throws URISyntaxException {\n    var checkstyleJar = Paths.get(getClass().getResource(\"/sonar-checkstyle-plugin-2.8.jar\").toURI());\n    var checkstyleInfo = PluginInfo.create(checkstyleJar);\n\n    assertThat(checkstyleInfo.getName()).isEqualTo(\"Checkstyle\");\n    assertThat(checkstyleInfo.getMinimalSqVersion()).isEqualTo(Version.create(\"2.8\"));\n  }\n\n  @Test\n  void test_toString() throws Exception {\n    var pluginInfo = new PluginInfo(\"java\").setVersion(Version.create(\"1.1\"));\n    assertThat(pluginInfo).hasToString(\"[java / 1.1]\");\n  }\n\n  @Test\n  void fail_when_jar_is_not_a_plugin(@TempDir Path temp) throws IOException {\n    // this JAR has a manifest but is not a plugin\n    var jarRootDir = Files.createTempDirectory(temp, \"myPlugin\").toFile();\n    FileUtils.write(new File(jarRootDir, \"META-INF/MANIFEST.MF\"), \"Build-Jdk: 1.6.0_15\", StandardCharsets.UTF_8);\n    var jar = temp.resolve(\"myPlugin.jar\");\n    ZipUtils.zipDir(jarRootDir, jar.toFile());\n\n    var thrown = assertThrows(IllegalStateException.class, () -> PluginInfo.create(jar));\n    assertThat(thrown).hasMessage(\"Error while reading plugin manifest from jar: \" + jar.toAbsolutePath());\n  }\n\n  PluginInfo withMinSqVersion(@Nullable String version) {\n    var pluginInfo = new PluginInfo(\"foo\");\n    if (version != null) {\n      pluginInfo.setMinimalSqVersion(Version.create(version));\n    }\n    return pluginInfo;\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/test/java/org/sonarsource/sonarlint/core/plugin/commons/loading/PluginInstancesLoaderTests.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.loading;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardCopyOption;\nimport java.util.List;\nimport java.util.Map;\nimport org.apache.commons.codec.digest.DigestUtils;\nimport org.apache.commons.io.FileUtils;\nimport org.assertj.core.data.MapEntry;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonar.api.Plugin;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.entry;\nimport static org.assertj.core.groups.Tuple.tuple;\n\nclass PluginInstancesLoaderTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  PluginInstancesLoader loader = new PluginInstancesLoader(new PluginClassloaderFactory());\n\n  @AfterEach\n  void closeLoader() throws IOException {\n    loader.close();\n  }\n\n  @Test\n  void instantiate_plugin_entry_point() {\n    var def = new PluginClassLoaderDef(\"fake\");\n    def.addMainClass(\"fake\", FakePlugin.class.getName());\n\n    var instances = loader.instantiatePluginClasses(Map.of(def, getClass().getClassLoader()));\n    assertThat(instances).containsOnlyKeys(\"fake\");\n    assertThat(instances.get(\"fake\")).isInstanceOf(FakePlugin.class);\n  }\n\n  @Test\n  void plugin_entry_point_must_be_no_arg_public() {\n    var def = new PluginClassLoaderDef(\"fake\");\n    def.addMainClass(\"fake\", IncorrectPlugin.class.getName());\n\n    loader.instantiatePluginClasses(Map.of(def, getClass().getClassLoader()));\n\n    assertThat(logTester.logs(LogOutput.Level.ERROR))\n      .contains(\"Fail to instantiate class [org.sonarsource.sonarlint.core.plugin.commons.loading.PluginInstancesLoaderTests$IncorrectPlugin] of plugin [fake]\");\n  }\n\n  @Test\n  void define_classloader(@TempDir Path tmp) throws IOException {\n    var jarFile = tmp.resolve(\"fakePlugin.jar\").toFile();\n    Files.createFile(jarFile.toPath());\n    var info = new PluginInfo(\"foo\")\n      .setJarFile(jarFile)\n      .setMainClass(\"org.foo.FooPlugin\")\n      .setMinimalSqVersion(Version.create(\"5.2\"));\n\n    var defs = loader.defineClassloaders(Map.of(\"foo\", info));\n\n    assertThat(defs).hasSize(1);\n    var def = defs.iterator().next();\n    assertThat(def.getBasePluginKey()).isEqualTo(\"foo\");\n    assertThat(def.getFiles()).containsExactly(jarFile);\n    assertThat(def.getMainClassesByPluginKey()).containsOnly(MapEntry.entry(\"foo\", \"org.foo.FooPlugin\"));\n    // TODO test mask - require change in sonar-classloader\n  }\n\n  @Test\n  void extract_dependencies() {\n    var jarFile = getFile(\"sonar-checkstyle-plugin-2.8.jar\");\n    var info = new PluginInfo(\"checkstyle\")\n      .setJarFile(jarFile)\n      .setMainClass(\"org.foo.FooPlugin\")\n      .setDependencies(List.of(\"META-INF/lib/commons-cli-1.0.jar\", \"META-INF/lib/checkstyle-5.1.jar\", \"META-INF/lib/antlr-2.7.6.jar\"));\n\n    var defs = loader.defineClassloaders(Map.of(\"checkstyle\", info));\n\n    assertThat(defs).hasSize(1);\n    var def = defs.iterator().next();\n    assertThat(def.getBasePluginKey()).isEqualTo(\"checkstyle\");\n    assertThat(def.getFiles()).hasSize(4);\n    assertThat(def.getFiles()).extracting(File::getName, f -> {\n      try {\n        return DigestUtils.md5Hex(Files.readAllBytes(f.toPath()));\n      } catch (IOException e) {\n        return e.getMessage();\n      }\n    }).containsExactlyInAnyOrder(\n      tuple(\"sonar-checkstyle-plugin-2.8.jar\", \"e7e5e17e5e297ac88d08122c56d72eb7\"),\n      tuple(\"commons-cli-1.0.jar\", \"d784fa8b6d98d27699781bd9a7cf19f0\"),\n      tuple(\"checkstyle-5.1.jar\", \"d784fa8b6d98d27699781bd9a7cf19f0\"),\n      tuple(\"antlr-2.7.6.jar\", \"d784fa8b6d98d27699781bd9a7cf19f0\"));\n  }\n\n  /**\n   * A plugin (the \"base\" plugin) can be extended by other plugins. In this case they share the same classloader.\n   */\n  @Test\n  void test_plugins_sharing_the_same_classloader(@TempDir Path tmp) throws IOException {\n    var baseJarFile = tmp.resolve(\"fakeBasePlugin.jar\").toFile();\n    baseJarFile.createNewFile();\n    var extensionJar1 = tmp.resolve(\"fakePlugin1.jar\").toFile();\n    extensionJar1.createNewFile();\n    var extensionJar2 = tmp.resolve(\"fakePlugin2.jar\").toFile();\n    extensionJar2.createNewFile();\n    var base = new PluginInfo(\"foo\")\n      .setJarFile(baseJarFile)\n      .setMainClass(\"org.foo.FooPlugin\");\n\n    var extension1 = new PluginInfo(\"fooExtension1\")\n      .setJarFile(extensionJar1)\n      .setMainClass(\"org.foo.Extension1Plugin\")\n      .setBasePlugin(\"foo\");\n\n    var extension2 = new PluginInfo(\"fooExtension2\")\n      .setJarFile(extensionJar2)\n      .setMainClass(\"org.foo.Extension2Plugin\")\n      .setBasePlugin(\"foo\");\n\n    var defs = loader.defineClassloaders(Map.of(\n      base.getKey(), base, extension1.getKey(), extension1, extension2.getKey(), extension2));\n\n    assertThat(defs).hasSize(1);\n    var def = defs.iterator().next();\n    assertThat(def.getBasePluginKey()).isEqualTo(\"foo\");\n    assertThat(def.getFiles()).containsOnly(baseJarFile, extensionJar1, extensionJar2);\n    assertThat(def.getMainClassesByPluginKey()).containsOnly(\n      entry(\"foo\", \"org.foo.FooPlugin\"),\n      entry(\"fooExtension1\", \"org.foo.Extension1Plugin\"),\n      entry(\"fooExtension2\", \"org.foo.Extension2Plugin\"));\n    // TODO test mask - require change in sonar-classloader\n  }\n\n  // SLCORE-222\n  @Test\n  void skip_plugins_when_base_plugin_missing(@TempDir Path tmp) throws IOException {\n    var extensionJar1 = tmp.resolve(\"fakePlugin1.jar\").toFile();\n    extensionJar1.createNewFile();\n    var extensionJar2 = tmp.resolve(\"fakePlugin2.jar\").toFile();\n    extensionJar2.createNewFile();\n\n    var extension1 = new PluginInfo(\"fooExtension1\")\n      .setJarFile(extensionJar1)\n      .setMainClass(\"org.foo.Extension1Plugin\");\n    var extension2 = new PluginInfo(\"fooExtension2\")\n      .setJarFile(extensionJar2)\n      .setMainClass(\"org.foo.Extension2Plugin\")\n      .setBasePlugin(\"foo\");\n\n    var defs = loader.defineClassloaders(Map.of(\n      extension1.getKey(), extension1, extension2.getKey(), extension2));\n\n    assertThat(defs).hasSize(1);\n    var def = defs.iterator().next();\n    assertThat(def.getFiles()).containsOnly(extensionJar1);\n    assertThat(def.getMainClassesByPluginKey()).containsOnly(\n      entry(\"fooExtension1\", \"org.foo.Extension1Plugin\"));\n  }\n\n  // SLCORE-557\n  @Test\n  void should_be_able_to_delete_jar_after_unload() throws IOException {\n    var jarFile = PluginClassloaderFactoryTests.testPluginJar(\"classloader-leak-plugin/target/classloader-leak-plugin-0.1-SNAPSHOT.jar\");\n\n    var tmpCopy = Files.createTempFile(\"leak-plugin\", \".jar\");\n    Files.copy(jarFile.toPath(), tmpCopy, StandardCopyOption.REPLACE_EXISTING);\n\n    var info = new PluginInfo(\"leak\")\n      .setJarFile(tmpCopy.toFile())\n      .setMainClass(\"org.sonar.plugins.leak.LeakPlugin\");\n\n    var instances = loader.instantiatePluginClasses(List.of(info));\n    var instance = instances.get(\"leak\");\n\n    // The code in the plugin will leak a file handle, see https://bugs.java.com/bugdatabase/view_bug?bug_id=JDK-8315993\n    instance.define(null);\n\n    loader.close();\n\n    Files.delete(tmpCopy);\n  }\n\n  public static class FakePlugin implements Plugin {\n    @Override\n    public void define(Context context) {\n      // no extensions\n    }\n  }\n\n  /**\n   * No public empty-param constructor\n   */\n  public static class IncorrectPlugin implements Plugin {\n    public IncorrectPlugin(String s) {\n    }\n\n    @Override\n    public void define(Context context) {\n      // no extensions\n    }\n  }\n\n  private File getFile(String filename) {\n    return FileUtils.toFile(getClass().getResource(\"/\" + filename));\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/test/java/org/sonarsource/sonarlint/core/plugin/commons/loading/SonarPluginManifestTests.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.loading;\n\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Paths;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.plugin.commons.loading.SonarPluginManifest.RequiredPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass SonarPluginManifestTests {\n\n  @Test\n  void test_RequiredPlugin() throws Exception {\n    var plugin = SonarPluginManifest.RequiredPlugin.parse(\"java:1.1\");\n    assertThat(plugin.getKey()).isEqualTo(\"java\");\n    assertThat(plugin.getMinimalVersion().getName()).isEqualTo(\"1.1\");\n\n    assertThrows(IllegalArgumentException.class, () -> SonarPluginManifest.RequiredPlugin.parse(\"java\"));\n  }\n\n  @Test\n  void test() {\n    var fake = Paths.get(\"fake.jar\");\n    assertThrows(RuntimeException.class, () -> SonarPluginManifest.fromJar(fake));\n  }\n\n  @Test\n  void should_create_manifest_from_jar() throws URISyntaxException, IOException {\n    var checkstyleJar = Paths.get(getClass().getResource(\"/sonar-checkstyle-plugin-2.8.jar\").toURI());\n    var manifest = SonarPluginManifest.fromJar(checkstyleJar);\n\n    assertThat(manifest.getKey()).isEqualTo(\"checkstyle\");\n    assertThat(manifest.getName()).isEqualTo(\"Checkstyle\");\n    assertThat(manifest.getRequiredPlugins()).isEmpty();\n    assertThat(manifest.getMainClass()).isEqualTo(\"org.sonar.plugins.checkstyle.CheckstylePlugin\");\n    assertThat(manifest.getVersion().length()).isGreaterThan(1);\n    assertThat(manifest.getJreMinVersion()).isEmpty();\n    assertThat(manifest.getNodeJsMinVersion()).isEmpty();\n  }\n\n  @Test\n  void should_add_requires_plugins() throws URISyntaxException, IOException {\n    var jar = getClass().getResource(\"/SonarPluginManifestTests/plugin-with-require-plugins.jar\");\n\n    var manifest = SonarPluginManifest.fromJar(Paths.get(jar.toURI()));\n\n    assertThat(manifest.getRequiredPlugins())\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsExactlyInAnyOrder(new RequiredPlugin(\"scm\", Version.create(\"1.0\")), new RequiredPlugin(\"fake\", Version.create(\"1.1\")));\n  }\n\n  @Test\n  void should_parse_jre_min_version() throws URISyntaxException, IOException {\n    var jar = getClass().getResource(\"/SonarPluginManifestTests/plugin-with-jre-min.jar\");\n\n    var manifest = SonarPluginManifest.fromJar(Paths.get(jar.toURI()));\n\n    assertThat(manifest.getJreMinVersion()).contains(Version.create(\"11\"));\n  }\n\n  @Test\n  void should_default_jre_min_version_to_null() throws URISyntaxException, IOException {\n    var jar = getClass().getResource(\"/SonarPluginManifestTests/plugin-without-jre-min.jar\");\n\n    var manifest = SonarPluginManifest.fromJar(Paths.get(jar.toURI()));\n\n    assertThat(manifest.getJreMinVersion()).isEmpty();\n  }\n\n  @Test\n  void should_parse_nodejs_min_version() throws URISyntaxException, IOException {\n    var jar = getClass().getResource(\"/SonarPluginManifestTests/plugin-with-nodejs-min.jar\");\n\n    var manifest = SonarPluginManifest.fromJar(Paths.get(jar.toURI()));\n\n    assertThat(manifest.getNodeJsMinVersion()).contains(Version.create(\"12.18.3\"));\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/test/java/org/sonarsource/sonarlint/core/plugin/commons/loading/SonarPluginRequirementsCheckerTests.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.loading;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.StandardOpenOption;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.function.Consumer;\nimport java.util.jar.Attributes;\nimport java.util.jar.JarFile;\nimport java.util.jar.Manifest;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport javax.annotation.Nullable;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonar.api.utils.ZipUtils;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput.Level;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\nimport org.sonarsource.sonarlint.core.plugin.commons.api.SkipReason;\nimport org.sonarsource.sonarlint.core.plugin.commons.api.SkipReason.UnsatisfiedRuntimeRequirement.RuntimeRequirement;\n\nimport static java.util.Arrays.asList;\nimport static java.util.stream.Collectors.joining;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\nimport static org.sonarsource.sonarlint.core.plugin.commons.loading.SonarPluginRequirementsChecker.isCompatibleWith;\n\nclass SonarPluginRequirementsCheckerTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private static final Set<SonarLanguage> NONE = Set.of();\n\n  private static final String V1_0 = \"1.0\";\n\n  private static final String FAKE_PLUGIN_KEY = \"pluginkey\";\n  private static final org.sonar.api.utils.Version FAKE_PLUGIN_API_VERSION = org.sonar.api.utils.Version.parse(\"8.1.2\");\n\n  private SonarPluginRequirementsChecker underTest;\n\n  @BeforeEach\n  void prepare() {\n    underTest = new SonarPluginRequirementsChecker(FAKE_PLUGIN_API_VERSION);\n  }\n\n  @Test\n  void load_no_plugins() {\n    var loadedPlugins = underTest.checkRequirements(Set.of(), NONE, null, null, false);\n\n    assertThat(loadedPlugins).isEmpty();\n  }\n\n  @Test\n  void load_plugin_fail_if_missing_jar() {\n    Set<Path> jars = Set.of(Paths.get(\"doesntexists.jar\"));\n    var checkRequirements = underTest.checkRequirements(jars, NONE, null, null, false);\n\n    assertThat(checkRequirements).isEmpty();\n    assertThat(logTester.logs(Level.ERROR)).contains(\"Unable to load plugin doesntexists.jar\");\n  }\n\n  @Test\n  void load_plugin_skip_corrupted_jar(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"sonarjs.jar\");\n    Set<Path> jars = Set.of(fakePlugin);\n\n    var checkRequirements = underTest.checkRequirements(jars, NONE, null, null, false);\n\n    assertThat(checkRequirements).isEmpty();\n    assertThat(logTester.logs(Level.ERROR)).contains(\"Unable to load plugin \" + fakePlugin);\n  }\n\n  @Test\n  void load_plugin_skip_unsupported_plugins_api_version(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"sonarjs.jar\", path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0,\n      withSqApiVersion(\"99.9\")));\n    Set<Path> jars = Set.of(fakePlugin);\n\n    var loadedPlugins = underTest.checkRequirements(jars, NONE, null, null, false);\n\n    assertThat(loadedPlugins.values())\n      .extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null))\n      .containsOnly(tuple(\"pluginkey\", true, SkipReason.IncompatiblePluginApi.INSTANCE));\n    assertThat(logsWithoutStartStop())\n      .contains(\"Plugin 'pluginkey' requires plugin API 99.9 while SonarLint supports only up to \" + FAKE_PLUGIN_API_VERSION + \". Skip loading it.\");\n  }\n\n  @Test\n  void load_plugin_skip_not_enabled_languages(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"sonarphp.jar\", path -> createPluginManifest(path, SonarPlugin.PHP.getKey(), V1_0));\n    Set<Path> jars = Set.of(fakePlugin);\n\n    var loadedPlugins = underTest.checkRequirements(jars, Set.of(SonarLanguage.TS), null, null, false);\n\n    assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null))\n      .containsOnly(tuple(\"php\", true, new SkipReason.LanguagesNotEnabled(new HashSet<>(List.of(SonarLanguage.PHP)))));\n    assertThat(logsWithoutStartStop()).contains(\"Plugin 'php' is excluded because language 'PHP' is not enabled. Skip loading it.\");\n  }\n\n  @Test\n  void load_plugin_skip_not_enabled_languages_multiple(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"sonarjs.jar\", path -> createPluginManifest(path, SonarPlugin.C_FAMILY.getKey(), V1_0));\n    Set<Path> jars = Set.of(fakePlugin);\n\n    var loadedPlugins = underTest.checkRequirements(jars, Set.of(SonarLanguage.JS), null, null, false);\n\n    assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null))\n      .containsOnly(tuple(\"cpp\", true, new SkipReason.LanguagesNotEnabled(new HashSet<>(asList(SonarLanguage.C, SonarLanguage.CPP, SonarLanguage.OBJC)))));\n    assertThat(logsWithoutStartStop()).contains(\"Plugin 'cpp' is excluded because none of languages 'C,CPP,OBJC' are enabled. Skip loading it.\");\n  }\n\n  @Test\n  void load_plugin_load_even_if_only_one_language_enabled(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"sonarjs.jar\", path -> createPluginManifest(path, SonarPlugin.C_FAMILY.getKey(), V1_0));\n    Set<Path> jars = Set.of(fakePlugin);\n\n    var loadedPlugins = underTest.checkRequirements(jars, Set.of(SonarLanguage.CPP), null, null, false);\n\n    assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null))\n      .containsOnly(tuple(\"cpp\", false, null));\n  }\n\n  @Test\n  void load_plugin_skip_plugins_having_missing_base_plugin(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"fake.jar\",\n      path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withBasePlugin(SonarPlugin.JS.getKey())));\n    Set<Path> jars = Set.of(fakePlugin);\n\n    var loadedPlugins = underTest.checkRequirements(jars, NONE, null, null, false);\n\n    assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null))\n      .containsOnly(tuple(\"pluginkey\", true, new SkipReason.UnsatisfiedDependency(\"javascript\")));\n    assertThat(logsWithoutStartStop()).contains(\"Plugin 'pluginkey' dependency on 'javascript' is unsatisfied. Skip loading it.\");\n  }\n\n  @Test\n  void load_plugin_skip_plugins_having_skipped_base_plugin(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"fake.jar\",\n      path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withBasePlugin(SonarPlugin.JS.getKey())));\n    var fakeBasePlugin = fakePlugin(storage, \"base.jar\",\n      path -> createPluginManifest(path, SonarPlugin.JS.getKey(), V1_0));\n    Set<Path> jars = Set.of(fakePlugin, fakeBasePlugin);\n\n    // Ensure base plugin is skipped because JS language is not enabled\n    var enabledLanguages = Set.of(SonarLanguage.C);\n\n    var loadedPlugins = underTest.checkRequirements(jars, enabledLanguages, null, null, false);\n\n    assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null))\n      .containsOnly(\n        tuple(\"pluginkey\", true, new SkipReason.UnsatisfiedDependency(\"javascript\")),\n        tuple(\"javascript\", true, new SkipReason.LanguagesNotEnabled(List.of(SonarLanguage.CSS, SonarLanguage.JS, SonarLanguage.TS, SonarLanguage.YAML, SonarLanguage.JSON))));\n    assertThat(logsWithoutStartStop()).contains(\"Plugin 'pluginkey' dependency on 'javascript' is unsatisfied. Skip loading it.\");\n  }\n\n  @Test\n  void load_plugin_having_base_plugin(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"fake.jar\",\n      path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withBasePlugin(SonarPlugin.JS.getKey())));\n    var fakeBasePlugin = fakePlugin(storage, \"base.jar\",\n      path -> createPluginManifest(path, SonarPlugin.JS.getKey(), V1_0));\n    Set<Path> jars = Set.of(fakePlugin, fakeBasePlugin);\n\n    var loadedPlugins = underTest.checkRequirements(jars, Set.of(SonarLanguage.JS), null, null, false);\n\n    assertThat(loadedPlugins.values())\n      .extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null))\n      .containsOnly(\n        tuple(\"pluginkey\", false, null),\n        tuple(\"javascript\", false, null));\n  }\n\n  @Test\n  void load_plugin_skip_plugins_having_missing_required_plugin(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"fake.jar\",\n      path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withRequiredPlugins(\"required2:1.0\")));\n    Set<Path> jars = Set.of(fakePlugin);\n\n    var loadedPlugins = underTest.checkRequirements(jars, NONE, null, null, false);\n\n    assertThat(loadedPlugins.values())\n      .extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null))\n      .containsOnly(tuple(\"pluginkey\", true, new SkipReason.UnsatisfiedDependency(\"required2\")));\n    assertThat(logsWithoutStartStop()).contains(\"Plugin 'pluginkey' dependency on 'required2' is unsatisfied. Skip loading it.\");\n  }\n\n  @Test\n  void load_plugin_skip_plugins_having_skipped_required_plugin(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"fake.jar\",\n      path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withRequiredPlugins(\"required2:1.0\")));\n    var fakeDepPlugin = fakePlugin(storage, \"dep.jar\", path -> createPluginManifest(path, \"required2\", V1_0, withNodejsMinVersion(\"99.9.9\")));\n    Set<Path> jars = Set.of(fakePlugin, fakeDepPlugin);\n\n    var loadedPlugins = underTest.checkRequirements(jars, NONE, null, Optional.of(Version.create(\"0.1.2\")), false);\n\n    assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null))\n      .containsOnly(tuple(\"pluginkey\", true, new SkipReason.UnsatisfiedDependency(\"required2\")),\n        tuple(\"required2\", true, new SkipReason.UnsatisfiedRuntimeRequirement(RuntimeRequirement.NODEJS, \"0.1.2\", \"99.9.9\")));\n    assertThat(logsWithoutStartStop()).contains(\"Plugin 'pluginkey' dependency on 'required2' is unsatisfied. Skip loading it.\");\n  }\n\n  @Test\n  void load_plugin(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"fake.jar\",\n      path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withSqApiVersion(\"7.9\")));\n    Set<Path> jars = Set.of(fakePlugin);\n\n    var loadedPlugins = underTest.checkRequirements(jars, NONE, null, null, false);\n\n    assertThat(loadedPlugins.values()).as(logsWithoutStartStop().collect(Collectors.joining(\"\\n\")))\n      .extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped)\n      .containsOnly(tuple(FAKE_PLUGIN_KEY, false));\n  }\n\n  @Test\n  void load_plugin_skip_plugins_having_unsatisfied_jre(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"fake.jar\", path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withJreMinVersion(\"11\")));\n    Set<Path> jars = Set.of(fakePlugin);\n\n    var loadedPlugins = underTest.checkRequirements(jars, NONE, Version.create(\"1.8\"), null, false);\n\n    assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null))\n      .containsOnly(tuple(\"pluginkey\", true, new SkipReason.UnsatisfiedRuntimeRequirement(RuntimeRequirement.JRE, \"1.8\", \"11\")));\n    assertThat(logsWithoutStartStop()).contains(\"Plugin 'pluginkey' requires JRE 11 while current is 1.8. Skip loading it.\");\n  }\n\n  @Test\n  void load_plugin_having_satisfied_nodejs(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"fake.jar\",\n      path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withNodejsMinVersion(\"10.1.2\")));\n    Set<Path> jars = Set.of(fakePlugin);\n\n    var loadedPlugins = underTest.checkRequirements(jars, NONE, null, Optional.of(Version.create(\"10.1.3\")), false);\n\n    assertThat(loadedPlugins.values())\n      .extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped)\n      .containsOnly(tuple(\"pluginkey\", false));\n  }\n\n  @Test\n  void load_plugin_having_satisfied_nodejs_nightly(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"fake.jar\",\n      path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withNodejsMinVersion(\"15.0.0\")));\n    Set<Path> jars = Set.of(fakePlugin);\n\n    var loadedPlugins = underTest.checkRequirements(jars, NONE, null, Optional.of(Version.create(\"15.0.0-nightly20200921039c274dde\")), false);\n\n    assertThat(loadedPlugins.values())\n      .extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped)\n      .containsOnly(tuple(\"pluginkey\", false));\n  }\n\n  @Test\n  void load_plugin_skip_plugins_having_unsatisfied_nodejs_version(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"fake.jar\",\n      path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withNodejsMinVersion(\"10.1.2\")));\n    Set<Path> jars = Set.of(fakePlugin);\n\n    var loadedPlugins = underTest.checkRequirements(jars, NONE, null, Optional.of(Version.create(\"10.1.1\")), false);\n\n    assertThat(loadedPlugins.values())\n      .extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null))\n      .containsOnly(tuple(\"pluginkey\", true, new SkipReason.UnsatisfiedRuntimeRequirement(RuntimeRequirement.NODEJS, \"10.1.1\", \"10.1.2\")));\n    assertThat(logsWithoutStartStop()).contains(\"Plugin 'pluginkey' requires Node.js 10.1.2 while current is 10.1.1. Skip loading it.\");\n  }\n\n  @Test\n  void load_plugin_skip_plugins_having_unsatisfied_nodejs(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"fake.jar\",\n      path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withNodejsMinVersion(\"10.1.2\")));\n    Set<Path> jars = Set.of(fakePlugin);\n\n    var loadedPlugins = underTest.checkRequirements(jars, NONE, null,  Optional.empty(), false);\n\n    assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null))\n      .containsOnly(tuple(\"pluginkey\", true, new SkipReason.UnsatisfiedRuntimeRequirement(RuntimeRequirement.NODEJS, null, \"10.1.2\")));\n    assertThat(logsWithoutStartStop()).contains(\"Plugin 'pluginkey' requires Node.js 10.1.2. Skip loading it.\");\n  }\n\n  @Test\n  void load_plugin_having_satisfied_jre(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"fake.jar\", path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withJreMinVersion(\"1.7\")));\n    Set<Path> jars = Set.of(fakePlugin);\n\n    var loadedPlugins = underTest.checkRequirements(jars, NONE, Version.create(\"1.8\"),  Optional.empty(), false);\n\n    assertThat(loadedPlugins.values())\n      .extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped)\n      .containsOnly(tuple(\"pluginkey\", false));\n  }\n\n  @Test\n  void load_plugin_skip_plugins_having_unsatisfied_python_frontend_dbd(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"fake.jar\",\n      path -> createPluginManifest(path, \"dbdpythonfrontend\", \"1.15\"));\n    Set<Path> jars = Set.of(fakePlugin);\n\n    var loadedPlugins = underTest.checkRequirements(jars, NONE, null, Optional.empty(), false);\n\n    assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null))\n      .containsOnly(tuple(\"dbdpythonfrontend\", true, SkipReason.UnsupportedFeature.INSTANCE));\n    assertThat(logsWithoutStartStop()).contains(\"DBD feature disabled. Skip loading plugin 'dbdpythonfrontend'.\");\n  }\n\n  @Test\n  void load_plugin_skip_plugins_having_unsatisfied_python_dbd(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"fake.jar\",\n      path -> createPluginManifest(path, \"dbd\", \"1.15\"));\n    Set<Path> jars = Set.of(fakePlugin);\n\n    var loadedPlugins = underTest.checkRequirements(jars, NONE, null, Optional.empty(), true);\n\n    assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null))\n      .containsOnly(tuple(\"dbd\", true, new SkipReason.UnsatisfiedDependency(SonarPlugin.PYTHON.getKey())));\n    assertThat(logsWithoutStartStop()).contains(\"Plugin 'dbd' dependency on 'python' is unsatisfied. Skip loading it.\");\n  }\n\n  @Test\n  void load_plugin_having_satisfied_python_frontend_dbd(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"fake.jar\",\n      path -> createPluginManifest(path, \"dbdpythonfrontend\", \"1.15\"));\n    var fakePythonPlugin = fakePlugin(storage, \"python.jar\",\n      path -> createPluginManifest(path, SonarPlugin.PYTHON.getKey(), \"3.25\"));\n    Set<Path> jars = Set.of(fakePlugin, fakePythonPlugin);\n\n    var loadedPlugins = underTest.checkRequirements(jars, Set.of(SonarLanguage.PYTHON), null, Optional.empty(), true);\n\n    assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null))\n      .containsOnly(\n        tuple(\"python\", false, null),\n        tuple(\"dbdpythonfrontend\", false, null)\n      );\n  }\n\n  @Test\n  void load_plugin_having_satisfied_python_dbd(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"fake.jar\",\n      path -> createPluginManifest(path, \"dbd\", \"1.15\"));\n    var fakePythonPlugin = fakePlugin(storage, \"python.jar\",\n      path -> createPluginManifest(path, SonarPlugin.PYTHON.getKey(), \"3.25\"));\n    Set<Path> jars = Set.of(fakePlugin, fakePythonPlugin);\n\n    var loadedPlugins = underTest.checkRequirements(jars, Set.of(SonarLanguage.PYTHON), null, Optional.empty(), true);\n\n    assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null))\n      .containsOnly(\n        tuple(\"python\", false, null),\n        tuple(\"dbd\", false, null)\n      );\n  }\n\n  @Test\n  void load_plugin_having_satisfied_python_dbd_but_no_feature_flag(@TempDir Path storage) throws IOException {\n    var fakePlugin = fakePlugin(storage, \"fake.jar\",\n      path -> createPluginManifest(path, \"dbd\", \"1.15\"));\n    var fakePythonPlugin = fakePlugin(storage, \"python.jar\",\n      path -> createPluginManifest(path, SonarPlugin.PYTHON.getKey(), \"3.25\"));\n    Set<Path> jars = Set.of(fakePlugin, fakePythonPlugin);\n\n    var loadedPlugins = underTest.checkRequirements(jars, Set.of(SonarLanguage.PYTHON), null, Optional.empty(), false);\n\n    assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null))\n      .containsOnly(\n        tuple(\"python\", false, null),\n        tuple(\"dbd\", true, SkipReason.UnsupportedFeature.INSTANCE)\n      );\n    assertThat(logsWithoutStartStop()).contains(\"DBD feature disabled. Skip loading plugin 'dbd'.\");\n  }\n\n  @Test\n  void test_isCompatibleWith() {\n    assertThat(isCompatibleWith(withMinSqVersion(\"1.1\"), Version.create(\"1.1\"))).isTrue();\n    assertThat(isCompatibleWith(withMinSqVersion(\"1.1\"), Version.create(\"1.1.0\"))).isTrue();\n    assertThat(isCompatibleWith(withMinSqVersion(\"1.0\"), Version.create(\"1.0.0\"))).isTrue();\n\n    assertThat(isCompatibleWith(withMinSqVersion(\"1.0\"), Version.create(\"1.1\"))).isTrue();\n    assertThat(isCompatibleWith(withMinSqVersion(\"1.1.1\"), Version.create(\"1.1.2\"))).isTrue();\n    assertThat(isCompatibleWith(withMinSqVersion(\"2.0\"), Version.create(\"2.1.0\"))).isTrue();\n    assertThat(isCompatibleWith(withMinSqVersion(\"3.2\"), Version.create(\"3.2-RC1\"))).isTrue();\n    assertThat(isCompatibleWith(withMinSqVersion(\"3.2\"), Version.create(\"3.2-RC2\"))).isTrue();\n    assertThat(isCompatibleWith(withMinSqVersion(\"3.2\"), Version.create(\"3.1-RC2\"))).isFalse();\n\n    assertThat(isCompatibleWith(withMinSqVersion(\"1.1\"), Version.create(\"1.0\"))).isFalse();\n    assertThat(isCompatibleWith(withMinSqVersion(\"2.0.1\"), Version.create(\"2.0.0\"))).isTrue();\n    assertThat(isCompatibleWith(withMinSqVersion(\"2.10\"), Version.create(\"2.1\"))).isFalse();\n    assertThat(isCompatibleWith(withMinSqVersion(\"10.10\"), Version.create(\"2.2\"))).isFalse();\n\n    assertThat(isCompatibleWith(withMinSqVersion(\"1.1-SNAPSHOT\"), Version.create(\"1.0\"))).isFalse();\n    assertThat(isCompatibleWith(withMinSqVersion(\"1.1-SNAPSHOT\"), Version.create(\"1.1\"))).isTrue();\n    assertThat(isCompatibleWith(withMinSqVersion(\"1.1-SNAPSHOT\"), Version.create(\"1.2\"))).isTrue();\n    assertThat(isCompatibleWith(withMinSqVersion(\"1.0.1-SNAPSHOT\"), Version.create(\"1.0\"))).isTrue();\n\n    assertThat(isCompatibleWith(withMinSqVersion(\"3.1-RC2\"), Version.create(\"3.2-SNAPSHOT\"))).isTrue();\n    assertThat(isCompatibleWith(withMinSqVersion(\"3.1-RC1\"), Version.create(\"3.2-RC2\"))).isTrue();\n    assertThat(isCompatibleWith(withMinSqVersion(\"3.1-RC1\"), Version.create(\"3.1-RC2\"))).isTrue();\n\n    assertThat(isCompatibleWith(withMinSqVersion(null), Version.create(\"0\"))).isTrue();\n    assertThat(isCompatibleWith(withMinSqVersion(null), Version.create(\"3.1\"))).isTrue();\n\n    assertThat(isCompatibleWith(withMinSqVersion(\"7.0.0.12345\"), Version.create(\"7.0\"))).isTrue();\n  }\n\n  PluginInfo withMinSqVersion(@Nullable String version) {\n    var plugin = mock(PluginInfo.class);\n    when(plugin.getKey()).thenReturn(\"foo\");\n    when(plugin.getMinimalSqVersion()).thenReturn(Optional.ofNullable(version).map(Version::create).orElse(null));\n    return plugin;\n  }\n\n  private void createPluginManifest(Path path, String pluginKey, String version, Consumer<Attributes>... manifestAttributesPopulators) {\n    var manifestPath = path.resolve(JarFile.MANIFEST_NAME);\n    try {\n      Files.createDirectories(manifestPath.getParent());\n      var manifest = new Manifest();\n      manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, \"1.0\");\n      manifest.getMainAttributes().putValue(SonarPluginManifest.KEY_ATTRIBUTE, pluginKey);\n      manifest.getMainAttributes().putValue(SonarPluginManifest.VERSION_ATTRIBUTE, version);\n      Stream.of(manifestAttributesPopulators).forEach(p -> p.accept(manifest.getMainAttributes()));\n      try (var fos = Files.newOutputStream(manifestPath, StandardOpenOption.CREATE_NEW)) {\n        manifest.write(fos);\n      }\n    } catch (IOException e) {\n      throw new IllegalStateException(e);\n    }\n  }\n\n  private Consumer<Attributes> withSqApiVersion(String sqApiVersion) {\n    return a -> a.putValue(SonarPluginManifest.SONAR_VERSION_ATTRIBUTE, sqApiVersion);\n  }\n\n  private Consumer<Attributes> withJreMinVersion(String jreMinVersion) {\n    return a -> a.putValue(SonarPluginManifest.JRE_MIN_VERSION, jreMinVersion);\n  }\n\n  private Consumer<Attributes> withNodejsMinVersion(String nodeMinVersion) {\n    return a -> a.putValue(SonarPluginManifest.NODEJS_MIN_VERSION, nodeMinVersion);\n  }\n\n  private Consumer<Attributes> withBasePlugin(String basePlugin) {\n    return a -> a.putValue(SonarPluginManifest.BASE_PLUGIN, basePlugin);\n  }\n\n  private Consumer<Attributes> withRequiredPlugins(String... requirePlugins) {\n    return a -> a.putValue(SonarPluginManifest.REQUIRE_PLUGINS_ATTRIBUTE, Stream.of(requirePlugins).collect(joining(\",\")));\n  }\n\n  private Path fakePlugin(Path storage, String filename, Consumer<Path>... populators) throws IOException {\n    var pluginJar = storage.resolve(filename);\n    var pluginTmpDir = Files.createTempDirectory(storage, \"plugin\");\n    Stream.of(populators).forEach(p -> p.accept(pluginTmpDir));\n    ZipUtils.zipDir(pluginTmpDir.toFile(), pluginJar.toFile());\n    return pluginJar;\n  }\n\n  private Stream<String> logsWithoutStartStop() {\n    return logTester.logs().stream()\n      .filter(s -> !s.equals(\"Load plugins\"))\n      .filter(s -> !s.matches(\"Load plugins \\\\(done\\\\) \\\\| time=(.*)ms\"));\n  }\n\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/test/java/org/sonarsource/sonarlint/core/plugin/commons/sonarapi/MapSettingsTests.java",
    "content": "/*\n * SonarLint Core - Plugin Commons\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.plugin.commons.sonarapi;\n\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.stream.IntStream;\nimport org.assertj.core.api.Assertions;\nimport org.assertj.core.data.Offset;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.sonar.api.Properties;\nimport org.sonar.api.Property;\nimport org.sonar.api.PropertyType;\nimport org.sonar.api.config.PropertyDefinition;\nimport org.sonar.api.config.PropertyDefinitions;\nimport org.sonar.api.utils.System2;\n\nimport static java.util.Collections.singletonList;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.sonarsource.sonarlint.core.plugin.commons.Utils.randomAlphanumeric;\n\nclass MapSettingsTests {\n\n  private PropertyDefinitions definitions;\n\n  @Properties({\n    @Property(key = \"hello\", name = \"Hello\", defaultValue = \"world\"),\n    @Property(key = \"date\", name = \"Date\", defaultValue = \"2010-05-18\"),\n    @Property(key = \"datetime\", name = \"DateTime\", defaultValue = \"2010-05-18T15:50:45+0100\"),\n    @Property(key = \"boolean\", name = \"Boolean\", defaultValue = \"true\"),\n    @Property(key = \"falseboolean\", name = \"False Boolean\", defaultValue = \"false\"),\n    @Property(key = \"integer\", name = \"Integer\", defaultValue = \"12345\"),\n    @Property(key = \"array\", name = \"Array\", defaultValue = \"one,two,three\"),\n    @Property(key = \"multi_values\", name = \"Array\", defaultValue = \"1,2,3\", multiValues = true),\n    @Property(key = \"sonar.jira\", name = \"Jira Server\", type = PropertyType.PROPERTY_SET),\n    @Property(key = \"newKey\", name = \"New key\", deprecatedKey = \"oldKey\"),\n    @Property(key = \"newKeyWithDefaultValue\", name = \"New key with default value\", deprecatedKey = \"oldKeyWithDefaultValue\", defaultValue = \"default_value\"),\n    @Property(key = \"new_multi_values\", name = \"New multi values\", defaultValue = \"1,2,3\", multiValues = true, deprecatedKey = \"old_multi_values\")\n  })\n  private static class Init {\n  }\n\n  @BeforeEach\n  void init_definitions() {\n    definitions = new PropertyDefinitions(System2.INSTANCE);\n    definitions.addComponent(Init.class);\n  }\n\n  @Test\n  void set_accepts_empty_value_and_trims_it() {\n    var random = new Random();\n    var key = randomAlphanumeric(3);\n\n    var underTest = new MapSettings(Map.of(key, blank(random)));\n\n    assertThat(underTest.getString(key)).isEmpty();\n  }\n\n  @Test\n  void default_values_should_be_loaded_from_definitions() {\n    var settings = new MapSettings(definitions, Map.of());\n    assertThat(settings.getDefaultValue(\"hello\")).isEqualTo(\"world\");\n  }\n\n  @Test\n  void set_property_string_array_trims_key() {\n    var key = randomAlphanumeric(3);\n\n    var random = new Random();\n    var blankBefore = blank(random);\n    var blankAfter = blank(random);\n\n    var underTest = new MapSettings(new PropertyDefinitions(System2.INSTANCE, singletonList(PropertyDefinition.builder(key).multiValues(true).build())),\n      Map.of(blankBefore + key + blankAfter, \"1,2\"));\n\n    assertThat(underTest.hasKey(key)).isTrue();\n  }\n\n  private static String blank(Random random) {\n    var b = new StringBuilder();\n    IntStream.range(0, random.nextInt(3)).mapToObj(s -> \" \").forEach(b::append);\n    return b.toString();\n  }\n\n  @Test\n  void setProperty_methods_trims_value() {\n    var random = new Random();\n    var blankBefore = blank(random);\n    var blankAfter = blank(random);\n    var key = randomAlphanumeric(3);\n    var value = randomAlphanumeric(3);\n\n    var underTest = new MapSettings(Map.of(key, blankBefore + value + blankAfter));\n\n    assertThat(underTest.getString(key)).isEqualTo(value);\n  }\n\n  @Test\n  void set_property_int() {\n    var settings = new MapSettings(Map.of(\"foo\", \"123\"));\n    assertThat(settings.getInt(\"foo\")).isEqualTo(123);\n    assertThat(settings.getString(\"foo\")).isEqualTo(\"123\");\n    assertThat(settings.getBoolean(\"foo\")).isFalse();\n  }\n\n  @Test\n  void default_number_values_are_zero() {\n    var settings = new MapSettings(Map.of());\n    assertThat(settings.getInt(\"foo\")).isZero();\n    assertThat(settings.getLong(\"foo\")).isZero();\n  }\n\n  @Test\n  void getInt_value_must_be_valid() {\n    var settings = new MapSettings(Map.of(\"foo\", \"not a number\"));\n\n    assertThrows(NumberFormatException.class, () -> settings.getInt(\"foo\"));\n  }\n\n  @Test\n  void all_values_should_be_trimmed_set_property() {\n    var settings = new MapSettings(Map.of(\"foo\", \"   FOO \"));\n    assertThat(settings.getString(\"foo\")).isEqualTo(\"FOO\");\n  }\n\n  @Test\n  void test_get_default_value() {\n    var settings = new MapSettings(definitions, Map.of());\n    assertThat(settings.getDefaultValue(\"unknown\")).isNull();\n  }\n\n  @Test\n  void test_get_string() {\n    var settings = new MapSettings(definitions, Map.of(\"hello\", \"Russia\"));\n    assertThat(settings.getString(\"hello\")).isEqualTo(\"Russia\");\n  }\n\n  @Test\n  void test_get_date() {\n    var settings = new MapSettings(definitions, Map.of());\n    assertThat(settings.getDate(\"unknown\")).isNull();\n    assertThat(settings.getDate(\"date\").getDate()).isEqualTo(18);\n    assertThat(settings.getDate(\"date\").getMonth()).isEqualTo(4);\n  }\n\n  @Test\n  void test_get_date_not_found() {\n    var settings = new MapSettings(definitions, Map.of());\n    assertThat(settings.getDate(\"unknown\")).isNull();\n  }\n\n  @Test\n  void test_get_datetime() {\n    var settings = new MapSettings(definitions, Map.of());\n    assertThat(settings.getDateTime(\"unknown\")).isNull();\n    assertThat(settings.getDateTime(\"datetime\").getDate()).isEqualTo(18);\n    assertThat(settings.getDateTime(\"datetime\").getMonth()).isEqualTo(4);\n    assertThat(settings.getDateTime(\"datetime\").getMinutes()).isEqualTo(50);\n  }\n\n  @Test\n  void test_get_double() {\n    var settings = new MapSettings(Map.of(\"from_string\", \"3.14159\"));\n    assertThat(settings.getDouble(\"from_string\")).isEqualTo(3.14159, Offset.offset(0.00001));\n    assertThat(settings.getDouble(\"unknown\")).isNull();\n  }\n\n  @Test\n  void test_get_float() {\n    var settings = new MapSettings(Map.of(\"from_string\", \"3.14159\"));\n    assertThat(settings.getDouble(\"from_string\")).isEqualTo(3.14159f, Offset.offset(0.00001));\n    assertThat(settings.getDouble(\"unknown\")).isNull();\n  }\n\n  @Test\n  void test_get_bad_float() {\n    var settings = new MapSettings(Map.of(\"foo\", \"bar\"));\n\n    var thrown = assertThrows(IllegalStateException.class, () -> settings.getFloat(\"foo\"));\n    assertThat(thrown).hasMessage(\"The property 'foo' is not a float value\");\n  }\n\n  @Test\n  void test_get_bad_double() {\n    var settings = new MapSettings(Map.of(\"foo\", \"bar\"));\n\n    var thrown = assertThrows(IllegalStateException.class, () -> settings.getDouble(\"foo\"));\n    assertThat(thrown).hasMessage(\"The property 'foo' is not a double value\");\n  }\n\n  @Test\n  void getStringArray() {\n    var settings = new MapSettings(definitions, Map.of());\n    var array = settings.getStringArray(\"array\");\n    assertThat(array).isEqualTo(new String[] {\"one\", \"two\", \"three\"});\n  }\n\n  @Test\n  void getStringArray_no_value() {\n    var settings = new MapSettings(Map.of());\n    var array = settings.getStringArray(\"array\");\n    assertThat(array).isEmpty();\n  }\n\n  @Test\n  void shouldTrimArray() {\n    var settings = new MapSettings(Map.of(\"foo\", \"  one,  two, three  \"));\n    var array = settings.getStringArray(\"foo\");\n    assertThat(array).isEqualTo(new String[] {\"one\", \"two\", \"three\"});\n  }\n\n  @Test\n  void shouldKeepEmptyValuesWhenSplitting() {\n    var settings = new MapSettings(Map.of(\"foo\", \"  one,  , two\"));\n    var array = settings.getStringArray(\"foo\");\n    assertThat(array).isEqualTo(new String[] {\"one\", \"\", \"two\"});\n  }\n\n  @Test\n  void testDefaultValueOfGetString() {\n    var settings = new MapSettings(definitions, Map.of());\n    assertThat(settings.getString(\"hello\")).isEqualTo(\"world\");\n  }\n\n  @Test\n  void set_property_boolean() {\n    var settings = new MapSettings(Map.of(\"foo\", \"true\", \"bar\", \"false\"));\n    assertThat(settings.getBoolean(\"foo\")).isTrue();\n    assertThat(settings.getBoolean(\"bar\")).isFalse();\n    assertThat(settings.getString(\"foo\")).isEqualTo(\"true\");\n    assertThat(settings.getString(\"bar\")).isEqualTo(\"false\");\n  }\n\n  @Test\n  void ignore_case_of_boolean_values() {\n    var settings = new MapSettings(Map.of(\"foo\", \"true\", \"bar\", \"TRUE\",\n      // labels in UI\n      \"baz\", \"True\"));\n\n    assertThat(settings.getBoolean(\"foo\")).isTrue();\n    assertThat(settings.getBoolean(\"bar\")).isTrue();\n    assertThat(settings.getBoolean(\"baz\")).isTrue();\n  }\n\n  @Test\n  void get_boolean() {\n    var settings = new MapSettings(definitions, Map.of());\n    assertThat(settings.getBoolean(\"boolean\")).isTrue();\n    assertThat(settings.getBoolean(\"falseboolean\")).isFalse();\n    assertThat(settings.getBoolean(\"unknown\")).isFalse();\n    assertThat(settings.getBoolean(\"hello\")).isFalse();\n  }\n\n  @Test\n  void shouldCreateByIntrospectingComponent() {\n    var settings = new MapSettings(Map.of());\n    settings.getDefinitions().addComponent(MyComponent.class);\n\n    // property definition has been loaded, ie for default value\n    assertThat(settings.getDefaultValue(\"foo\")).isEqualTo(\"bar\");\n  }\n\n  @Property(key = \"foo\", name = \"Foo\", defaultValue = \"bar\")\n  public static class MyComponent {\n\n  }\n\n  @Test\n  void getStringLines_no_value() {\n    Assertions.assertThat(new MapSettings(Map.of()).getStringLines(\"foo\")).isEmpty();\n  }\n\n  @Test\n  void getStringLines_single_line() {\n    var settings = new MapSettings(Map.of(\"foo\", \"the line\"));\n    assertThat(settings.getStringLines(\"foo\")).isEqualTo(new String[] {\"the line\"});\n  }\n\n  @Test\n  void getStringLines_linux() {\n    var settings = new MapSettings(Map.of(\"foo\", \"one\\ntwo\"));\n    assertThat(settings.getStringLines(\"foo\")).isEqualTo(new String[] {\"one\", \"two\"});\n\n    settings = new MapSettings(Map.of(\"foo\", \"one\\ntwo\\n\"));\n    assertThat(settings.getStringLines(\"foo\")).isEqualTo(new String[] {\"one\", \"two\"});\n  }\n\n  @Test\n  void getStringLines_windows() {\n    var settings = new MapSettings(Map.of(\"foo\", \"one\\r\\ntwo\"));\n    assertThat(settings.getStringLines(\"foo\")).isEqualTo(new String[] {\"one\", \"two\"});\n\n    settings = new MapSettings(Map.of(\"foo\", \"one\\r\\ntwo\\r\\n\"));\n    assertThat(settings.getStringLines(\"foo\")).isEqualTo(new String[] {\"one\", \"two\"});\n  }\n\n  @Test\n  void getStringLines_mix() {\n    var settings = new MapSettings(Map.of(\"foo\", \"one\\r\\ntwo\\nthree\"));\n    assertThat(settings.getStringLines(\"foo\")).isEqualTo(new String[] {\"one\", \"two\", \"three\"});\n  }\n\n  @Test\n  void getKeysStartingWith() {\n    var settings = new MapSettings(Map.of(\"sonar.jdbc.url\", \"foo\", \"sonar.jdbc.username\", \"bar\", \"sonar.security\", \"admin\"));\n\n    assertThat(settings.getKeysStartingWith(\"sonar\")).containsOnly(\"sonar.jdbc.url\", \"sonar.jdbc.username\", \"sonar.security\");\n    assertThat(settings.getKeysStartingWith(\"sonar.jdbc\")).containsOnly(\"sonar.jdbc.url\", \"sonar.jdbc.username\");\n    assertThat(settings.getKeysStartingWith(\"other\")).isEmpty();\n  }\n\n  @Test\n  void should_fallback_deprecated_key_to_default_value_of_new_key() {\n    var settings = new MapSettings(definitions, Map.of());\n\n    assertThat(settings.getString(\"newKeyWithDefaultValue\")).isEqualTo(\"default_value\");\n    assertThat(settings.getString(\"oldKeyWithDefaultValue\")).isEqualTo(\"default_value\");\n  }\n\n  @Test\n  void should_fallback_deprecated_key_to_new_key() {\n    var settings = new MapSettings(definitions, Map.of(\"newKey\", \"value of newKey\"));\n\n    assertThat(settings.getString(\"newKey\")).isEqualTo(\"value of newKey\");\n    assertThat(settings.getString(\"oldKey\")).isEqualTo(\"value of newKey\");\n  }\n\n  @Test\n  void should_load_value_of_deprecated_key() {\n    // it's used for example when deprecated settings are set through command-line\n    var settings = new MapSettings(definitions, Map.of(\"oldKey\", \"value of oldKey\"));\n\n    assertThat(settings.getString(\"newKey\")).isEqualTo(\"value of oldKey\");\n    assertThat(settings.getString(\"oldKey\")).isEqualTo(\"value of oldKey\");\n  }\n\n  @Test\n  void should_load_values_of_deprecated_key() {\n    var settings = new MapSettings(definitions, Map.of(\"oldKey\", \"a,b\"));\n\n    assertThat(settings.getStringArray(\"newKey\")).containsOnly(\"a\", \"b\");\n    assertThat(settings.getStringArray(\"oldKey\")).containsOnly(\"a\", \"b\");\n  }\n\n  @Test\n  void should_support_deprecated_props_with_multi_values() {\n    var settings = new MapSettings(definitions, Map.of(\"new_multi_values\", \" A , B \"));\n    assertThat(settings.getStringArray(\"new_multi_values\")).isEqualTo(new String[] {\"A\", \"B\"});\n    assertThat(settings.getStringArray(\"old_multi_values\")).isEqualTo(new String[] {\"A\", \"B\"});\n  }\n\n  @Test\n  void testParsingMultiValues() {\n    assertThat(getStringArray(\"\")).isEmpty();\n    assertThat(getStringArray(\",\")).isEmpty();\n    assertThat(getStringArray(\",,\")).isEmpty();\n    assertThat(getStringArray(\"a\")).containsExactly(\"a\");\n    assertThat(getStringArray(\"a b\")).containsExactly(\"a b\");\n    assertThat(getStringArray(\"a , b\")).containsExactly(\"a\", \"b\");\n    assertThat(getStringArray(\"\\\"a \\\",\\\" b\\\"\")).containsExactly(\"a \", \" b\");\n    assertThat(getStringArray(\"\\\"a,b\\\",c\")).containsExactly(\"a,b\", \"c\");\n    assertThat(getStringArray(\"\\\"a\\nb\\\",c\")).containsExactly(\"a\\nb\", \"c\");\n    assertThat(getStringArray(\"\\\"a\\\",\\n  b\\n\")).containsExactly(\"a\", \"b\");\n    assertThat(getStringArray(\"a\\n,b\\n\")).containsExactly(\"a\", \"b\");\n    assertThat(getStringArray(\"a\\n,b\\n,\\\"\\\"\")).containsExactly(\"a\", \"b\", \"\");\n    assertThat(getStringArray(\"a\\n,  \\\"  \\\"  ,b\\n\")).containsExactly(\"a\", \"  \", \"b\");\n    assertThat(getStringArray(\"  \\\" , ,, \\\", a\\n,b\\n\")).containsExactly(\" , ,, \", \"a\", \"b\");\n    assertThat(getStringArray(\"a\\n,,b\\n\")).containsExactly(\"a\", \"b\");\n    assertThat(getStringArray(\"a,\\n\\nb,c\")).containsExactly(\"a\", \"b\", \"c\");\n    assertThat(getStringArray(\"a,b\\n\\nc,d\")).containsExactly(\"a\", \"b\\nc\", \"d\");\n    assertThat(getStringArray(\"a,\\\"\\\",b\")).containsExactly(\"a\", \"\", \"b\");\n\n    var thrown = assertThrows(IllegalStateException.class, () -> getStringArray(\"\\\"a ,b\"));\n    assertThat(thrown).hasMessage(\"Property: 'multi_values' doesn't contain a valid CSV value: '\\\"a ,b'\");\n  }\n\n  private String[] getStringArray(String value) {\n    var settings = new MapSettings(definitions, Map.of(\"multi_values\", value));\n    return settings.getStringArray(\"multi_values\");\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/test/projects/.gitignore",
    "content": "# see README.txt\n!*/target/\n*/target/classes/\n*/target/maven-archiver/\n*/target/maven-status/\n*/target/test-*/\n\n"
  },
  {
    "path": "backend/plugin-commons/src/test/projects/README.txt",
    "content": "This directory provides the fake plugins used by tests. These tests are rarely changed, so generated\nartifacts are stored in Git repository (see .gitignore). It avoids from adding unnecessary modules\nto build.\n"
  },
  {
    "path": "backend/plugin-commons/src/test/projects/base-plugin/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <groupId>org.sonarsource.sonarqube.tests</groupId>\n  <artifactId>base-plugin</artifactId>\n  <version>0.1-SNAPSHOT</version>\n  <packaging>sonar-plugin</packaging>\n  <name>Base Plugin</name>\n  <description>Fake plugin used to verify building of plugin classloaders</description>\n\n  <dependencies>\n    <dependency>\n      <groupId>org.sonarsource.api.plugin</groupId>\n      <artifactId>sonar-plugin-api</artifactId>\n      <version>9.17.0.587</version>\n      <scope>provided</scope>\n    </dependency>\n  </dependencies>\n  <build>\n    <sourceDirectory>src</sourceDirectory>\n    <plugins>\n      <plugin>\n        <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>\n        <artifactId>sonar-packaging-maven-plugin</artifactId>\n        <version>1.15</version>\n        <extensions>true</extensions>\n        <configuration>\n          <pluginKey>base</pluginKey>\n          <pluginClass>org.sonar.plugins.base.BasePlugin</pluginClass>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n\n</project>\n"
  },
  {
    "path": "backend/plugin-commons/src/test/projects/base-plugin/src/org/sonar/plugins/base/BasePlugin.java",
    "content": "package org.sonar.plugins.base;\n\nimport org.sonar.api.Plugin;\nimport org.sonar.api.Plugin.Context;\n\npublic class BasePlugin implements Plugin {\n  @Override\n  public void define(Context context) {\n    // no extensions\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/test/projects/base-plugin/src/org/sonar/plugins/base/api/BaseApi.java",
    "content": "package org.sonar.plugins.base.api;\n\npublic class BaseApi {\n  public void doNothing() {\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/test/projects/classloader-leak-plugin/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <groupId>org.sonarsource.sonarlint.core.plugins.tests</groupId>\n  <artifactId>classloader-leak-plugin</artifactId>\n  <version>0.1-SNAPSHOT</version>\n  <packaging>sonar-plugin</packaging>\n  <name>Leak Plugin</name>\n  <description>Fake plugin used to reproduce a JAR file leak when using URLClassloader</description>\n\n  <properties>\n    <maven.compiler.target>11</maven.compiler.target>\n    <maven.compiler.source>11</maven.compiler.source>\n    <maven.compiler.release>11</maven.compiler.release>\n  </properties>\n  <dependencies>\n    <dependency>\n      <groupId>org.sonarsource.api.plugin</groupId>\n      <artifactId>sonar-plugin-api</artifactId>\n      <version>9.17.0.587</version>\n      <scope>provided</scope>\n    </dependency>\n  </dependencies>\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>\n        <artifactId>sonar-packaging-maven-plugin</artifactId>\n        <version>1.15</version>\n        <extensions>true</extensions>\n        <configuration>\n          <pluginKey>base</pluginKey>\n          <pluginClass>org.sonar.plugins.leak.LeakPlugin</pluginClass>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n\n</project>\n"
  },
  {
    "path": "backend/plugin-commons/src/test/projects/classloader-leak-plugin/src/main/java/org/sonar/plugins/leak/LeakPlugin.java",
    "content": "package org.sonar.plugins.leak;\n\nimport java.io.IOException;\nimport org.sonar.api.Plugin;\n\npublic class LeakPlugin implements Plugin {\n  @Override\n  public void define(Context context) {\n    // See SLCORE-557\n    var resource = this.getClass().getClassLoader().getResource(\"Hello.txt\");\n    // https://bugs.java.com/bugdatabase/view_bug?bug_id=JDK-8315993\n    try (var conn = resource.openConnection().getInputStream()) {\n      conn.readAllBytes();\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n    // no extensions\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/test/projects/classloader-leak-plugin/src/main/resources/Hello.txt",
    "content": "Hello World"
  },
  {
    "path": "backend/plugin-commons/src/test/projects/dependent-plugin/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <groupId>org.sonarsource.sonarqube.tests</groupId>\n  <artifactId>dependent-plugin</artifactId>\n  <version>0.1-SNAPSHOT</version>\n  <packaging>sonar-plugin</packaging>\n  <name>Dependent Plugin</name>\n  <description>Fake plugin used to verify that plugins can export some resources to other plugins</description>\n\n  <dependencies>\n    <dependency>\n      <groupId>org.sonarsource.api.plugin</groupId>\n      <artifactId>sonar-plugin-api</artifactId>\n      <version>9.17.0.587</version>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.sonarqube.tests</groupId>\n      <artifactId>base-plugin</artifactId>\n      <version>0.1-SNAPSHOT</version>\n      <type>sonar-plugin</type>\n      <scope>provided</scope>\n    </dependency>\n  </dependencies>\n  <build>\n    <sourceDirectory>src</sourceDirectory>\n    <plugins>\n      <plugin>\n        <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>\n        <artifactId>sonar-packaging-maven-plugin</artifactId>\n        <version>1.15</version>\n        <extensions>true</extensions>\n        <configuration>\n          <pluginKey>dependent</pluginKey>\n          <pluginClass>org.sonar.plugins.dependent.DependentPlugin</pluginClass>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n\n</project>\n"
  },
  {
    "path": "backend/plugin-commons/src/test/projects/dependent-plugin/src/org/sonar/plugins/dependent/DependentPlugin.java",
    "content": "package org.sonar.plugins.dependent;\n\nimport org.sonar.api.Plugin;\nimport org.sonar.api.Plugin.Context;\nimport org.sonar.plugins.base.api.BaseApi;\n\npublic class DependentPlugin implements Plugin {\n  public DependentPlugin() {\n    // uses a class that is exported by base-plugin\n    new BaseApi().doNothing();\n  }\n\n  @Override\n  public void define(Context context) {\n    // no extensions\n  }\n}\n"
  },
  {
    "path": "backend/plugin-commons/src/test/projects/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <groupId>org.sonarsource.sonarqube.tests</groupId>\n  <artifactId>parent</artifactId>\n  <version>0.1-SNAPSHOT</version>\n  <packaging>pom</packaging>\n  <modules>\n    <module>base-plugin</module>\n    <module>dependent-plugin</module>\n  </modules>\n\n</project>\n"
  },
  {
    "path": "backend/plugin-commons/src/test/resources/logback-test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE configuration>\n\n<configuration>\n  <include resource=\"logback-shared.xml\"/>\n</configuration>"
  },
  {
    "path": "backend/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-core-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../pom.xml</relativePath>\n  </parent>\n  <artifactId>sonarlint-backend-parent</artifactId>\n  <packaging>pom</packaging>\n  <name>SonarLint Core - Backend</name>\n\n  <modules>\n    <module>analysis-engine</module>\n    <module>cli</module>\n    <module>commons</module>\n    <module>core</module>\n    <module>http</module>\n    <module>plugin-api</module>\n    <module>plugin-commons</module>\n    <module>rpc-impl</module>\n    <module>rule-extractor</module>\n    <module>server-api</module>\n    <module>server-connection</module>\n    <module>telemetry</module>\n  </modules>\n\n</project>\n"
  },
  {
    "path": "backend/rpc-impl/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-backend-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../pom.xml</relativePath>\n  </parent>\n  <artifactId>sonarlint-rpc-impl</artifactId>\n  <name>SonarLint Core - RPC Implementation</name>\n  <description>Entry point for SonarLint RPC</description>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.code.findbugs</groupId>\n      <artifactId>jsr305</artifactId>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.eclipse.lsp4j</groupId>\n      <artifactId>org.eclipse.lsp4j.jsonrpc</artifactId>\n      <version>${lsp4j.version}</version>\n    </dependency>\n    <!-- lsp4j is logging to jul -->\n    <dependency>\n      <groupId>org.slf4j</groupId>\n      <artifactId>jul-to-slf4j</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-rpc-protocol</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-core</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>ch.qos.logback</groupId>\n      <artifactId>logback-classic</artifactId>\n    </dependency>\n\n    <!-- unit tests -->\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-engine</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.assertj</groupId>\n      <artifactId>assertj-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n</project>\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/AbstractRpcServiceDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.Executor;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.function.Supplier;\nimport javax.annotation.Nullable;\nimport org.slf4j.MDC;\nimport org.sonarsource.sonarlint.core.SonarLintMDC;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.ExecutorServiceShutdownWatchable;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.springframework.beans.factory.BeanFactory;\n\nabstract class AbstractRpcServiceDelegate {\n\n  private final Supplier<BeanFactory> beanFactorySupplier;\n  private final ExecutorServiceShutdownWatchable<?> requestsExecutor;\n  private final Executor requestAndNotificationsSequentialExecutor;\n  private final Supplier<RpcClientLogOutput> logOutputSupplier;\n\n  protected AbstractRpcServiceDelegate(SonarLintRpcServerImpl server) {\n    this.beanFactorySupplier = server::getInitializedApplicationContext;\n    this.requestsExecutor = server.getRequestsExecutor();\n    this.requestAndNotificationsSequentialExecutor = server.getRequestAndNotificationsSequentialExecutor();\n    this.logOutputSupplier = server::getLogOutput;\n  }\n\n  protected <T> T getBean(Class<T> clazz) {\n    return beanFactorySupplier.get().getBean(clazz);\n  }\n\n  protected <R> CompletableFuture<R> requestAsync(Function<SonarLintCancelMonitor, R> code) {\n    return requestAsync(code, null);\n  }\n\n  protected <R> CompletableFuture<R> requestAsync(Function<SonarLintCancelMonitor, R> code, @Nullable String configScopeId) {\n    var cancelMonitor = new SonarLintCancelMonitor();\n    cancelMonitor.watchForShutdown(requestsExecutor);\n    // First we schedule the processing of the request on the sequential executor, to maintain ordering of notifications, requests, responses, and cancellations\n    // We can maybe cancel early\n    var sequentialFuture = CompletableFuture.runAsync(cancelMonitor::checkCanceled, requestAndNotificationsSequentialExecutor);\n    // Then requests are processed asynchronously to not block the processing of notifications, responses and cancellations\n    var requestFuture = sequentialFuture.thenApplyAsync(unused -> computeWithLogger(() -> {\n      cancelMonitor.checkCanceled();\n      return code.apply(cancelMonitor);\n    }, configScopeId), requestsExecutor);\n    requestFuture.whenComplete((result, error) -> {\n      if (error instanceof CancellationException) {\n        cancelMonitor.cancel();\n      }\n    });\n    return requestFuture;\n  }\n\n  protected <R> CompletableFuture<R> requestFutureAsync(Function<SonarLintCancelMonitor, CompletableFuture<R>> code, @Nullable String configScopeId) {\n    var cancelMonitor = new SonarLintCancelMonitor();\n    cancelMonitor.watchForShutdown(requestsExecutor);\n    // First we schedule the processing of the request on the sequential executor, to maintain ordering of notifications, requests, responses, and cancellations\n    // We can maybe cancel early\n    var sequentialFuture = CompletableFuture.runAsync(cancelMonitor::checkCanceled, requestAndNotificationsSequentialExecutor);\n    // Then requests are processed asynchronously to not block the processing of notifications, responses and cancellations\n    var requestFuture = sequentialFuture.thenComposeAsync(unused -> computeWithLogger(() -> {\n      cancelMonitor.checkCanceled();\n      return code.apply(cancelMonitor);\n    }, configScopeId), requestsExecutor);\n    requestFuture.whenComplete((result, error) -> {\n      if (error instanceof CancellationException) {\n        cancelMonitor.cancel();\n      }\n    });\n    return requestFuture;\n  }\n\n  protected CompletableFuture<Void> runAsync(Consumer<SonarLintCancelMonitor> code, @Nullable String configScopeId) {\n    var cancelMonitor = new SonarLintCancelMonitor();\n    cancelMonitor.watchForShutdown(requestsExecutor);\n    // First we schedule the processing of the request on the sequential executor, to maintain ordering of notifications, requests, responses, and cancellations\n    // We can maybe cancel early\n    var sequentialFuture = CompletableFuture.runAsync(cancelMonitor::checkCanceled, requestAndNotificationsSequentialExecutor);\n    // Then requests are processed asynchronously to not block the processing of notifications, responses and cancellations\n    var requestFuture = sequentialFuture.<Void>thenApplyAsync(unused -> {\n      doWithLogger(() -> {\n        cancelMonitor.checkCanceled();\n        code.accept(cancelMonitor);\n      }, configScopeId);\n      return null;\n    }, requestsExecutor);\n    requestFuture.whenComplete((result, error) -> {\n      if (error instanceof CancellationException) {\n        cancelMonitor.cancel();\n      }\n    });\n    return requestFuture;\n  }\n\n  /**\n   * We don't want to risk a long notification to block the message processor thread and to prevent cancellation of requests,\n   * so we are also moving notifications to a separate thread pool. Still we want to preserve ordering of requests and notifications.\n   */\n  protected void notify(Runnable code) {\n    notify(code, null);\n  }\n\n  protected void notify(Runnable code, @Nullable String configScopeId) {\n    requestAndNotificationsSequentialExecutor.execute(() -> doWithLogger(() -> {\n      try {\n        code.run();\n      } catch (Throwable throwable) {\n        SonarLintLogger.get().error(\"Error when handling notification\", throwable);\n      }\n    }, configScopeId));\n  }\n\n  private void doWithLogger(Runnable code, @Nullable String configScopeId) {\n    SonarLintLogger.get().setTarget(logOutputSupplier.get());\n    SonarLintMDC.putConfigScopeId(configScopeId);\n    logOutputSupplier.get().setConfigScopeId(configScopeId);\n    try {\n      code.run();\n    } finally {\n      MDC.clear();\n      SonarLintLogger.get().setTarget(null);\n      logOutputSupplier.get().setConfigScopeId(null);\n    }\n  }\n\n  private <G> G computeWithLogger(Supplier<G> code, @Nullable String configScopeId) {\n    SonarLintLogger.get().setTarget(logOutputSupplier.get());\n    SonarLintMDC.putConfigScopeId(configScopeId);\n    logOutputSupplier.get().setConfigScopeId(configScopeId);\n    try {\n      return code.get();\n    } finally {\n      MDC.clear();\n      SonarLintLogger.get().setTarget(null);\n      logOutputSupplier.get().setConfigScopeId(null);\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/AiAgentRpcServiceDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.sonarsource.sonarlint.core.ai.ide.AiAgentService;\nimport org.sonarsource.sonarlint.core.ai.ide.AiHookService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.AiAgentRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.GetHookScriptContentParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.GetHookScriptContentResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.GetRuleFileContentParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.GetRuleFileContentResponse;\n\npublic class AiAgentRpcServiceDelegate extends AbstractRpcServiceDelegate implements AiAgentRpcService {\n  public AiAgentRpcServiceDelegate(SonarLintRpcServerImpl sonarLintRpcServer) {\n    super(sonarLintRpcServer);\n  }\n\n  @Override\n  public CompletableFuture<GetRuleFileContentResponse> getRuleFileContent(GetRuleFileContentParams params) {\n    return requestAsync(cancelMonitor -> getBean(AiAgentService.class).getRuleFileContent(params.getAiAgent()));\n  }\n\n  @Override\n  public CompletableFuture<GetHookScriptContentResponse> getHookScriptContent(GetHookScriptContentParams params) {\n    return requestAsync(cancelMonitor -> getBean(AiHookService.class).getHookScriptContent(params.getAiAgent()));\n  }\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/AiCodeFixRpcServiceDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.sonarsource.sonarlint.core.remediation.aicodefix.AiCodeFixService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.remediation.aicodefix.AiCodeFixRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.remediation.aicodefix.SuggestFixParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.remediation.aicodefix.SuggestFixResponse;\n\npublic class AiCodeFixRpcServiceDelegate extends AbstractRpcServiceDelegate implements AiCodeFixRpcService {\n\n  public AiCodeFixRpcServiceDelegate(SonarLintRpcServerImpl sonarLintRpcServer) {\n    super(sonarLintRpcServer);\n  }\n\n  @Override\n  public CompletableFuture<SuggestFixResponse> suggestFix(SuggestFixParams params) {\n    return requestAsync(cancelMonitor -> getBean(AiCodeFixService.class).suggestFix(params.getConfigurationScopeId(), params.getIssueId(), cancelMonitor));\n  }\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/AnalysisRpcServiceDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.CompletableFuture;\nimport org.sonarsource.sonarlint.core.analysis.AnalysisResult;\nimport org.sonarsource.sonarlint.core.analysis.AnalysisService;\nimport org.sonarsource.sonarlint.core.analysis.NodeJsService;\nimport org.sonarsource.sonarlint.core.analysis.RawIssue;\nimport org.sonarsource.sonarlint.core.analysis.api.TriggerType;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFileListParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFullProjectParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeOpenFilesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeVCSChangedFilesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.DidChangeAnalysisPropertiesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.DidChangeAutomaticAnalysisSettingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.DidChangeClientNodeJsPathParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.DidChangePathToCompileCommandsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.ForceAnalyzeResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.GetAutoDetectedNodeJsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.GetForcedNodeJsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.GetSupportedFilePatternsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.GetSupportedFilePatternsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.NodeJsDetailsDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.ShouldUseEnterpriseCSharpAnalyzerParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.ShouldUseEnterpriseCSharpAnalyzerResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.FileEditDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.QuickFixDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.RawIssueDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.RawIssueFlowDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.RawIssueLocationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.TextEditDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;\nimport org.sonarsource.sonarlint.core.rules.RuleDetailsAdapter;\n\nimport static java.util.Objects.requireNonNull;\nimport static java.util.stream.Collectors.toMap;\n\nclass AnalysisRpcServiceDelegate extends AbstractRpcServiceDelegate implements AnalysisRpcService {\n\n  public AnalysisRpcServiceDelegate(SonarLintRpcServerImpl server) {\n    super(server);\n  }\n\n  @Override\n  public CompletableFuture<GetSupportedFilePatternsResponse> getSupportedFilePatterns(GetSupportedFilePatternsParams params) {\n    return requestAsync(\n      cancelChecker -> new GetSupportedFilePatternsResponse(getBean(AnalysisService.class).getSupportedFilePatterns(params.getConfigScopeId())),\n      params.getConfigScopeId());\n  }\n\n  @Override\n  public CompletableFuture<GetForcedNodeJsResponse> didChangeClientNodeJsPath(DidChangeClientNodeJsPathParams params) {\n    return requestAsync(cancelChecker -> {\n      var forcedNodeJs = getBean(NodeJsService.class).didChangeClientNodeJsPath(params.getClientNodeJsPath());\n      var dto = forcedNodeJs == null ? null : new NodeJsDetailsDto(forcedNodeJs.getPath(), forcedNodeJs.getVersion().toString());\n      return new GetForcedNodeJsResponse(dto);\n    });\n  }\n\n  @Override\n  public CompletableFuture<GetAutoDetectedNodeJsResponse> getAutoDetectedNodeJs() {\n    return requestAsync(cancelChecker -> {\n      var autoDetectedNodeJs = getBean(AnalysisService.class).getAutoDetectedNodeJs();\n      var dto = autoDetectedNodeJs == null ? null : new NodeJsDetailsDto(autoDetectedNodeJs.getPath(), autoDetectedNodeJs.getVersion().toString());\n      return new GetAutoDetectedNodeJsResponse(dto);\n    });\n  }\n\n  @Override\n  public CompletableFuture<AnalyzeFilesResponse> analyzeFilesAndTrack(AnalyzeFilesAndTrackParams params) {\n    var configurationScopeId = params.getConfigurationScopeId();\n    return requestFutureAsync(cancelChecker -> getBean(AnalysisService.class)\n      .scheduleAnalysis(params.getConfigurationScopeId(), params.getAnalysisId(), Set.copyOf(params.getFilesToAnalyze()),\n        params.getExtraProperties(), params.isShouldFetchServerIssues(), TriggerType.FORCED, cancelChecker)\n      .thenApply(AnalysisRpcServiceDelegate::generateAnalyzeFilesResponse), configurationScopeId);\n  }\n\n  @Override\n  public void didSetUserAnalysisProperties(DidChangeAnalysisPropertiesParams params) {\n    notify(() -> getBean(AnalysisService.class).setUserAnalysisProperties(params.getConfigurationScopeId(), params.getProperties()));\n  }\n\n  @Override\n  public void didChangePathToCompileCommands(DidChangePathToCompileCommandsParams params) {\n    notify(() -> getBean(AnalysisService.class).didChangePathToCompileCommands(params.getConfigurationScopeId(), params.getPathToCompileCommands()));\n  }\n\n  @Override\n  public void didChangeAutomaticAnalysisSetting(DidChangeAutomaticAnalysisSettingParams params) {\n    notify(() -> getBean(AnalysisService.class).didChangeAutomaticAnalysisSetting(params.isEnabled()));\n  }\n\n  @Override\n  public CompletableFuture<ForceAnalyzeResponse> analyzeFullProject(AnalyzeFullProjectParams params) {\n    return requestAsync(\n      cancelChecker -> new ForceAnalyzeResponse(getBean(AnalysisService.class)\n        .analyzeFullProject(params.getConfigScopeId(), params.isHotspotsOnly())));\n  }\n\n  @Override\n  public CompletableFuture<ForceAnalyzeResponse> analyzeFileList(AnalyzeFileListParams params) {\n    return requestAsync(\n      cancelChecker -> new ForceAnalyzeResponse(getBean(AnalysisService.class)\n        .analyzeFileList(params.getConfigScopeId(), params.getFilesToAnalyze())));\n  }\n\n  @Override\n  public CompletableFuture<ForceAnalyzeResponse> analyzeOpenFiles(AnalyzeOpenFilesParams params) {\n    return requestAsync(\n      cancelChecker -> new ForceAnalyzeResponse(getBean(AnalysisService.class).forceAnalyzeOpenFiles(params.getConfigScopeId())));\n  }\n\n  @Override\n  public CompletableFuture<ForceAnalyzeResponse> analyzeVCSChangedFiles(AnalyzeVCSChangedFilesParams params) {\n    return requestAsync(\n      cancelChecker -> new ForceAnalyzeResponse(getBean(AnalysisService.class).analyzeVCSChangedFiles(params.getConfigScopeId())));\n  }\n\n  @Override\n  public CompletableFuture<ShouldUseEnterpriseCSharpAnalyzerResponse> shouldUseEnterpriseCSharpAnalyzer(ShouldUseEnterpriseCSharpAnalyzerParams params) {\n    return requestAsync(\n      cancelChecker -> new ShouldUseEnterpriseCSharpAnalyzerResponse(getBean(AnalysisService.class)\n        .shouldUseEnterpriseCSharpAnalyzer(params.getConfigurationScopeId())));\n  }\n\n  private static AnalyzeFilesResponse generateAnalyzeFilesResponse(AnalysisResult analysisResults) {\n    return new AnalyzeFilesResponse(analysisResults.failedAnalysisFiles(), analysisResults.rawIssues().stream().map(AnalysisRpcServiceDelegate::toDto).toList());\n  }\n\n  static RawIssueDto toDto(RawIssue issue) {\n    var range = issue.getTextRange();\n    var textRange = range != null ? adapt(range) : null;\n    var fileUri = issue.getFileUri();\n    var flows = issue.getFlows().stream().map(flow -> {\n      var locations = flow.locations().stream().map(location -> {\n        var locationTextRange = location.getTextRange();\n        var locationTextRangeDto = locationTextRange == null ? null : adapt(locationTextRange);\n        var locationInputFile = location.getInputFile();\n        var locationFileUri = locationInputFile == null ? null : locationInputFile.uri();\n        return new RawIssueLocationDto(locationTextRangeDto, location.getMessage(), locationFileUri);\n      }).toList();\n      return new RawIssueFlowDto(locations);\n    }).toList();\n    return new RawIssueDto(\n      RuleDetailsAdapter.adapt(issue.getSeverity()),\n      RuleDetailsAdapter.adapt(issue.getRuleType()),\n      RuleDetailsAdapter.adapt(issue.getCleanCodeAttribute()),\n      issue.getImpacts().entrySet().stream().map(entry -> Map.entry(SoftwareQuality.valueOf(entry.getKey().name()), ImpactSeverity.valueOf(entry.getValue().name())))\n        .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)),\n      issue.getRuleKey(),\n      requireNonNull(issue.getMessage()),\n      fileUri,\n      flows,\n      issue.getQuickFixes().stream()\n        .map(quickFix -> new QuickFixDto(\n          quickFix.inputFileEdits().stream()\n            .map(fileEdit -> new FileEditDto(fileEdit.target().uri(),\n              fileEdit.textEdits().stream().map(textEdit -> new TextEditDto(adapt(textEdit.range()), textEdit.newText())).toList()))\n            .toList(),\n          quickFix.message()))\n        .toList(),\n      textRange,\n      issue.getRuleDescriptionContextKey(),\n      RuleDetailsAdapter.adapt(issue.getVulnerabilityProbability()));\n  }\n\n  private static TextRangeDto adapt(TextRange textRange) {\n    return new TextRangeDto(textRange.getStartLine(), textRange.getStartLineOffset(), textRange.getEndLine(), textRange.getEndLineOffset());\n  }\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/BackendJsonRpcLauncher.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.io.Closeable;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\npublic class BackendJsonRpcLauncher implements Closeable {\n\n  private final SonarLintRpcServerImpl server;\n\n  public BackendJsonRpcLauncher(InputStream in, OutputStream out) {\n    server = new SonarLintRpcServerImpl(in, out);\n  }\n\n  public SonarLintRpcServerImpl getServer() {\n    return server;\n  }\n\n  /**\n   * @deprecated All related codes moved to org.sonarsource.sonarlint.core.rpc.impl.SonarLintRpcServerImpl#shutdown()\n   * Calling server shutdown method is enough.\n   */\n  @Override\n  @Deprecated(since = \"10.4\", forRemoval = true)\n  public void close() {\n    // This method is used by the language server. It will be removed once the usage has been removed\n  }\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/BindingRpcServiceDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseError;\nimport org.sonarsource.sonarlint.core.BindingSuggestionProvider;\nimport org.sonarsource.sonarlint.core.SharedConnectedModeSettingsProvider;\nimport org.sonarsource.sonarlint.core.commons.SonarLintException;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.binding.BindingRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.binding.GetBindingSuggestionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.binding.GetSharedConnectedModeConfigFileParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.binding.GetSharedConnectedModeConfigFileResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.GetBindingSuggestionsResponse;\n\nclass BindingRpcServiceDelegate extends AbstractRpcServiceDelegate implements BindingRpcService {\n\n  public BindingRpcServiceDelegate(SonarLintRpcServerImpl server) {\n    super(server);\n  }\n\n  @Override\n  public CompletableFuture<GetBindingSuggestionsResponse> getBindingSuggestions(GetBindingSuggestionParams params) {\n    return requestAsync(\n      cancelMonitor -> new GetBindingSuggestionsResponse(\n        getBean(BindingSuggestionProvider.class).getBindingSuggestions(params.getConfigScopeId(), params.getConnectionId(), cancelMonitor)),\n      params.getConfigScopeId());\n  }\n\n  @Override\n  public CompletableFuture<GetSharedConnectedModeConfigFileResponse> getSharedConnectedModeConfigFileContents(GetSharedConnectedModeConfigFileParams params) {\n    return requestAsync(cancelMonitor -> {\n      try {\n        return new GetSharedConnectedModeConfigFileResponse(\n          getBean(SharedConnectedModeSettingsProvider.class).getSharedConnectedModeConfigFileContents(params.getConfigScopeId()));\n      } catch (SonarLintException e) {\n        var error = new ResponseError(SonarLintRpcErrorCode.CONFIG_SCOPE_NOT_BOUND, e.getMessage(), params.getConfigScopeId());\n        throw new ResponseErrorException(error);\n      }\n    }, params.getConfigScopeId());\n  }\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/ConfigurationRpcServiceDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport org.sonarsource.sonarlint.core.ConfigurationService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.ConfigurationRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidRemoveConfigurationScopeParams;\n\nclass ConfigurationRpcServiceDelegate extends AbstractRpcServiceDelegate implements ConfigurationRpcService {\n\n  public ConfigurationRpcServiceDelegate(SonarLintRpcServerImpl server) {\n    super(server);\n  }\n\n  @Override\n  public void didAddConfigurationScopes(DidAddConfigurationScopesParams params) {\n    notify(() -> getBean(ConfigurationService.class).didAddConfigurationScopes(params.getAddedScopes()));\n  }\n\n  @Override\n  public void didRemoveConfigurationScope(DidRemoveConfigurationScopeParams params) {\n    notify(() -> getBean(ConfigurationService.class).didRemoveConfigurationScope(params.getRemovedId()));\n  }\n\n  @Override\n  public void didUpdateBinding(DidUpdateBindingParams params) {\n    notify(() -> getBean(ConfigurationService.class).didUpdateBinding(params.getConfigScopeId(), params.getUpdatedBinding()\n    ));\n  }\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/ConnectionRpcServiceDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseError;\nimport org.sonarsource.sonarlint.core.ConnectionService;\nimport org.sonarsource.sonarlint.core.ConnectionSuggestionProvider;\nimport org.sonarsource.sonarlint.core.MCPServerConfigurationProvider;\nimport org.sonarsource.sonarlint.core.OrganizationsCache;\nimport org.sonarsource.sonarlint.core.SonarProjectsCache;\nimport org.sonarsource.sonarlint.core.commons.SonarLintException;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.ConnectionRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.GetConnectionSuggestionsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.GetMCPServerConfigurationParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.GetMCPServerConfigurationResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth.HelpGenerateUserTokenParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth.HelpGenerateUserTokenResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarCloudConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidChangeCredentialsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidUpdateConnectionsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.FuzzySearchUserOrganizationsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.FuzzySearchUserOrganizationsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.GetOrganizationParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.GetOrganizationResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.ListUserOrganizationsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.ListUserOrganizationsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.FuzzySearchProjectsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.FuzzySearchProjectsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.GetAllProjectsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.GetAllProjectsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.GetProjectNamesByKeyParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.GetProjectNamesByKeyResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.validate.ValidateConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.validate.ValidateConnectionResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.GetConnectionSuggestionsParams;\n\nclass ConnectionRpcServiceDelegate extends AbstractRpcServiceDelegate implements ConnectionRpcService {\n\n  public ConnectionRpcServiceDelegate(SonarLintRpcServerImpl server) {\n    super(server);\n  }\n\n  @Override\n  public void didUpdateConnections(DidUpdateConnectionsParams params) {\n    notify(() -> getBean(ConnectionService.class).didUpdateConnections(params.getSonarQubeConnections(), params.getSonarCloudConnections()));\n  }\n\n  @Override\n  public void didChangeCredentials(DidChangeCredentialsParams params) {\n    notify(() -> getBean(ConnectionService.class).didChangeCredentials(params.getConnectionId()));\n  }\n\n  @Override\n  public CompletableFuture<HelpGenerateUserTokenResponse> helpGenerateUserToken(HelpGenerateUserTokenParams params) {\n    return requestAsync(cancelMonitor -> getBean(ConnectionService.class).helpGenerateUserToken(params.getServerUrl(), params.getUtm(), cancelMonitor));\n  }\n\n  @Override\n  public CompletableFuture<ValidateConnectionResponse> validateConnection(ValidateConnectionParams params) {\n    return requestAsync(cancelMonitor -> getBean(ConnectionService.class).validateConnection(params.getTransientConnection(), cancelMonitor));\n  }\n\n  @Override\n  public CompletableFuture<ListUserOrganizationsResponse> listUserOrganizations(ListUserOrganizationsParams params) {\n    return requestAsync(cancelMonitor -> new ListUserOrganizationsResponse(getBean(OrganizationsCache.class)\n      .listUserOrganizations(new TransientSonarCloudConnectionDto(null, params.getCredentials(), params.getRegion()), cancelMonitor)));\n  }\n\n  @Override\n  public CompletableFuture<GetOrganizationResponse> getOrganization(GetOrganizationParams params) {\n    return requestAsync(cancelMonitor -> new GetOrganizationResponse(getBean(OrganizationsCache.class)\n      .getOrganization(new TransientSonarCloudConnectionDto(params.getOrganizationKey(), params.getCredentials(), params.getRegion()), cancelMonitor)));\n  }\n\n  @Override\n  public CompletableFuture<FuzzySearchUserOrganizationsResponse> fuzzySearchUserOrganizations(FuzzySearchUserOrganizationsParams params) {\n    return requestAsync(cancelMonitor -> new FuzzySearchUserOrganizationsResponse(getBean(OrganizationsCache.class)\n      .fuzzySearchOrganizations(new TransientSonarCloudConnectionDto(null, params.getCredentials(), params.getRegion()), params.getSearchText(), cancelMonitor)));\n  }\n\n  @Override\n  public CompletableFuture<GetAllProjectsResponse> getAllProjects(GetAllProjectsParams params) {\n    return requestAsync(cancelMonitor -> new GetAllProjectsResponse(getBean(ConnectionService.class).getAllProjects(params.getTransientConnection(), cancelMonitor)));\n  }\n\n  @Override\n  public CompletableFuture<FuzzySearchProjectsResponse> fuzzySearchProjects(FuzzySearchProjectsParams params) {\n    return requestAsync(cancelMonitor -> new FuzzySearchProjectsResponse(getBean(SonarProjectsCache.class)\n      .fuzzySearchProjects(params.getConnectionId(), params.getSearchText(), cancelMonitor)));\n  }\n\n  @Override\n  public CompletableFuture<GetProjectNamesByKeyResponse> getProjectNamesByKey(GetProjectNamesByKeyParams params) {\n    return requestAsync(cancelMonitor -> new GetProjectNamesByKeyResponse(getBean(ConnectionService.class)\n      .getProjectNamesByKey(params.getTransientConnection(), params.getProjectKeys(), cancelMonitor)));\n  }\n\n  @Override\n  public CompletableFuture<GetConnectionSuggestionsResponse> getConnectionSuggestions(GetConnectionSuggestionsParams params) {\n    return requestAsync(\n      cancelMonitor -> new GetConnectionSuggestionsResponse(getBean(ConnectionSuggestionProvider.class)\n        .getConnectionSuggestions(params.getConfigurationScopeId(), cancelMonitor)));\n  }\n\n  @Override\n  public CompletableFuture<GetMCPServerConfigurationResponse> getMCPServerConfiguration(GetMCPServerConfigurationParams params) {\n    return requestAsync(cancelMonitor -> {\n      try {\n        return new GetMCPServerConfigurationResponse(\n          getBean(MCPServerConfigurationProvider.class).getMCPServerConfigurationJSON(params.getConnectionId(), params.getToken()));\n      } catch (SonarLintException e) {\n        var error = new ResponseError(SonarLintRpcErrorCode.CONNECTION_NOT_FOUND, e.getMessage(), params.getConnectionId());\n        throw new ResponseErrorException(error);\n      }\n    });\n  }\n\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/DependencyRiskRpcServiceDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseError;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.ChangeDependencyRiskStatusParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.CheckDependencyRiskSupportedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.CheckDependencyRiskSupportedResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.DependencyRiskRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.ListAllDependencyRisksResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.OpenDependencyRiskInBrowserParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.ListAllParams;\nimport org.sonarsource.sonarlint.core.sca.DependencyRiskService;\n\npublic class DependencyRiskRpcServiceDelegate extends AbstractRpcServiceDelegate implements DependencyRiskRpcService {\n\n  public DependencyRiskRpcServiceDelegate(SonarLintRpcServerImpl server) {\n    super(server);\n  }\n\n  @Override\n  public CompletableFuture<ListAllDependencyRisksResponse> listAll(ListAllParams params) {\n    return requestAsync(cancelMonitor -> new ListAllDependencyRisksResponse(getBean(DependencyRiskService.class)\n      .listAll(params.getConfigurationScopeId(), params.shouldRefresh(), cancelMonitor)));\n  }\n\n  @Override\n  public CompletableFuture<Void> changeStatus(ChangeDependencyRiskStatusParams params) {\n    return runAsync(cancelMonitor -> {\n      try {\n        getBean(DependencyRiskService.class).changeStatus(\n          params.getConfigurationScopeId(),\n          params.getDependencyRiskKey(),\n          params.getTransition(),\n          params.getComment(),\n          cancelMonitor);\n      } catch (DependencyRiskService.DependencyRiskNotFoundException e) {\n        var error = new ResponseError(SonarLintRpcErrorCode.ISSUE_NOT_FOUND,\n          \"Dependency Risk with key \" + e.getKey() + \" was not found\", e.getKey());\n        throw new ResponseErrorException(error);\n      } catch (IllegalArgumentException e) {\n        var error = new ResponseError(SonarLintRpcErrorCode.INVALID_ARGUMENT, e.getMessage(), null);\n        throw new ResponseErrorException(error);\n      }\n    }, params.getConfigurationScopeId());\n  }\n\n  @Override\n  public CompletableFuture<Void> openDependencyRiskInBrowser(OpenDependencyRiskInBrowserParams params) {\n    return runAsync(cancelMonitor -> {\n      try {\n        getBean(DependencyRiskService.class).openDependencyRiskInBrowser(\n          params.getConfigScopeId(),\n          params.getDependencyRiskKey());\n      } catch (IllegalArgumentException e) {\n        var error = new ResponseError(SonarLintRpcErrorCode.INVALID_ARGUMENT, e.getMessage(), null);\n        throw new ResponseErrorException(error);\n      }\n    }, params.getConfigScopeId());\n  }\n\n  @Override\n  public CompletableFuture<CheckDependencyRiskSupportedResponse> checkSupported(CheckDependencyRiskSupportedParams params) {\n    return requestAsync(cancelMonitor ->\n      getBean(DependencyRiskService.class).checkSupported(params.getConfigurationScopeId()), params.getConfigurationScopeId());\n  }\n\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/DogfoodingRpcServiceDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.sonarsource.sonarlint.core.commons.dogfood.DogfoodEnvironmentDetectionService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.dogfooding.DogfoodingRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.dogfooding.IsDogfoodingEnvironmentResponse;\n\npublic class DogfoodingRpcServiceDelegate extends AbstractRpcServiceDelegate implements DogfoodingRpcService {\n  private final DogfoodEnvironmentDetectionService dogfoodEnvironmentDetectionService;\n  public DogfoodingRpcServiceDelegate(SonarLintRpcServerImpl sonarLintRpcServer) {\n    super(sonarLintRpcServer);\n    this.dogfoodEnvironmentDetectionService = new DogfoodEnvironmentDetectionService();\n  }\n\n  @Override\n  public CompletableFuture<IsDogfoodingEnvironmentResponse> isDogfoodingEnvironment() {\n    return requestAsync(cancelMonitor -> new IsDogfoodingEnvironmentResponse(dogfoodEnvironmentDetectionService.isDogfoodEnvironment()));\n  }\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/FileRpcServiceDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.sonarsource.sonarlint.core.fs.ClientFileSystemService;\nimport org.sonarsource.sonarlint.core.fs.FileExclusionService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidCloseFileParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidOpenFileParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidUpdateFileSystemParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.FileRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.GetFilesStatusParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.GetFilesStatusResponse;\n\npublic class FileRpcServiceDelegate extends AbstractRpcServiceDelegate implements FileRpcService {\n\n  protected FileRpcServiceDelegate(SonarLintRpcServerImpl server) {\n    super(server);\n  }\n\n  @Override\n  public CompletableFuture<GetFilesStatusResponse> getFilesStatus(GetFilesStatusParams params) {\n    return requestAsync(cancelChecker -> {\n      var statuses = getBean(FileExclusionService.class).getFilesStatus(params.getFileUrisByConfigScopeId());\n      return new GetFilesStatusResponse(statuses);\n    });\n  }\n\n  @Override\n  public void didUpdateFileSystem(DidUpdateFileSystemParams params) {\n    notify(() -> getBean(ClientFileSystemService.class).didUpdateFileSystem(params));\n  }\n\n  @Override\n  public void didOpenFile(DidOpenFileParams params) {\n    notify(() -> getBean(ClientFileSystemService.class).didOpenFile(params.getConfigurationScopeId(), params.getFileUri()));\n  }\n\n  @Override\n  public void didCloseFile(DidCloseFileParams params) {\n    notify(() -> getBean(ClientFileSystemService.class).didCloseFile(params.getConfigurationScopeId(), params.getFileUri()));\n  }\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/HotspotRpcServiceDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.hotspot.HotspotService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.ChangeHotspotStatusParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.CheckLocalDetectionSupportedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.CheckLocalDetectionSupportedResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.CheckStatusChangePermittedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.CheckStatusChangePermittedResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.HotspotRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.HotspotStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.OpenHotspotInBrowserParams;\n\nclass HotspotRpcServiceDelegate extends AbstractRpcServiceDelegate implements HotspotRpcService {\n\n  public HotspotRpcServiceDelegate(SonarLintRpcServerImpl server) {\n    super(server);\n  }\n\n  @Override\n  public void openHotspotInBrowser(OpenHotspotInBrowserParams params) {\n    notify(() -> getBean(HotspotService.class).openHotspotInBrowser(params.getConfigScopeId(), params.getHotspotKey()), params.getConfigScopeId());\n  }\n\n  @Override\n  public CompletableFuture<CheckLocalDetectionSupportedResponse> checkLocalDetectionSupported(CheckLocalDetectionSupportedParams params) {\n    return requestAsync(cancelChecker -> getBean(HotspotService.class).checkLocalDetectionSupported(params.getConfigScopeId()), params.getConfigScopeId());\n  }\n\n  @Override\n  public CompletableFuture<CheckStatusChangePermittedResponse> checkStatusChangePermitted(CheckStatusChangePermittedParams params) {\n    return requestAsync(cancelChecker -> getBean(HotspotService.class).checkStatusChangePermitted(params.getConnectionId(), params.getHotspotKey(), cancelChecker));\n  }\n\n  @Override\n  public CompletableFuture<Void> changeStatus(ChangeHotspotStatusParams params) {\n    return runAsync(\n      cancelMonitor -> getBean(HotspotService.class).changeStatus(params.getConfigurationScopeId(), params.getHotspotKey(), adapt(params.getNewStatus()), cancelMonitor),\n      params.getConfigurationScopeId());\n  }\n\n  private static HotspotReviewStatus adapt(HotspotStatus newStatus) {\n    return HotspotReviewStatus.valueOf(newStatus.name());\n  }\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/IdeLabsRpcServiceDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.sonarsource.sonarlint.core.labs.IdeLabsService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.labs.IdeLabsRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.labs.JoinIdeLabsProgramParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.labs.JoinIdeLabsProgramResponse;\n\npublic class IdeLabsRpcServiceDelegate extends AbstractRpcServiceDelegate implements IdeLabsRpcService {\n  public IdeLabsRpcServiceDelegate(SonarLintRpcServerImpl server) {\n    super(server);\n  }\n\n  @Override\n  public CompletableFuture<JoinIdeLabsProgramResponse> joinIdeLabsProgram(JoinIdeLabsProgramParams params) {\n    return requestAsync(cancelChecker -> getBean(IdeLabsService.class).joinIdeLabsProgram(params.getEmail(), params.getIde()));\n  }\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/IssueRpcServiceDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseError;\nimport org.sonarsource.sonarlint.core.issue.IssueNotFoundException;\nimport org.sonarsource.sonarlint.core.issue.IssueService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.AddIssueCommentParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ChangeIssueStatusParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.CheckAnticipatedStatusChangeSupportedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.CheckAnticipatedStatusChangeSupportedResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.CheckStatusChangePermittedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.CheckStatusChangePermittedResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.GetEffectiveIssueDetailsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.GetEffectiveIssueDetailsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.IssueRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ReopenAllIssuesForFileParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ReopenAllIssuesForFileResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ReopenIssueParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ReopenIssueResponse;\nimport org.sonarsource.sonarlint.core.rules.RuleNotFoundException;\n\npublic class IssueRpcServiceDelegate extends AbstractRpcServiceDelegate implements IssueRpcService {\n  public IssueRpcServiceDelegate(SonarLintRpcServerImpl server) {\n    super(server);\n  }\n\n  @Override\n  public CompletableFuture<Void> changeStatus(ChangeIssueStatusParams params) {\n    return runAsync(cancelMonitor -> getBean(IssueService.class).changeStatus(params.getConfigurationScopeId(), params.getIssueKey(), params.getNewStatus(), params.isTaintIssue(),\n      cancelMonitor), params.getConfigurationScopeId());\n  }\n\n  @Override\n  public CompletableFuture<Void> addComment(AddIssueCommentParams params) {\n    return runAsync(cancelMonitor -> getBean(IssueService.class).addComment(params.getConfigurationScopeId(), params.getIssueKey(), params.getText(), cancelMonitor),\n      params.getConfigurationScopeId());\n  }\n\n  @Override\n  public CompletableFuture<CheckAnticipatedStatusChangeSupportedResponse> checkAnticipatedStatusChangeSupported(CheckAnticipatedStatusChangeSupportedParams params) {\n    return requestAsync(cancelMonitor -> new CheckAnticipatedStatusChangeSupportedResponse(\n      getBean(IssueService.class).checkAnticipatedStatusChangeSupported(params.getConfigScopeId())), params.getConfigScopeId());\n  }\n\n  @Override\n  public CompletableFuture<CheckStatusChangePermittedResponse> checkStatusChangePermitted(CheckStatusChangePermittedParams params) {\n    return requestAsync(cancelMonitor -> getBean(IssueService.class).checkStatusChangePermitted(params.getConnectionId(), params.getIssueKey(), cancelMonitor));\n  }\n\n  @Override\n  public CompletableFuture<ReopenIssueResponse> reopenIssue(ReopenIssueParams params) {\n    return requestAsync(\n      cancelMonitor -> new ReopenIssueResponse(\n        getBean(IssueService.class).reopenIssue(params.getConfigurationScopeId(), params.getIssueId(), params.isTaintIssue(), cancelMonitor)),\n      params.getConfigurationScopeId());\n  }\n\n  @Override\n  public CompletableFuture<ReopenAllIssuesForFileResponse> reopenAllIssuesForFile(ReopenAllIssuesForFileParams params) {\n    return requestAsync(cancelMonitor -> new ReopenAllIssuesForFileResponse(getBean(IssueService.class).reopenAllIssuesForFile(params, cancelMonitor)),\n      params.getConfigurationScopeId());\n  }\n\n  @Override\n  public CompletableFuture<GetEffectiveIssueDetailsResponse> getEffectiveIssueDetails(GetEffectiveIssueDetailsParams params) {\n    return requestAsync(cancelMonitor -> {\n      try {\n        return new GetEffectiveIssueDetailsResponse(getBean(IssueService.class)\n          .getEffectiveIssueDetails(params.getConfigurationScopeId(), params.getIssueId(), cancelMonitor));\n      } catch (IssueNotFoundException e) {\n        var error = new ResponseError(SonarLintRpcErrorCode.ISSUE_NOT_FOUND, e.getMessage(), e.getIssueKey());\n        throw new ResponseErrorException(error);\n      } catch (RuleNotFoundException e) {\n        var error = new ResponseError(SonarLintRpcErrorCode.RULE_NOT_FOUND, e.getMessage(), e.getRuleKey());\n        throw new ResponseErrorException(error);\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/LogServiceDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport org.sonarsource.sonarlint.core.log.LogService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.log.LogRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.log.SetLogLevelParams;\n\npublic class LogServiceDelegate extends AbstractRpcServiceDelegate implements LogRpcService {\n  public LogServiceDelegate(SonarLintRpcServerImpl sonarLintRpcServer) {\n    super(sonarLintRpcServer);\n  }\n\n  @Override\n  public void setLogLevel(SetLogLevelParams params) {\n    notify(() -> getBean(LogService.class).setLogLevel(params.getNewLevel()));\n  }\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/NewCodeRpcServiceDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.sonarsource.sonarlint.core.newcode.NewCodeService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.newcode.GetNewCodeDefinitionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.newcode.GetNewCodeDefinitionResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.newcode.NewCodeRpcService;\n\npublic class NewCodeRpcServiceDelegate extends AbstractRpcServiceDelegate implements NewCodeRpcService {\n\n  public NewCodeRpcServiceDelegate(SonarLintRpcServerImpl server) {\n    super(server);\n  }\n\n  @Override\n  public CompletableFuture<GetNewCodeDefinitionResponse> getNewCodeDefinition(GetNewCodeDefinitionParams params) {\n    return requestAsync(cancelMonitor -> getBean(NewCodeService.class).getNewCodeDefinition(params.getConfigScopeId()), params.getConfigScopeId());\n  }\n\n  @Override\n  public void didToggleFocus() {\n    notify(() -> getBean(NewCodeService.class).didToggleFocus());\n  }\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/PluginRpcServiceDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.util.concurrent.CompletableFuture;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.Binding;\nimport org.sonarsource.sonarlint.core.plugin.PluginStatusMapper;\nimport org.sonarsource.sonarlint.core.plugin.PluginsService;\nimport org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.GetPluginStatusesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.GetPluginStatusesResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.PluginRpcService;\n\npublic class PluginRpcServiceDelegate extends AbstractRpcServiceDelegate implements PluginRpcService {\n\n  public PluginRpcServiceDelegate(SonarLintRpcServerImpl server) {\n    super(server);\n  }\n\n  @Override\n  public CompletableFuture<GetPluginStatusesResponse> getPluginStatuses(GetPluginStatusesParams params) {\n    return requestAsync(cancelMonitor -> {\n      var configScopeId = params.getConfigurationScopeId();\n      var connectionId = resolveConnectionId(configScopeId);\n      var statuses = getBean(PluginsService.class).getPluginStatuses(connectionId);\n      return new GetPluginStatusesResponse(PluginStatusMapper.toDto(statuses));\n    }, params.getConfigurationScopeId());\n  }\n\n  @Nullable\n  private String resolveConnectionId(@Nullable String configurationScopeId) {\n    if (configurationScopeId == null) {\n      return null;\n    }\n    return getBean(ConfigurationRepository.class)\n      .getEffectiveBinding(configurationScopeId)\n      .map(Binding::connectionId)\n      .orElse(null);\n  }\n\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/RpcClientLogOutput.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.time.Instant;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogLevel;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\n\nclass RpcClientLogOutput implements LogOutput {\n\n  private final SonarLintRpcClient client;\n\n  private final InheritableThreadLocal<String> configScopeId = new InheritableThreadLocal<>();\n\n  RpcClientLogOutput(SonarLintRpcClient client) {\n    this.client = client;\n  }\n\n  @Override\n  public void log(@Nullable String msg, Level level, @Nullable String stacktrace) {\n    client.log(new LogParams(LogLevel.valueOf(level.name()), msg, configScopeId.get(), stacktrace, Instant.now()));\n  }\n\n  public void setConfigScopeId(@Nullable String configScopeId) {\n    this.configScopeId.set(configScopeId);\n  }\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/RulesRpcServiceDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseError;\nimport org.sonarsource.sonarlint.core.active.rules.ActiveRulesService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.GetEffectiveRuleDetailsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.GetEffectiveRuleDetailsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.GetStandaloneRuleDescriptionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.GetStandaloneRuleDescriptionResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.ListAllStandaloneRulesDefinitionsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RulesRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.UpdateStandaloneRulesConfigurationParams;\nimport org.sonarsource.sonarlint.core.rules.RuleNotFoundException;\nimport org.sonarsource.sonarlint.core.rules.RulesService;\n\nclass RulesRpcServiceDelegate extends AbstractRpcServiceDelegate implements RulesRpcService {\n\n  public RulesRpcServiceDelegate(SonarLintRpcServerImpl server) {\n    super(server);\n  }\n\n  @Override\n  public CompletableFuture<GetEffectiveRuleDetailsResponse> getEffectiveRuleDetails(GetEffectiveRuleDetailsParams params) {\n    return requestAsync(cancelMonitor -> {\n      try {\n        return new GetEffectiveRuleDetailsResponse(\n          getBean(ActiveRulesService.class).getEffectiveRuleDetails(params.getConfigurationScopeId(), params.getRuleKey(), params.getContextKey(), cancelMonitor));\n      } catch (RuleNotFoundException e) {\n        var error = new ResponseError(SonarLintRpcErrorCode.RULE_NOT_FOUND, e.getMessage(), e.getRuleKey());\n        throw new ResponseErrorException(error);\n      }\n    }, params.getConfigurationScopeId());\n  }\n\n  @Override\n  public CompletableFuture<ListAllStandaloneRulesDefinitionsResponse> listAllStandaloneRulesDefinitions() {\n    return requestAsync(cancelMonitor -> new ListAllStandaloneRulesDefinitionsResponse(getBean(RulesService.class).listAllStandaloneRulesDefinitions()));\n  }\n\n  @Override\n  public CompletableFuture<GetStandaloneRuleDescriptionResponse> getStandaloneRuleDetails(GetStandaloneRuleDescriptionParams params) {\n    return requestAsync(cancelMonitor -> getBean(ActiveRulesService.class).getStandaloneRuleDescription(params.getRuleKey()));\n  }\n\n  @Override\n  public void updateStandaloneRulesConfiguration(UpdateStandaloneRulesConfigurationParams params) {\n    notify(() -> getBean(ActiveRulesService.class).updateStandaloneRulesConfiguration(params.getRuleConfigByKey()));\n  }\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/SonarLintRpcClientLogbackAppender.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport ch.qos.logback.classic.pattern.ThrowableProxyConverter;\nimport ch.qos.logback.classic.spi.ILoggingEvent;\nimport ch.qos.logback.classic.spi.IThrowableProxy;\nimport ch.qos.logback.core.AppenderBase;\nimport java.time.Instant;\nimport org.sonarsource.sonarlint.core.SonarLintMDC;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogLevel;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\n\nclass SonarLintRpcClientLogbackAppender extends AppenderBase<ILoggingEvent> {\n\n  private final SonarLintRpcClient rpcClient;\n  private final ThrowableProxyConverter tpc = new ThrowableProxyConverter();\n\n  public SonarLintRpcClientLogbackAppender(SonarLintRpcClient client) {\n    rpcClient = client;\n  }\n\n  @Override\n  public void start() {\n    tpc.start();\n    super.start();\n  }\n\n  @Override\n  protected void append(ILoggingEvent eventObject) {\n    var configScopeId = eventObject.getMDCPropertyMap().get(SonarLintMDC.CONFIG_SCOPE_ID_MDC_KEY);\n    var threadName = eventObject.getThreadName();\n    var loggerName = eventObject.getLoggerName();\n    var formattedMessage = eventObject.getFormattedMessage();\n    var loggedAt = Instant.ofEpochMilli(eventObject.getTimeStamp());\n    IThrowableProxy tp = eventObject.getThrowableProxy();\n    String stackTrace = null;\n    if (tp != null) {\n      stackTrace = tpc.convert(eventObject);\n    }\n    rpcClient.log(new LogParams(LogLevel.valueOf(eventObject.getLevel().levelStr), formattedMessage, configScopeId, threadName, loggerName, stackTrace, loggedAt));\n  }\n\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/SonarLintRpcServerImpl.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport ch.qos.logback.classic.Level;\nimport com.google.common.util.concurrent.MoreExecutors;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.PrintWriter;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Paths;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.CompletionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport jetbrains.exodus.core.execution.JobProcessor;\nimport jetbrains.exodus.core.execution.ThreadJobProcessorPool;\nimport org.eclipse.lsp4j.jsonrpc.CompletableFutures;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseError;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.slf4j.bridge.SLF4JBridgeHandler;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.ExecutorServiceShutdownWatchable;\nimport org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabase;\nimport org.sonarsource.sonarlint.core.embedded.server.EmbeddedServer;\nimport org.sonarsource.sonarlint.core.log.LogService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.RpcErrorHandler;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SingleThreadedMessageConsumer;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintLauncherBuilder;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.AiAgentRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.binding.BindingRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.branch.SonarProjectBranchRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.ConfigurationRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.ConnectionRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.dogfooding.DogfoodingRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.FileRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.HotspotRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.IssueRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.labs.IdeLabsRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.log.LogRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.newcode.NewCodeRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.PluginRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.progress.TaskProgressRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.remediation.aicodefix.AiCodeFixRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RulesRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.DependencyRiskRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.TelemetryRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityTrackingRpcService;\nimport org.sonarsource.sonarlint.core.serverapi.exception.ServerRequestException;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.LocalOnlyIssuesRepository;\nimport org.sonarsource.sonarlint.core.spring.SpringApplicationContextInitializer;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.springframework.context.ConfigurableApplicationContext;\n\npublic class SonarLintRpcServerImpl implements SonarLintRpcServer {\n\n  private static final Logger LOG = LoggerFactory.getLogger(SonarLintRpcServerImpl.class);\n  private final SonarLintRpcClient client;\n  private final AtomicBoolean initializeCalled = new AtomicBoolean(false);\n  private final AtomicBoolean initialized = new AtomicBoolean(false);\n  private final Future<Void> clientListener;\n  private final ExecutorServiceShutdownWatchable<ExecutorService> requestsExecutor;\n  private final ExecutorService requestAndNotificationsSequentialExecutor;\n  private final RpcClientLogOutput logOutput;\n  private final ExecutorService messageReaderExecutor;\n  private final ExecutorService messageWriterExecutor;\n  private SpringApplicationContextInitializer springApplicationContextInitializer;\n\n  public SonarLintRpcServerImpl(InputStream in, OutputStream out) {\n    this.messageReaderExecutor = Executors.newCachedThreadPool(r -> {\n      var t = new Thread(r);\n      t.setName(\"Server message reader\");\n      return t;\n    });\n    this.messageWriterExecutor = Executors.newCachedThreadPool(r -> {\n      var t = new Thread(r);\n      t.setName(\"Server message writer\");\n      return t;\n    });\n    this.requestAndNotificationsSequentialExecutor = Executors.newSingleThreadExecutor(r -> new Thread(r, \"SonarLint Server RPC sequential executor\"));\n    this.requestsExecutor = new ExecutorServiceShutdownWatchable<>(Executors.newCachedThreadPool(r -> new Thread(r, \"SonarLint Server RPC request executor\")));\n    var launcher = new SonarLintLauncherBuilder<SonarLintRpcClient>()\n      .setLocalService(this)\n      .setRemoteInterface(SonarLintRpcClient.class)\n      .setInput(in)\n      .setOutput(out)\n      .setExecutorService(messageReaderExecutor)\n      .wrapMessages(m -> new SingleThreadedMessageConsumer(m, messageWriterExecutor, System.err::println))\n      .traceMessages(getMessageTracer())\n      .setExceptionHandler(this::handleError)\n      .create();\n\n    this.client = launcher.getRemoteProxy();\n    this.logOutput = new RpcClientLogOutput(client);\n\n    // Remove existing handlers attached to j.u.l root logger\n    SLF4JBridgeHandler.removeHandlersForRootLogger();\n    // add SLF4JBridgeHandler to j.u.l's root logger, should be done once during\n    // the initialization phase of your application\n    SLF4JBridgeHandler.install();\n\n    var rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);\n    rootLogger.detachAndStopAllAppenders();\n    var rpcAppender = new SonarLintRpcClientLogbackAppender(client);\n    rpcAppender.start();\n    rootLogger.addAppender(rpcAppender);\n\n    this.clientListener = launcher.startListening();\n  }\n\n  private ResponseError handleError(Throwable throwable) {\n    if (shouldSkipExceptionCapture(throwable)) {\n      return new ResponseError(ResponseErrorCode.RequestFailed, throwable.getMessage(), toStringStacktrace(throwable));\n    }\n    return RpcErrorHandler.handleError(throwable);\n  }\n\n  private static boolean shouldSkipExceptionCapture(Throwable throwable) {\n    return throwable instanceof ServerRequestException\n      || (throwable instanceof CompletionException && throwable.getCause() instanceof ServerRequestException);\n  }\n\n  private static String toStringStacktrace(Throwable throwable) {\n    var sw = new java.io.StringWriter();\n    throwable.printStackTrace(new PrintWriter(sw));\n    return sw.toString();\n  }\n\n  private static PrintWriter getMessageTracer() {\n    if (\"true\".equals(System.getProperty(\"sonarlint.debug.rpc\"))) {\n      try {\n        return new PrintWriter(Paths.get(System.getProperty(\"user.home\")).resolve(\".sonarlint\").resolve(\"rpc_backend_session.log\").toFile(), StandardCharsets.UTF_8);\n      } catch (IOException e) {\n        System.err.println(\"Cannot write rpc debug logs file\");\n        e.printStackTrace();\n      }\n    }\n    return null;\n  }\n\n  public Future<Void> getClientListener() {\n    return clientListener;\n  }\n\n  @Override\n  public CompletableFuture<Void> initialize(InitializeParams params) {\n    return CompletableFutures.computeAsync(requestAndNotificationsSequentialExecutor, cancelChecker -> {\n      SonarLintLogger.get().setLevel(LogService.convert(params.getLogLevel()));\n      SonarLintLogger.get().setTarget(logOutput);\n      // for flyway logging level\n      setLogbackRootLogger(params);\n      if (initializeCalled.compareAndSet(false, true) && !initialized.get()) {\n        springApplicationContextInitializer = new SpringApplicationContextInitializer(client, params);\n        initialized.set(true);\n      } else {\n        var error = new ResponseError(SonarLintRpcErrorCode.BACKEND_ALREADY_INITIALIZED, \"Backend already initialized\", null);\n        throw new ResponseErrorException(error);\n      }\n      return null;\n    });\n  }\n\n  private static void setLogbackRootLogger(InitializeParams params) {\n    var root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);\n    var logLevel = switch (params.getLogLevel()) {\n      case OFF -> Level.OFF;\n      case ERROR -> Level.ERROR;\n      case WARN -> Level.WARN;\n      case INFO -> Level.INFO;\n      case DEBUG -> Level.DEBUG;\n      case TRACE -> Level.TRACE;\n    };\n    root.setLevel(logLevel);\n  }\n\n  public ConfigurableApplicationContext getInitializedApplicationContext() {\n    if (!initialized.get()) {\n      throw new IllegalStateException(\"Backend is not initialized\");\n    }\n    return springApplicationContextInitializer.getInitializedApplicationContext();\n  }\n\n  @Override\n  public ConnectionRpcService getConnectionService() {\n    return new ConnectionRpcServiceDelegate(this);\n  }\n\n  @Override\n  public ConfigurationRpcService getConfigurationService() {\n    return new ConfigurationRpcServiceDelegate(this);\n  }\n\n  @Override\n  public FileRpcService getFileService() {\n    return new FileRpcServiceDelegate(this);\n  }\n\n  @Override\n  public HotspotRpcService getHotspotService() {\n    return new HotspotRpcServiceDelegate(this);\n  }\n\n  @Override\n  public TelemetryRpcService getTelemetryService() {\n    return new TelemetryRpcServiceDelegate(this);\n  }\n\n  @Override\n  public AnalysisRpcService getAnalysisService() {\n    return new AnalysisRpcServiceDelegate(this);\n  }\n\n  @Override\n  public RulesRpcService getRulesService() {\n    return new RulesRpcServiceDelegate(this);\n  }\n\n  @Override\n  public BindingRpcService getBindingService() {\n    return new BindingRpcServiceDelegate(this);\n  }\n\n  public SonarProjectBranchRpcService getSonarProjectBranchService() {\n    return new SonarProjectBranchRpcServiceDelegate(this);\n  }\n\n  @Override\n  public IssueRpcService getIssueService() {\n    return new IssueRpcServiceDelegate(this);\n  }\n\n  @Override\n  public NewCodeRpcService getNewCodeService() {\n    return new NewCodeRpcServiceDelegate(this);\n  }\n\n  @Override\n  public TaintVulnerabilityTrackingRpcService getTaintVulnerabilityTrackingService() {\n    return new TaintVulnerabilityTrackingRpcServiceDelegate(this);\n  }\n\n  @Override\n  public DogfoodingRpcService getDogfoodingService() {\n    return new DogfoodingRpcServiceDelegate(this);\n  }\n\n  @Override\n  public AiCodeFixRpcService getAiCodeFixRpcService() {\n    return new AiCodeFixRpcServiceDelegate(this);\n  }\n\n  @Override\n  public TaskProgressRpcService getTaskProgressRpcService() {\n    return new TaskProgressRpcServiceDelegate(this);\n  }\n\n  @Override\n  public DependencyRiskRpcService getDependencyRiskService() {\n    return new DependencyRiskRpcServiceDelegate(this);\n  }\n\n  @Override\n  public AiAgentRpcService getAiAgentService() {\n    return new AiAgentRpcServiceDelegate(this);\n  }\n\n  @Override\n  public LogRpcService getLogService() {\n    return new LogServiceDelegate(this);\n  }\n\n  @Override\n  public IdeLabsRpcService getIdeLabsService() {\n    return new IdeLabsRpcServiceDelegate(this);\n  }\n\n  @Override\n  public PluginRpcService getPluginService() {\n    return new PluginRpcServiceDelegate(this);\n  }\n\n  @Override\n  public CompletableFuture<Void> shutdown() {\n    LOG.info(\"SonarLint backend shutting down, instance={}\", this);\n    var executor = Executors.newSingleThreadExecutor(r -> new Thread(r, \"SonarLint Server shutdown\"));\n    CompletableFuture<Void> future = CompletableFutures.computeAsync(executor, cancelChecker -> {\n      SonarLintLogger.get().setTarget(logOutput);\n      var wasInitialized = initialized.getAndSet(false);\n      MoreExecutors.shutdownAndAwaitTermination(requestsExecutor, 1, TimeUnit.SECONDS);\n      MoreExecutors.shutdownAndAwaitTermination(requestAndNotificationsSequentialExecutor, 1, TimeUnit.SECONDS);\n      if (wasInitialized) {\n        try {\n          springApplicationContextInitializer.close();\n        } catch (Exception e) {\n          SonarLintLogger.get().error(\"Error while closing Spring context\", e);\n        }\n      }\n      ThreadJobProcessorPool.getProcessors().forEach(JobProcessor::finish);\n      shutdownReaderAndWriter();\n      return null;\n    });\n    executor.shutdown();\n    return future;\n  }\n\n  public void shutdownReaderAndWriter() {\n    messageReaderExecutor.shutdownNow();\n\n    // shutdown writer and disconnect from client asynchronously to make sure the client gets the response\n    var scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();\n    try {\n      scheduledExecutorService.schedule(() -> {\n        messageWriterExecutor.shutdownNow();\n        disconnectFromClient();\n      }, 1, TimeUnit.SECONDS);\n    } finally {\n      scheduledExecutorService.shutdown();\n    }\n  }\n\n  private void disconnectFromClient() {\n    clientListener.cancel(true);\n  }\n\n  public boolean isReaderShutdown() {\n    return messageReaderExecutor.isShutdown();\n  }\n\n  public int getEmbeddedServerPort() {\n    return getInitializedApplicationContext().getBean(EmbeddedServer.class).getPort();\n  }\n\n  public StorageService getIssueStorageService() {\n    return getInitializedApplicationContext().getBean(StorageService.class);\n  }\n\n  public LocalOnlyIssuesRepository getLocalOnlyIssuesRepository() {\n    return getInitializedApplicationContext().getBean(LocalOnlyIssuesRepository.class);\n  }\n\n  public SonarLintDatabase getDatabase() {\n    return getInitializedApplicationContext().getBean(SonarLintDatabase.class);\n  }\n\n  ExecutorServiceShutdownWatchable<ExecutorService> getRequestsExecutor() {\n    return requestsExecutor;\n  }\n\n  ExecutorService getRequestAndNotificationsSequentialExecutor() {\n    return requestAndNotificationsSequentialExecutor;\n  }\n\n  RpcClientLogOutput getLogOutput() {\n    return logOutput;\n  }\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/SonarProjectBranchRpcServiceDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.sonarsource.sonarlint.core.branch.SonarProjectBranchTrackingService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.branch.DidVcsRepositoryChangeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.branch.GetMatchedSonarProjectBranchParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.branch.GetMatchedSonarProjectBranchResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.branch.SonarProjectBranchRpcService;\n\nclass SonarProjectBranchRpcServiceDelegate extends AbstractRpcServiceDelegate implements SonarProjectBranchRpcService {\n\n  public SonarProjectBranchRpcServiceDelegate(SonarLintRpcServerImpl server) {\n    super(server);\n  }\n\n  @Override\n  public void didVcsRepositoryChange(DidVcsRepositoryChangeParams params) {\n    notify(() -> getBean(SonarProjectBranchTrackingService.class).didVcsRepositoryChange(params.getConfigurationScopeId()));\n  }\n\n  @Override\n  public CompletableFuture<GetMatchedSonarProjectBranchResponse> getMatchedSonarProjectBranch(GetMatchedSonarProjectBranchParams params) {\n    return requestAsync(\n      cancelMonitor -> new GetMatchedSonarProjectBranchResponse(\n        getBean(SonarProjectBranchTrackingService.class).awaitEffectiveSonarProjectBranch(params.getConfigurationScopeId()).orElse(null)));\n  }\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/TaintVulnerabilityTrackingRpcServiceDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.ListAllParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.ListAllResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityTrackingRpcService;\nimport org.sonarsource.sonarlint.core.tracking.TaintVulnerabilityTrackingService;\n\npublic class TaintVulnerabilityTrackingRpcServiceDelegate extends AbstractRpcServiceDelegate implements TaintVulnerabilityTrackingRpcService {\n  public TaintVulnerabilityTrackingRpcServiceDelegate(SonarLintRpcServerImpl sonarLintRpcServer) {\n    super(sonarLintRpcServer);\n  }\n\n  @Override\n  public CompletableFuture<ListAllResponse> listAll(ListAllParams params) {\n    return requestAsync(cancelMonitor -> new ListAllResponse(getBean(TaintVulnerabilityTrackingService.class)\n      .listAll(params.getConfigurationScopeId(), params.shouldRefresh(), cancelMonitor)));\n  }\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/TaskProgressRpcServiceDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport org.sonarsource.sonarlint.core.commons.progress.TaskManager;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.progress.CancelTaskParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.progress.TaskProgressRpcService;\n\npublic class TaskProgressRpcServiceDelegate extends AbstractRpcServiceDelegate implements TaskProgressRpcService {\n\n  public TaskProgressRpcServiceDelegate(SonarLintRpcServerImpl sonarLintRpcServer) {\n    super(sonarLintRpcServer);\n  }\n\n  @Override\n  public void cancelTask(CancelTaskParams params) {\n    notify(() -> getBean(TaskManager.class).cancel(params.getTaskId()));\n  }\n\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/TelemetryRpcServiceDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.GetStatusResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.TelemetryRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AcceptedBindingSuggestionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AddQuickFixAppliedForRuleParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AddReportedRulesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AnalysisDoneOnSingleLanguageParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AnalysisReportingTriggeredParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.DevNotificationsClickedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.FindingsFilteredParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.FixSuggestionResolvedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.IdeLabsExternalLinkClickedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.IdeLabsFeedbackLinkClickedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.HelpAndFeedbackClickedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.McpTransportModeUsedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.ToolCalledParams;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryService;\n\nclass TelemetryRpcServiceDelegate extends AbstractRpcServiceDelegate implements TelemetryRpcService {\n\n  public TelemetryRpcServiceDelegate(SonarLintRpcServerImpl server) {\n    super(server);\n  }\n\n  @Override\n  public CompletableFuture<GetStatusResponse> getStatus() {\n    return requestAsync(cancelMonitor -> getBean(TelemetryService.class).getStatus());\n  }\n\n  @Override\n  public void enableTelemetry() {\n    notify(() -> getBean(TelemetryService.class).enableTelemetry());\n  }\n\n  @Override\n  public void disableTelemetry() {\n    notify(() -> getBean(TelemetryService.class).disableTelemetry());\n  }\n\n  @Override\n  public void analysisDoneOnSingleLanguage(AnalysisDoneOnSingleLanguageParams params) {\n    notify(() -> getBean(TelemetryService.class).analysisDoneOnSingleLanguage(params.getLanguage(), params.getAnalysisTimeMs()));\n  }\n\n  @Override\n  public void analysisDoneOnMultipleFiles() {\n    notify(() -> getBean(TelemetryService.class).analysisDoneOnMultipleFiles());\n  }\n\n  @Override\n  public void devNotificationsClicked(DevNotificationsClickedParams params) {\n    notify(() -> getBean(TelemetryService.class).smartNotificationsClicked(params.getEventType()));\n  }\n\n  @Override\n  public void taintVulnerabilitiesInvestigatedLocally() {\n    notify(() -> getBean(TelemetryService.class).taintVulnerabilitiesInvestigatedLocally());\n  }\n\n  @Override\n  public void taintVulnerabilitiesInvestigatedRemotely() {\n    notify(() -> getBean(TelemetryService.class).taintVulnerabilitiesInvestigatedRemotely());\n  }\n\n  @Override\n  public void addReportedRules(AddReportedRulesParams params) {\n    notify(() -> getBean(TelemetryService.class).addReportedRules(params.getRuleKeys()));\n  }\n\n  @Override\n  public void addQuickFixAppliedForRule(AddQuickFixAppliedForRuleParams params) {\n    notify(() -> getBean(TelemetryService.class).addQuickFixAppliedForRule(params.getRuleKey()));\n  }\n\n  @Override\n  public void helpAndFeedbackLinkClicked(HelpAndFeedbackClickedParams params) {\n    notify(() -> getBean(TelemetryService.class).helpAndFeedbackLinkClicked(params));\n  }\n\n  @Override\n  public void mcpIntegrationEnabled() {\n    notify(() -> getBean(TelemetryService.class).mcpIntegrationEnabled());\n  }\n\n  @Override\n  public void mcpTransportModeUsed(McpTransportModeUsedParams params) {\n    notify(() -> getBean(TelemetryService.class).mcpTransportModeUsed(params.getMcpTransportMode()));\n  }\n\n  @Override\n  public void toolCalled(ToolCalledParams params) {\n    notify(() -> getBean(TelemetryService.class).toolCalled(params));\n  }\n\n  @Override\n  public void analysisReportingTriggered(AnalysisReportingTriggeredParams params) {\n    notify(() -> getBean(TelemetryService.class).analysisReportingTriggered(params));\n  }\n\n  @Override\n  public void fixSuggestionResolved(FixSuggestionResolvedParams params) {\n    notify(() -> getBean(TelemetryService.class).fixSuggestionResolved(params));\n  }\n\n  @Override\n  public void addedManualBindings() {\n    notify(() -> getBean(TelemetryService.class).addedManualBindings());\n  }\n\n  @Override\n  public void acceptedBindingSuggestion(AcceptedBindingSuggestionParams params) {\n    notify(() -> getBean(TelemetryService.class).acceptedBindingSuggestion(params.getOrigin()));\n  }\n\n  @Override\n  public void addedImportedBindings() {\n    notify(() -> getBean(TelemetryService.class).addedImportedBindings());\n  }\n\n  @Override\n  public void addedAutomaticBindings() {\n    notify(() -> getBean(TelemetryService.class).addedAutomaticBindings());\n  }\n\n  @Override\n  public void taintInvestigatedLocally() {\n    notify(() -> getBean(TelemetryService.class).taintInvestigatedLocally());\n  }\n\n  @Override\n  public void taintInvestigatedRemotely() {\n    notify(() -> getBean(TelemetryService.class).taintInvestigatedRemotely());\n  }\n\n  @Override\n  public void hotspotInvestigatedLocally() {\n    notify(() -> getBean(TelemetryService.class).hotspotInvestigatedLocally());\n  }\n\n  @Override\n  public void hotspotInvestigatedRemotely() {\n    notify(() -> getBean(TelemetryService.class).hotspotInvestigatedRemotely());\n  }\n\n  @Override\n  public void issueInvestigatedLocally() {\n    notify(() -> getBean(TelemetryService.class).issueInvestigatedLocally());\n  }\n\n  @Override\n  public void dependencyRiskInvestigatedLocally() {\n    notify(() -> getBean(TelemetryService.class).dependencyRiskInvestigatedLocally());\n  }\n\n  @Override\n  public void findingsFiltered(FindingsFilteredParams params) {\n    notify(() -> getBean(TelemetryService.class).findingsFiltered(params.getFilterType()));\n  }\n\n  @Override\n  public void ideLabsExternalLinkClicked(IdeLabsExternalLinkClickedParams params) {\n    notify(() -> getBean(TelemetryService.class).ideLabsLinkClicked(params.getLinkId()));\n  }\n\n  @Override\n  public void ideLabsFeedbackLinkClicked(IdeLabsFeedbackLinkClickedParams params) {\n    notify(() -> getBean(TelemetryService.class).ideLabsFeedbackLinkClicked(params.getFeatureId()));\n  }\n\n  @Override\n  public void supportedLanguagesPanelOpened() {\n    notify(() -> getBean(TelemetryService.class).supportedLanguagesPanelOpened());\n  }\n\n  @Override\n  public void supportedLanguagesPanelCtaClicked() {\n    notify(() -> getBean(TelemetryService.class).supportedLanguagesPanelCtaClicked());\n  }\n\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "backend/rpc-impl/src/main/resources/logback.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE configuration>\n\n<configuration>\n  <include resource=\"logback-shared.xml\"/>\n</configuration>"
  },
  {
    "path": "backend/rpc-impl/src/test/java/org/sonarsource/sonarlint/core/rpc/impl/AnalysisServiceTests.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport org.junit.jupiter.api.Test;\nimport org.sonar.api.batch.sensor.issue.IssueLocation;\nimport org.sonarsource.sonarlint.core.active.rules.ActiveRuleDetails;\nimport org.sonarsource.sonarlint.core.analysis.RawIssue;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFileEdit;\nimport org.sonarsource.sonarlint.core.analysis.api.Flow;\nimport org.sonarsource.sonarlint.core.analysis.api.Issue;\nimport org.sonarsource.sonarlint.core.analysis.api.QuickFix;\nimport org.sonarsource.sonarlint.core.analysis.api.TextEdit;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.DefaultTextPointer;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.DefaultTextRange;\nimport org.sonarsource.sonarlint.core.analysis.container.analysis.filesystem.SonarLintInputFile;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass AnalysisServiceTests {\n\n  @Test\n  void it_should_convert_issue_flaws_and_quick_fixes_to_raw_issue_dto() throws IOException {\n    var issueLocation = mock(IssueLocation.class);\n    var inputComponent = mock(SonarLintInputFile.class);\n    when(inputComponent.isFile()).thenReturn(true);\n    when(inputComponent.key()).thenReturn(\"inputComponentKey\");\n    when(issueLocation.message()).thenReturn(\"issue location message\");\n    when(issueLocation.textRange()).thenReturn(new DefaultTextRange(new DefaultTextPointer(1, 2),\n      new DefaultTextPointer(3, 4)));\n    when(issueLocation.inputComponent()).thenReturn(inputComponent);\n    var clientInputFile = mock(ClientInputFile.class);\n    when(clientInputFile.contents()).thenReturn(\"content\");\n    var issue = new Issue(new ActiveRuleDetails(\"repo:ruleKey\", \"languageKey\", null, null, org.sonarsource.sonarlint.core.commons.IssueSeverity.BLOCKER,\n      org.sonarsource.sonarlint.core.commons.RuleType.BUG, org.sonarsource.sonarlint.core.commons.CleanCodeAttribute.CLEAR,\n      Map.of(), org.sonarsource.sonarlint.core.commons.VulnerabilityProbability.HIGH),\n      \"primary message\", Map.of(),\n      new DefaultTextRange(new DefaultTextPointer(1, 1), new DefaultTextPointer(1, 1)),\n      clientInputFile, List.of(new Flow(List.of(issueLocation))), List.of(new QuickFix(List.of(\n        new ClientInputFileEdit(clientInputFile, List.of(new TextEdit(\n          new TextRange(5, 6, 7, 8), \"Quick fix text\")))),\n        \"Quick fix message\")),\n      Optional.of(\"\"));\n\n    var rawIssueDto = AnalysisRpcServiceDelegate.toDto(new RawIssue(issue));\n\n    assertThat(rawIssueDto.getRuleKey()).isEqualTo(\"repo:ruleKey\");\n    var rawIssueLocationDto = rawIssueDto.getFlows().get(0).getLocations().get(0);\n    assertThat(rawIssueLocationDto.getMessage()).isEqualTo(\"issue location message\");\n    var issueLocationTextRange = rawIssueLocationDto.getTextRange();\n    assertThat(issueLocationTextRange).isNotNull();\n    assertThat(issueLocationTextRange.getStartLine()).isEqualTo(1);\n    assertThat(issueLocationTextRange.getStartLineOffset()).isEqualTo(2);\n    assertThat(issueLocationTextRange.getEndLine()).isEqualTo(3);\n    assertThat(issueLocationTextRange.getEndLineOffset()).isEqualTo(4);\n    var quickFix = rawIssueDto.getQuickFixes().get(0);\n    assertThat(quickFix).isNotNull();\n    var fileEdit = quickFix.fileEdits().get(0);\n    assertThat(fileEdit).isNotNull();\n    var textEdit = fileEdit.textEdits().get(0);\n    assertThat(textEdit).isNotNull();\n    var textRange = textEdit.range();\n    assertThat(textRange).isNotNull();\n    assertThat(textRange.getStartLine()).isEqualTo(5);\n    assertThat(textRange.getStartLineOffset()).isEqualTo(6);\n    assertThat(textRange.getEndLine()).isEqualTo(7);\n    assertThat(textRange.getEndLineOffset()).isEqualTo(8);\n  }\n\n}\n"
  },
  {
    "path": "backend/rpc-impl/src/test/java/org/sonarsource/sonarlint/core/rpc/impl/SonarLintRpcServerImplTests.java",
    "content": "/*\n * SonarLint Core - RPC Implementation\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.impl;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.time.Duration;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SonarLintRpcServerImplTests {\n\n  @Test\n  void it_should_fail_to_use_services_if_the_backend_is_not_initialized() {\n    var in = new ByteArrayInputStream(new byte[0]);\n    var out = new ByteArrayOutputStream();\n    var backend = new SonarLintRpcServerImpl(in, out);\n\n    assertThat(backend.getTelemetryService().getStatus())\n      .failsWithin(1, TimeUnit.MINUTES)\n      .withThrowableOfType(ExecutionException.class)\n      .withCauseInstanceOf(IllegalStateException.class)\n      .withStackTraceContaining(\"Backend is not initialized\");\n  }\n\n  @Test\n  void it_should_silently_shutdown_the_backend_if_it_was_not_initialized() {\n    var in = new ByteArrayInputStream(new byte[0]);\n    var out = new ByteArrayOutputStream();\n    var backend = new SonarLintRpcServerImpl(in, out);\n\n    var future = backend.shutdown();\n\n    assertThat(future)\n      .succeedsWithin(Duration.ofSeconds(1));\n  }\n\n}\n"
  },
  {
    "path": "backend/rule-extractor/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-backend-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../pom.xml</relativePath>\n  </parent>\n  <artifactId>sonarlint-rule-extractor</artifactId>\n  <name>SonarLint Core - Rule Extractor</name>\n  <description>Extract rules metadata from plugins</description>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.code.findbugs</groupId>\n      <artifactId>jsr305</artifactId>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-plugin-commons</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-commons</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.sonarqube</groupId>\n      <artifactId>sonar-markdown</artifactId>\n      <version>${sonar-markdown.version}</version>\n      <exclusions>\n        <exclusion>\n          <!-- maybe this will be removed from markdown soon -->\n          <groupId>org.slf4j</groupId>\n          <artifactId>slf4j-api</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n\n    <!-- Tests -->\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-engine</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.assertj</groupId>\n      <artifactId>assertj-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>ch.qos.logback</groupId>\n      <artifactId>logback-classic</artifactId>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-dependency-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>copy-open-source-plugins-for-mediumtests</id>\n            <phase>generate-test-resources</phase>\n            <goals>\n              <goal>copy</goal>\n            </goals>\n            <configuration>\n              <artifactItems>\n                <artifactItem>\n                  <groupId>org.sonarsource.java</groupId>\n                  <artifactId>sonar-java-plugin</artifactId>\n                  <version>8.25.0.42802</version>\n                  <type>jar</type>\n                </artifactItem>\n                <artifactItem>\n                  <groupId>org.sonarsource.javascript</groupId>\n                  <artifactId>sonar-javascript-plugin</artifactId>\n                  <version>11.8.0.37897</version>\n                  <type>jar</type>\n                </artifactItem>\n                <artifactItem>\n                  <groupId>org.sonarsource.php</groupId>\n                  <artifactId>sonar-php-plugin</artifactId>\n                  <version>3.55.0.15704</version>\n                  <type>jar</type>\n                </artifactItem>\n                <artifactItem>\n                  <groupId>org.sonarsource.python</groupId>\n                  <artifactId>sonar-python-plugin</artifactId>\n                  <version>5.18.0.31561</version>\n                  <type>jar</type>\n                </artifactItem>\n                <artifactItem>\n                  <groupId>org.sonarsource.kotlin</groupId>\n                  <artifactId>sonar-kotlin-plugin</artifactId>\n                  <version>3.4.0.8957</version>\n                  <type>jar</type>\n                </artifactItem>\n                <artifactItem>\n                  <groupId>org.sonarsource.slang</groupId>\n                  <artifactId>sonar-ruby-plugin</artifactId>\n                  <version>1.22.0.1992</version>\n                  <type>jar</type>\n                </artifactItem>\n                <artifactItem>\n                  <groupId>org.sonarsource.slang</groupId>\n                  <artifactId>sonar-scala-plugin</artifactId>\n                  <version>1.21.0.1997</version>\n                  <type>jar</type>\n                </artifactItem>\n                <artifactItem>\n                  <groupId>org.sonarsource.html</groupId>\n                  <artifactId>sonar-html-plugin</artifactId>\n                  <version>3.24.0.7341</version>\n                  <type>jar</type>\n                </artifactItem>\n                <artifactItem>\n                  <groupId>org.sonarsource.xml</groupId>\n                  <artifactId>sonar-xml-plugin</artifactId>\n                  <version>2.16.0.7616</version>\n                  <type>jar</type>\n                </artifactItem>\n              </artifactItems>\n              <outputDirectory>${project.build.directory}/plugins</outputDirectory>\n              <overWriteReleases>false</overWriteReleases>\n              <overWriteSnapshots>true</overWriteSnapshots>\n              <stripVersion>false</stripVersion>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n    </plugins>\n  </build>\n\n  <profiles>\n    <profile>\n      <id>commercial</id>\n      <activation>\n        <property>\n          <name>commercial</name>\n        </property>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-dependency-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>copy-commercial-plugins-for-mediumtests</id>\n                <phase>generate-test-resources</phase>\n                <goals>\n                  <goal>copy</goal>\n                </goals>\n                <configuration>\n                  <artifactItems>\n                    <artifactItem>\n                      <groupId>com.sonarsource.abap</groupId>\n                      <artifactId>sonar-abap-plugin</artifactId>\n                      <version>3.17.0.7722</version>\n                      <type>jar</type>\n                    </artifactItem>\n                    <artifactItem>\n                      <groupId>com.sonarsource.cpp</groupId>\n                      <artifactId>sonar-cfamily-plugin</artifactId>\n                      <version>6.78.0.96395</version>\n                      <type>jar</type>\n                    </artifactItem>\n                    <artifactItem>\n                      <groupId>com.sonarsource.cobol</groupId>\n                      <artifactId>sonar-cobol-plugin</artifactId>\n                      <version>5.11.0.10062</version>\n                      <type>jar</type>\n                    </artifactItem>\n                    <artifactItem>\n                      <groupId>com.sonarsource.pli</groupId>\n                      <artifactId>sonar-pli-plugin</artifactId>\n                      <version>1.21.0.7212</version>\n                      <type>jar</type>\n                    </artifactItem>\n                    <artifactItem>\n                      <groupId>com.sonarsource.plsql</groupId>\n                      <artifactId>sonar-plsql-plugin</artifactId>\n                      <version>3.18.1.230</version>\n                      <type>jar</type>\n                    </artifactItem>\n                    <artifactItem>\n                      <groupId>com.sonarsource.rpg</groupId>\n                      <artifactId>sonar-rpg-plugin</artifactId>\n                      <version>3.13.0.7515</version>\n                      <type>jar</type>\n                    </artifactItem>\n                    <artifactItem>\n                      <groupId>com.sonarsource.swift</groupId>\n                      <artifactId>sonar-swift-plugin</artifactId>\n                      <version>5.1.0.12421</version>\n                      <type>jar</type>\n                    </artifactItem>\n                    <artifactItem>\n                      <groupId>com.sonarsource.slang</groupId>\n                      <artifactId>sonar-apex-plugin</artifactId>\n                      <version>1.24.0.2040</version>\n                      <type>jar</type>\n                    </artifactItem>\n                    <artifactItem>\n                      <groupId>com.sonarsource.tsql</groupId>\n                      <artifactId>sonar-tsql-plugin</artifactId>\n                      <version>1.16.1.9133</version>\n                      <type>jar</type>\n                    </artifactItem>\n                  </artifactItems>\n                  <outputDirectory>${project.build.directory}/plugins</outputDirectory>\n                  <overWriteReleases>false</overWriteReleases>\n                  <overWriteSnapshots>true</overWriteSnapshots>\n                  <stripVersion>false</stripVersion>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <!-- Workaround for https://issues.apache.org/jira/projects/MJAR/issues/MJAR-138 -->\n    <profile>\n      <id>conditionally-add-commons-tests-if-tests-not-skipped</id>\n      <activation>\n        <property>\n          <name>maven.test.skip</name>\n          <value>!true</value>\n        </property>\n      </activation>\n      <dependencies>\n        <dependency>\n          <groupId>${project.groupId}</groupId>\n          <artifactId>sonarlint-commons</artifactId>\n          <version>${project.version}</version>\n          <classifier>tests</classifier>\n          <type>test-jar</type>\n          <scope>test</scope>\n        </dependency>\n      </dependencies>\n    </profile>\n  </profiles>\n</project>\n"
  },
  {
    "path": "backend/rule-extractor/src/main/java/org/sonarsource/sonarlint/core/rule/extractor/EmptyConfiguration.java",
    "content": "/*\n * SonarLint Core - Rule Extractor\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rule.extractor;\n\nimport java.util.Optional;\nimport org.sonar.api.config.Configuration;\n\npublic class EmptyConfiguration implements Configuration {\n  @Override\n  public Optional<String> get(String key) {\n    return Optional.empty();\n  }\n\n  @Override\n  public boolean hasKey(String key) {\n    return false;\n  }\n\n  @Override\n  public String[] getStringArray(String key) {\n    return new String[0];\n  }\n}\n"
  },
  {
    "path": "backend/rule-extractor/src/main/java/org/sonarsource/sonarlint/core/rule/extractor/LegacyHotspotRuleDescriptionSectionsGenerator.java",
    "content": "/*\n * SonarLint Core - Rule Extractor\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rule.extractor;\n\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.function.Predicate;\nimport java.util.stream.Stream;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rule.extractor.SonarLintRuleDescriptionSection.Context;\n\nimport static org.apache.commons.lang3.StringUtils.trimToNull;\nimport static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY;\nimport static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY;\nimport static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY;\n\n/**\n * @see <a href=\"https://github.com/SonarSource/sonar-enterprise/blob/36eae8ba853a6a411d1932d6faa2265510843580/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/LegacyHotspotRuleDescriptionSectionsGenerator.java\">Original on SonarQube</a>\n */\npublic class LegacyHotspotRuleDescriptionSectionsGenerator {\n\n  private LegacyHotspotRuleDescriptionSectionsGenerator() {\n    // Static stuff only\n  }\n\n  static List<SonarLintRuleDescriptionSection> extractDescriptionSectionsFromHtml(@Nullable String descriptionInHtml) {\n    if (descriptionInHtml == null || descriptionInHtml.isEmpty()) {\n      return List.of();\n    }\n    String[] split = extractSection(\"\", descriptionInHtml);\n    String remainingText = split[0];\n    String ruleDescriptionSection = split[1];\n\n    split = extractSection(\"<h2>Exceptions</h2>\", remainingText);\n    remainingText = split[0];\n    String exceptions = split[1];\n\n    split = extractSection(\"<h2>Ask Yourself Whether</h2>\", remainingText);\n    remainingText = split[0];\n    String askSection = split[1];\n\n    split = extractSection(\"<h2>Sensitive Code Example</h2>\", remainingText);\n    remainingText = split[0];\n    String sensitiveSection = split[1];\n\n    split = extractSection(\"<h2>Noncompliant Code Example</h2>\", remainingText);\n    remainingText = split[0];\n    String noncompliantSection = split[1];\n\n    split = extractSection(\"<h2>Recommended Secure Coding Practices</h2>\", remainingText);\n    remainingText = split[0];\n    String recommendedSection = split[1];\n\n    split = extractSection(\"<h2>Compliant Solution</h2>\", remainingText);\n    remainingText = split[0];\n    String compliantSection = split[1];\n\n    split = extractSection(\"<h2>See</h2>\", remainingText);\n    remainingText = split[0];\n    String seeSection = split[1];\n\n    Optional<SonarLintRuleDescriptionSection> rootSection = createSection(ROOT_CAUSE_SECTION_KEY, ruleDescriptionSection, exceptions, remainingText);\n    Optional<SonarLintRuleDescriptionSection> assessSection = createSection(ASSESS_THE_PROBLEM_SECTION_KEY, askSection, sensitiveSection, noncompliantSection);\n    Optional<SonarLintRuleDescriptionSection> fixSection = createSection(HOW_TO_FIX_SECTION_KEY, recommendedSection, compliantSection, seeSection);\n\n    return Stream.of(rootSection, assessSection, fixSection)\n      .filter(Predicate.not(Optional::isEmpty))\n      .flatMap(Optional::stream)\n      .toList();\n  }\n\n  private static String[] extractSection(String beginning, String description) {\n    var endSection = \"<h2>\";\n    var beginningIndex = description.indexOf(beginning);\n    if (beginningIndex != -1) {\n      var endIndex = description.indexOf(endSection, beginningIndex + beginning.length());\n      if (endIndex == -1) {\n        endIndex = description.length();\n      }\n      return new String[] {\n        description.substring(0, beginningIndex) + description.substring(endIndex),\n        description.substring(beginningIndex, endIndex)\n      };\n    } else {\n      return new String[] {description, \"\"};\n    }\n  }\n\n  private static Optional<SonarLintRuleDescriptionSection> createSection(String sectionKey, String... contentPieces) {\n    var content = trimToNull(String.join(\"\", contentPieces));\n    if (content == null) {\n      return Optional.empty();\n    }\n    return Optional.of(new SonarLintRuleDescriptionSection(sectionKey, content, emptyContextForConvertedHotspotSection()));\n  }\n\n  private static Optional<Context> emptyContextForConvertedHotspotSection() {\n    return Optional.empty();\n  }\n}\n"
  },
  {
    "path": "backend/rule-extractor/src/main/java/org/sonarsource/sonarlint/core/rule/extractor/NoopTempFolder.java",
    "content": "/*\n * SonarLint Core - Rule Extractor\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rule.extractor;\n\nimport java.io.File;\nimport javax.annotation.Nullable;\nimport org.sonar.api.utils.TempFolder;\n\npublic class NoopTempFolder implements TempFolder {\n\n  @Override\n  public File newDir() {\n    throw new UnsupportedOperationException(\"newDir\");\n  }\n\n  @Override\n  public File newDir(String name) {\n    throw new UnsupportedOperationException(\"newDir\");\n  }\n\n  @Override\n  public File newFile() {\n    throw new UnsupportedOperationException(\"newFile\");\n  }\n\n  @Override\n  public File newFile(@Nullable String prefix, @Nullable String suffix) {\n    throw new UnsupportedOperationException(\"newFile\");\n  }\n\n}\n"
  },
  {
    "path": "backend/rule-extractor/src/main/java/org/sonarsource/sonarlint/core/rule/extractor/RuleDefinitionsLoader.java",
    "content": "/*\n * SonarLint Core - Rule Extractor\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rule.extractor;\n\nimport java.util.List;\nimport java.util.Optional;\nimport org.sonar.api.server.rule.RulesDefinition;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\n/**\n * Load rules directly from plugins {@link RulesDefinition}\n */\npublic class RuleDefinitionsLoader {\n\n  private final RulesDefinition.Context context;\n\n  public RuleDefinitionsLoader(Optional<List<RulesDefinition>> pluginDefs) {\n    context = new RulesDefinition.Context();\n    for (var pluginDefinition : pluginDefs.orElse(List.of())) {\n      try {\n        pluginDefinition.define(context);\n      } catch (Exception e) {\n        SonarLintLogger.get().warn(String.format(\"Failed to load rule definitions for %s, associated rules will be skipped\", pluginDefinition), e);\n      }\n    }\n  }\n\n  public RulesDefinition.Context getContext() {\n    return context;\n  }\n\n}\n"
  },
  {
    "path": "backend/rule-extractor/src/main/java/org/sonarsource/sonarlint/core/rule/extractor/RuleExtractionSettings.java",
    "content": "/*\n * SonarLint Core - Rule Extractor\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rule.extractor;\n\nimport org.sonar.api.config.PropertyDefinitions;\nimport org.sonarsource.sonarlint.core.plugin.commons.sonarapi.MapSettings;\n\npublic class RuleExtractionSettings extends MapSettings {\n  public RuleExtractionSettings(PropertyDefinitions definitions, RuleSettings settings) {\n    super(definitions, settings.settings());\n  }\n}\n"
  },
  {
    "path": "backend/rule-extractor/src/main/java/org/sonarsource/sonarlint/core/rule/extractor/RuleSettings.java",
    "content": "/*\n * SonarLint Core - Rule Extractor\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rule.extractor;\n\nimport java.util.Map;\n\npublic record RuleSettings(Map<String, String> settings) {\n}\n"
  },
  {
    "path": "backend/rule-extractor/src/main/java/org/sonarsource/sonarlint/core/rule/extractor/RulesDefinitionExtractor.java",
    "content": "/*\n * SonarLint Core - Rule Extractor\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rule.extractor;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport org.sonar.api.Plugin;\nimport org.sonar.api.rules.RuleType;\nimport org.sonar.api.server.rule.RulesDefinition;\nimport org.sonar.api.server.rule.RulesDefinition.Context;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\n\npublic class RulesDefinitionExtractor {\n\n  public List<SonarLintRuleDefinition> extractRules(Map<String, Plugin> pluginInstancesByKeys, Set<SonarLanguage> enabledLanguages,\n    boolean includeTemplateRules, boolean includeSecurityHotspots, RuleSettings settings) {\n    Context context;\n    try {\n      var container = new RulesDefinitionExtractorContainer(pluginInstancesByKeys, settings);\n      container.execute(null);\n      context = container.getRulesDefinitionContext();\n    } catch (Exception e) {\n      throw new IllegalStateException(\"Unable to extract rules metadata\", e);\n    }\n\n    List<SonarLintRuleDefinition> rules = new ArrayList<>();\n\n    for (var repoDef : context.repositories()) {\n      if (repoDef.isExternal()) {\n        continue;\n      }\n      var repoLanguage = SonarLanguage.forKey(repoDef.language());\n      if (repoLanguage.isEmpty() || !enabledLanguages.contains(repoLanguage.get())) {\n        continue;\n      }\n      for (RulesDefinition.Rule ruleDef : repoDef.rules()) {\n        if (shouldIgnoreAsHotspot(includeSecurityHotspots, ruleDef) || shouldIgnoreAsTemplate(includeTemplateRules, ruleDef)) {\n          continue;\n        }\n        rules.add(new SonarLintRuleDefinition(ruleDef));\n      }\n    }\n\n    return rules;\n\n  }\n\n  private static boolean shouldIgnoreAsTemplate(boolean includeTemplateRules, RulesDefinition.Rule ruleDef) {\n    return ruleDef.template() && !includeTemplateRules;\n  }\n\n  private static boolean shouldIgnoreAsHotspot(boolean hotspotsEnabled, RulesDefinition.Rule ruleDef) {\n    return ruleDef.type() == RuleType.SECURITY_HOTSPOT && !hotspotsEnabled;\n  }\n\n}\n"
  },
  {
    "path": "backend/rule-extractor/src/main/java/org/sonarsource/sonarlint/core/rule/extractor/RulesDefinitionExtractorContainer.java",
    "content": "/*\n * SonarLint Core - Rule Extractor\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rule.extractor;\n\nimport java.util.Map;\nimport org.sonar.api.Plugin;\nimport org.sonar.api.SonarQubeVersion;\nimport org.sonar.api.batch.sensor.Sensor;\nimport org.sonar.api.server.rule.RulesDefinition.Context;\nimport org.sonar.api.server.rule.RulesDefinitionXmlLoader;\nimport org.sonar.api.utils.AnnotationUtils;\nimport org.sonarsource.api.sonarlint.SonarLintSide;\nimport org.sonarsource.sonarlint.core.plugin.commons.ApiVersions;\nimport org.sonarsource.sonarlint.core.plugin.commons.ExtensionInstaller;\nimport org.sonarsource.sonarlint.core.plugin.commons.ExtensionUtils;\nimport org.sonarsource.sonarlint.core.plugin.commons.container.SpringComponentContainer;\nimport org.sonarsource.sonarlint.core.plugin.commons.sonarapi.ConfigurationBridge;\nimport org.sonarsource.sonarlint.core.plugin.commons.sonarapi.SonarLintRuntimeImpl;\n\npublic class RulesDefinitionExtractorContainer extends SpringComponentContainer {\n  private Context rulesDefinitionContext;\n  private final Map<String, Plugin> pluginInstancesByKeys;\n  private final RuleSettings settings;\n\n  public RulesDefinitionExtractorContainer(Map<String, Plugin> pluginInstancesByKeys, RuleSettings settings) {\n    this.pluginInstancesByKeys = pluginInstancesByKeys;\n    this.settings = settings;\n  }\n\n  @Override\n  protected void doBeforeStart() {\n    var sonarPluginApiVersion = ApiVersions.loadSonarPluginApiVersion();\n    var sonarlintPluginApiVersion = ApiVersions.loadSonarLintPluginApiVersion();\n\n    var sonarLintRuntime = new SonarLintRuntimeImpl(sonarPluginApiVersion, sonarlintPluginApiVersion, -1);\n\n    var extensionInstaller = new ExtensionInstaller(sonarLintRuntime, new EmptyConfiguration());\n    extensionInstaller.install(this, pluginInstancesByKeys, (key, ext) -> {\n      if (ExtensionUtils.isType(ext, Sensor.class)) {\n        // Optimization, and allows to run with the Xoo plugin\n        return false;\n      }\n      var annotation = AnnotationUtils.getAnnotation(ext, SonarLintSide.class);\n      if (annotation != null) {\n        var lifespan = annotation.lifespan();\n        return SonarLintSide.SINGLE_ANALYSIS.equals(lifespan);\n      }\n      return false;\n    });\n    add(\n      settings,\n      ConfigurationBridge.class,\n      RuleExtractionSettings.class,\n      sonarLintRuntime,\n      new SonarQubeVersion(sonarPluginApiVersion),\n      RulesDefinitionXmlLoader.class,\n      RuleDefinitionsLoader.class,\n      NoopTempFolder.class);\n  }\n\n  @Override\n  protected void doAfterStart() {\n    this.rulesDefinitionContext = getComponentByType(RuleDefinitionsLoader.class).getContext();\n  }\n\n  public Context getRulesDefinitionContext() {\n    return rulesDefinitionContext;\n  }\n\n}\n"
  },
  {
    "path": "backend/rule-extractor/src/main/java/org/sonarsource/sonarlint/core/rule/extractor/SecurityStandards.java",
    "content": "/*\n * SonarLint Core - Rule Extractor\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rule.extractor;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport javax.annotation.concurrent.Immutable;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\n\nimport static java.util.Collections.singleton;\nimport static java.util.Collections.singletonList;\nimport static java.util.stream.Collectors.toSet;\nimport static org.sonarsource.sonarlint.core.commons.VulnerabilityProbability.HIGH;\nimport static org.sonarsource.sonarlint.core.commons.VulnerabilityProbability.LOW;\nimport static org.sonarsource.sonarlint.core.commons.VulnerabilityProbability.MEDIUM;\n\n@Immutable\npublic final class SecurityStandards {\n\n  public static final String UNKNOWN_STANDARD = \"unknown\";\n  private static final String CWE_PREFIX = \"cwe:\";\n\n  public enum SLCategory {\n    BUFFER_OVERFLOW(\"buffer-overflow\", HIGH),\n    SQL_INJECTION(\"sql-injection\", HIGH),\n    RCE(\"rce\", MEDIUM),\n    OBJECT_INJECTION(\"object-injection\", LOW),\n    COMMAND_INJECTION(\"command-injection\", HIGH),\n    PATH_TRAVERSAL_INJECTION(\"path-traversal-injection\", HIGH),\n    LDAP_INJECTION(\"ldap-injection\", LOW),\n    XPATH_INJECTION(\"xpath-injection\", LOW),\n    LOG_INJECTION(\"log-injection\", LOW),\n    XXE(\"xxe\", MEDIUM),\n    XSS(\"xss\", HIGH),\n    DOS(\"dos\", MEDIUM),\n    SSRF(\"ssrf\", MEDIUM),\n    CSRF(\"csrf\", HIGH),\n    HTTP_RESPONSE_SPLITTING(\"http-response-splitting\", LOW),\n    OPEN_REDIRECT(\"open-redirect\", MEDIUM),\n    WEAK_CRYPTOGRAPHY(\"weak-cryptography\", MEDIUM),\n    AUTH(\"auth\", HIGH),\n    INSECURE_CONF(\"insecure-conf\", LOW),\n    FILE_MANIPULATION(\"file-manipulation\", LOW),\n    ENCRYPTION_OF_SENSITIVE_DATA(\"encrypt-data\", LOW),\n    TRACEABILITY(\"traceability\", LOW),\n    PERMISSION(\"permission\", MEDIUM),\n    OTHERS(\"others\", LOW);\n\n    private final String key;\n    private final VulnerabilityProbability vulnerability;\n\n    SLCategory(String key, VulnerabilityProbability vulnerability) {\n      this.key = key;\n      this.vulnerability = vulnerability;\n    }\n\n    public String getKey() {\n      return key;\n    }\n\n    public VulnerabilityProbability getVulnerability() {\n      return vulnerability;\n    }\n  }\n\n  public static final Map<SLCategory, Set<String>> CWES_BY_SL_CATEGORY = Map.ofEntries(\n      Map.entry(SLCategory.BUFFER_OVERFLOW, Set.of(\"119\", \"120\", \"131\", \"676\", \"788\")),\n      Map.entry(SLCategory.SQL_INJECTION, Set.of(\"89\", \"564\", \"943\")),\n      Map.entry(SLCategory.COMMAND_INJECTION, Set.of(\"77\", \"78\", \"88\", \"214\")),\n      Map.entry(SLCategory.PATH_TRAVERSAL_INJECTION, Set.of(\"22\")),\n      Map.entry(SLCategory.LDAP_INJECTION, Set.of(\"90\")),\n      Map.entry(SLCategory.XPATH_INJECTION, Set.of(\"643\")),\n      Map.entry(SLCategory.RCE, Set.of(\"94\", \"95\")),\n      Map.entry(SLCategory.DOS, Set.of(\"400\", \"624\")),\n      Map.entry(SLCategory.SSRF, Set.of(\"918\")),\n      Map.entry(SLCategory.CSRF, Set.of(\"352\")),\n      Map.entry(SLCategory.XSS, Set.of(\"79\", \"80\", \"81\", \"82\", \"83\", \"84\", \"85\", \"86\", \"87\")),\n      Map.entry(SLCategory.LOG_INJECTION, Set.of(\"117\")),\n      Map.entry(SLCategory.HTTP_RESPONSE_SPLITTING, Set.of(\"113\")),\n      Map.entry(SLCategory.OPEN_REDIRECT, Set.of(\"601\")),\n      Map.entry(SLCategory.XXE, Set.of(\"611\", \"827\")),\n      Map.entry(SLCategory.OBJECT_INJECTION, Set.of(\"134\", \"470\", \"502\")),\n      Map.entry(SLCategory.WEAK_CRYPTOGRAPHY, Set.of(\"295\", \"297\", \"321\", \"322\", \"323\", \"324\", \"325\", \"326\", \"327\", \"328\", \"330\", \"780\")),\n      Map.entry(SLCategory.AUTH, Set.of(\"798\", \"640\", \"620\", \"549\", \"522\", \"521\", \"263\", \"262\", \"261\", \"259\", \"308\")),\n      Map.entry(SLCategory.INSECURE_CONF, Set.of(\"102\", \"215\", \"346\", \"614\", \"489\", \"942\")),\n      Map.entry(SLCategory.FILE_MANIPULATION, Set.of(\"97\", \"73\")),\n      Map.entry(SLCategory.ENCRYPTION_OF_SENSITIVE_DATA, Set.of(\"311\", \"315\", \"319\")),\n      Map.entry(SLCategory.TRACEABILITY, Set.of(\"778\")),\n      Map.entry(SLCategory.PERMISSION, Set.of(\"266\", \"269\", \"284\", \"668\", \"732\")));\n\n  private final Set<String> standards;\n  private final Set<String> cwe;\n  private final SLCategory sLCategory;\n  private final Set<SLCategory> ignoredSLCategories;\n\n  private SecurityStandards(Set<String> standards, Set<String> cwe, SLCategory sLCategory, Set<SLCategory> ignoredSLCategories) {\n    this.standards = standards;\n    this.cwe = cwe;\n    this.sLCategory = sLCategory;\n    this.ignoredSLCategories = ignoredSLCategories;\n  }\n\n  public SLCategory getSlCategory() {\n    return sLCategory;\n  }\n\n  /**\n   * If CWEs mapped to multiple {@link SLCategory}, those which are not taken into account are listed here.\n   */\n  public Set<SLCategory> getIgnoredSLCategories() {\n    return ignoredSLCategories;\n  }\n\n  public Set<String> getStandards() {\n    return standards;\n  }\n\n  public Set<String> getCwe() {\n    return cwe;\n  }\n\n  /**\n   * @throws IllegalStateException if {@code securityStandards} maps to multiple {@link SLCategory SLCategories}\n   */\n  public static SecurityStandards fromSecurityStandards(Set<String> securityStandards) {\n    Set<String> standards = securityStandards.stream().filter(Objects::nonNull).collect(toSet());\n    Set<String> cwe = toCwes(standards);\n    List<SLCategory> sl = toSLCategories(cwe);\n    var slCategory = sl.iterator().next();\n    Set<SLCategory> ignoredSLCategories = sl.stream().skip(1).collect(toSet());\n    return new SecurityStandards(standards, cwe, slCategory, ignoredSLCategories);\n  }\n\n  private static Set<String> toCwes(Collection<String> securityStandards) {\n    Set<String> result = securityStandards.stream()\n      .filter(s -> s.startsWith(CWE_PREFIX))\n      .map(s -> s.substring(CWE_PREFIX.length()))\n      .collect(toSet());\n    return result.isEmpty() ? singleton(UNKNOWN_STANDARD) : result;\n  }\n\n  private static List<SLCategory> toSLCategories(Collection<String> cwe) {\n    List<SLCategory> result = CWES_BY_SL_CATEGORY\n      .keySet()\n      .stream()\n      .filter(k -> cwe.stream().anyMatch(CWES_BY_SL_CATEGORY.get(k)::contains))\n      .toList();\n    return result.isEmpty() ? singletonList(SLCategory.OTHERS) : result;\n  }\n\n}\n"
  },
  {
    "path": "backend/rule-extractor/src/main/java/org/sonarsource/sonarlint/core/rule/extractor/SonarLintRuleDefinition.java",
    "content": "/*\n * SonarLint Core - Rule Extractor\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rule.extractor;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport org.sonar.api.rule.RuleKey;\nimport org.sonar.api.server.rule.RulesDefinition;\nimport org.sonar.api.server.rule.RulesDefinition.Param;\nimport org.sonar.markdown.Markdown;\nimport org.sonarsource.sonarlint.core.commons.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\n\nimport static java.util.stream.Collectors.toSet;\nimport static org.sonarsource.sonarlint.core.rule.extractor.SecurityStandards.fromSecurityStandards;\n\npublic class SonarLintRuleDefinition {\n\n  private final String key;\n  private final String name;\n  private final IssueSeverity defaultSeverity;\n  private final RuleType type;\n  private final CleanCodeAttribute cleanCodeAttribute;\n  private final Map<SoftwareQuality, ImpactSeverity> defaultImpacts;\n  private final String description;\n  private final List<SonarLintRuleDescriptionSection> descriptionSections;\n  private final Map<String, SonarLintRuleParamDefinition> params;\n  private final Map<String, String> defaultParams = new HashMap<>();\n  private final boolean isActiveByDefault;\n  private final SonarLanguage language;\n  private final String[] tags;\n  private final Set<String> deprecatedKeys;\n  private final Set<String> educationPrincipleKeys;\n  private final Optional<String> internalKey;\n  // Relevant for Hotspot rules only\n  private final Optional<VulnerabilityProbability> vulnerabilityProbability;\n\n  public SonarLintRuleDefinition(RulesDefinition.Rule rule) {\n    this.key = RuleKey.of(rule.repository().key(), rule.key()).toString();\n    this.name = rule.name();\n    this.defaultSeverity = IssueSeverity.valueOf(rule.severity());\n    this.type = RuleType.valueOf(rule.type().name());\n    this.cleanCodeAttribute = Optional.ofNullable(rule.cleanCodeAttribute()).map(Enum::name).map(CleanCodeAttribute::valueOf)\n      .orElse(CleanCodeAttribute.defaultCleanCodeAttribute());\n    this.defaultImpacts = rule.defaultImpacts().entrySet()\n      .stream()\n      .map(e -> Map.entry(SoftwareQuality.valueOf(e.getKey().name()), ImpactSeverity.valueOf(e.getValue().name())))\n      .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\n    var htmlDescription = rule.htmlDescription() != null ? rule.htmlDescription() : Markdown.convertToHtml(rule.markdownDescription());\n    if (rule.type() == org.sonar.api.rules.RuleType.SECURITY_HOTSPOT) {\n      this.description = null;\n      this.descriptionSections = LegacyHotspotRuleDescriptionSectionsGenerator.extractDescriptionSectionsFromHtml(htmlDescription);\n    } else {\n      this.description = htmlDescription;\n      this.descriptionSections = rule.ruleDescriptionSections().stream().map(s -> new SonarLintRuleDescriptionSection(s.getKey(), s.getHtmlContent(),\n        s.getContext().map(c -> new SonarLintRuleDescriptionSection.Context(c.getKey(), c.getDisplayName())))).toList();\n    }\n\n    this.isActiveByDefault = rule.activatedByDefault();\n    this.language = SonarLanguage.forKey(rule.repository().language()).orElseThrow(() -> new IllegalStateException(\"Unknown language with key: \" + rule.repository().language()));\n    this.tags = rule.tags().toArray(new String[0]);\n    this.deprecatedKeys = rule.deprecatedRuleKeys().stream().map(RuleKey::toString).collect(toSet());\n    this.educationPrincipleKeys = rule.educationPrincipleKeys();\n    this.vulnerabilityProbability =\n      rule.type() == org.sonar.api.rules.RuleType.SECURITY_HOTSPOT ?\n        Optional.of(fromSecurityStandards(rule.securityStandards()).getSlCategory().getVulnerability()) : Optional.empty();\n    Map<String, SonarLintRuleParamDefinition> builder = new HashMap<>();\n    for (Param param : rule.params()) {\n      var paramDefinition = new SonarLintRuleParamDefinition(param);\n      builder.put(param.key(), paramDefinition);\n      var defaultValue = paramDefinition.defaultValue();\n      if (defaultValue != null) {\n        defaultParams.put(param.key(), defaultValue);\n      }\n    }\n    params = Collections.unmodifiableMap(builder);\n    this.internalKey = Optional.ofNullable(rule.internalKey());\n  }\n\n  public String getKey() {\n    return key;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public IssueSeverity getDefaultSeverity() {\n    return defaultSeverity;\n  }\n\n  public RuleType getType() {\n    return type;\n  }\n\n  public Optional<CleanCodeAttribute> getCleanCodeAttribute() {\n    return Optional.ofNullable(cleanCodeAttribute);\n  }\n\n  public Map<SoftwareQuality, ImpactSeverity> getDefaultImpacts() {\n    return defaultImpacts;\n  }\n\n  public Map<String, SonarLintRuleParamDefinition> getParams() {\n    return params;\n  }\n\n  public Map<String, String> getDefaultParams() {\n    return defaultParams;\n  }\n\n  public boolean isActiveByDefault() {\n    return isActiveByDefault;\n  }\n\n  public String getHtmlDescription() {\n    return description;\n  }\n\n  public List<SonarLintRuleDescriptionSection> getDescriptionSections() {\n    return descriptionSections;\n  }\n\n  public SonarLanguage getLanguage() {\n    return language;\n  }\n\n  public String[] getTags() {\n    return tags;\n  }\n\n  public Set<String> getDeprecatedKeys() {\n    return deprecatedKeys;\n  }\n\n  public Set<String> getEducationPrincipleKeys() {\n    return educationPrincipleKeys;\n  }\n\n  public Optional<String> getInternalKey() {\n    return internalKey;\n  }\n\n  public Optional<VulnerabilityProbability> getVulnerabilityProbability() {\n    return vulnerabilityProbability;\n  }\n}\n"
  },
  {
    "path": "backend/rule-extractor/src/main/java/org/sonarsource/sonarlint/core/rule/extractor/SonarLintRuleDescriptionSection.java",
    "content": "/*\n * SonarLint Core - Rule Extractor\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rule.extractor;\n\nimport java.util.Optional;\n\npublic class SonarLintRuleDescriptionSection {\n  private final String key;\n  private final String htmlContent;\n  private final Optional<Context> context;\n\n  public SonarLintRuleDescriptionSection(String key, String htmlContent, Optional<Context> context) {\n    this.key = key;\n    this.htmlContent = htmlContent;\n    this.context = context;\n  }\n\n  public String getKey() {\n    return key;\n  }\n\n  public String getHtmlContent() {\n    return htmlContent;\n  }\n\n  public Optional<Context> getContext() {\n    return context;\n  }\n\n  public static class Context {\n    private final String key;\n    private final String displayName;\n\n    public Context(String key, String displayName) {\n      this.key = key;\n      this.displayName = displayName;\n    }\n\n    public String getKey() {\n      return key;\n    }\n\n    public String getDisplayName() {\n      return displayName;\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/rule-extractor/src/main/java/org/sonarsource/sonarlint/core/rule/extractor/SonarLintRuleParamDefinition.java",
    "content": "/*\n * SonarLint Core - Rule Extractor\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rule.extractor;\n\nimport java.util.Collections;\nimport java.util.List;\nimport javax.annotation.CheckForNull;\nimport org.sonar.api.server.rule.RuleParamType;\nimport org.sonar.api.server.rule.RulesDefinition.Param;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\npublic class SonarLintRuleParamDefinition {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final String key;\n  private final String name;\n  private final String description;\n  private final String defaultValue;\n  private final SonarLintRuleParamType type;\n  private final boolean multiple;\n  private final List<String> possibleValues;\n\n  public SonarLintRuleParamDefinition(Param param) {\n    this.key = param.key();\n    this.name = param.name();\n    this.description = param.description();\n    this.defaultValue = param.defaultValue();\n    var apiType = param.type();\n    this.type = from(apiType);\n    this.multiple = apiType.multiple();\n    this.possibleValues = Collections.unmodifiableList(apiType.values());\n  }\n\n  private static SonarLintRuleParamType from(RuleParamType apiType) {\n    try {\n      return SonarLintRuleParamType.valueOf(apiType.type());\n    } catch (IllegalArgumentException unknownType) {\n      LOG.warn(\"Unknown parameter type: \" + apiType.type());\n      return SonarLintRuleParamType.STRING;\n    }\n  }\n\n  public String key() {\n    return key;\n  }\n\n  public String name() {\n    return name;\n  }\n\n  public String description() {\n    return description;\n  }\n\n  @CheckForNull\n  public String defaultValue() {\n    return defaultValue;\n  }\n\n  public SonarLintRuleParamType type() {\n    return type;\n  }\n\n  public boolean multiple() {\n    return multiple;\n  }\n\n  public List<String> possibleValues() {\n    return possibleValues;\n  }\n}\n"
  },
  {
    "path": "backend/rule-extractor/src/main/java/org/sonarsource/sonarlint/core/rule/extractor/SonarLintRuleParamType.java",
    "content": "/*\n * SonarLint Core - Rule Extractor\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rule.extractor;\n\npublic enum SonarLintRuleParamType {\n\n  /**\n   * Keep in sync with constants in org.sonar.api.server.rule.RuleParamType\n   */\n  STRING, TEXT, BOOLEAN, INTEGER, FLOAT\n\n}\n"
  },
  {
    "path": "backend/rule-extractor/src/main/java/org/sonarsource/sonarlint/core/rule/extractor/package-info.java",
    "content": "/*\n * SonarLint Core - Rule Extractor\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rule.extractor;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/rule-extractor/src/test/java/mediumtests/RuleExtractorMediumTests.java",
    "content": "/*\n * SonarLint Core - Rule Extractor\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtests;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.EnumSet;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.plugin.commons.PluginsLoader;\nimport org.sonarsource.sonarlint.core.rule.extractor.RuleSettings;\nimport org.sonarsource.sonarlint.core.rule.extractor.RulesDefinitionExtractor;\nimport org.sonarsource.sonarlint.core.rule.extractor.SonarLintRuleDefinition;\nimport org.sonarsource.sonarlint.core.rule.extractor.SonarLintRuleParamType;\n\nimport static java.util.stream.Collectors.toSet;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.entry;\n\nclass RuleExtractorMediumTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private static final int COMMERCIAL_RULE_TEMPLATES_COUNT = 11;\n  private static final int NON_COMMERCIAL_RULE_TEMPLATES_COUNT = 16;\n  private static final int COMMERCIAL_SECURITY_HOTSPOTS_COUNT = 82;\n  private static final int NON_COMMERCIAL_SECURITY_HOTSPOTS_COUNT = 298;\n  private static final int ALL_RULES_COUNT_WITHOUT_COMMERCIAL = 2732;\n  private static final int ALL_RULES_COUNT_WITH_COMMERCIAL = 4823;\n  // commercial plugins might not be available\n  // (if you pass -Dcommercial to maven, a profile will be activated that downloads the commercial plugins)\n  private static final boolean COMMERCIAL_ENABLED = System.getProperty(\"commercial\") != null;\n  private static final Optional<Version> NODE_VERSION = Optional.of(Version.create(\"20.20.0\"));\n  private static final RuleSettings EMPTY_SETTINGS = new RuleSettings(Map.of());\n  private static Set<Path> allJars;\n\n  @BeforeAll\n  static void prepare() throws IOException {\n    var dir = Paths.get(\"target/plugins/\");\n    try (var files = Files.list(dir)) {\n      allJars = files.filter(x -> x.getFileName().toString().endsWith(\".jar\")).collect(toSet());\n    }\n  }\n\n  @Test\n  void extractAllRules() {\n    var enabledLanguages = Set.of(SonarLanguage.values());\n    var config = new PluginsLoader.Configuration(allJars, enabledLanguages, false, NODE_VERSION);\n    var result = new PluginsLoader().load(config, Set.of());\n\n    var allRules = new RulesDefinitionExtractor().extractRules(result.getLoadedPlugins().getAllPluginInstancesByKeys(), enabledLanguages, false, false, EMPTY_SETTINGS);\n    if (COMMERCIAL_ENABLED) {\n      assertThat(allJars).hasSize(18);\n      assertThat(allRules).hasSize(ALL_RULES_COUNT_WITH_COMMERCIAL);\n    } else {\n      assertThat(allJars).hasSize(9);\n      assertThat(allRules).hasSize(ALL_RULES_COUNT_WITHOUT_COMMERCIAL);\n    }\n\n    var pythonRule = allRules.stream().filter(r -> r.getKey().equals(\"python:S139\")).findFirst();\n    assertThat(pythonRule).hasValueSatisfying(rule -> {\n      assertThat(rule.getKey()).isEqualTo(\"python:S139\");\n      assertThat(rule.getType()).isEqualTo(RuleType.CODE_SMELL);\n      assertThat(rule.getDefaultSeverity()).isEqualTo(IssueSeverity.MINOR);\n      assertThat(rule.getLanguage()).isEqualTo(SonarLanguage.PYTHON);\n      assertThat(rule.getName()).isEqualTo(\"Comments should not be located at the end of lines of code\");\n      assertThat(rule.isActiveByDefault()).isFalse();\n      assertThat(rule.getParams())\n        .hasSize(1)\n        .hasEntrySatisfying(\"legalTrailingCommentPattern\", param -> {\n          assertThat(param.defaultValue()).isEqualTo(\"^#\\\\s*+([^\\\\s]++|fmt.*|type.*|noqa.*)$\");\n          assertThat(param.description())\n            .isEqualTo(\"Pattern for text of trailing comments that are allowed. By default, Mypy and Black pragma comments as well as comments containing only one word.\");\n          assertThat(param.key()).isEqualTo(\"legalTrailingCommentPattern\");\n          assertThat(param.multiple()).isFalse();\n          assertThat(param.name()).isEqualTo(\"legalTrailingCommentPattern\");\n          assertThat(param.possibleValues()).isEmpty();\n          assertThat(param.type()).isEqualTo(SonarLintRuleParamType.STRING);\n        });\n      assertThat(rule.getDefaultParams()).containsOnly(entry(\"legalTrailingCommentPattern\", \"^#\\\\s*+([^\\\\s]++|fmt.*|type.*|noqa.*)$\"));\n      assertThat(rule.getDeprecatedKeys()).isEmpty();\n      assertThat(rule.getHtmlDescription()).contains(\"<p>This rule verifies that single-line comments are not located\");\n      assertThat(rule.getTags()).containsOnly(\"convention\");\n      assertThat(rule.getInternalKey()).isEmpty();\n    });\n\n    var ruleWithInternalKey = allRules.stream().filter(r -> r.getKey().equals(\"java:NoSonar\")).findFirst();\n    assertThat(ruleWithInternalKey).isNotEmpty();\n    assertThat(ruleWithInternalKey.get().getInternalKey()).contains(\"S1291\");\n  }\n\n  @Test\n  void extractAllRules_include_rule_templates() {\n    var enabledLanguages = Set.of(SonarLanguage.values());\n    var config = new PluginsLoader.Configuration(allJars, enabledLanguages, false, NODE_VERSION);\n    var result = new PluginsLoader().load(config, Set.of());\n\n    var allRules = new RulesDefinitionExtractor().extractRules(result.getLoadedPlugins().getAllPluginInstancesByKeys(), enabledLanguages, true, false, EMPTY_SETTINGS);\n    if (COMMERCIAL_ENABLED) {\n      assertThat(allJars).hasSize(18);\n      assertThat(allRules).hasSize(ALL_RULES_COUNT_WITH_COMMERCIAL + NON_COMMERCIAL_RULE_TEMPLATES_COUNT + COMMERCIAL_RULE_TEMPLATES_COUNT);\n    } else {\n      assertThat(allJars).hasSize(9);\n      assertThat(allRules).hasSize(ALL_RULES_COUNT_WITHOUT_COMMERCIAL + NON_COMMERCIAL_RULE_TEMPLATES_COUNT);\n    }\n  }\n\n  @Test\n  void extractAllRules_include_security_hotspots() {\n    var enabledLanguages = Set.of(SonarLanguage.values());\n    var config = new PluginsLoader.Configuration(allJars, enabledLanguages, false, NODE_VERSION);\n    var result = new PluginsLoader().load(config, Set.of());\n\n    var allRules = new RulesDefinitionExtractor().extractRules(result.getLoadedPlugins().getAllPluginInstancesByKeys(), enabledLanguages, false, true, EMPTY_SETTINGS);\n    if (COMMERCIAL_ENABLED) {\n      assertThat(allJars).hasSize(18);\n      assertThat(allRules).hasSize(ALL_RULES_COUNT_WITH_COMMERCIAL + NON_COMMERCIAL_SECURITY_HOTSPOTS_COUNT + COMMERCIAL_SECURITY_HOTSPOTS_COUNT);\n    } else {\n      assertThat(allJars).hasSize(9);\n      assertThat(allRules).hasSize(ALL_RULES_COUNT_WITHOUT_COMMERCIAL + NON_COMMERCIAL_SECURITY_HOTSPOTS_COUNT);\n    }\n  }\n\n  @Test\n  void onlyLoadRulesOfEnabledLanguages() {\n    Set<SonarLanguage> enabledLanguages = EnumSet.of(\n      SonarLanguage.JAVA,\n      // Enable JS but not TS\n      SonarLanguage.JS,\n      SonarLanguage.PHP,\n      SonarLanguage.PYTHON);\n\n    if (COMMERCIAL_ENABLED) {\n      // Enable C but not C++\n      enabledLanguages.add(SonarLanguage.C);\n    }\n    var config = new PluginsLoader.Configuration(allJars, enabledLanguages, false, NODE_VERSION);\n    var result = new PluginsLoader().load(config, Set.of());\n\n    var allRules = new RulesDefinitionExtractor().extractRules(result.getLoadedPlugins().getAllPluginInstancesByKeys(), enabledLanguages, false, false, EMPTY_SETTINGS);\n\n    assertThat(allRules.stream().map(SonarLintRuleDefinition::getLanguage).distinct()).hasSameElementsAs(enabledLanguages);\n  }\n\n  @Test\n  void loadNoRuleIfThereIsNoPlugin() {\n    var enabledLanguages = Set.of(SonarLanguage.values());\n    var config = new PluginsLoader.Configuration(Set.of(), enabledLanguages, false, NODE_VERSION);\n    var result = new PluginsLoader().load(config, Set.of());\n    var allRules = new RulesDefinitionExtractor().extractRules(result.getLoadedPlugins().getAllPluginInstancesByKeys(), enabledLanguages, false, false, EMPTY_SETTINGS);\n\n    assertThat(allRules).isEmpty();\n  }\n\n}\n"
  },
  {
    "path": "backend/rule-extractor/src/test/java/org/sonarsource/sonarlint/core/rule/extractor/EmptyConfigurationTest.java",
    "content": "/*\n * SonarLint Core - Rule Extractor\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rule.extractor;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass EmptyConfigurationTest {\n  @Test\n  void should_be_empty() {\n    var emptyConfiguration = new EmptyConfiguration();\n\n    assertThat(emptyConfiguration.hasKey(\"\")).isFalse();\n    assertThat(emptyConfiguration.get(\"\")).isEmpty();\n    assertThat(emptyConfiguration.getStringArray(\"\")).isEmpty();\n  }\n}\n"
  },
  {
    "path": "backend/rule-extractor/src/test/java/org/sonarsource/sonarlint/core/rule/extractor/LegacyHotspotRuleDescriptionSectionsGeneratorTest.java",
    "content": "/*\n * SonarLint Core - Rule Extractor\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rule.extractor;\n\nimport java.util.Map;\nimport java.util.stream.Collectors;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY;\nimport static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY;\nimport static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY;\nimport static org.sonarsource.sonarlint.core.rule.extractor.LegacyHotspotRuleDescriptionSectionsGenerator.extractDescriptionSectionsFromHtml;\n\n/**\n * @see <a href=\"https://github.com/SonarSource/sonar-enterprise/blob/bbb92fe6b1aa426783aff59e97a303b6b79e5b72/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/LegacyHotspotRuleDescriptionSectionsGeneratorTest.java\">SonarQube Test</a>\n */\nclass LegacyHotspotRuleDescriptionSectionsGeneratorTest {\n\n  /*\n   * Bunch of static constant to create rule description.\n   */\n  private static final String DESCRIPTION =\n    \"\"\"\n      <p>The use of operators pairs ( <code>=+</code>, <code>=-</code> or <code>=!</code> ) where the reversed, single operator was meant (<code>+=</code>,\n      <code>-=</code> or <code>!=</code>) will compile and run, but not produce the expected results.</p>\n      <p>This rule raises an issue when <code>=+</code>, <code>=-</code>, or <code>=!</code> is used without any spacing between the two operators and when\n      there is at least one whitespace character after.</p>\"\"\";\n  private static final String NON_COMPLIANT_CODE = \"\"\"\n    <h2>Noncompliant Code Example</h2>\n    <pre>Integer target = -5;\n    Integer num = 3;\n    \n    target =- num;  // Noncompliant; target = -3. Is that really what's meant?\n    target =+ num; // Noncompliant; target = 3\n    </pre>\"\"\";\n\n  private static final String COMPLIANT_CODE =\n    \"\"\"\n      <h2>Compliant Solution</h2>\n      <pre>Integer target = -5;\n      Integer num = 3;\n      \n      target = -num;  // Compliant; intent to assign inverse value of num is clear\n      target += num;\n      </pre>\"\"\";\n\n  private static final String SEE =\n    \"\"\"\n      <h2>See</h2>\n      <ul>\n        <li> <a href=\"https://cwe.mitre.org/data/definitions/352.html\">MITRE, CWE-352</a> - Cross-Site Request Forgery (CSRF) </li>\n        <li> <a href=\"https://www.owasp.org/index.php/Top_10-2017_A6-Security_Misconfiguration\">OWASP Top 10 2017 Category A6</a> - Security\n        Misconfiguration </li>\n        <li> <a href=\"https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29\">OWASP: Cross-Site Request Forgery</a> </li>\n        <li> <a href=\"https://www.sans.org/top25-software-errors/#cat1\">SANS Top 25</a> - Insecure Interaction Between Components </li>\n        <li> Derived from FindSecBugs rule <a href=\"https://find-sec-bugs.github.io/bugs.htm#SPRING_CSRF_PROTECTION_DISABLED\">SPRING_CSRF_PROTECTION_DISABLED</a> </li>\n        <li> <a href=\"https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html#when-to-use-csrf-protection\">Spring Security\n        Official Documentation: When to use CSRF protection</a> </li>\n      </ul>\"\"\";\n\n  private static final String RECOMMENDED_CODING_PRACTICE =\n    \"\"\"\n      <h2>Recommended Secure Coding Practices</h2>\n      <ul>\n        <li> activate Spring Security's CSRF protection. </li>\n      </ul>\"\"\";\n\n  private static final String ASK_AT_RISK =\n    \"\"\"\n      <h2>Ask Yourself Whether</h2>\n      <ul>\n        <li> Any URLs responding with <code>Access-Control-Allow-Origin: *</code> include sensitive content. </li>\n        <li> Any domains specified in <code>Access-Control-Allow-Origin</code> headers are checked against a whitelist. </li>\n      </ul>\"\"\";\n\n  private static final String SENSITIVE_CODE = \"\"\"\n    <h2>Sensitive Code Example</h2>\n    <pre>\n    // === Java Servlet ===\n    @Override\n    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n      resp.setHeader(\"Content-Type\", \"text/plain; charset=utf-8\");\n      resp.setHeader(\"Access-Control-Allow-Origin\", \"http://localhost:8080\"); // Questionable\n      resp.setHeader(\"Access-Control-Allow-Credentials\", \"true\"); // Questionable\n      resp.setHeader(\"Access-Control-Allow-Methods\", \"GET\"); // Questionable\n      resp.getWriter().write(\"response\");\n    }\n    </pre>\n    <pre>\n    // === Spring MVC Controller annotation ===\n    @CrossOrigin(origins = \"http://domain1.com\") // Questionable\n    @RequestMapping(\"\")\n    public class TestController {\n        public String home(ModelMap model) {\n            model.addAttribute(\"message\", \"ok \");\n            return \"view\";\n        }\n    \n        @CrossOrigin(origins = \"http://domain2.com\") // Questionable\n        @RequestMapping(value = \"/test1\")\n        public ResponseEntity&lt;String&gt; test1() {\n            return ResponseEntity.ok().body(\"ok\");\n        }\n    }\n    </pre>\"\"\";\n\n  @Test\n  void shouldReturnNoSectionForNullDescription() {\n    assertThat(extractDescriptionSectionsFromHtml(null)).isEmpty();\n  }\n\n  @Test\n  void shouldReturnNoSectionForEmptyDescription() {\n    assertThat(extractDescriptionSectionsFromHtml(\"\")).isEmpty();\n  }\n\n  @Test\n  void parse_to_risk_description_fields_when_desc_contains_no_section() {\n    var descriptionWithoutTitles = \"description without titles\";\n\n    assertThat(sectionsMapFromHtml(descriptionWithoutTitles)).hasSize(1)\n      .containsEntry(ROOT_CAUSE_SECTION_KEY, descriptionWithoutTitles);\n  }\n\n\n  @Test\n  void parse_return_null_risk_when_desc_starts_with_ask_yourself_title() {\n    assertThat(sectionsMapFromHtml(ASK_AT_RISK + RECOMMENDED_CODING_PRACTICE)).hasSize(2)\n      .containsEntry(ASSESS_THE_PROBLEM_SECTION_KEY, ASK_AT_RISK)\n      .containsEntry(HOW_TO_FIX_SECTION_KEY, RECOMMENDED_CODING_PRACTICE);\n  }\n\n\n  @Test\n  void parse_return_null_vulnerable_when_no_ask_yourself_whether_title() {\n    assertThat(sectionsMapFromHtml(DESCRIPTION + RECOMMENDED_CODING_PRACTICE)).hasSize(2)\n      .containsEntry(ROOT_CAUSE_SECTION_KEY, DESCRIPTION)\n      .containsEntry(HOW_TO_FIX_SECTION_KEY, RECOMMENDED_CODING_PRACTICE);\n  }\n\n  @Test\n  void parse_return_null_fixIt_when_desc_has_no_Recommended_Secure_Coding_Practices_title() {\n    assertThat(sectionsMapFromHtml(DESCRIPTION + ASK_AT_RISK)).hasSize(2)\n      .containsEntry(ROOT_CAUSE_SECTION_KEY, DESCRIPTION)\n      .containsEntry(ASSESS_THE_PROBLEM_SECTION_KEY, ASK_AT_RISK);\n  }\n\n  @Test\n  void parse_with_noncompliant_section_not_removed() {\n    assertThat(sectionsMapFromHtml(DESCRIPTION + NON_COMPLIANT_CODE + COMPLIANT_CODE)).hasSize(3)\n      .containsEntry(ROOT_CAUSE_SECTION_KEY, DESCRIPTION)\n      .containsEntry(ASSESS_THE_PROBLEM_SECTION_KEY, NON_COMPLIANT_CODE)\n      .containsEntry(HOW_TO_FIX_SECTION_KEY, COMPLIANT_CODE);\n  }\n\n  @Test\n  void parse_moved_noncompliant_code() {\n    assertThat(sectionsMapFromHtml(DESCRIPTION + RECOMMENDED_CODING_PRACTICE + NON_COMPLIANT_CODE + SEE)).hasSize(3)\n      .containsEntry(ROOT_CAUSE_SECTION_KEY, DESCRIPTION)\n      .containsEntry(ASSESS_THE_PROBLEM_SECTION_KEY, NON_COMPLIANT_CODE)\n      .containsEntry(HOW_TO_FIX_SECTION_KEY, RECOMMENDED_CODING_PRACTICE + SEE);\n  }\n\n  @Test\n  void parse_moved_sensitivecode_code() {\n    assertThat(sectionsMapFromHtml(DESCRIPTION + ASK_AT_RISK + RECOMMENDED_CODING_PRACTICE + SENSITIVE_CODE + SEE)).hasSize(3)\n      .containsEntry(ROOT_CAUSE_SECTION_KEY, DESCRIPTION)\n      .containsEntry(ASSESS_THE_PROBLEM_SECTION_KEY, ASK_AT_RISK + SENSITIVE_CODE)\n      .containsEntry(HOW_TO_FIX_SECTION_KEY, RECOMMENDED_CODING_PRACTICE + SEE);\n  }\n\n  private static Map<String, String> sectionsMapFromHtml(String html) {\n    return extractDescriptionSectionsFromHtml(html).stream()\n      .collect(Collectors.toMap(SonarLintRuleDescriptionSection::getKey, SonarLintRuleDescriptionSection::getHtmlContent));\n  }\n}\n"
  },
  {
    "path": "backend/rule-extractor/src/test/java/org/sonarsource/sonarlint/core/rule/extractor/NoopTempFolderTest.java",
    "content": "/*\n * SonarLint Core - Rule Extractor\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rule.extractor;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass NoopTempFolderTest {\n  @Test\n  void should_not_be_implemented() {\n    var noopTempFolder = new NoopTempFolder();\n\n    assertThrows(UnsupportedOperationException.class, noopTempFolder::newDir);\n    assertThrows(UnsupportedOperationException.class, noopTempFolder::newFile);\n    assertThrows(UnsupportedOperationException.class, () -> noopTempFolder.newDir(\"name\"));\n    assertThrows(UnsupportedOperationException.class, () -> noopTempFolder.newFile(\"prefix\", \"suffix\"));\n  }\n}\n"
  },
  {
    "path": "backend/rule-extractor/src/test/java/org/sonarsource/sonarlint/core/rule/extractor/SecurityStandardsTest.java",
    "content": "/*\n * SonarLint Core - Rule Extractor\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rule.extractor;\n\nimport org.junit.jupiter.api.Test;\n\nimport static java.util.Collections.emptySet;\nimport static java.util.Collections.singleton;\nimport static java.util.stream.Collectors.toSet;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.sonarsource.sonarlint.core.rule.extractor.SecurityStandards.CWES_BY_SL_CATEGORY;\nimport static org.sonarsource.sonarlint.core.rule.extractor.SecurityStandards.fromSecurityStandards;\n\nclass SecurityStandardsTest {\n  @Test\n  void fromSecurityStandards_from_empty_set_has_SLCategory_OTHERS() {\n    SecurityStandards securityStandards = fromSecurityStandards(emptySet());\n\n    assertThat(securityStandards.getStandards()).isEmpty();\n    assertThat(securityStandards.getSlCategory()).isEqualTo(SecurityStandards.SLCategory.OTHERS);\n    assertThat(securityStandards.getIgnoredSLCategories()).isEmpty();\n  }\n\n  @Test\n  void fromSecurityStandards_from_empty_set_has_unknown_cwe_standard() {\n    SecurityStandards securityStandards = fromSecurityStandards(emptySet());\n\n    assertThat(securityStandards.getStandards()).isEmpty();\n    assertThat(securityStandards.getCwe()).containsOnly(\"unknown\");\n  }\n\n  @Test\n  void fromSecurityStandards_finds_SLCategory_from_any_if_the_mapped_CWE_standard() {\n    CWES_BY_SL_CATEGORY.forEach((slCategory, cwes) -> {\n      cwes.forEach(cwe -> {\n        SecurityStandards securityStandards = fromSecurityStandards(singleton(\"cwe:\" + cwe));\n\n        assertThat(securityStandards.getSlCategory()).isEqualTo(slCategory);\n      });\n    });\n  }\n\n  @Test\n  void fromSecurityStandards_finds_SLCategory_from_multiple_of_the_mapped_CWE_standard() {\n    CWES_BY_SL_CATEGORY.forEach((slCategory, cwes) -> {\n      SecurityStandards securityStandards = fromSecurityStandards(cwes.stream().map(t -> \"cwe:\" + t).collect(toSet()));\n\n      assertThat(securityStandards.getSlCategory()).isEqualTo(slCategory);\n    });\n  }\n}\n"
  },
  {
    "path": "backend/rule-extractor/src/test/java/org/sonarsource/sonarlint/core/rule/extractor/SonarLintRuleDefinitionTests.java",
    "content": "/*\n * SonarLint Core - Rule Extractor\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rule.extractor;\n\nimport org.junit.jupiter.api.Test;\nimport org.sonar.api.server.rule.RulesDefinition;\nimport org.sonar.api.server.rule.RulesDefinition.NewRepository;\nimport org.sonar.api.server.rule.RulesDefinition.NewRule;\nimport org.sonar.api.server.rule.RulesDefinition.Rule;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SonarLintRuleDefinitionTests {\n\n  @Test\n  void convertMarkdownDescriptionToHtml() {\n    RulesDefinition.Context context = new RulesDefinition.Context();\n    NewRepository newRepository = context.createRepository(\"my-repo\", \"java\");\n    NewRule createRule = newRepository.createRule(\"my-rule-with-markdown-description\")\n      .setName(\"My Rule\");\n    createRule.setMarkdownDescription(\"  = Title\\n  * one\\n* two\");\n    newRepository.done();\n\n    Rule rule = context.repositories().get(0).rule(\"my-rule-with-markdown-description\");\n\n    SonarLintRuleDefinition underTest = new SonarLintRuleDefinition(rule);\n\n    assertThat(underTest.getHtmlDescription()).isEqualTo(\"<h1>Title</h1><ul><li>one</li>\\n\"\n      + \"<li>two</li></ul>\");\n  }\n\n}\n"
  },
  {
    "path": "backend/rule-extractor/src/test/resources/logback-test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE configuration>\n\n<configuration>\n  <include resource=\"logback-shared.xml\"/>\n</configuration>"
  },
  {
    "path": "backend/server-api/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-backend-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../pom.xml</relativePath>\n  </parent>\n  <artifactId>sonarlint-server-api</artifactId>\n  <name>SonarLint Core - Server API</name>\n  <description>Interaction with the server through its web API</description>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.code.findbugs</groupId>\n      <artifactId>jsr305</artifactId>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-lang3</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.google.guava</groupId>\n      <artifactId>guava</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-commons</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-http</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>com.google.code.gson</groupId>\n      <artifactId>gson</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.google.protobuf</groupId>\n      <artifactId>protobuf-java</artifactId>\n      <version>${protobuf.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.sonarqube</groupId>\n      <artifactId>sonar-scanner-protocol</artifactId>\n      <version>${sonar-scanner-protocol.version}</version>\n      <exclusions>\n        <!-- Only interested in the protobuf stubs -->\n        <exclusion>\n          <groupId>*</groupId>\n          <artifactId>*</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n\n    <!-- unit tests -->\n    <dependency>\n      <groupId>com.squareup.okhttp3</groupId>\n      <artifactId>okhttp</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>com.squareup.okhttp3</groupId>\n      <artifactId>mockwebserver3</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-engine</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.assertj</groupId>\n      <artifactId>assertj-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.awaitility</groupId>\n      <artifactId>awaitility</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-params</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <!-- For Apache HTTPClient -->\n    <dependency>\n      <groupId>ch.qos.logback</groupId>\n      <artifactId>logback-classic</artifactId>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>kr.motd.maven</groupId>\n        <artifactId>os-maven-plugin</artifactId>\n        <executions>\n          <execution>\n            <phase>initialize</phase>\n            <goals>\n              <goal>detect</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.xolstice.maven.plugins</groupId>\n        <artifactId>protobuf-maven-plugin</artifactId>\n        <executions>\n          <execution>\n            <goals>\n              <goal>compile</goal>\n              <goal>test-compile</goal>\n            </goals>\n          </execution>\n        </executions>\n        <configuration>\n          <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n\n\n  <profiles>\n    <!-- Workaround for https://issues.apache.org/jira/projects/MJAR/issues/MJAR-138 -->\n    <profile>\n      <id>conditionally-add-commons-tests-if-tests-not-skipped</id>\n      <activation>\n        <property>\n          <name>maven.test.skip</name>\n          <value>!true</value>\n        </property>\n      </activation>\n      <dependencies>\n        <dependency>\n          <groupId>${project.groupId}</groupId>\n          <artifactId>sonarlint-commons</artifactId>\n          <version>${project.version}</version>\n          <classifier>tests</classifier>\n          <type>test-jar</type>\n          <scope>test</scope>\n        </dependency>\n      </dependencies>\n    </profile>\n  </profiles>\n</project>\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/EndpointParams.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi;\n\nimport java.util.Optional;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\n/**\n * SonarQube or SonarCloud endpoint parameters\n */\npublic class EndpointParams {\n\n  private final String baseUrl;\n  @Nullable\n  // For SonarQube Cloud, some APIs are located under a dedicated subdomain. Null for SonarQube Server\n  private final String apiBaseUrl;\n  private final boolean sonarCloud;\n  @Nullable\n  private final String organization;\n\n  public EndpointParams(String baseUrl, @Nullable String apiBaseUrl, boolean isSonarCloud, @Nullable String organization) {\n    this.baseUrl = baseUrl;\n    this.apiBaseUrl = apiBaseUrl;\n    this.sonarCloud = isSonarCloud;\n    this.organization = organization;\n  }\n\n  public String getBaseUrl() {\n    return baseUrl;\n  }\n\n  @CheckForNull\n  public String getApiBaseUrl() {\n    return apiBaseUrl;\n  }\n\n  public boolean isSonarCloud() {\n    return sonarCloud;\n  }\n\n  /**\n   * Organization can be missing even for SonarCloud, because some API calls are made before knowing the organization (like fetching user organizations)\n   */\n  public Optional<String> getOrganization() {\n    return sonarCloud ? Optional.ofNullable(organization) : Optional.empty();\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/ServerApi.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi;\n\nimport java.util.Optional;\nimport org.sonarsource.sonarlint.core.http.HttpClient;\nimport org.sonarsource.sonarlint.core.serverapi.authentication.AuthenticationApi;\nimport org.sonarsource.sonarlint.core.serverapi.branches.ProjectBranchesApi;\nimport org.sonarsource.sonarlint.core.serverapi.component.ComponentApi;\nimport org.sonarsource.sonarlint.core.serverapi.developers.DevelopersApi;\nimport org.sonarsource.sonarlint.core.serverapi.features.FeaturesApi;\nimport org.sonarsource.sonarlint.core.serverapi.fixsuggestions.FixSuggestionsApi;\nimport org.sonarsource.sonarlint.core.serverapi.hotspot.HotspotApi;\nimport org.sonarsource.sonarlint.core.serverapi.issue.IssueApi;\nimport org.sonarsource.sonarlint.core.serverapi.newcode.NewCodeApi;\nimport org.sonarsource.sonarlint.core.serverapi.organization.OrganizationApi;\nimport org.sonarsource.sonarlint.core.serverapi.plugins.PluginsApi;\nimport org.sonarsource.sonarlint.core.serverapi.projectbindings.ProjectBindingsApi;\nimport org.sonarsource.sonarlint.core.serverapi.push.PushApi;\nimport org.sonarsource.sonarlint.core.serverapi.qualityprofile.QualityProfileApi;\nimport org.sonarsource.sonarlint.core.serverapi.rules.RulesApi;\nimport org.sonarsource.sonarlint.core.serverapi.sca.ScaApi;\nimport org.sonarsource.sonarlint.core.serverapi.settings.SettingsApi;\nimport org.sonarsource.sonarlint.core.serverapi.source.SourceApi;\nimport org.sonarsource.sonarlint.core.serverapi.system.SystemApi;\nimport org.sonarsource.sonarlint.core.serverapi.users.UsersApi;\n\npublic class ServerApi {\n  private final ServerApiHelper helper;\n\n  public ServerApi(EndpointParams endpoint, HttpClient client) {\n    this(new ServerApiHelper(endpoint, client));\n  }\n\n  public ServerApi(ServerApiHelper helper) {\n    this.helper = helper;\n  }\n\n  public AuthenticationApi authentication() {\n    return new AuthenticationApi(helper);\n  }\n\n  public ProjectBindingsApi projectBindings() {\n    return new ProjectBindingsApi(helper);\n  }\n\n  public ComponentApi component() {\n    return new ComponentApi(helper);\n  }\n\n  public DevelopersApi developers() {\n    return new DevelopersApi(helper);\n  }\n\n  public HotspotApi hotspot() {\n    return new HotspotApi(helper);\n  }\n\n  public OrganizationApi organization() {\n    return new OrganizationApi(helper);\n  }\n\n  public IssueApi issue() {\n    return new IssueApi(helper);\n  }\n\n  public SourceApi source() {\n    return new SourceApi(helper);\n  }\n\n  public SettingsApi settings() {\n    return new SettingsApi(helper);\n  }\n\n  public QualityProfileApi qualityProfile() {\n    return new QualityProfileApi(helper);\n  }\n\n  public PluginsApi plugins() {\n    return new PluginsApi(helper);\n  }\n\n  public RulesApi rules() {\n    return new RulesApi(helper);\n  }\n\n  public SystemApi system() {\n    return new SystemApi(helper);\n  }\n\n  public ProjectBranchesApi branches() {\n    return new ProjectBranchesApi(helper);\n  }\n\n  public PushApi push() {\n    return new PushApi(helper);\n  }\n\n  public NewCodeApi newCodeApi() {\n    return new NewCodeApi(helper);\n  }\n\n  public FixSuggestionsApi fixSuggestions() {\n    return new FixSuggestionsApi(helper);\n  }\n\n  public FeaturesApi features() {\n    return new FeaturesApi(helper);\n  }\n\n  public ScaApi sca() {\n    return new ScaApi(helper);\n  }\n\n  public UsersApi users() {\n    return new UsersApi(helper);\n  }\n\n  public boolean isSonarCloud() {\n    return helper.isSonarCloud();\n  }\n\n  public Optional<String> getOrganizationKey() {\n    return helper.getOrganizationKey();\n  }\n\n  public ServerApiHelper getHelper() {\n    return helper;\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/ServerApiHelper.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonDeserializationContext;\nimport com.google.gson.JsonDeserializer;\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonParseException;\nimport com.google.gson.JsonParser;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.lang.reflect.Type;\nimport java.net.HttpURLConnection;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.time.ZonedDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.function.LongConsumer;\nimport java.util.function.Supplier;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.Strings;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.http.HttpClient;\nimport org.sonarsource.sonarlint.core.http.HttpConnectionListener;\nimport org.sonarsource.sonarlint.core.serverapi.exception.ForbiddenException;\nimport org.sonarsource.sonarlint.core.serverapi.exception.NetworkException;\nimport org.sonarsource.sonarlint.core.serverapi.exception.NotFoundException;\nimport org.sonarsource.sonarlint.core.serverapi.exception.ServerErrorException;\nimport org.sonarsource.sonarlint.core.serverapi.exception.TooManyRequestsException;\nimport org.sonarsource.sonarlint.core.serverapi.exception.UnauthorizedException;\nimport org.sonarsource.sonarlint.core.serverapi.exception.UnexpectedBodyException;\nimport org.sonarsource.sonarlint.core.serverapi.exception.UnexpectedServerResponseException;\n\nimport static java.util.Objects.requireNonNull;\nimport static org.sonarsource.sonarlint.core.http.HttpClient.JSON_CONTENT_TYPE;\n\n/**\n * Wrapper around HttpClient to avoid repetitive code, like support of pagination, and log timing of requests\n */\npublic class ServerApiHelper {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  public static final int PAGE_SIZE = 500;\n  public static final int MAX_PAGES = 20;\n  public static final int HTTP_TOO_MANY_REQUESTS = 429;\n\n  private final HttpClient client;\n  private final EndpointParams endpointParams;\n  // avoid Gson replacing characters like < > or = with Unicode representation\n  private static final Gson gson = new GsonBuilder()\n    .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeDeserializer())\n    .disableHtmlEscaping()\n    .create();\n\n  public ServerApiHelper(EndpointParams endpointParams, HttpClient client) {\n    this.endpointParams = endpointParams;\n    this.client = client;\n  }\n\n  public boolean isSonarCloud() {\n    return endpointParams.isSonarCloud();\n  }\n\n  public HttpClient.Response getAnonymous(String path, SonarLintCancelMonitor cancelMonitor) {\n    var response = rawGetUrlAnonymous(buildEndpointUrl(path), cancelMonitor);\n    if (!response.isSuccessful()) {\n      throw handleError(response);\n    }\n    return response;\n  }\n\n  public <T> T getAnonymousJson(String path, Class<T> responseClass, SonarLintCancelMonitor cancelMonitor) {\n    try (var response = getAnonymous(path, cancelMonitor)) {\n      return deserializeJsonBody(response, responseClass);\n    }\n  }\n\n  public HttpClient.Response get(String path, SonarLintCancelMonitor cancelMonitor) {\n    var response = rawGet(path, cancelMonitor);\n    if (!response.isSuccessful()) {\n      throw handleError(response);\n    }\n    return response;\n  }\n\n  public <T> T getJson(String path, Class<T> responseClass, SonarLintCancelMonitor cancelMonitor) {\n    try (var response = get(path, cancelMonitor)) {\n      return deserializeJsonBody(response, responseClass);\n    }\n  }\n\n  public <T> T apiGetJson(String path, Class<T> responseClass, SonarLintCancelMonitor cancelMonitor) {\n    try (var response = rawGetUrl(buildApiEndpointUrl(path), cancelMonitor)) {\n      if (!response.isSuccessful()) {\n        throw handleError(response);\n      }\n      return deserializeJsonBody(response, responseClass);\n    }\n  }\n\n  public HttpClient.Response post(String relativePath, String contentType, String body, SonarLintCancelMonitor cancelMonitor) {\n    return postUrl(buildEndpointUrl(relativePath), contentType, body, cancelMonitor);\n  }\n\n  public void postJson(String relativePath, Object requestBody, SonarLintCancelMonitor cancelMonitor) {\n    postJson(relativePath, requestBody, null, cancelMonitor);\n  }\n\n  public <T> T postJson(String relativePath, Object requestBody, @Nullable Class<T> responseClass, SonarLintCancelMonitor cancelMonitor) {\n    var body = gson.toJson(requestBody);\n    try (var response = post(relativePath, JSON_CONTENT_TYPE, body, cancelMonitor)) {\n      return responseClass == null ? null : deserializeJsonBody(response, responseClass);\n    }\n  }\n\n  public void apiPostJson(String relativePath, Object requestBody, SonarLintCancelMonitor cancelMonitor) {\n    apiPostJson(relativePath, requestBody, null, cancelMonitor);\n  }\n\n  public <T> T apiPostJson(String relativePath, Object requestBody, @Nullable Class<T> responseClass, SonarLintCancelMonitor cancelMonitor) {\n    var body = gson.toJson(requestBody);\n    try (var response = postUrl(buildApiEndpointUrl(relativePath), JSON_CONTENT_TYPE, body, cancelMonitor)) {\n      return responseClass == null ? null : deserializeJsonBody(response, responseClass);\n    }\n  }\n\n  private HttpClient.Response postUrl(String url, String contentType, String body, SonarLintCancelMonitor cancelMonitor) {\n    var response = rawPost(url, contentType, body, cancelMonitor);\n    if (!response.isSuccessful()) {\n      throw handleError(response);\n    }\n    return response;\n  }\n\n  /**\n   * Execute GET and don't check response\n   */\n  public HttpClient.Response rawGet(String relativePath, SonarLintCancelMonitor cancelMonitor) {\n    return rawGetUrl(buildEndpointUrl(relativePath), cancelMonitor);\n  }\n\n  private HttpClient.Response rawGetUrl(String url, SonarLintCancelMonitor cancelMonitor) {\n    var startTime = Instant.now();\n    var httpFuture = client.getAsync(url);\n    return processResponse(\"GET\", cancelMonitor, httpFuture, startTime, url);\n  }\n\n  private HttpClient.Response rawGetUrlAnonymous(String url, SonarLintCancelMonitor cancelMonitor) {\n    var startTime = Instant.now();\n    var httpFuture = client.getAsyncAnonymous(url);\n    return processResponse(\"GET\", cancelMonitor, httpFuture, startTime, url);\n  }\n\n  public HttpClient.Response rawPost(String url, String contentType, String body, SonarLintCancelMonitor cancelMonitor) {\n    var startTime = Instant.now();\n    var httpFuture = client.postAsync(url, contentType, body);\n    return processResponse(\"POST\", cancelMonitor, httpFuture, startTime, url);\n  }\n\n  private static HttpClient.Response processResponse(String method, SonarLintCancelMonitor cancelMonitor, CompletableFuture<HttpClient.Response> httpFuture,\n    Instant startTime, String url) {\n    cancelMonitor.onCancel(() -> httpFuture.cancel(true));\n    try {\n      var response = httpFuture.join();\n      logTime(method, startTime, url, response.code());\n      return response;\n    } catch (Exception e) {\n      logFailure(method, startTime, url, e.getMessage());\n      throw new NetworkException(\"Request failed\", e);\n    }\n  }\n\n  private static void logTime(String method, Instant startTime, String url, int responseCode) {\n    var duration = Duration.between(startTime, Instant.now());\n    LOG.debug(\"{} {} {} | response time={}ms\", method, responseCode, url, duration.toMillis());\n  }\n\n  private static void logFailure(String method, Instant startTime, String url, String message) {\n    var duration = Duration.between(startTime, Instant.now());\n    LOG.debug(\"{} {} {} | failed after {}ms\", method, url, message, duration.toMillis());\n  }\n\n  private String buildEndpointUrl(String relativePath) {\n    return concat(endpointParams.getBaseUrl(), relativePath);\n  }\n\n  private String buildApiEndpointUrl(String relativePath) {\n    return concat(requireNonNull(endpointParams.getApiBaseUrl()), relativePath);\n  }\n\n  public static String concat(String baseUrl, String relativePath) {\n    return Strings.CS.appendIfMissing(baseUrl, \"/\") +\n      (relativePath.startsWith(\"/\") ? relativePath.substring(1) : relativePath);\n  }\n\n  public static RuntimeException handleError(HttpClient.Response toBeClosed) {\n    try (var failedResponse = toBeClosed) {\n      if (failedResponse.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {\n        return new UnauthorizedException(\"Not authorized. Please check server credentials.\");\n      }\n      if (failedResponse.code() == HttpURLConnection.HTTP_FORBIDDEN) {\n        // Details are in the response content\n        var error = tryParseAsJsonError(failedResponse);\n        if (error == null) {\n          error = \"Access denied\";\n        }\n        return new ForbiddenException(error);\n      }\n      if (failedResponse.code() == HttpURLConnection.HTTP_NOT_FOUND) {\n        return new NotFoundException(formatHttpFailedResponse(failedResponse, null));\n      }\n      if (failedResponse.code() >= HttpURLConnection.HTTP_INTERNAL_ERROR) {\n        return new ServerErrorException(formatHttpFailedResponse(failedResponse, null));\n      }\n      if (failedResponse.code() == HTTP_TOO_MANY_REQUESTS) {\n        return new TooManyRequestsException(\"Too many requests have been made.\");\n      }\n\n      var errorMsg = tryParseAsJsonError(failedResponse);\n      return new UnexpectedServerResponseException(formatHttpFailedResponse(failedResponse, errorMsg));\n    }\n  }\n\n  private static String formatHttpFailedResponse(HttpClient.Response failedResponse, @Nullable String errorMsg) {\n    return \"Error \" + failedResponse.code() + \" on \" + failedResponse.url() + (errorMsg != null ? (\": \" + errorMsg) : \"\");\n  }\n\n  @CheckForNull\n  private static String tryParseAsJsonError(HttpClient.Response response) {\n    try {\n      var content = response.bodyAsString();\n      if (StringUtils.isBlank(content)) {\n        return null;\n      }\n      var obj = JsonParser.parseString(content).getAsJsonObject();\n      var errors = obj.getAsJsonArray(\"errors\");\n      if (errors == null) {\n        return null;\n      }\n      List<String> errorMessages = new ArrayList<>();\n      for (JsonElement e : errors) {\n        errorMessages.add(e.getAsJsonObject().get(\"msg\").getAsString());\n      }\n      return String.join(\", \", errorMessages);\n    } catch (Exception e) {\n      LOG.error(\"Error parsing JSON error\", e);\n    }\n    return null;\n  }\n\n  public Optional<String> getOrganizationKey() {\n    return endpointParams.getOrganization();\n  }\n\n  public <G, F> void getPaginated(String relativeUrlWithoutPaginationParams, CheckedFunction<InputStream, G> responseParser, Function<G, Number> getPagingTotal,\n    Function<G, List<F>> itemExtractor, Consumer<F> itemConsumer, boolean limitToTwentyPages, SonarLintCancelMonitor cancelChecker) {\n    getPaginated(relativeUrlWithoutPaginationParams, responseParser, getPagingTotal, itemExtractor, itemConsumer, limitToTwentyPages, cancelChecker, \"p\", \"ps\");\n  }\n\n  public <G, F> void getPaginated(String relativeUrlWithoutPaginationParams, CheckedFunction<InputStream, G> responseParser, Function<G, Number> getPagingTotal,\n    Function<G, List<F>> itemExtractor, Consumer<F> itemConsumer, boolean limitToTwentyPages, SonarLintCancelMonitor cancelChecker, String pageFieldName,\n    String pageSizeFieldName) {\n    var baseUrl = buildEndpointUrl(relativeUrlWithoutPaginationParams);\n    getPaginatedBaseUrl(baseUrl, responseParser, getPagingTotal, itemExtractor,\n      itemConsumer, limitToTwentyPages, cancelChecker, pageFieldName,\n      pageSizeFieldName);\n  }\n\n  public <G, F> void apiGetPaginated(String relativeUrlWithoutPaginationParams, CheckedFunction<InputStream, G> responseParser, Function<G, Number> getPagingTotal,\n    Function<G, List<F>> itemExtractor, Consumer<F> itemConsumer, boolean limitToTwentyPages, SonarLintCancelMonitor cancelChecker, String pageFieldName,\n    String pageSizeFieldName) {\n    var baseUrl = buildApiEndpointUrl(relativeUrlWithoutPaginationParams);\n    getPaginatedBaseUrl(baseUrl, responseParser, getPagingTotal, itemExtractor,\n      itemConsumer, limitToTwentyPages, cancelChecker, pageFieldName,\n      pageSizeFieldName);\n  }\n\n  private <G, F> void getPaginatedBaseUrl(String baseUrl, CheckedFunction<InputStream, G> responseParser, Function<G, Number> getPagingTotal,\n    Function<G, List<F>> itemExtractor, Consumer<F> itemConsumer, boolean limitToTwentyPages, SonarLintCancelMonitor cancelChecker, String pageFieldName,\n    String pageSizeFieldName) {\n    var page = new AtomicInteger(0);\n    var stop = new AtomicBoolean(false);\n    var loaded = new AtomicInteger(0);\n    do {\n      page.incrementAndGet();\n      var fullUrl = baseUrl + (baseUrl.contains(\"?\") ? \"&\" : \"?\") +\n        pageSizeFieldName + \"=\" + PAGE_SIZE + \"&\" + pageFieldName + \"=\" + page;\n      ServerApiHelper.consumeTimed(\n        () -> rawGetUrl(fullUrl, cancelChecker),\n        response -> processPage(baseUrl, responseParser, getPagingTotal, itemExtractor, itemConsumer, limitToTwentyPages, page, stop, loaded,\n          response),\n        duration -> LOG.debug(\"Page downloaded in {}ms\", duration));\n    } while (!stop.get() && !cancelChecker.isCanceled());\n  }\n\n  private static <F, G> void processPage(String baseUrl, CheckedFunction<InputStream, G> responseParser, Function<G, Number> getPagingTotal, Function<G, List<F>> itemExtractor,\n    Consumer<F> itemConsumer, boolean limitToTwentyPages, AtomicInteger page, AtomicBoolean stop, AtomicInteger loaded,\n    HttpClient.Response response)\n    throws IOException {\n    if (!response.isSuccessful()) {\n      throw handleError(response);\n    }\n    G protoBufResponse;\n    try (var body = response.bodyAsStream()) {\n      protoBufResponse = responseParser.apply(body);\n    }\n\n    var items = itemExtractor.apply(protoBufResponse);\n    for (F item : items) {\n      itemConsumer.accept(item);\n      loaded.incrementAndGet();\n    }\n    var isEmpty = items.isEmpty();\n    var pagingTotal = getPagingTotal.apply(protoBufResponse).longValue();\n    // SONAR-9150 Some WS used to miss the paging information, so iterate until response is empty\n    stop.set(isEmpty || (pagingTotal > 0 && (long) page.get() * PAGE_SIZE >= pagingTotal));\n    if (!stop.get() && limitToTwentyPages && page.get() >= MAX_PAGES) {\n      stop.set(true);\n      LOG.debug(\"Limiting number of requested pages from '{}' to {}. Some of the data won't be fetched\", baseUrl, MAX_PAGES);\n    }\n  }\n\n  public HttpClient.AsyncRequest getEventStream(String path, HttpConnectionListener connectionListener, Consumer<String> messageConsumer) {\n    return client.getEventStream(buildEndpointUrl(path),\n      connectionListener,\n      messageConsumer);\n  }\n\n  @FunctionalInterface\n  public interface CheckedFunction<T, R> {\n    R apply(T t) throws IOException;\n  }\n\n  public static <G> G processTimed(Supplier<HttpClient.Response> responseSupplier, IOFunction<HttpClient.Response, G> responseProcessor,\n    LongConsumer durationConsumer) {\n    var startTime = Instant.now();\n    G result;\n    try (var response = responseSupplier.get()) {\n      result = responseProcessor.apply(response);\n    } catch (IOException e) {\n      throw new IllegalStateException(\"Unable to parse WS response: \" + e.getMessage(), e);\n    }\n    durationConsumer.accept(Duration.between(startTime, Instant.now()).toMillis());\n    return result;\n  }\n\n  public static void consumeTimed(Supplier<HttpClient.Response> responseSupplier, IOConsumer<HttpClient.Response> responseConsumer,\n    LongConsumer durationConsumer) {\n    processTimed(responseSupplier, r -> {\n      responseConsumer.accept(r);\n      return null;\n    }, durationConsumer);\n  }\n\n  @FunctionalInterface\n  public interface IOFunction<T, R> {\n    R apply(T t) throws IOException;\n  }\n\n  @FunctionalInterface\n  public interface IOConsumer<T> {\n    void accept(T t) throws IOException;\n  }\n\n  /**\n   * Deserialize JSON response body to the specified type.\n   *\n   * @param response the HTTP response containing JSON body\n   * @param responseClass the class to deserialize to\n   * @return the deserialized object\n   * @throws UnexpectedBodyException if the response body cannot be deserialized\n   */\n  private static <T> T deserializeJsonBody(HttpClient.Response response, Class<T> responseClass) {\n    try {\n      var responseStr = response.bodyAsString();\n      return gson.fromJson(responseStr, responseClass);\n    } catch (Exception e) {\n      throw new UnexpectedBodyException(e);\n    }\n  }\n\n  private static class ZonedDateTimeDeserializer implements JsonDeserializer<ZonedDateTime> {\n    private static final String DATETIME_FORMAT = \"yyyy-MM-dd'T'HH:mm:ssZ\";\n    private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(DATETIME_FORMAT);\n\n    @Override\n    public ZonedDateTime deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {\n      return ZonedDateTime.parse(json.getAsJsonPrimitive().getAsString(), TIME_FORMATTER);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/UrlUtils.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi;\n\nimport java.net.URLEncoder;\nimport java.nio.charset.StandardCharsets;\n\npublic class UrlUtils {\n\n  private UrlUtils() {\n  }\n\n  public static String urlEncode(String toEncode) {\n    return URLEncoder.encode(toEncode, StandardCharsets.UTF_8);\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/authentication/AuthenticationApi.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.authentication;\n\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\nimport org.sonarsource.sonarlint.core.serverapi.system.ValidationResult;\n\npublic class AuthenticationApi {\n\n  private final ServerApiHelper serverApiHelper;\n\n  public AuthenticationApi(ServerApiHelper serverApiHelper) {\n    this.serverApiHelper = serverApiHelper;\n  }\n\n  public ValidationResult validate(SonarLintCancelMonitor cancelMonitor) {\n    var validateResponse = serverApiHelper.getJson(\"api/authentication/validate?format=json\", ValidateResponseDto.class, cancelMonitor);\n    return new ValidationResult(validateResponse.valid(), validateResponse.valid() ? \"Authentication successful\" : \"Authentication failed\");\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/authentication/ValidateResponseDto.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.authentication;\n\npublic record ValidateResponseDto(boolean valid) {\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/authentication/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.authentication;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/branches/ProjectBranchesApi.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.branches;\n\nimport java.util.List;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\nimport org.sonarsource.sonarlint.core.serverapi.UrlUtils;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common.BranchType;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.ProjectBranches;\n\npublic class ProjectBranchesApi {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private static final String LIST_ALL_PROJECT_BRANCHES_URL = \"/api/project_branches/list.protobuf\";\n  private final ServerApiHelper helper;\n\n  public ProjectBranchesApi(ServerApiHelper helper) {\n    this.helper = helper;\n  }\n\n  public List<ServerBranch> getAllBranches(String projectKey, SonarLintCancelMonitor cancelMonitor) {\n    ProjectBranches.ListWsResponse response;\n    try (var wsResponse = helper.get(LIST_ALL_PROJECT_BRANCHES_URL + \"?project=\" + UrlUtils.urlEncode(projectKey), cancelMonitor); var is = wsResponse.bodyAsStream()) {\n      response = ProjectBranches.ListWsResponse.parseFrom(is);\n    } catch (Exception e) {\n      LOG.error(\"Error while fetching project branches\", e);\n      return List.of();\n    }\n    return response.getBranchesList().stream()\n      .filter(b -> b.getType() == BranchType.BRANCH || b.getType() == BranchType.LONG)\n      .map(branchWs -> new ServerBranch(branchWs.getName(), branchWs.getIsMain())).toList();\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/branches/ServerBranch.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.branches;\n\npublic class ServerBranch {\n\n  private final String name;\n  private final boolean isMain;\n\n  public ServerBranch(String name, boolean isMain) {\n    this.name = name;\n    this.isMain = isMain;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public boolean isMain() {\n    return isMain;\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/branches/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.branches;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/component/Component.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.component;\n\npublic record Component(String key, String name, boolean isAiCodeFixEnabled) {\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/component/ComponentApi.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.component;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.function.Function;\nimport javax.annotation.CheckForNull;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\nimport org.sonarsource.sonarlint.core.serverapi.UrlUtils;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Components;\n\npublic class ComponentApi {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final String ORGANIZATION_PARAM = \"&organization=\";\n\n  private final ServerApiHelper helper;\n\n  public ComponentApi(ServerApiHelper helper) {\n    this.helper = helper;\n  }\n\n  public List<String> getAllFileKeys(String projectKey, SonarLintCancelMonitor cancelMonitor) {\n    var path = buildAllFileKeysPath(projectKey);\n    List<String> files = new ArrayList<>();\n\n    helper.getPaginated(path,\n      Components.TreeWsResponse::parseFrom,\n      r -> r.getPaging().getTotal(),\n      Components.TreeWsResponse::getComponentsList,\n      component -> files.add(component.getKey()), false, cancelMonitor);\n    return files;\n  }\n\n  private String buildAllFileKeysPath(String projectKey) {\n    var url = new StringBuilder();\n    url.append(\"api/components/tree.protobuf?qualifiers=FIL,UTS&\");\n    url.append(\"component=\").append(UrlUtils.urlEncode(projectKey));\n    helper.getOrganizationKey().ifPresent(org -> url.append(ORGANIZATION_PARAM).append(UrlUtils.urlEncode(org)));\n    return url.toString();\n  }\n\n  public Optional<ServerProject> getProject(String projectKey, SonarLintCancelMonitor cancelMonitor) {\n    return fetchComponent(projectKey, cancelMonitor).map(component -> new ServerProject(component.key(), component.name(), component.isAiCodeFixEnabled()));\n  }\n\n  public List<ServerProject> getAllProjects(SonarLintCancelMonitor cancelMonitor) {\n    List<ServerProject> serverProjects = new ArrayList<>();\n    helper.getPaginated(getAllProjectsUrl(),\n      Components.SearchWsResponse::parseFrom,\n      r -> r.getPaging().getTotal(),\n      Components.SearchWsResponse::getComponentsList,\n      project -> serverProjects.add(new ServerProject(project.getKey(), project.getName(), project.getIsAiCodeFixEnabled())),\n      true,\n      cancelMonitor);\n    return serverProjects;\n  }\n\n  private String getAllProjectsUrl() {\n    var searchUrl = new StringBuilder();\n    searchUrl.append(\"api/components/search.protobuf?qualifiers=TRK\");\n    helper.getOrganizationKey()\n      .ifPresent(org -> searchUrl.append(ORGANIZATION_PARAM).append(UrlUtils.urlEncode(org)));\n    return searchUrl.toString();\n  }\n\n  @CheckForNull\n  public SearchProjectResponse searchProjects(String projectId, SonarLintCancelMonitor cancelMonitor) {\n    var encodedProjectId = UrlUtils.urlEncode(projectId);\n    var organization = helper.getOrganizationKey();\n\n    if (organization.isEmpty()) {\n      LOG.warn(\"Organization key is not set, cannot search projects for ID: {}\", projectId);\n      return null;\n    }\n    var path = \"/api/components/search_projects?projectIds=\" + encodedProjectId + ORGANIZATION_PARAM + organization.get();\n\n    var searchResponse = helper.getJson(path, SearchProjectResponseDto.class, cancelMonitor);\n    return searchResponse.components().stream()\n      .findFirst()\n      .map(component -> new SearchProjectResponse(component.key(), component.name()))\n      .orElse(null);\n  }\n\n  private Optional<Component> fetchComponent(String componentKey, SonarLintCancelMonitor cancelMonitor) {\n    return fetchComponent(componentKey, response -> {\n      var wsComponent = response.getComponent();\n      return new Component(wsComponent.getKey(), wsComponent.getName(), wsComponent.getIsAiCodeFixEnabled());\n    }, cancelMonitor);\n  }\n\n  public Optional<String> fetchFirstAncestorKey(String componentKey, SonarLintCancelMonitor cancelMonitor) {\n    return fetchComponent(componentKey, response -> response.getAncestorsList().stream().map(Components.Component::getKey).findFirst().orElse(null), cancelMonitor);\n  }\n\n  private <T> Optional<T> fetchComponent(String componentKey, Function<Components.ShowWsResponse, T> responseConsumer, SonarLintCancelMonitor cancelMonitor) {\n    return ServerApiHelper.processTimed(\n      () -> helper.rawGet(\"api/components/show.protobuf?component=\" + UrlUtils.urlEncode(componentKey), cancelMonitor),\n      response -> {\n        if (response.isSuccessful()) {\n          var wsResponse = Components.ShowWsResponse.parseFrom(response.bodyAsStream());\n          return Optional.ofNullable(responseConsumer.apply(wsResponse));\n        }\n        return Optional.empty();\n      },\n      duration -> LOG.debug(\"Downloaded project details in {}ms\", duration));\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/component/SearchProjectResponse.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.component;\n\npublic record SearchProjectResponse(String projectKey, String projectName) {\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/component/SearchProjectResponseDto.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.component;\n\nimport java.util.List;\n\npublic record SearchProjectResponseDto(List<ProjectComponent> components) {\n  public record ProjectComponent(String key, String name) {}\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/component/ServerProject.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.component;\n\npublic record ServerProject(String key, String name, boolean isAiCodeFixEnabled) {\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/component/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.component;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/developers/DevelopersApi.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.developers;\n\nimport java.time.ZonedDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.Map;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\nimport org.sonarsource.sonarlint.core.serverapi.UrlUtils;\n\npublic class DevelopersApi {\n  private static final String API_PATH = \"api/developers/search_events\";\n  public static final String DATETIME_FORMAT = \"yyyy-MM-dd'T'HH:mm:ssZ\";\n  private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(DATETIME_FORMAT);\n\n  private final ServerApiHelper helper;\n\n  public DevelopersApi(ServerApiHelper helper) {\n    this.helper = helper;\n  }\n\n  public SearchEventsResponseDto searchEvents(Map<String, ZonedDateTime> projectTimestamps, SonarLintCancelMonitor cancelMonitor) {\n    return helper.getJson(getWsPath(projectTimestamps), SearchEventsResponseDto.class, cancelMonitor);\n  }\n\n  private static String getWsPath(Map<String, ZonedDateTime> projectTimestamps) {\n    // Sort project keys to simplify testing\n    var sortedProjectKeys = projectTimestamps.keySet().stream().sorted().toList();\n    var builder = new StringBuilder();\n    builder.append(API_PATH);\n    builder.append(\"?projects=\");\n    builder.append(sortedProjectKeys.stream()\n      .map(UrlUtils::urlEncode)\n      .collect(Collectors.joining(\",\")));\n\n    builder.append(\"&from=\");\n    builder.append(sortedProjectKeys.stream()\n      .map(projectTimestamps::get)\n      .map(timestamp -> timestamp.format(TIME_FORMATTER))\n      .map(UrlUtils::urlEncode)\n      .collect(Collectors.joining(\",\")));\n\n    return builder.toString();\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/developers/SearchEventsResponseDto.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.developers;\n\nimport java.time.ZonedDateTime;\nimport java.util.List;\n\nimport static java.util.Objects.requireNonNull;\n\npublic record SearchEventsResponseDto(List<Event> events) {\n  public record Event(String category, String message, String link, String project, ZonedDateTime date) {\n    public Event {\n      requireNonNull(category);\n      requireNonNull(message);\n      requireNonNull(link);\n      requireNonNull(project);\n      requireNonNull(date);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/developers/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.developers;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/exception/ForbiddenException.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.exception;\n\npublic class ForbiddenException extends ServerRequestException {\n  public ForbiddenException(String message) {\n    super(message);\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/exception/NetworkException.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.exception;\n\npublic class NetworkException extends ServerRequestException {\n\n  public NetworkException(String message, Throwable cause) {\n    super(message, cause);\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/exception/NotFoundException.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.exception;\n\npublic class NotFoundException extends ServerRequestException {\n  public NotFoundException(String msg) {\n    super(msg);\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/exception/ProjectNotFoundException.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.exception;\n\nimport javax.annotation.Nullable;\n\npublic class ProjectNotFoundException extends ServerRequestException {\n\n  public ProjectNotFoundException(String moduleKey, @Nullable String organizationKey) {\n    super(formatMessage(moduleKey, organizationKey));\n  }\n\n  private static String formatMessage(String moduleKey, @Nullable String organizationKey) {\n    if (organizationKey != null) {\n      return String.format(\"Project with key '%s' in organization '%s' not found on SonarQube Cloud (was it deleted?)\", moduleKey, organizationKey);\n    }\n    return String.format(\"Project with key '%s' not found on your SonarQube Server instance (was it deleted?)\", moduleKey);\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/exception/ServerErrorException.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.exception;\n\npublic class ServerErrorException extends ServerRequestException {\n  public ServerErrorException(String message) {\n    super(message);\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/exception/ServerRequestException.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.exception;\n\nimport org.sonarsource.sonarlint.core.commons.SonarLintException;\n\npublic class ServerRequestException extends SonarLintException {\n  public ServerRequestException(String message) {\n    super(message);\n  }\n\n  public ServerRequestException(String message, Throwable cause) {\n    super(message, cause);\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/exception/TooManyRequestsException.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.exception;\n\npublic class TooManyRequestsException extends ServerRequestException {\n  public TooManyRequestsException(String message) {\n    super(message);\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/exception/UnauthorizedException.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.exception;\n\npublic class UnauthorizedException extends ServerRequestException {\n  public UnauthorizedException(String message) {\n    super(message);\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/exception/UnexpectedBodyException.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.exception;\n\npublic class UnexpectedBodyException extends ServerRequestException {\n  public UnexpectedBodyException(Throwable cause) {\n    super(\"Unexpected body received\", cause);\n  }\n\n  public UnexpectedBodyException(String message) {\n    super(message);\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/exception/UnexpectedServerResponseException.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.exception;\n\nimport org.sonarsource.sonarlint.core.commons.SonarLintException;\n\npublic class UnexpectedServerResponseException extends SonarLintException {\n  public UnexpectedServerResponseException(String message) {\n    super(message);\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/exception/UnsupportedServerException.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.exception;\n\npublic class UnsupportedServerException extends ServerRequestException {\n\n  public UnsupportedServerException(String msg) {\n    super(msg);\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/exception/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.exception;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/features/Feature.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.features;\n\nimport java.util.Arrays;\nimport java.util.Optional;\n\npublic enum Feature {\n  AI_CODE_FIX(\"fix-suggestions\"),\n  SCA(\"sca\");\n\n  public static Optional<Feature> fromKey(String key) {\n    return Arrays.stream(values()).filter(f -> f.key.equals(key)).findFirst();\n  }\n\n  private final String key;\n\n  Feature(String key) {\n    this.key = key;\n  }\n\n  public String getKey() {\n    return key;\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/features/FeaturesApi.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.features;\n\nimport java.util.Arrays;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\n\npublic class FeaturesApi {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final ServerApiHelper helper;\n\n  public FeaturesApi(ServerApiHelper helper) {\n    this.helper = helper;\n  }\n\n  public Set<Feature> list(SonarLintCancelMonitor cancelMonitor) {\n    try {\n      var featureKeys = helper.getJson(\"api/features/list\", String[].class, cancelMonitor);\n      return Arrays.stream(featureKeys).flatMap(key -> Feature.fromKey(key).stream()).collect(Collectors.toSet());\n    } catch (Exception e) {\n      LOG.error(\"Error while fetching the list of features\", e);\n      throw e;\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/features/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.features;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/fixsuggestions/AiCodeFixConfiguration.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.fixsuggestions;\n\nimport java.util.Set;\nimport javax.annotation.Nullable;\n\npublic record AiCodeFixConfiguration(SuggestionFeatureEnablement enablement, @Nullable Set<String> enabledProjectKeys, boolean organizationEligible) {\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/fixsuggestions/AiSuggestionRequestBodyDto.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.fixsuggestions;\n\nimport javax.annotation.Nullable;\n\npublic record AiSuggestionRequestBodyDto(@Nullable String organizationKey, String projectKey, Issue issue) {\n  public record Issue(String message, Integer startLine, Integer endLine, String ruleKey, String sourceCode) {\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/fixsuggestions/AiSuggestionResponseBodyDto.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.fixsuggestions;\n\nimport java.util.List;\nimport java.util.UUID;\n\npublic record AiSuggestionResponseBodyDto(UUID id, String explanation, List<ChangeDto> changes) {\n  public record ChangeDto(int startLine, int endLine, String newCode) {\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/fixsuggestions/FixSuggestionsApi.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.fixsuggestions;\n\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\nimport org.sonarsource.sonarlint.core.serverapi.UrlUtils;\nimport org.sonarsource.sonarlint.core.serverapi.exception.TooManyRequestsException;\n\npublic class FixSuggestionsApi {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final ServerApiHelper helper;\n\n  public FixSuggestionsApi(ServerApiHelper helper) {\n    this.helper = helper;\n  }\n\n  public AiSuggestionResponseBodyDto getAiSuggestion(AiSuggestionRequestBodyDto dto, SonarLintCancelMonitor cancelMonitor) {\n    try {\n      return helper.isSonarCloud()\n        ? helper.apiPostJson(\"/fix-suggestions/ai-suggestions\", dto, AiSuggestionResponseBodyDto.class, cancelMonitor)\n        : helper.postJson(\"/api/v2/fix-suggestions/ai-suggestions\", dto, AiSuggestionResponseBodyDto.class, cancelMonitor);\n    } catch (TooManyRequestsException e) {\n      throw e;\n    } catch (Exception e) {\n      LOG.error(\"Error while generating an AI CodeFix\", e);\n      throw e;\n    }\n  }\n\n  public SupportedRulesResponseDto getSupportedRules(SonarLintCancelMonitor cancelMonitor) {\n    try {\n      return helper.isSonarCloud()\n        ? helper.apiGetJson(\"/fix-suggestions/supported-rules\", SupportedRulesResponseDto.class, cancelMonitor)\n        : helper.getJson(\"/api/v2/fix-suggestions/supported-rules\", SupportedRulesResponseDto.class, cancelMonitor);\n    } catch (Exception e) {\n      LOG.error(\"Error while fetching the list of AI CodeFix supported rules\", e);\n      throw e;\n    }\n  }\n\n  public OrganizationConfigsResponseDto getOrganizationConfigs(String organizationId, SonarLintCancelMonitor cancelMonitor) {\n    try {\n      return helper.apiGetJson(\"/fix-suggestions/organization-configs/\" + UrlUtils.urlEncode(organizationId),\n        OrganizationConfigsResponseDto.class, cancelMonitor);\n    } catch (Exception e) {\n      LOG.error(\"Error while fetching the AI CodeFix organization config\", e);\n      throw e;\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/fixsuggestions/OrganizationConfigsResponseDto.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.fixsuggestions;\n\npublic record OrganizationConfigsResponseDto(String organizationId, AiCodeFixConfiguration aiCodeFix) {\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/fixsuggestions/SuggestionFeatureEnablement.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.fixsuggestions;\n\npublic enum SuggestionFeatureEnablement {\n  DISABLED,\n  ENABLED_FOR_ALL_PROJECTS,\n  ENABLED_FOR_SOME_PROJECTS\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/fixsuggestions/SupportedRulesResponseDto.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.fixsuggestions;\n\nimport java.util.Set;\n\npublic record SupportedRulesResponseDto(Set<String> rules) {\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/fixsuggestions/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.fixsuggestions;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/hotspot/HotspotApi.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.hotspot;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\nimport org.sonarsource.sonarlint.core.serverapi.UrlUtils;\nimport org.sonarsource.sonarlint.core.serverapi.exception.UnexpectedBodyException;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Hotspots;\nimport org.sonarsource.sonarlint.core.serverapi.source.SourceApi;\nimport org.sonarsource.sonarlint.core.serverapi.util.ServerApiUtils;\n\nimport static org.sonarsource.sonarlint.core.http.HttpClient.FORM_URL_ENCODED_CONTENT_TYPE;\nimport static org.sonarsource.sonarlint.core.serverapi.UrlUtils.urlEncode;\nimport static org.sonarsource.sonarlint.core.serverapi.util.ProtobufUtil.readMessages;\nimport static org.sonarsource.sonarlint.core.serverapi.util.ServerApiUtils.toSonarQubePath;\n\npublic class HotspotApi {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  public static final Version MIN_SQ_VERSION_SUPPORTING_PULL = Version.create(\"10.1\");\n\n  private static final String HOTSPOTS_SEARCH_API_URL = \"/api/hotspots/search.protobuf\";\n  private static final String HOTSPOTS_SHOW_API_URL = \"/api/hotspots/show.protobuf\";\n  private static final String HOTSPOTS_PULL_API_URL = \"/api/hotspots/pull\";\n  private static final String PROJECT_KEY_QUERY_PARAM = \"?projectKey=\";\n\n  private final ServerApiHelper helper;\n\n  public HotspotApi(ServerApiHelper helper) {\n    this.helper = helper;\n  }\n\n  public void changeStatus(String hotspotKey, HotspotReviewStatus status, SonarLintCancelMonitor cancelMonitor) {\n    var isReviewed = status.isReviewed();\n    var webApiStatus = isReviewed ? \"REVIEWED\" : \"TO_REVIEW\";\n    var body = \"hotspot=\" + urlEncode(hotspotKey) + \"&status=\" + urlEncode(webApiStatus);\n    if (isReviewed) {\n      body += \"&resolution=\" + urlEncode(status.name());\n    }\n    helper.post(\"api/hotspots/change_status\", FORM_URL_ENCODED_CONTENT_TYPE, body, cancelMonitor);\n  }\n\n  public Collection<ServerHotspot> getAll(String projectKey, String branchName, SonarLintCancelMonitor cancelMonitor) {\n    return searchHotspots(getSearchUrl(projectKey, null, branchName), cancelMonitor);\n  }\n\n  public Collection<ServerHotspot> getFromFile(String projectKey, Path filePath, String branchName, SonarLintCancelMonitor cancelMonitor) {\n    return searchHotspots(getSearchUrl(projectKey, filePath, branchName), cancelMonitor);\n  }\n\n  public HotspotApi.HotspotsPullResult pullHotspots(String projectKey, String branchName, Set<SonarLanguage> enabledLanguages, @Nullable Long changedSince\n    , SonarLintCancelMonitor cancelMonitor) {\n    return ServerApiHelper.processTimed(\n      () -> helper.get(getPullHotspotsUrl(projectKey, branchName, enabledLanguages, changedSince), cancelMonitor),\n      response -> {\n        var input = response.bodyAsStream();\n        var timestamp = Hotspots.HotspotPullQueryTimestamp.parseDelimitedFrom(input);\n        return new HotspotApi.HotspotsPullResult(timestamp, readMessages(input, Hotspots.HotspotLite.parser()));\n      },\n      duration -> LOG.debug(\"Pulled issues in {}ms\", duration));\n  }\n\n  public static class HotspotsPullResult {\n    private final Hotspots.HotspotPullQueryTimestamp timestamp;\n    private final List<Hotspots.HotspotLite> hotspots;\n\n    public HotspotsPullResult(Hotspots.HotspotPullQueryTimestamp timestamp, List<Hotspots.HotspotLite> hotspots) {\n      this.timestamp = timestamp;\n      this.hotspots = hotspots;\n    }\n\n    public Hotspots.HotspotPullQueryTimestamp getTimestamp() {\n      return timestamp;\n    }\n\n    public List<Hotspots.HotspotLite> getHotspots() {\n      return hotspots;\n    }\n  }\n\n  private static String getPullHotspotsUrl(String projectKey, String branchName, Set<SonarLanguage> enabledLanguages, @Nullable Long changedSince) {\n    var enabledLanguageKeys = enabledLanguages.stream().map(SonarLanguage::getSonarLanguageKey).collect(Collectors.joining(\",\"));\n    var url = new StringBuilder()\n      .append(HOTSPOTS_PULL_API_URL)\n      .append(PROJECT_KEY_QUERY_PARAM)\n      .append(UrlUtils.urlEncode(projectKey))\n      .append(\"&branchName=\")\n      .append(UrlUtils.urlEncode(branchName));\n    if (!enabledLanguageKeys.isEmpty()) {\n      url.append(\"&languages=\").append(enabledLanguageKeys);\n    }\n    if (changedSince != null) {\n      url.append(\"&changedSince=\").append(changedSince);\n    }\n    return url.toString();\n  }\n\n  public boolean supportHotspotsPull(Supplier<Version> serverVersion) {\n    return supportHotspotsPull(helper.isSonarCloud(), serverVersion.get());\n  }\n\n  public static boolean supportHotspotsPull(boolean isSonarCloud, Version serverVersion) {\n    return !isSonarCloud && serverVersion.compareToIgnoreQualifier(HotspotApi.MIN_SQ_VERSION_SUPPORTING_PULL) >= 0;\n  }\n\n  private Collection<ServerHotspot> searchHotspots(String searchUrl, SonarLintCancelMonitor cancelMonitor) {\n    Collection<ServerHotspot> hotspots = new ArrayList<>();\n    Map<String, Path> componentPathsByKey = new HashMap<>();\n    helper.getPaginated(\n      searchUrl,\n      Hotspots.SearchWsResponse::parseFrom,\n      r -> r.getPaging().getTotal(),\n      r -> {\n        componentPathsByKey.clear();\n        componentPathsByKey.putAll(r.getComponentsList().stream().collect(Collectors.toMap(Hotspots.Component::getKey, component -> Path.of(component.getPath()))));\n        return r.getHotspotsList();\n      },\n      hotspot -> {\n        var filePath = componentPathsByKey.get(hotspot.getComponent());\n        if (filePath != null) {\n          hotspots.add(adapt(hotspot, filePath));\n        } else {\n          LOG.error(\"Error while fetching security hotspots, the component '\" + hotspot.getComponent() + \"' is missing\");\n        }\n      },\n      false,\n      cancelMonitor);\n    return hotspots;\n  }\n\n  private static String getSearchUrl(String projectKey, @Nullable Path filePath, String branchName) {\n    return HOTSPOTS_SEARCH_API_URL\n      + PROJECT_KEY_QUERY_PARAM + urlEncode(projectKey)\n      + (filePath != null ? (\"&files=\" + urlEncode(toSonarQubePath(filePath))) : \"\")\n      + \"&branch=\" + urlEncode(branchName);\n  }\n\n  public ServerHotspotDetails show(String hotspotKey, SonarLintCancelMonitor cancelMonitor) {\n    try (var wsResponse = helper.get(getShowUrl(hotspotKey), cancelMonitor); var is = wsResponse.bodyAsStream()) {\n      return adapt(Hotspots.ShowWsResponse.parseFrom(is), null);\n    } catch (IOException e) {\n      throw new UnexpectedBodyException(e);\n    }\n  }\n\n  public Optional<ServerHotspotDetails> fetch(String hotspotKey, SonarLintCancelMonitor cancelMonitor) {\n    Hotspots.ShowWsResponse response;\n    try (var wsResponse = helper.get(getShowUrl(hotspotKey), cancelMonitor); var is = wsResponse.bodyAsStream()) {\n      response = Hotspots.ShowWsResponse.parseFrom(is);\n    } catch (Exception e) {\n      LOG.error(\"Error while fetching security hotspot\", e);\n      return Optional.empty();\n    }\n    var fileKey = response.getComponent().getKey();\n    var source = new SourceApi(helper).getRawSourceCode(fileKey, cancelMonitor);\n    String codeSnippet;\n    if (source.isPresent()) {\n      try {\n        codeSnippet = ServerApiUtils.extractCodeSnippet(source.get(), response.getTextRange());\n      } catch (Exception e) {\n        LOG.debug(\"Unable to compute code snippet of '\" + fileKey + \"' for text range: \" + response.getTextRange(), e);\n        codeSnippet = null;\n      }\n    } else {\n      codeSnippet = null;\n    }\n    return Optional.of(adapt(response, codeSnippet));\n  }\n\n  private static ServerHotspotDetails adapt(Hotspots.ShowWsResponse hotspot, @Nullable String codeSnippet) {\n    return new ServerHotspotDetails(\n      hotspot.getMessage(),\n      Path.of(hotspot.getComponent().getPath()),\n      convertTextRange(hotspot.getTextRange()),\n      hotspot.getAuthor(),\n      ServerHotspotDetails.Status.valueOf(hotspot.getStatus()),\n      hotspot.hasResolution() ? ServerHotspotDetails.Resolution.valueOf(hotspot.getResolution()) : null,\n      adapt(hotspot.getRule()),\n      codeSnippet, hotspot.getCanChangeStatus());\n  }\n\n  private static ServerHotspotDetails.Rule adapt(Hotspots.Rule rule) {\n    return new ServerHotspotDetails.Rule(rule.getKey(), rule.getName(), rule.getSecurityCategory(),\n      VulnerabilityProbability.valueOf(rule.getVulnerabilityProbability()),\n      rule.getRiskDescription(), rule.getVulnerabilityDescription(), rule.getFixRecommendations());\n  }\n\n  private static ServerHotspot adapt(Hotspots.SearchWsResponse.Hotspot hotspot, Path filePath) {\n    return new ServerHotspot(\n      hotspot.getKey(),\n      hotspot.getRuleKey(),\n      hotspot.getMessage(),\n      filePath,\n      convertTextRange(hotspot.getTextRange()),\n      ServerApiUtils.parseOffsetDateTime(hotspot.getCreationDate()).toInstant(),\n      getStatus(hotspot),\n      VulnerabilityProbability.valueOf(hotspot.getVulnerabilityProbability()),\n      hotspot.getAssignee());\n  }\n\n  private static HotspotReviewStatus getStatus(Hotspots.SearchWsResponse.Hotspot hotspot) {\n    var status = hotspot.getStatus();\n    var resolution = hotspot.hasResolution() ? hotspot.getResolution() : null;\n    return HotspotReviewStatus.fromStatusAndResolution(status, resolution);\n  }\n\n  private static String getShowUrl(String hotspotKey) {\n    return HOTSPOTS_SHOW_API_URL\n      + \"?hotspot=\" + urlEncode(hotspotKey);\n  }\n\n  private static TextRangeWithHash convertTextRange(Common.TextRange textRange) {\n    return new TextRangeWithHash(textRange.getStartLine(), textRange.getStartOffset(), textRange.getEndLine(), textRange.getEndOffset(), \"\");\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/hotspot/ServerHotspot.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.hotspot;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.UUID;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\n\npublic class ServerHotspot {\n  private final UUID id;\n  private final String key;\n  private final String ruleKey;\n  private final String message;\n  private Path filePath;\n  private final TextRange textRange;\n  private final Instant creationDate;\n  private HotspotReviewStatus status;\n  private final VulnerabilityProbability vulnerabilityProbability;\n  @Nullable\n  private String assignee;\n\n  public ServerHotspot(@Nullable UUID id, String key,\n    String ruleKey,\n    String message,\n    Path filePath,\n    TextRange textRange,\n    Instant creationDate,\n    HotspotReviewStatus status,\n    VulnerabilityProbability vulnerabilityProbability,\n    @Nullable String assignee) {\n    this.id = id;\n    this.key = key;\n    this.ruleKey = ruleKey;\n    this.message = message;\n    this.filePath = filePath;\n    this.textRange = textRange;\n    this.creationDate = creationDate;\n    this.status = status;\n    this.vulnerabilityProbability = vulnerabilityProbability;\n    this.assignee = assignee;\n  }\n\n  /**\n   * constructor for backward compatibility, after finalization of migration from Xodus to H2 should not be used\n   * when using with H2 UUID should always be set\n   */\n  public ServerHotspot(String key,\n    String ruleKey,\n    String message,\n    Path filePath,\n    TextRange textRange,\n    Instant creationDate,\n    HotspotReviewStatus status,\n    VulnerabilityProbability vulnerabilityProbability,\n    @Nullable String assignee) {\n    this(null, key, ruleKey, message, filePath, textRange, creationDate, status, vulnerabilityProbability, assignee);\n  }\n\n  @CheckForNull\n  public UUID getId() {\n    return id;\n  }\n\n  public void setFilePath(Path filePath) {\n    this.filePath = filePath;\n  }\n\n  public String getKey() {\n    return key;\n  }\n\n  public String getRuleKey() {\n    return ruleKey;\n  }\n\n  public String getMessage() {\n    return message;\n  }\n\n  public Path getFilePath() {\n    return filePath;\n  }\n\n  public TextRange getTextRange() {\n    return textRange;\n  }\n\n  public Instant getCreationDate() {\n    return creationDate;\n  }\n\n  public HotspotReviewStatus getStatus() {\n    return status;\n  }\n\n  public ServerHotspot withStatus(HotspotReviewStatus newStatus) {\n    return new ServerHotspot(key, ruleKey, message, filePath, textRange, creationDate, newStatus, vulnerabilityProbability, assignee);\n  }\n\n  public VulnerabilityProbability getVulnerabilityProbability() {\n    return vulnerabilityProbability;\n  }\n\n  public String getAssignee() {\n    return assignee;\n  }\n\n  public void setStatus(HotspotReviewStatus status) {\n    this.status = status;\n  }\n\n  public void setAssignee(String assignee) {\n    this.assignee = assignee;\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/hotspot/ServerHotspotDetails.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.hotspot;\n\nimport java.nio.file.Path;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\n\npublic class ServerHotspotDetails {\n\n  @Deprecated(forRemoval = true)\n  public final String message;\n  public final Path filePath;\n  @Deprecated(forRemoval = true)\n  public final TextRange textRange;\n  @Deprecated(forRemoval = true)\n  public final String author;\n  @Deprecated(forRemoval = true)\n  public final Status status;\n  @Deprecated(forRemoval = true)\n  @CheckForNull\n  public final Resolution resolution;\n  @Deprecated(forRemoval = true)\n  public final Rule rule;\n  @Deprecated(forRemoval = true)\n  @CheckForNull\n  public final String codeSnippet;\n  public final boolean canChangeStatus;\n\n  public ServerHotspotDetails(String message,\n    Path filePath,\n    TextRange textRange,\n    String author,\n    Status status,\n    @Nullable Resolution resolution,\n    Rule rule,\n    @Nullable String codeSnippet,\n    boolean canChangeStatus) {\n    this.message = message;\n    this.filePath = filePath;\n    this.textRange = textRange;\n    this.author = author;\n    this.status = status;\n    this.resolution = resolution;\n    this.rule = rule;\n    this.codeSnippet = codeSnippet;\n    this.canChangeStatus = canChangeStatus;\n  }\n\n  @Deprecated(forRemoval = true)\n  public static class Rule {\n\n    public final String key;\n    public final String name;\n    public final String securityCategory;\n    public final VulnerabilityProbability vulnerabilityProbability;\n    public final String riskDescription;\n    public final String vulnerabilityDescription;\n    public final String fixRecommendations;\n\n    public Rule(String key,\n      String name,\n      String securityCategory,\n      VulnerabilityProbability vulnerabilityProbability,\n      String riskDescription,\n      String vulnerabilityDescription,\n      String fixRecommendations) {\n\n      this.key = key;\n      this.name = name;\n      this.securityCategory = securityCategory;\n      this.vulnerabilityProbability = vulnerabilityProbability;\n      this.riskDescription = riskDescription;\n      this.vulnerabilityDescription = vulnerabilityDescription;\n      this.fixRecommendations = fixRecommendations;\n    }\n\n  }\n\n  @Deprecated(forRemoval = true)\n  public enum Status {\n    TO_REVIEW(\"To review\"), REVIEWED(\"Reviewed\");\n\n    Status(String description) {\n      this.description = description;\n    }\n\n    public final String description;\n  }\n\n  @Deprecated(forRemoval = true)\n  public enum Resolution {\n    FIXED(\"fixed\"), SAFE(\"safe\"), ACKNOWLEDGED(\"acknowledged\");\n\n    Resolution(String description) {\n      this.description = description;\n    }\n\n    public final String description;\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/hotspot/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.hotspot;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/issue/IssueApi.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.issue;\n\nimport com.google.gson.Gson;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.sonar.scanner.protocol.input.ScannerInput;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.LocalOnlyIssue;\nimport org.sonarsource.sonarlint.core.commons.Transition;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\nimport org.sonarsource.sonarlint.core.serverapi.UrlUtils;\nimport org.sonarsource.sonarlint.core.serverapi.exception.UnexpectedBodyException;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues.Component;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues.Issue;\nimport org.sonarsource.sonarlint.core.serverapi.source.SourceApi;\nimport org.sonarsource.sonarlint.core.serverapi.util.ServerApiUtils;\n\nimport static java.util.Objects.requireNonNull;\nimport static org.sonarsource.sonarlint.core.http.HttpClient.FORM_URL_ENCODED_CONTENT_TYPE;\nimport static org.sonarsource.sonarlint.core.http.HttpClient.JSON_CONTENT_TYPE;\nimport static org.sonarsource.sonarlint.core.serverapi.UrlUtils.urlEncode;\nimport static org.sonarsource.sonarlint.core.serverapi.util.ProtobufUtil.readMessages;\nimport static org.sonarsource.sonarlint.core.serverapi.util.ServerApiUtils.toSonarQubePath;\n\npublic class IssueApi {\n\n  private static final Map<IssueStatus, Transition> transitionByStatus = Map.of(\n    IssueStatus.ACCEPT, Transition.ACCEPT,\n    IssueStatus.WONT_FIX, Transition.WONT_FIX,\n    IssueStatus.FALSE_POSITIVE, Transition.FALSE_POSITIVE\n  );\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final String ORGANIZATION_PARAM = \"&organization=\";\n\n  private final ServerApiHelper serverApiHelper;\n\n  public IssueApi(ServerApiHelper serverApiHelper) {\n    this.serverApiHelper = serverApiHelper;\n  }\n\n  /**\n   * Fetch vulnerabilities of the component with specified key.\n   * If the component doesn't exist or it exists but has no issues, an empty iterator is returned.\n   *\n   * @param key project key, or file key.\n   */\n  public DownloadIssuesResult downloadVulnerabilitiesForRules(String key, Set<String> ruleKeys, @Nullable String branchName, SonarLintCancelMonitor cancelMonitor) {\n    var searchUrl = new StringBuilder();\n    searchUrl.append(getVulnerabilitiesUrl(key, ruleKeys));\n    searchUrl.append(getUrlBranchParameter(branchName));\n    serverApiHelper.getOrganizationKey()\n      .ifPresent(org -> searchUrl.append(ORGANIZATION_PARAM).append(UrlUtils.urlEncode(org)));\n    List<Issue> result = new ArrayList<>();\n    Map<String, Path> componentsPathByKey = new HashMap<>();\n    serverApiHelper.getPaginated(searchUrl.toString(),\n      Issues.SearchWsResponse::parseFrom,\n      r -> r.getPaging().getTotal(),\n      r -> {\n        componentsPathByKey.clear();\n        // Ignore project level issues\n        componentsPathByKey.putAll(r.getComponentsList().stream().filter(Component::hasPath)\n          .collect(Collectors.toMap(Component::getKey, component -> Path.of(component.getPath()))));\n        return r.getIssuesList();\n      },\n      result::add,\n      true,\n      cancelMonitor);\n\n    return new DownloadIssuesResult(result, componentsPathByKey);\n  }\n\n  public static class DownloadIssuesResult {\n    private final List<Issue> issues;\n    private final Map<String, Path> componentPathsByKey;\n\n    private DownloadIssuesResult(List<Issue> issues, Map<String, Path> componentPathsByKey) {\n      this.issues = issues;\n      this.componentPathsByKey = componentPathsByKey;\n    }\n\n    public List<Issue> getIssues() {\n      return issues;\n    }\n\n    public Map<String, Path> getComponentPathsByKey() {\n      return componentPathsByKey;\n    }\n\n  }\n\n  private static String getVulnerabilitiesUrl(String key, Set<String> ruleKeys) {\n    var encodedKey = urlEncode(key);\n    return \"/api/issues/search.protobuf?statuses=OPEN,CONFIRMED,REOPENED,RESOLVED&types=VULNERABILITY&componentKeys=\"\n      + encodedKey + \"&components=\" + encodedKey + \"&rules=\" + urlEncode(String.join(\",\", ruleKeys));\n  }\n\n  private static String getUrlBranchParameter(@Nullable String branchName) {\n    if (branchName != null) {\n      return \"&branch=\" + urlEncode(branchName);\n    }\n    return \"\";\n  }\n\n  public List<ScannerInput.ServerIssue> downloadAllFromBatchIssues(String key, @Nullable String branchName, SonarLintCancelMonitor cancelMonitor) {\n    String batchIssueUrl = getBatchIssuesUrl(key) + getUrlBranchParameter(branchName);\n    return ServerApiHelper.processTimed(\n      () -> serverApiHelper.rawGet(batchIssueUrl, cancelMonitor),\n      response -> {\n        if (response.code() == 403 || response.code() == 404) {\n          return Collections.emptyList();\n        } else if (!response.isSuccessful()) {\n          throw ServerApiHelper.handleError(response);\n        }\n        var input = response.bodyAsStream();\n        var parser = ScannerInput.ServerIssue.parser();\n        return readMessages(input, parser);\n      },\n      duration -> LOG.debug(\"Downloaded issues in {}ms\", duration));\n  }\n\n  private static String getBatchIssuesUrl(String key) {\n    return \"/batch/issues?key=\" + UrlUtils.urlEncode(key);\n  }\n\n  private static String getPullIssuesUrl(String projectKey, String branchName, Set<SonarLanguage> enabledLanguages, @Nullable Long changedSince) {\n    var enabledLanguageKeys = enabledLanguages.stream().map(SonarLanguage::getSonarLanguageKey).collect(Collectors.joining(\",\"));\n    var url = new StringBuilder()\n      .append(\"/api/issues/pull?projectKey=\")\n      .append(UrlUtils.urlEncode(projectKey)).append(\"&branchName=\").append(UrlUtils.urlEncode(branchName));\n    if (!enabledLanguageKeys.isEmpty()) {\n      url.append(\"&languages=\").append(enabledLanguageKeys);\n    }\n    if (changedSince != null) {\n      url.append(\"&changedSince=\").append(changedSince);\n    }\n    return url.toString();\n  }\n\n  public IssuesPullResult pullIssues(String projectKey, String branchName, Set<SonarLanguage> enabledLanguages, @Nullable Long changedSince,\n    SonarLintCancelMonitor cancelMonitor) {\n    return ServerApiHelper.processTimed(\n      () -> serverApiHelper.get(getPullIssuesUrl(projectKey, branchName, enabledLanguages, changedSince), cancelMonitor),\n      response -> {\n        var input = response.bodyAsStream();\n        var timestamp = Issues.IssuesPullQueryTimestamp.parseDelimitedFrom(input);\n        return new IssuesPullResult(timestamp, readMessages(input, Issues.IssueLite.parser()));\n      },\n      duration -> LOG.debug(\"Pulled issues in {}ms\", duration));\n  }\n\n  public static class IssuesPullResult {\n    private final Issues.IssuesPullQueryTimestamp timestamp;\n    private final List<Issues.IssueLite> issues;\n\n    public IssuesPullResult(Issues.IssuesPullQueryTimestamp timestamp, List<Issues.IssueLite> issues) {\n      this.timestamp = timestamp;\n      this.issues = issues;\n    }\n\n    public Issues.IssuesPullQueryTimestamp getTimestamp() {\n      return timestamp;\n    }\n\n    public List<Issues.IssueLite> getIssues() {\n      return issues;\n    }\n  }\n\n  private static String getPullTaintIssuesUrl(String projectKey, String branchName, Set<SonarLanguage> enabledLanguages, @Nullable Long changedSince) {\n    var enabledLanguageKeys = enabledLanguages.stream().map(SonarLanguage::getSonarLanguageKey).collect(Collectors.joining(\",\"));\n    var url = new StringBuilder()\n      .append(\"/api/issues/pull_taint?projectKey=\")\n      .append(UrlUtils.urlEncode(projectKey)).append(\"&branchName=\").append(UrlUtils.urlEncode(branchName));\n    if (!enabledLanguageKeys.isEmpty()) {\n      url.append(\"&languages=\").append(enabledLanguageKeys);\n    }\n    if (changedSince != null) {\n      url.append(\"&changedSince=\").append(changedSince);\n    }\n    return url.toString();\n  }\n\n  public TaintIssuesPullResult pullTaintIssues(String projectKey, String branchName, Set<SonarLanguage> enabledLanguages, @Nullable Long changedSince,\n    SonarLintCancelMonitor cancelMonitor) {\n    return ServerApiHelper.processTimed(\n      () -> serverApiHelper.get(getPullTaintIssuesUrl(projectKey, branchName, enabledLanguages, changedSince), cancelMonitor),\n      response -> {\n        var input = response.bodyAsStream();\n        var timestamp = Issues.TaintVulnerabilityPullQueryTimestamp.parseDelimitedFrom(input);\n        return new TaintIssuesPullResult(timestamp, readMessages(input, Issues.TaintVulnerabilityLite.parser()));\n      },\n      duration -> LOG.debug(\"Pulled taint issues in {}ms\", duration));\n  }\n\n  public void changeStatus(String issueKey, Transition transition, SonarLintCancelMonitor cancelMonitor) {\n    var body = \"issue=\" + urlEncode(issueKey) + \"&transition=\" + urlEncode(transition.getStatus());\n    serverApiHelper.post(\"/api/issues/do_transition\", FORM_URL_ENCODED_CONTENT_TYPE, body, cancelMonitor);\n  }\n\n  public void addComment(String issueKey, String text, SonarLintCancelMonitor cancelMonitor) {\n    var body = \"issue=\" + urlEncode(issueKey) + \"&text=\" + urlEncode(text);\n    serverApiHelper.post(\"/api/issues/add_comment\", FORM_URL_ENCODED_CONTENT_TYPE, body, cancelMonitor);\n  }\n\n  public Issue searchByKey(String issueKey, SonarLintCancelMonitor cancelMonitor) {\n    var searchUrl = new StringBuilder();\n    searchUrl.append(\"/api/issues/search.protobuf?issues=\").append(urlEncode(issueKey)).append(\"&additionalFields=transitions\");\n    serverApiHelper.getOrganizationKey()\n      .ifPresent(org -> searchUrl.append(ORGANIZATION_PARAM).append(UrlUtils.urlEncode(org)));\n    searchUrl.append(\"&ps=1&p=1\");\n    try (var wsResponse = serverApiHelper.get(searchUrl.toString(), cancelMonitor); var body = wsResponse.bodyAsStream()) {\n      var pbResponse = Issues.SearchWsResponse.parseFrom(body);\n      if (pbResponse.getIssuesList().isEmpty()) {\n        throw new UnexpectedBodyException(\"No issue found with key '\" + issueKey + \"'\");\n      }\n      return pbResponse.getIssuesList().get(0);\n    } catch (IOException e) {\n      LOG.error(\"Error when searching issue + '\" + issueKey + \"'\", e);\n      throw new UnexpectedBodyException(e);\n    }\n  }\n\n  public Optional<ServerIssueDetails> fetchServerIssue(String issueKey, String projectKey, String branch, @Nullable String pullRequest, SonarLintCancelMonitor cancelMonitor) {\n    String searchUrl = \"/api/issues/search.protobuf?issues=\" + urlEncode(issueKey) + \"&componentKeys=\" + projectKey + \"&components=\" + projectKey + \"&ps=1&p=1\";\n    if (pullRequest != null && !pullRequest.isEmpty()) {\n      searchUrl = searchUrl.concat(\"&pullRequest=\").concat(urlEncode(pullRequest));\n    } else if (!branch.isEmpty()) {\n      // If we do have a pullRequest, no need to pass branch too\n      searchUrl = searchUrl.concat(\"&branch=\").concat(urlEncode(branch));\n    }\n\n    try (var wsResponse = serverApiHelper.get(searchUrl, cancelMonitor); var is = wsResponse.bodyAsStream()) {\n      var response = Issues.SearchWsResponse.parseFrom(is);\n      if (response.getIssuesList().isEmpty() || response.getComponentsList().isEmpty()) {\n        LOG.warn(\"No issue found with key '\" + issueKey + \"'\");\n        return Optional.empty();\n      }\n      var issue = response.getIssuesList().get(0);\n      var optionalComponentWithPath = response.getComponentsList().stream().filter(component -> component.getKey().equals(issue.getComponent())).findFirst();\n      if (optionalComponentWithPath.isEmpty()) {\n        LOG.warn(\"No path found in components for the issue with key '\" + issueKey + \"'\");\n        return Optional.empty();\n      }\n\n      var fileKey = issue.getComponent();\n      var codeSnippet = getCodeSnippet(fileKey, issue.getTextRange(), branch, pullRequest, cancelMonitor);\n\n      return Optional.of(new ServerIssueDetails(issue, Path.of(optionalComponentWithPath.get().getPath()), response.getComponentsList(), codeSnippet.orElse(\"\")));\n    } catch (Exception e) {\n      LOG.warn(\"Error while fetching issue\", e.getMessage());\n      return Optional.empty();\n    }\n  }\n\n  public Optional<String> getCodeSnippet(String fileKey, Common.TextRange textRange, String branch, @Nullable String pullRequest, SonarLintCancelMonitor cancelMonitor) {\n    var source = new SourceApi(serverApiHelper).getRawSourceCodeForBranchAndPullRequest(fileKey, branch, pullRequest, cancelMonitor);\n    if (source.isPresent()) {\n      try {\n        var codeSnippet = ServerApiUtils.extractCodeSnippet(source.get(), textRange);\n        return Optional.of(codeSnippet);\n      } catch (Exception e) {\n        LOG.debug(\"Unable to compute code snippet of '\" + fileKey + \"' for text range: \" + textRange, e);\n        return Optional.empty();\n      }\n    } else {\n      return Optional.empty();\n    }\n  }\n\n  public void anticipatedTransitions(String projectKey, List<LocalOnlyIssue> resolvedLocalOnlyIssues, SonarLintCancelMonitor cancelMonitor) {\n    serverApiHelper.post(\"/api/issues/anticipated_transitions?projectKey=\" + projectKey, JSON_CONTENT_TYPE, new Gson().toJson(adapt(resolvedLocalOnlyIssues)), cancelMonitor);\n  }\n\n  private static List<IssueAnticipatedTransition> adapt(List<LocalOnlyIssue> resolvedLocalOnlyIssues) {\n    return resolvedLocalOnlyIssues.stream().map(IssueApi::adapt).toList();\n  }\n\n  private static IssueAnticipatedTransition adapt(LocalOnlyIssue issue) {\n    Integer lineNumber = null;\n    String lineHash = null;\n    var lineWithHash = issue.getLineWithHash();\n    if (lineWithHash != null) {\n      lineNumber = lineWithHash.getNumber();\n      lineHash = lineWithHash.getHash();\n    }\n    var resolution = requireNonNull(issue.getResolution());\n    return new IssueAnticipatedTransition(toSonarQubePath(issue.getServerRelativePath()), lineNumber, lineHash, issue.getRuleKey(), issue.getMessage(),\n      transitionByStatus.get(resolution.getStatus()).getStatus(), resolution.getComment());\n  }\n\n  public static class TaintIssuesPullResult {\n    private final Issues.TaintVulnerabilityPullQueryTimestamp timestamp;\n    private final List<Issues.TaintVulnerabilityLite> issues;\n\n    public TaintIssuesPullResult(Issues.TaintVulnerabilityPullQueryTimestamp timestamp, List<Issues.TaintVulnerabilityLite> issues) {\n      this.timestamp = timestamp;\n      this.issues = issues;\n    }\n\n    public Issues.TaintVulnerabilityPullQueryTimestamp getTimestamp() {\n      return timestamp;\n    }\n\n    public List<Issues.TaintVulnerabilityLite> getTaintIssues() {\n      return issues;\n    }\n  }\n\n  public static class ServerIssueDetails {\n    public final String key;\n    public final String ruleKey;\n    public final String codeSnippet;\n    public final String creationDate;\n    public final String message;\n    public final Path path;\n    public final Common.TextRange textRange;\n    public final List<Common.Flow> flowList;\n    public final List<Component> componentsList;\n\n    public ServerIssueDetails(Issue issue, Path path, List<Component> componentsList, String codeSnippet) {\n      this.key = issue.getKey();\n      this.ruleKey = issue.getRule();\n      this.textRange = issue.getTextRange();\n      this.path = path;\n      this.flowList = issue.getFlowsList();\n      this.message = issue.getMessage();\n      this.creationDate = issue.getCreationDate();\n      this.componentsList = componentsList;\n      this.codeSnippet = codeSnippet;\n    }\n  }\n\n  private static class IssueAnticipatedTransition {\n    public final String filePath;\n    public final Integer line;\n    public final String hash;\n    public final String ruleKey;\n    public final String issueMessage;\n    public final String transition;\n    public final String comment;\n\n    private IssueAnticipatedTransition(String filePath, @Nullable Integer line, @Nullable String hash, String ruleKey, String issueMessage, String transition,\n      @Nullable String comment) {\n      this.filePath = filePath;\n      this.line = line;\n      this.hash = hash;\n      this.ruleKey = ruleKey;\n      this.issueMessage = issueMessage;\n      this.transition = transition;\n      this.comment = comment;\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/issue/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.issue;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/newcode/NewCodeApi.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.newcode;\n\nimport java.util.Optional;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.NewCodeDefinition;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\nimport org.sonarsource.sonarlint.core.serverapi.UrlUtils;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Measures;\n\nimport static org.sonarsource.sonarlint.core.serverapi.util.ServerApiUtils.parseOffsetDateTime;\n\npublic class NewCodeApi {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private static final String GET_NEW_CODE_DEFINITION_URL = \"/api/measures/component.protobuf\";\n  private static final String OLD_SQ_OR_SC_PERIOD = \"periods\";\n  private static final String NEW_SQ_PERIOD = \"period\";\n  private static final Version NEW_SQ_VERSION = Version.create(\"8.1\");\n  private final ServerApiHelper helper;\n\n  public NewCodeApi(ServerApiHelper helper) {\n    this.helper = helper;\n  }\n\n  public Optional<NewCodeDefinition> getNewCodeDefinition(String projectKey, @Nullable String branch, Version serverVersion, SonarLintCancelMonitor cancelMonitor) {\n    Measures.ComponentWsResponse response;\n    var period = getPeriodForServer(helper, serverVersion);\n    var requestPath = new StringBuilder().append(GET_NEW_CODE_DEFINITION_URL)\n      .append(\"?additionalFields=\")\n      .append(period)\n      .append(\"&metricKeys=projects&component=\")\n      .append(UrlUtils.urlEncode(projectKey));\n    if (branch != null) {\n      requestPath.append(\"&branch=\").append(UrlUtils.urlEncode(branch));\n    }\n    try (\n      var wsResponse = helper.get(requestPath.toString(), cancelMonitor);\n      var is = wsResponse.bodyAsStream()) {\n      response = Measures.ComponentWsResponse.parseFrom(is);\n    } catch (Exception e) {\n      LOG.error(\"Error while fetching new code definition\", e);\n      return Optional.empty();\n    }\n    var periodFromWs = getPeriodFromWs(response);\n    var modeString = periodFromWs.getMode();\n    var parameter = periodFromWs.hasParameter() ? periodFromWs.getParameter() : null;\n    if (modeString.equals(\"REFERENCE_BRANCH\") && parameter != null) {\n      return Optional.of(NewCodeDefinition.withReferenceBranch(parameter));\n    }\n    var date = periodFromWs.hasDate() ? parseOffsetDateTime(periodFromWs.getDate()).toInstant().toEpochMilli() : 0;\n    if ((modeString.equals(\"NUMBER_OF_DAYS\") || modeString.equals(\"days\")) && parameter != null) {\n      var days = Integer.parseInt(parameter);\n      return Optional.of(NewCodeDefinition.withNumberOfDaysWithDate(days, date));\n    }\n    if (modeString.equalsIgnoreCase(\"PREVIOUS_VERSION\")) {\n      return Optional.of(NewCodeDefinition.withPreviousVersion(date, parameter));\n    }\n    if (modeString.equals(\"SPECIFIC_ANALYSIS\") || modeString.equals(\"version\") || modeString.equals(\"date\")) {\n      return Optional.of(NewCodeDefinition.withSpecificAnalysis(date));\n    }\n    LOG.warn(\"Unsupported mode of new code definition: \" + modeString);\n    return Optional.empty();\n  }\n\n  static Measures.Period getPeriodFromWs(Measures.ComponentWsResponse response) {\n    if (response.hasPeriods()) {\n      return response.getPeriods().getPeriods(0);\n    }\n    return response.getPeriod();\n  }\n\n  static String getPeriodForServer(ServerApiHelper helper, Version serverVersion) {\n    if (helper.isSonarCloud()) {\n      return OLD_SQ_OR_SC_PERIOD;\n    }\n    if (serverVersion.compareToIgnoreQualifier(NEW_SQ_VERSION) < 0) {\n      return OLD_SQ_OR_SC_PERIOD;\n    }\n    return NEW_SQ_PERIOD;\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/newcode/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.newcode;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/organization/GetOrganizationsResponseDto.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.organization;\n\nimport java.util.UUID;\n\npublic record GetOrganizationsResponseDto(String id, UUID uuidV4) {\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/organization/OrganizationApi.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.organization;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Optional;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\nimport org.sonarsource.sonarlint.core.serverapi.UrlUtils;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarcloud.ws.Organizations;\n\npublic class OrganizationApi {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final ServerApiHelper helper;\n\n  public OrganizationApi(ServerApiHelper helper) {\n    this.helper = helper;\n  }\n\n  public List<ServerOrganization> listUserOrganizations(SonarLintCancelMonitor cancelMonitor) {\n    var url = \"api/organizations/search.protobuf?member=true\";\n    return getPaginatedOrganizations(url, cancelMonitor);\n  }\n\n  public Optional<ServerOrganization> searchOrganization(String organizationKey, SonarLintCancelMonitor cancelMonitor) {\n    var url = \"api/organizations/search.protobuf?organizations=\" + UrlUtils.urlEncode(organizationKey);\n    return getPaginatedOrganizations(url, cancelMonitor)\n      .stream()\n      .findFirst();\n  }\n\n  public GetOrganizationsResponseDto getOrganizationByKey(SonarLintCancelMonitor cancelMonitor) {\n    var organizationKey = helper.getOrganizationKey().orElseThrow(() -> new IllegalArgumentException(\"Organizations are only supported for SonarQube Cloud\"));\n    try {\n      return helper.apiGetJson(\"/organizations/organizations?organizationKey=\" + UrlUtils.urlEncode(organizationKey) + \"&excludeEligibility=true\",\n        GetOrganizationsResponseDto[].class, cancelMonitor)[0];\n    } catch (Exception e) {\n      LOG.error(\"Error while fetching the organization\", e);\n      throw e;\n    }\n  }\n\n  private List<ServerOrganization> getPaginatedOrganizations(String url, SonarLintCancelMonitor cancelMonitor) {\n    List<ServerOrganization> result = new ArrayList<>();\n\n    helper.getPaginated(url,\n      Organizations.SearchWsResponse::parseFrom,\n      r -> r.getPaging().getTotal(),\n      Organizations.SearchWsResponse::getOrganizationsList,\n      org -> result.add(new ServerOrganization(org)),\n      false,\n      cancelMonitor);\n\n    return result;\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/organization/ServerOrganization.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.organization;\n\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarcloud.ws.Organizations.Organization;\n\npublic class ServerOrganization {\n  private final String key;\n  private final String name;\n  private final String description;\n\n  public ServerOrganization(Organization org) {\n    this.key = org.getKey();\n    this.name = org.getName();\n    this.description = org.getDescription();\n  }\n\n  public String getKey() {\n    return key;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public String getDescription() {\n    return description;\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/organization/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.organization;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/plugins/InstalledPluginsPayloadDto.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.plugins;\n\npublic record InstalledPluginsPayloadDto(InstalledPluginPayloadDto[] plugins) {\n  public record InstalledPluginPayloadDto(String key, String hash, String filename, boolean sonarLintSupported) {\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/plugins/PluginsApi.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.plugins;\n\nimport java.io.InputStream;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.function.Consumer;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.http.HttpClient;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\n\npublic class PluginsApi {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  public static final String API_PLUGINS_INSTALLED_PATH = \"/api/plugins/installed\";\n\n  private final ServerApiHelper helper;\n\n  public PluginsApi(ServerApiHelper helper) {\n    this.helper = helper;\n  }\n\n  public List<ServerPlugin> getInstalled(SonarLintCancelMonitor cancelMonitor) {\n    var start = System.currentTimeMillis();\n    var plugins = helper.isSonarCloud()\n      ? helper.getAnonymousJson(API_PLUGINS_INSTALLED_PATH, InstalledPluginsPayloadDto.class, cancelMonitor)\n      : helper.getJson(API_PLUGINS_INSTALLED_PATH, InstalledPluginsPayloadDto.class, cancelMonitor);\n    var result = Arrays.stream(plugins.plugins()).map(PluginsApi::toInstalledPlugin).toList();\n    var duration = System.currentTimeMillis() - start;\n    LOG.info(\"Downloaded plugin list in {}ms\", duration);\n    return result;\n  }\n\n  private static ServerPlugin toInstalledPlugin(InstalledPluginsPayloadDto.InstalledPluginPayloadDto payload) {\n    return new ServerPlugin(payload.key(), payload.hash(), payload.filename(), payload.sonarLintSupported());\n  }\n\n  public void getPlugin(String key, Consumer<InputStream> pluginFileConsumer, SonarLintCancelMonitor cancelMonitor) {\n    var url = \"api/plugins/download?plugin=\" + key;\n    var start = System.currentTimeMillis();\n    try (var response = get(url, cancelMonitor)) {\n      pluginFileConsumer.accept(response.bodyAsStream());\n      var duration = System.currentTimeMillis() - start;\n      LOG.info(\"Downloaded '{}' in {}ms\", key, duration);\n    }\n  }\n\n  private HttpClient.Response get(String path, SonarLintCancelMonitor cancelMonitor) {\n    return helper.isSonarCloud() ? helper.getAnonymous(path, cancelMonitor) : helper.get(path, cancelMonitor);\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/plugins/ServerPlugin.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.plugins;\n\npublic class ServerPlugin {\n  private final String key;\n  private final String hash;\n  private final String filename;\n  private final boolean sonarLintSupported;\n\n  public ServerPlugin(String key, String hash, String filename, boolean sonarLintSupported) {\n    this.key = key;\n    this.hash = hash;\n    this.filename = filename;\n    this.sonarLintSupported = sonarLintSupported;\n  }\n\n  public String getKey() {\n    return key;\n  }\n\n  public String getHash() {\n    return hash;\n  }\n\n  public String getFilename() {\n    return filename;\n  }\n\n  public boolean isSonarLintSupported() {\n    return sonarLintSupported;\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/plugins/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.plugins;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/projectbindings/ProjectBindingsApi.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.projectbindings;\n\nimport javax.annotation.CheckForNull;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\nimport org.sonarsource.sonarlint.core.serverapi.UrlUtils;\n\npublic class ProjectBindingsApi {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final ServerApiHelper serverApiHelper;\n\n  public ProjectBindingsApi(ServerApiHelper serverApiHelper) {\n    this.serverApiHelper = serverApiHelper;\n  }\n\n  @CheckForNull\n  public SQCProjectBindingsResponse getSQCProjectBindings(String url, SonarLintCancelMonitor cancelMonitor) {\n    var encodedUrl = UrlUtils.urlEncode(url);\n\n    var path = \"/dop-translation/project-bindings?url=\" + encodedUrl;\n\n    try {\n      var dto = serverApiHelper.apiGetJson(path, SQCProjectBindingsResponseDto.class, cancelMonitor);\n      var bindings = dto.bindings();\n\n      if (!bindings.isEmpty()) {\n        return new SQCProjectBindingsResponse(bindings.get(0).projectId());\n      }\n    } catch (Exception e) {\n      LOG.error(\"Error retrieving project bindings for URL: {}\", url, e);\n    }\n\n    return null;\n  }\n\n  @CheckForNull\n  public SQSProjectBindingsResponse getSQSProjectBindings(String url, SonarLintCancelMonitor cancelMonitor) {\n    var encodedUrl = UrlUtils.urlEncode(url);\n    var dto = serverApiHelper.getJson(\"/api/v2/dop-translation/project-bindings?repositoryUrl=\" + encodedUrl, SQSProjectBindingsResponseDto.class, cancelMonitor);\n    var bindings = dto.projectBindings();\n    if (!bindings.isEmpty()) {\n      return new SQSProjectBindingsResponse(dto.projectBindings().get(0).projectId(), dto.projectBindings().get(0).projectKey());\n    }\n    return null;\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/projectbindings/SQCProjectBindingsResponse.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.projectbindings;\n\npublic record SQCProjectBindingsResponse(String projectId) {\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/projectbindings/SQCProjectBindingsResponseDto.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.projectbindings;\n\nimport java.util.List;\n\npublic record SQCProjectBindingsResponseDto(List<Binding> bindings) {\n\n  public record Binding(String projectId) {\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/projectbindings/SQSProjectBindingsResponse.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.projectbindings;\n\npublic record SQSProjectBindingsResponse(String projectId, String projectKey) {\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/projectbindings/SQSProjectBindingsResponseDto.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.projectbindings;\n\nimport java.util.List;\n\npublic record SQSProjectBindingsResponseDto(List<ProjectBinding> projectBindings) {\n\n  public record ProjectBinding(String projectId, String projectKey) {\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/projectbindings/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.projectbindings;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/IssueChangedEvent.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push;\n\nimport java.util.List;\nimport java.util.Map;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\n\npublic class IssueChangedEvent implements SonarProjectEvent {\n  private final String projectKey;\n  private final List<Issue> impactedIssues;\n  private final IssueSeverity userSeverity;\n  private final RuleType userType;\n  private final Boolean resolved;\n\n  public IssueChangedEvent(String projectKey, List<Issue> impactedIssues, @Nullable IssueSeverity userSeverity, @Nullable RuleType userType, @Nullable Boolean resolved) {\n    this.projectKey = projectKey;\n    this.impactedIssues = impactedIssues;\n    this.userSeverity = userSeverity;\n    this.userType = userType;\n    this.resolved = resolved;\n  }\n\n  @Override\n  public String getProjectKey() {\n    return projectKey;\n  }\n\n  public List<Issue> getImpactedIssues() {\n    return impactedIssues;\n  }\n\n  /**\n   * @return null when not changed\n   */\n  @CheckForNull\n  public IssueSeverity getUserSeverity() {\n    return userSeverity;\n  }\n\n  /**\n   * @return null when not changed\n   */\n  @CheckForNull\n  public RuleType getUserType() {\n    return userType;\n  }\n\n  /**\n   * @return null when not changed\n   */\n  @CheckForNull\n  public Boolean getResolved() {\n    return resolved;\n  }\n\n  public static class Issue {\n    private final String issueKey;\n    private final String branchName;\n    private final Map<SoftwareQuality, ImpactSeverity> impacts;\n\n    public Issue(String issueKey, String branchName, Map<SoftwareQuality, ImpactSeverity> impacts) {\n      this.issueKey = issueKey;\n      this.branchName = branchName;\n      this.impacts = impacts;\n    }\n\n    public String getIssueKey() {\n      return issueKey;\n    }\n\n    public String getBranchName() {\n      return branchName;\n    }\n\n    public Map<SoftwareQuality, ImpactSeverity> getImpacts() {\n      return impacts;\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/PushApi.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push;\n\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\nimport org.sonarsource.sonarlint.core.serverapi.UrlUtils;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.EventParser;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.IssueChangedEventParser;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.RuleSetChangedEventParser;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.SecurityHotspotChangedEventParser;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.SecurityHotspotClosedEventParser;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.SecurityHotspotRaisedEventParser;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.TaintVulnerabilityClosedEventParser;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.TaintVulnerabilityRaisedEventParser;\nimport org.sonarsource.sonarlint.core.serverapi.stream.Event;\nimport org.sonarsource.sonarlint.core.serverapi.stream.EventStream;\n\npublic class PushApi {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private static final String API_PATH = \"api/push/sonarlint_events\";\n  private static final Map<String, EventParser<?>> parsersByType = Map.of(\n    \"RuleSetChanged\", new RuleSetChangedEventParser(),\n    \"IssueChanged\", new IssueChangedEventParser(),\n    \"TaintVulnerabilityRaised\", new TaintVulnerabilityRaisedEventParser(),\n    \"TaintVulnerabilityClosed\", new TaintVulnerabilityClosedEventParser(),\n    \"SecurityHotspotRaised\", new SecurityHotspotRaisedEventParser(),\n    \"SecurityHotspotChanged\", new SecurityHotspotChangedEventParser(),\n    \"SecurityHotspotClosed\", new SecurityHotspotClosedEventParser());\n\n  private final ServerApiHelper helper;\n\n  public PushApi(ServerApiHelper helper) {\n    this.helper = helper;\n  }\n\n  public EventStream subscribe(Set<String> projectKeys, Set<SonarLanguage> enabledLanguages, Consumer<SonarServerEvent> serverEventConsumer) {\n    return new EventStream(helper, rawEvent -> handleRawEvent(rawEvent, serverEventConsumer))\n      .connect(getWsPath(projectKeys, enabledLanguages));\n  }\n\n  private static String getWsPath(Set<String> projectKeys, Set<SonarLanguage> enabledLanguages) {\n    return API_PATH + \"?projectKeys=\" +\n      projectKeys.stream().map(UrlUtils::urlEncode).collect(Collectors.joining(\",\")) +\n      \"&languages=\" +\n      enabledLanguages.stream().map(SonarLanguage::getSonarLanguageKey).map(UrlUtils::urlEncode).collect(Collectors.joining(\",\"));\n  }\n\n  private static void handleRawEvent(Event rawEvent, Consumer<SonarServerEvent> serverEventConsumer) {\n    LOG.debug(\"Server event received: {}\", rawEvent);\n    parse(rawEvent).ifPresent(serverEventConsumer);\n  }\n\n  private static Optional<? extends SonarServerEvent> parse(Event event) {\n    var eventType = event.getType();\n    if (!parsersByType.containsKey(eventType)) {\n      LOG.error(\"Unknown '{}' event type \", eventType);\n      return Optional.empty();\n    }\n    try {\n      return parsersByType.get(eventType).parse(event.getData());\n    } catch (Exception e) {\n      LOG.error(\"Cannot parse '{}' received event\", eventType, e);\n    }\n    return Optional.empty();\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/RuleSetChangedEvent.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push;\n\nimport java.util.List;\nimport java.util.Map;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.common.ImpactPayload;\n\npublic class RuleSetChangedEvent implements SonarServerEvent {\n  private final List<String> projectKeys;\n  private final List<ActiveRule> activatedRules;\n  private final List<String> deactivatedRules;\n\n  public RuleSetChangedEvent(List<String> projectKeys, List<ActiveRule> activatedRules, List<String> deactivatedRules) {\n    this.projectKeys = projectKeys;\n    this.activatedRules = activatedRules;\n    this.deactivatedRules = deactivatedRules;\n  }\n\n  public List<String> getProjectKeys() {\n    return projectKeys;\n  }\n\n  public List<ActiveRule> getActivatedRules() {\n    return activatedRules;\n  }\n\n  public List<String> getDeactivatedRules() {\n    return deactivatedRules;\n  }\n\n  public static class ActiveRule {\n    private final String key;\n    private final String languageKey;\n    private final IssueSeverity severity;\n    private final Map<String, String> parameters;\n    private final String templateKey;\n    private final List<ImpactPayload> overridenImpacts;\n\n    public ActiveRule(String key, String languageKey, IssueSeverity severity, Map<String, String> parameters,\n      @Nullable String templateKey, List<ImpactPayload> overridenImpacts) {\n      this.key = key;\n      this.languageKey = languageKey;\n      this.severity = severity;\n      this.parameters = parameters;\n      this.templateKey = templateKey;\n      this.overridenImpacts = overridenImpacts;\n    }\n\n    public String getKey() {\n      return key;\n    }\n\n    public String getLanguageKey() {\n      return languageKey;\n    }\n\n    public IssueSeverity getSeverity() {\n      return severity;\n    }\n\n    public Map<String, String> getParameters() {\n      return parameters;\n    }\n\n    @CheckForNull\n    public String getTemplateKey() {\n      return templateKey;\n    }\n\n    public List<ImpactPayload> getOverriddenImpacts() {\n      return overridenImpacts;\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/SecurityHotspotChangedEvent.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\n\npublic class SecurityHotspotChangedEvent implements ServerHotspotEvent {\n  private final String hotspotKey;\n  private final String projectKey;\n  private final Instant updateDate;\n  private final HotspotReviewStatus status;\n  private final String assignee;\n  private final Path filePath;\n\n  public SecurityHotspotChangedEvent(String hotspotKey, String projectKey, Instant updateDate, HotspotReviewStatus status, String assignee, Path filePath) {\n    this.hotspotKey = hotspotKey;\n    this.projectKey = projectKey;\n    this.updateDate = updateDate;\n    this.status = status;\n    this.assignee = assignee;\n    this.filePath = filePath;\n  }\n\n  public String getHotspotKey() {\n    return hotspotKey;\n  }\n\n  @Override\n  public String getProjectKey() {\n    return projectKey;\n  }\n\n  public Instant getUpdateDate() {\n    return updateDate;\n  }\n\n  public HotspotReviewStatus getStatus() {\n    return status;\n  }\n\n  public String getAssignee() {\n    return assignee;\n  }\n\n  @Override\n  public Path getFilePath() {\n    return filePath;\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/SecurityHotspotClosedEvent.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push;\n\nimport java.nio.file.Path;\n\npublic class SecurityHotspotClosedEvent implements ServerHotspotEvent {\n  private final String projectKey;\n  private final String hotspotKey;\n  private final Path filePath;\n\n  public SecurityHotspotClosedEvent(String projectKey, String hotspotKey, Path filePath) {\n    this.projectKey = projectKey;\n    this.hotspotKey = hotspotKey;\n    this.filePath = filePath;\n  }\n  @Override\n  public String getProjectKey() {\n    return projectKey;\n  }\n  public String getHotspotKey() {\n    return hotspotKey;\n  }\n  @Override\n  public Path getFilePath() {\n    return filePath;\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/SecurityHotspotRaisedEvent.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\n\npublic class SecurityHotspotRaisedEvent implements ServerHotspotEvent {\n  private final String hotspotKey;\n  private final String projectKey;\n  private final VulnerabilityProbability vulnerabilityProbability;\n  private final HotspotReviewStatus status;\n  private final Instant creationDate;\n  private final String branch;\n  private final TaintVulnerabilityRaisedEvent.Location mainLocation;\n  private final String ruleKey;\n  @Nullable\n  private final String ruleDescriptionContextKey;\n  @Nullable\n  private final String assignee;\n\n  public SecurityHotspotRaisedEvent(String hotspotKey, String projectKey, VulnerabilityProbability vulnerabilityProbability,\n    HotspotReviewStatus status, Instant creationDate, String branch, TaintVulnerabilityRaisedEvent.Location mainLocation, String ruleKey,\n    @Nullable String ruleDescriptionContextKey, @Nullable String assignee) {\n    this.hotspotKey = hotspotKey;\n    this.projectKey = projectKey;\n    this.vulnerabilityProbability = vulnerabilityProbability;\n    this.status = status;\n    this.creationDate = creationDate;\n    this.branch = branch;\n    this.mainLocation = mainLocation;\n    this.ruleKey = ruleKey;\n    this.ruleDescriptionContextKey = ruleDescriptionContextKey;\n    this.assignee = assignee;\n  }\n\n  public String getHotspotKey() {\n    return hotspotKey;\n  }\n\n  @Override\n  public String getProjectKey() {\n    return projectKey;\n  }\n\n  public VulnerabilityProbability getVulnerabilityProbability() {\n    return vulnerabilityProbability;\n  }\n\n  public HotspotReviewStatus getStatus() {\n    return status;\n  }\n\n  public Instant getCreationDate() {\n    return creationDate;\n  }\n\n  public String getBranch() {\n    return branch;\n  }\n\n  public TaintVulnerabilityRaisedEvent.Location getMainLocation() {\n    return mainLocation;\n  }\n\n  public String getRuleKey() {\n    return ruleKey;\n  }\n\n  @Nullable\n  public String getRuleDescriptionContextKey() {\n    return ruleDescriptionContextKey;\n  }\n\n  @Override\n  public Path getFilePath() {\n    return mainLocation.getFilePath();\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/ServerHotspotEvent.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push;\n\nimport java.nio.file.Path;\n\npublic interface ServerHotspotEvent extends SonarProjectEvent {\n  Path getFilePath();\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/SonarProjectEvent.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push;\n\npublic interface SonarProjectEvent extends SonarServerEvent {\n  String getProjectKey();\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/SonarServerEvent.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push;\n\npublic interface SonarServerEvent {\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/TaintVulnerabilityClosedEvent.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push;\n\npublic class TaintVulnerabilityClosedEvent implements SonarProjectEvent {\n  private final String projectKey;\n  private final String taintIssueKey;\n\n  public TaintVulnerabilityClosedEvent(String projectKey, String taintIssueKey) {\n    this.projectKey = projectKey;\n    this.taintIssueKey = taintIssueKey;\n  }\n\n  @Override\n  public String getProjectKey() {\n    return projectKey;\n  }\n\n  public String getTaintIssueKey() {\n    return taintIssueKey;\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/TaintVulnerabilityRaisedEvent.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\n\npublic class TaintVulnerabilityRaisedEvent implements SonarProjectEvent {\n  private final String key;\n  private final String projectKey;\n  private final String branchName;\n  private final Instant creationDate;\n  private final String ruleKey;\n  private final IssueSeverity severity;\n  private final RuleType type;\n  private final Location mainLocation;\n  private final List<Flow> flows;\n  @Nullable\n  private final String ruleDescriptionContextKey;\n  @Nullable\n  private final CleanCodeAttribute cleanCodeAttribute;\n  private final Map<SoftwareQuality, ImpactSeverity> impacts;\n\n  public TaintVulnerabilityRaisedEvent(String key, String projectKey, String branchName, Instant creationDate, String ruleKey, IssueSeverity severity, RuleType type,\n    Location mainLocation, List<Flow> flows, @Nullable String ruleDescriptionContextKey, @Nullable CleanCodeAttribute cleanCodeAttribute,\n    Map<SoftwareQuality, ImpactSeverity> impacts) {\n    this.key = key;\n    this.projectKey = projectKey;\n    this.branchName = branchName;\n    this.creationDate = creationDate;\n    this.ruleKey = ruleKey;\n    this.severity = severity;\n    this.type = type;\n    this.mainLocation = mainLocation;\n    this.flows = flows;\n    this.ruleDescriptionContextKey = ruleDescriptionContextKey;\n    this.cleanCodeAttribute = cleanCodeAttribute;\n    this.impacts = impacts;\n  }\n\n  public String getKey() {\n    return key;\n  }\n\n  @Override\n  public String getProjectKey() {\n    return projectKey;\n  }\n\n  public String getBranchName() {\n    return branchName;\n  }\n\n  public Instant getCreationDate() {\n    return creationDate;\n  }\n\n  public String getRuleKey() {\n    return ruleKey;\n  }\n\n  public IssueSeverity getSeverity() {\n    return severity;\n  }\n\n  public RuleType getType() {\n    return type;\n  }\n\n  public Location getMainLocation() {\n    return mainLocation;\n  }\n\n  public List<Flow> getFlows() {\n    return flows;\n  }\n\n  public Optional<CleanCodeAttribute> getCleanCodeAttribute() {\n    return Optional.ofNullable(cleanCodeAttribute);\n  }\n\n  public Map<SoftwareQuality, ImpactSeverity> getImpacts() {\n    return impacts;\n  }\n\n  @CheckForNull\n  public String getRuleDescriptionContextKey() {\n    return ruleDescriptionContextKey;\n  }\n\n  public static class Location {\n    private final Path filePath;\n    private final String message;\n    private final TextRange textRange;\n\n    public Location(Path filePath, String message, TextRange textRange) {\n      this.filePath = filePath;\n      this.message = message;\n      this.textRange = textRange;\n    }\n\n    public Path getFilePath() {\n      return filePath;\n    }\n\n    public String getMessage() {\n      return message;\n    }\n\n    public TextRange getTextRange() {\n      return textRange;\n    }\n\n    public static class TextRange {\n      private final int startLine;\n      private final int startLineOffset;\n      private final int endLine;\n      private final int endLineOffset;\n      private final String hash;\n\n      public TextRange(int startLine, int startLineOffset, int endLine, int endLineOffset, String hash) {\n        this.startLine = startLine;\n        this.startLineOffset = startLineOffset;\n        this.endLine = endLine;\n        this.endLineOffset = endLineOffset;\n        this.hash = hash;\n      }\n\n      public int getStartLine() {\n        return startLine;\n      }\n\n      public int getStartLineOffset() {\n        return startLineOffset;\n      }\n\n      public int getEndLine() {\n        return endLine;\n      }\n\n      public int getEndLineOffset() {\n        return endLineOffset;\n      }\n\n      public String getHash() {\n        return hash;\n      }\n    }\n  }\n\n  public static class Flow {\n    private final List<Location> locations;\n\n    public Flow(List<Location> locations) {\n      this.locations = locations;\n    }\n\n    public List<Location> getLocations() {\n      return locations;\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.push;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/parsing/EventParser.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push.parsing;\n\nimport java.util.Optional;\nimport org.sonarsource.sonarlint.core.serverapi.push.SonarServerEvent;\n\npublic interface EventParser<T extends SonarServerEvent> {\n  Optional<T> parse(String jsonData);\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/parsing/IssueChangedEventParser.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push.parsing;\n\nimport com.google.gson.Gson;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.serverapi.push.IssueChangedEvent;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.common.ImpactPayload;\n\nimport static org.sonarsource.sonarlint.core.serverapi.util.ServerApiUtils.isBlank;\n\npublic class IssueChangedEventParser implements EventParser<IssueChangedEvent> {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final Gson gson = new Gson();\n\n  @Override\n  public Optional<IssueChangedEvent> parse(String jsonData) {\n    var payload = gson.fromJson(jsonData, IssueChangedEventPayload.class);\n    if (payload.isInvalid()) {\n      LOG.error(\"Invalid payload for 'IssueChangedEvent' event: {}\", jsonData);\n      return Optional.empty();\n    }\n    return Optional.of(new IssueChangedEvent(\n      payload.projectKey,\n      payload.issues.stream()\n        .map(issueChange ->\n          new IssueChangedEvent.Issue(issueChange.issueKey, issueChange.branchName, adapt(issueChange.impacts))\n        )\n        .toList(),\n      payload.userSeverity != null ? IssueSeverity.valueOf(payload.userSeverity) : null,\n      payload.userType != null ? RuleType.valueOf(payload.userType) : null,\n      payload.resolved));\n  }\n\n  public static Map<SoftwareQuality, ImpactSeverity> adapt(@Nullable ImpactPayload[] payloads) {\n    if (payloads == null) {\n      return Map.of();\n    }\n    return Arrays.stream(payloads)\n      .collect(Collectors.toMap(\n        payload -> SoftwareQuality.valueOf(payload.getSoftwareQuality()),\n        payload -> ImpactSeverity.valueOf(payload.getSeverity())\n      ));\n  }\n\n  private static class IssueChangedEventPayload {\n    private String projectKey;\n    private List<ChangedIssuePayload> issues;\n    private String userSeverity;\n    private String userType;\n    private Boolean resolved;\n\n    private boolean isInvalid() {\n      return isBlank(projectKey) || isBlank(issues) || issues.stream().anyMatch(ChangedIssuePayload::isInvalid)\n        || (isBlank(userSeverity) && isBlank(userType) && resolved == null);\n    }\n\n    private static class ChangedIssuePayload {\n      private String issueKey;\n      private String branchName;\n      @Nullable\n      private ImpactPayload[] impacts;\n\n      private boolean isInvalid() {\n        return isBlank(issueKey) || isBlank(branchName);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/parsing/RuleSetChangedEventParser.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push.parsing;\n\nimport com.google.gson.Gson;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.serverapi.push.RuleSetChangedEvent;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.common.ImpactPayload;\n\nimport static org.sonarsource.sonarlint.core.serverapi.util.ServerApiUtils.areBlank;\nimport static org.sonarsource.sonarlint.core.serverapi.util.ServerApiUtils.isBlank;\n\npublic class RuleSetChangedEventParser implements EventParser<RuleSetChangedEvent> {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final Gson gson = new Gson();\n\n  @Override\n  public Optional<RuleSetChangedEvent> parse(String jsonData) {\n    var payload = gson.fromJson(jsonData, RuleSetChangedEventPayload.class);\n    if (payload.isInvalid()) {\n      LOG.error(\"Invalid payload for 'RuleSetChanged' event: {}\", jsonData);\n      return Optional.empty();\n    }\n    return Optional.of(new RuleSetChangedEvent(\n      payload.projects,\n      payload.activatedRules.stream().map(changedRule -> new RuleSetChangedEvent.ActiveRule(\n          changedRule.key,\n          changedRule.language,\n          IssueSeverity.valueOf(changedRule.severity),\n          changedRule.params.stream().filter(p -> p.value != null).collect(Collectors.toMap(p -> p.key, p -> p.value)),\n          changedRule.templateKey,\n          changedRule.impacts == null ? Collections.emptyList() : changedRule.impacts.stream()\n            .map(impact -> new ImpactPayload(impact.getSoftwareQuality(), impact.getSeverity())).toList()\n        ))\n        .toList(),\n      payload.deactivatedRules));\n  }\n\n  private static class RuleSetChangedEventPayload {\n    private List<String> projects;\n    private List<ActiveRulePayload> activatedRules;\n    private List<String> deactivatedRules;\n\n    private boolean isInvalid() {\n      return isBlank(projects) || areBlank(activatedRules, deactivatedRules);\n    }\n\n    private static class ActiveRulePayload {\n      private String key;\n      private String language;\n      private String severity;\n      private List<RuleParameterPayload> params;\n      private String templateKey;\n      private List<ImpactPayload> impacts;\n    }\n\n    private static class RuleParameterPayload {\n      private String key;\n      private String value;\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/parsing/SecurityHotspotChangedEventParser.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push.parsing;\n\nimport com.google.gson.Gson;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.Optional;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.serverapi.push.SecurityHotspotChangedEvent;\n\nimport static org.sonarsource.sonarlint.core.serverapi.util.ServerApiUtils.isBlank;\n\npublic class SecurityHotspotChangedEventParser implements EventParser<SecurityHotspotChangedEvent> {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final Gson gson = new Gson();\n\n  @Override\n  public Optional<SecurityHotspotChangedEvent> parse(String jsonData) {\n    var payload = gson.fromJson(jsonData, HotspotChangedEventPayload.class);\n    if (payload.isInvalid()) {\n      LOG.error(\"Invalid payload for 'SecurityHotspotChanged' event: {}\", jsonData);\n      return Optional.empty();\n    }\n    return Optional.of(new SecurityHotspotChangedEvent(\n      payload.key,\n      payload.projectKey,\n      Instant.ofEpochMilli(payload.updateDate),\n      HotspotReviewStatus.fromStatusAndResolution(payload.status, payload.resolution),\n      payload.assignee,\n      Path.of(payload.filePath)));\n  }\n\n  private static class HotspotChangedEventPayload {\n    private String key;\n    private String projectKey;\n    private String status;\n    private String resolution;\n    private long updateDate;\n    private String assignee;\n    private String filePath;\n\n    public String getKey() {\n      return key;\n    }\n\n    public String getProjectKey() {\n      return projectKey;\n    }\n\n    public String getStatus() {\n      return status;\n    }\n\n    public String getResolution() {\n      return resolution;\n    }\n\n    public long getUpdateDate() {\n      return updateDate;\n    }\n\n    public String getAssignee() {\n      return assignee;\n    }\n\n    private boolean isInvalid() {\n      return isBlank(key) || isBlank(projectKey) || updateDate == 0L || isBlank(filePath);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/parsing/SecurityHotspotClosedEventParser.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push.parsing;\n\nimport com.google.gson.Gson;\nimport java.nio.file.Path;\nimport java.util.Optional;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.serverapi.push.SecurityHotspotClosedEvent;\n\nimport static org.sonarsource.sonarlint.core.serverapi.util.ServerApiUtils.isBlank;\n\npublic class SecurityHotspotClosedEventParser implements EventParser<SecurityHotspotClosedEvent> {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final Gson gson = new Gson();\n\n  @Override\n  public Optional<SecurityHotspotClosedEvent> parse(String jsonData) {\n    var payload = gson.fromJson(jsonData, HotspotClosedEventPayload.class);\n    if (payload.isInvalid()) {\n      LOG.error(\"Invalid payload for 'SecurityHotspotClosed' event: {}\", jsonData);\n      return Optional.empty();\n    }\n    return Optional.of(new SecurityHotspotClosedEvent(payload.projectKey, payload.key, Path.of(payload.filePath)));\n  }\n\n  private static class HotspotClosedEventPayload {\n    private String projectKey;\n    private String key;\n    private String filePath;\n\n    private boolean isInvalid() {\n      return isBlank(projectKey) || isBlank(key) || isBlank(filePath);\n    }\n\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/parsing/SecurityHotspotRaisedEventParser.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push.parsing;\n\nimport com.google.gson.Gson;\nimport java.time.Instant;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.serverapi.push.SecurityHotspotRaisedEvent;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.common.LocationPayload;\n\nimport static org.sonarsource.sonarlint.core.serverapi.push.parsing.TaintVulnerabilityRaisedEventParser.adapt;\nimport static org.sonarsource.sonarlint.core.serverapi.util.ServerApiUtils.isBlank;\n\npublic class SecurityHotspotRaisedEventParser implements EventParser<SecurityHotspotRaisedEvent> {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final Gson gson = new Gson();\n\n  @Override\n  public Optional<SecurityHotspotRaisedEvent> parse(String jsonData) {\n    var payload = gson.fromJson(jsonData, HotspotRaisedEventPayload.class);\n    if (payload.isInvalid()) {\n      LOG.error(\"Invalid payload for 'SecurityHotspotRaised' event: {}\", jsonData);\n      return Optional.empty();\n    }\n    return Optional.of(new SecurityHotspotRaisedEvent(\n      payload.key,\n      payload.projectKey,\n      VulnerabilityProbability.valueOf(payload.vulnerabilityProbability),\n      HotspotReviewStatus.fromStatusAndResolution(payload.status, payload.resolution),\n      Instant.ofEpochMilli(payload.creationDate),\n      payload.branch,\n      adapt(payload.mainLocation),\n      payload.ruleKey,\n      payload.ruleDescriptionContextKey, payload.assignee));\n  }\n\n  private static class HotspotRaisedEventPayload {\n    private String key;\n    private String projectKey;\n    private String status;\n    @Nullable\n    private String resolution;\n    private String branch;\n    private String vulnerabilityProbability;\n    private long creationDate;\n    private String ruleKey;\n    private LocationPayload mainLocation;\n    @Nullable\n    private String ruleDescriptionContextKey;\n    @Nullable\n    private String assignee;\n\n    private boolean isInvalid() {\n      return isBlank(key) || isBlank(projectKey) || isBlank(vulnerabilityProbability) || creationDate == 0L || isBlank(branch) || isBlank(ruleKey)\n        || mainLocation.isInvalid();\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/parsing/TaintVulnerabilityClosedEventParser.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push.parsing;\n\nimport com.google.gson.Gson;\nimport java.util.Optional;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.serverapi.push.TaintVulnerabilityClosedEvent;\n\nimport static org.sonarsource.sonarlint.core.serverapi.util.ServerApiUtils.isBlank;\n\npublic class TaintVulnerabilityClosedEventParser implements EventParser<TaintVulnerabilityClosedEvent> {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final Gson gson = new Gson();\n\n  @Override\n  public Optional<TaintVulnerabilityClosedEvent> parse(String jsonData) {\n    var payload = gson.fromJson(jsonData, TaintVulnerabilityClosedEventPayload.class);\n    if (payload.isInvalid()) {\n      LOG.error(\"Invalid payload for 'TaintVulnerabilityClosed' event: {}\", jsonData);\n      return Optional.empty();\n    }\n    return Optional.of(new TaintVulnerabilityClosedEvent(payload.projectKey, payload.key));\n  }\n\n  private static class TaintVulnerabilityClosedEventPayload {\n    private String projectKey;\n    private String key;\n\n    private boolean isInvalid() {\n      return isBlank(projectKey) || isBlank(key);\n    }\n\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/parsing/TaintVulnerabilityRaisedEventParser.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push.parsing;\n\nimport com.google.gson.Gson;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.serverapi.push.TaintVulnerabilityRaisedEvent;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.common.ImpactPayload;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.common.LocationPayload;\n\nimport static java.util.Objects.isNull;\nimport static org.sonarsource.sonarlint.core.serverapi.util.ServerApiUtils.isBlank;\n\npublic class TaintVulnerabilityRaisedEventParser implements EventParser<TaintVulnerabilityRaisedEvent> {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final Gson gson = new Gson();\n\n  @Override\n  public Optional<TaintVulnerabilityRaisedEvent> parse(String jsonData) {\n    var payload = gson.fromJson(jsonData, TaintVulnerabilityRaisedEventPayload.class);\n    if (payload.isInvalid()) {\n      LOG.error(\"Invalid payload for 'TaintVulnerabilityRaised' event: {}\", jsonData);\n      return Optional.empty();\n    }\n    return Optional.of(new TaintVulnerabilityRaisedEvent(\n      payload.key,\n      payload.projectKey,\n      payload.branch,\n      Instant.ofEpochMilli(payload.creationDate),\n      payload.ruleKey,\n      IssueSeverity.valueOf(payload.severity),\n      RuleType.valueOf(payload.type),\n      adapt(payload.mainLocation),\n      adapt(payload.flows),\n      payload.ruleDescriptionContextKey,\n      adapt(payload.cleanCodeAttribute),\n      adapt(payload.impacts)\n    ));\n  }\n\n  private static List<TaintVulnerabilityRaisedEvent.Flow> adapt(List<TaintVulnerabilityRaisedEventPayload.FlowPayload> flows) {\n    return flows.stream()\n      .map(f -> new TaintVulnerabilityRaisedEvent.Flow(\n        f.locations.stream()\n          .map(TaintVulnerabilityRaisedEventParser::adapt)\n          .toList()))\n      .toList();\n  }\n\n  public static TaintVulnerabilityRaisedEvent.Location adapt(LocationPayload payload) {\n    return new TaintVulnerabilityRaisedEvent.Location(Path.of(payload.getFilePath()), payload.getMessage(), adapt(payload.getTextRange()));\n  }\n\n  public static CleanCodeAttribute adapt(String cleanCodeAttribute) {\n    return Optional.ofNullable(cleanCodeAttribute).map(CleanCodeAttribute::valueOf).orElse(null);\n  }\n\n  public static Map<SoftwareQuality, ImpactSeverity> adapt(@Nullable ImpactPayload[] payloads) {\n    if (payloads == null) {\n      return Map.of();\n    }\n    return Arrays.stream(payloads)\n      .collect(Collectors.toMap(\n        payload -> SoftwareQuality.valueOf(payload.getSoftwareQuality()),\n        payload -> ImpactSeverity.valueOf(payload.getSeverity())\n      ));\n  }\n\n  private static TaintVulnerabilityRaisedEvent.Location.TextRange adapt(LocationPayload.TextRangePayload payload) {\n    return new TaintVulnerabilityRaisedEvent.Location.TextRange(payload.getStartLine(), payload.getStartLineOffset(),\n      payload.getEndLine(), payload.getEndLineOffset(), payload.getHash());\n  }\n\n  private static class TaintVulnerabilityRaisedEventPayload {\n    private String key;\n    private String projectKey;\n    private String branch;\n    private long creationDate;\n    private String ruleKey;\n    private String severity;\n    private String type;\n    private LocationPayload mainLocation;\n    private List<FlowPayload> flows;\n    @Nullable\n    private String ruleDescriptionContextKey;\n    @Nullable\n    private String cleanCodeAttribute;\n    @Nullable\n    private ImpactPayload[] impacts;\n\n\n    private boolean isInvalid() {\n      return isBlank(key) || isBlank(projectKey) || isBlank(branch) || creationDate == 0L || isBlank(ruleKey) || isBlank(severity) || isBlank(type) ||\n        isNull(mainLocation) || mainLocation.isInvalid() || isNull(flows) || flows.stream().anyMatch(FlowPayload::isInvalid);\n    }\n\n    private static class FlowPayload {\n      private List<LocationPayload> locations;\n\n      private boolean isInvalid() {\n        return isNull(locations) || locations.stream().anyMatch(LocationPayload::isInvalid);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/parsing/common/ImpactPayload.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push.parsing.common;\n\npublic class ImpactPayload {\n\n  private String softwareQuality;\n  private String severity;\n\n  public ImpactPayload(String softwareQuality, String severity) {\n    this.softwareQuality = softwareQuality;\n    this.severity = severity;\n  }\n\n  public String getSoftwareQuality() {\n    return softwareQuality;\n  }\n\n  public void setSoftwareQuality(String softwareQuality) {\n    this.softwareQuality = softwareQuality;\n  }\n\n  public String getSeverity() {\n    return severity;\n  }\n\n  public void setSeverity(String severity) {\n    this.severity = severity;\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/parsing/common/LocationPayload.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push.parsing.common;\n\nimport static java.util.Objects.isNull;\nimport static org.sonarsource.sonarlint.core.serverapi.util.ServerApiUtils.isBlank;\n\npublic class LocationPayload {\n  private String filePath;\n  private String message;\n  private TextRangePayload textRange;\n\n  public String getFilePath() {\n    return filePath;\n  }\n\n  public String getMessage() {\n    return message;\n  }\n\n  public TextRangePayload getTextRange() {\n    return textRange;\n  }\n\n  public boolean isInvalid() {\n    return isBlank(filePath) || isBlank(message) || isNull(textRange) || textRange.isInvalid();\n  }\n\n  public static class TextRangePayload {\n    private int startLine;\n    private int startLineOffset;\n    private int endLine;\n    private int endLineOffset;\n    private String hash;\n\n    public int getStartLine() {\n      return startLine;\n    }\n\n    public int getStartLineOffset() {\n      return startLineOffset;\n    }\n\n    public int getEndLine() {\n      return endLine;\n    }\n\n    public int getEndLineOffset() {\n      return endLineOffset;\n    }\n\n    public String getHash() {\n      return hash;\n    }\n\n    public boolean isInvalid() {\n      return isBlank(hash);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/parsing/common/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.push.parsing.common;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/push/parsing/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.push.parsing;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/qualityprofile/QualityProfile.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.qualityprofile;\n\npublic class QualityProfile {\n  private final boolean isDefault;\n  private final String key;\n  private final String name;\n  private final String language;\n  private final String languageName;\n  private final long activeRuleCount;\n  private final String rulesUpdatedAt;\n  private final String userUpdatedAt;\n\n  public QualityProfile(boolean isDefault, String key, String name, String language, String languageName,\n    long activeRuleCount, String rulesUpdatedAt, String userUpdatedAt) {\n    this.isDefault = isDefault;\n    this.key = key;\n    this.name = name;\n    this.language = language;\n    this.languageName = languageName;\n    this.activeRuleCount = activeRuleCount;\n    this.rulesUpdatedAt = rulesUpdatedAt;\n    this.userUpdatedAt = userUpdatedAt;\n  }\n\n  public boolean isDefault() {\n    return isDefault;\n  }\n\n  public String getKey() {\n    return key;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public String getLanguage() {\n    return language;\n  }\n\n  public String getLanguageName() {\n    return languageName;\n  }\n\n  public long getActiveRuleCount() {\n    return activeRuleCount;\n  }\n\n  public String getRulesUpdatedAt() {\n    return rulesUpdatedAt;\n  }\n\n  public String getUserUpdatedAt() {\n    return userUpdatedAt;\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/qualityprofile/QualityProfileApi.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.qualityprofile;\n\nimport java.util.List;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\nimport org.sonarsource.sonarlint.core.serverapi.UrlUtils;\nimport org.sonarsource.sonarlint.core.serverapi.exception.NotFoundException;\nimport org.sonarsource.sonarlint.core.serverapi.exception.ProjectNotFoundException;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Qualityprofiles;\n\npublic class QualityProfileApi {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final String DEFAULT_QP_SEARCH_URL = \"/api/qualityprofiles/search.protobuf\";\n\n  private final ServerApiHelper helper;\n\n  public QualityProfileApi(ServerApiHelper helper) {\n    this.helper = helper;\n  }\n\n  public List<QualityProfile> getQualityProfiles(String projectKey, SonarLintCancelMonitor cancelMonitor) {\n    Qualityprofiles.SearchWsResponse qpResponse;\n    var url = new StringBuilder();\n    url.append(DEFAULT_QP_SEARCH_URL + \"?project=\");\n    url.append(UrlUtils.urlEncode(projectKey));\n    helper.getOrganizationKey()\n      .ifPresent(org -> url.append(\"&organization=\").append(UrlUtils.urlEncode(org)));\n    try {\n      qpResponse = ServerApiHelper.processTimed(\n        () -> helper.get(url.toString(), cancelMonitor),\n        response -> Qualityprofiles.SearchWsResponse.parseFrom(response.bodyAsStream()),\n        duration -> LOG.debug(\"Downloaded project quality profiles in {}ms\", duration));\n      return qpResponse.getProfilesList().stream().map(QualityProfileApi::adapt).toList();\n    } catch (NotFoundException e) {\n      throw new ProjectNotFoundException(projectKey, helper.getOrganizationKey().orElse(null));\n    }\n  }\n\n  private static QualityProfile adapt(Qualityprofiles.SearchWsResponse.QualityProfile wsQualityProfile) {\n    return new QualityProfile(\n      wsQualityProfile.getIsDefault(),\n      wsQualityProfile.getKey(),\n      wsQualityProfile.getName(),\n      wsQualityProfile.getLanguage(),\n      wsQualityProfile.getLanguageName(),\n      wsQualityProfile.getActiveRuleCount(),\n      wsQualityProfile.getRulesUpdatedAt(),\n      wsQualityProfile.getUserUpdatedAt());\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/qualityprofile/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.qualityprofile;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/rules/RulesApi.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.rules;\n\nimport com.google.common.base.Enums;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.Set;\nimport org.sonarsource.sonarlint.core.commons.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\nimport org.sonarsource.sonarlint.core.serverapi.UrlUtils;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Rules;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.common.ImpactPayload;\n\nimport static java.util.stream.Collectors.joining;\nimport static java.util.stream.Collectors.toMap;\n\npublic class RulesApi {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  public static final Map<SonarLanguage, String> TAINT_REPOS_BY_LANGUAGE = Map.of(\n    SonarLanguage.GO, \"gosecurity\",\n    SonarLanguage.JAVA, \"javasecurity\",\n    SonarLanguage.JS, \"jssecurity\",\n    SonarLanguage.KOTLIN, \"kotlinsecurity\",\n    SonarLanguage.PHP, \"phpsecurity\",\n    SonarLanguage.PYTHON, \"pythonsecurity\",\n    SonarLanguage.CS, \"roslyn.sonaranalyzer.security.cs\",\n    SonarLanguage.TS, \"tssecurity\",\n    SonarLanguage.VBNET, \"vbnetsecurity\");\n\n  public static final Set<String> TAINT_REPOS = Set.copyOf(TAINT_REPOS_BY_LANGUAGE.values());\n\n  public static final String RULE_SHOW_URL = \"/api/rules/show.protobuf?key=\";\n\n  private final ServerApiHelper serverApiHelper;\n\n  public RulesApi(ServerApiHelper serverApiHelper) {\n    this.serverApiHelper = serverApiHelper;\n  }\n\n  public Optional<ServerRule> getRule(String ruleKey, SonarLintCancelMonitor cancelMonitor) {\n    var builder = new StringBuilder(RULE_SHOW_URL + ruleKey);\n    serverApiHelper.getOrganizationKey().ifPresent(org -> builder.append(\"&organization=\").append(UrlUtils.urlEncode(org)));\n    try (var response = serverApiHelper.get(builder.toString(), cancelMonitor)) {\n      var rule = Rules.ShowResponse.parseFrom(response.bodyAsStream()).getRule();\n      var cleanCodeAttribute = Enums.getIfPresent(CleanCodeAttribute.class, rule.getCleanCodeAttribute().name()).orNull();\n      var impacts = rule.getImpacts().getImpactsList().stream().collect(toMap(\n        impact -> SoftwareQuality.valueOf(impact.getSoftwareQuality().name()),\n        impact -> ImpactSeverity.mapSeverity(impact.getSeverity().name())));\n      return Optional.of(new ServerRule(rule.getName(), IssueSeverity.valueOf(rule.getSeverity()), RuleType.valueOf(rule.getType().name()), rule.getLang(), rule.getHtmlDesc(),\n        convertDescriptionSections(rule),\n        rule.getHtmlNote(), Set.copyOf(rule.getEducationPrinciples().getEducationPrinciplesList()), cleanCodeAttribute, impacts));\n    } catch (Exception e) {\n      LOG.error(\"Error when fetching rule '\" + ruleKey + \"'\", e);\n    }\n    return Optional.empty();\n  }\n\n  private static List<ServerRule.DescriptionSection> convertDescriptionSections(Rules.Rule rule) {\n    if (rule.hasDescriptionSections()) {\n      return rule.getDescriptionSections().getDescriptionSectionsList().stream()\n        .map(s -> {\n          ServerRule.DescriptionSection.Context context = null;\n          if (s.hasContext()) {\n            var contextFromServer = s.getContext();\n            context = new ServerRule.DescriptionSection.Context(contextFromServer.getKey(), contextFromServer.getDisplayName());\n          }\n          return new ServerRule.DescriptionSection(s.getKey(), s.getContent(), Optional.ofNullable(context));\n        }).toList();\n    }\n    return Collections.emptyList();\n  }\n\n  public Collection<ServerActiveRule> getAllActiveRules(String qualityProfileKey, SonarLintCancelMonitor cancelMonitor) {\n    // Use a map to avoid duplicates during pagination\n    Map<String, ServerActiveRule> activeRulesByKey = new HashMap<>();\n    Map<String, String> ruleTemplatesByRuleKey = new HashMap<>();\n    serverApiHelper.getPaginated(getSearchByQualityProfileUrl(qualityProfileKey),\n      Rules.SearchResponse::parseFrom,\n      r -> r.hasPaging() ? r.getPaging().getTotal() : r.getTotal(),\n      r -> {\n        ruleTemplatesByRuleKey.putAll(r.getRulesList().stream().collect(toMap(Rules.Rule::getKey, Rules.Rule::getTemplateKey)));\n        return List.copyOf(r.getActives().getActivesMap().entrySet());\n      },\n      activeEntry -> {\n        var ruleKey = activeEntry.getKey();\n        // Since we are querying rules for a given profile, we know there will be only one active rule per rule\n        Rules.Active ar = activeEntry.getValue().getActiveListList().get(0);\n        activeRulesByKey.put(ruleKey, new ServerActiveRule(\n          ruleKey,\n          IssueSeverity.valueOf(ar.getSeverity()),\n          ar.getParamsList().stream().collect(toMap(Rules.Active.Param::getKey, Rules.Active.Param::getValue)),\n          ruleTemplatesByRuleKey.get(ruleKey),\n          ar.getImpacts().getImpactsList().stream()\n            .map(impact -> new ImpactPayload(impact.getSoftwareQuality().toString(), ImpactSeverity.mapSeverity(impact.getSeverity().name()).name()))\n            .toList()));\n\n      },\n      false,\n      cancelMonitor);\n    return activeRulesByKey.values();\n  }\n\n  private String getSearchByQualityProfileUrl(String qualityProfileKey) {\n    var builder = new StringBuilder();\n    builder.append(\"/api/rules/search.protobuf?qprofile=\");\n    builder.append(UrlUtils.urlEncode(qualityProfileKey));\n    serverApiHelper.getOrganizationKey().ifPresent(org -> builder.append(\"&organization=\").append(UrlUtils.urlEncode(org)));\n    builder.append(\"&activation=true&f=templateKey,actives&types=CODE_SMELL,BUG,VULNERABILITY,SECURITY_HOTSPOT&s=key\");\n    return builder.toString();\n  }\n\n  public Set<String> getAllTaintRules(List<SonarLanguage> enabledLanguages, SonarLintCancelMonitor cancelMonitor) {\n    Set<String> taintRules = new HashSet<>();\n    serverApiHelper.getPaginated(getSearchByRepoUrl(enabledLanguages.stream().map(TAINT_REPOS_BY_LANGUAGE::get).filter(Objects::nonNull).toList()),\n      Rules.SearchResponse::parseFrom,\n      Rules.SearchResponse::getTotal,\n      Rules.SearchResponse::getRulesList,\n      rule -> taintRules.add(rule.getKey()),\n      false,\n      cancelMonitor);\n    return taintRules;\n  }\n\n  private String getSearchByRepoUrl(List<String> repositories) {\n    var builder = new StringBuilder();\n    builder.append(\"/api/rules/search.protobuf?repositories=\");\n    builder.append(repositories.stream().map(UrlUtils::urlEncode).collect(joining(\",\")));\n    serverApiHelper.getOrganizationKey().ifPresent(org -> builder.append(\"&organization=\").append(UrlUtils.urlEncode(org)));\n    // Add only f=repo even if we don't need it, else too many fields are returned by default\n    builder.append(\"&f=repo&s=key\");\n    return builder.toString();\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/rules/ServerActiveRule.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.rules;\n\nimport java.util.List;\nimport java.util.Map;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.common.ImpactPayload;\n\npublic class ServerActiveRule {\n  private final String ruleKey;\n  private final IssueSeverity severity;\n  private final Map<String, String> params;\n  private final String templateKey;\n  private final List<ImpactPayload> overriddenImpacts;\n\n  public ServerActiveRule(String ruleKey, IssueSeverity severity, Map<String, String> params, @Nullable String templateKey, List<ImpactPayload> overriddenImpacts) {\n    this.ruleKey = ruleKey;\n    this.severity = severity;\n    this.params = params;\n    this.templateKey = templateKey;\n    this.overriddenImpacts = overriddenImpacts;\n  }\n\n  public List<ImpactPayload> getOverriddenImpacts() {\n    return overriddenImpacts;\n  }\n\n  public IssueSeverity getSeverity() {\n    return severity;\n  }\n\n  public Map<String, String> getParams() {\n    return params;\n  }\n\n  public String getRuleKey() {\n    return ruleKey;\n  }\n\n  public String getTemplateKey() {\n    return templateKey;\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/rules/ServerRule.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.rules;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\n\npublic class ServerRule {\n  private final String name;\n  private final String htmlDesc;\n  private final List<DescriptionSection> descriptionSections;\n  private final String htmlNote;\n  private final IssueSeverity severity;\n  private final RuleType type;\n  private final SonarLanguage language;\n  private final Set<String> educationPrincipleKeys;\n  private final CleanCodeAttribute cleanCodeAttribute;\n  private final Map<SoftwareQuality, ImpactSeverity> impacts;\n\n\n  public ServerRule(String name, IssueSeverity severity, RuleType type, String language, String htmlDesc, List<DescriptionSection> descriptionSections, String htmlNote,\n    Set<String> educationPrincipleKeys, @Nullable CleanCodeAttribute cleanCodeAttribute, Map<SoftwareQuality, ImpactSeverity> impacts) {\n    this.name = name;\n    this.severity = severity;\n    this.type = type;\n    this.language = SonarLanguage.forKey(language).orElseThrow(() -> new IllegalArgumentException(\"Unknown language with key: \" + language));\n    this.htmlDesc = htmlDesc;\n    this.descriptionSections = descriptionSections;\n    this.htmlNote = htmlNote;\n    this.educationPrincipleKeys = educationPrincipleKeys;\n    this.cleanCodeAttribute = cleanCodeAttribute;\n    this.impacts = impacts;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public String getHtmlDesc() {\n    return htmlDesc;\n  }\n\n  public List<DescriptionSection> getDescriptionSections() {\n    return descriptionSections;\n  }\n\n  public String getHtmlNote() {\n    return htmlNote;\n  }\n\n  public IssueSeverity getSeverity() {\n    return severity;\n  }\n\n  public RuleType getType() {\n    return type;\n  }\n\n  public SonarLanguage getLanguage() {\n    return language;\n  }\n\n  public Set<String> getEducationPrincipleKeys() {\n    return educationPrincipleKeys;\n  }\n\n  @CheckForNull\n  public CleanCodeAttribute getCleanCodeAttribute() {\n    return cleanCodeAttribute;\n  }\n\n  public Map<SoftwareQuality, ImpactSeverity> getImpacts() {\n    return impacts;\n  }\n\n  public static class DescriptionSection {\n    private final String key;\n    private final String htmlContent;\n    private final Optional<Context> context;\n\n    public DescriptionSection(String key, String htmlContent, Optional<Context> context) {\n      this.key = key;\n      this.htmlContent = htmlContent;\n      this.context = context;\n    }\n\n    public String getKey() {\n      return key;\n    }\n\n    public String getHtmlContent() {\n      return htmlContent;\n    }\n\n    public Optional<Context> getContext() {\n      return context;\n    }\n\n    public static class Context {\n      private final String key;\n      private final String displayName;\n\n      public Context(String key, String displayName) {\n        this.key = key;\n        this.displayName = displayName;\n      }\n\n      public String getKey() {\n        return key;\n      }\n\n      public String getDisplayName() {\n        return displayName;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/rules/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.rules;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/sca/GetIssuesReleasesResponse.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.sca;\n\nimport java.util.List;\nimport java.util.UUID;\nimport javax.annotation.Nullable;\n\npublic record GetIssuesReleasesResponse(List<IssuesRelease> issuesReleases, Page page) {\n  public record IssuesRelease(UUID key, Type type, Severity severity, SoftwareQuality quality, Status status, Release release,\n                              @Nullable String vulnerabilityId, @Nullable String cvssScore, List<Transition> transitions) {\n    public record Release(String packageName, String version) {\n    }\n\n    public enum Severity {\n      INFO, LOW, MEDIUM, HIGH, BLOCKER\n    }\n\n    public enum SoftwareQuality {\n      MAINTAINABILITY,\n      RELIABILITY,\n      SECURITY\n    }\n\n    public enum Type {\n      VULNERABILITY, PROHIBITED_LICENSE\n    }\n\n    public enum Status {\n      OPEN, CONFIRM, ACCEPT, SAFE, FIXED\n    }\n\n    public enum Transition {\n      CONFIRM, REOPEN, SAFE, FIXED, ACCEPT\n    }\n  }\n\n  public record Page(int total) {\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/sca/GetScaEnablementResponse.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.sca;\n\npublic record GetScaEnablementResponse(boolean enabled) {\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/sca/ScaApi.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.sca;\n\nimport com.google.gson.Gson;\nimport jakarta.annotation.Nullable;\nimport java.io.InputStreamReader;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.UUID;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\nimport org.sonarsource.sonarlint.core.serverapi.UrlUtils;\n\npublic class ScaApi {\n\n  private final ServerApiHelper serverApiHelper;\n\n  public ScaApi(ServerApiHelper serverApiHelper) {\n    this.serverApiHelper = serverApiHelper;\n  }\n\n  public GetIssuesReleasesResponse getIssuesReleases(String projectKey, String branchKey, SonarLintCancelMonitor cancelMonitor) {\n    var urlPrefix = serverApiHelper.isSonarCloud() ? \"\" : \"/api/v2\";\n    var url = urlPrefix + \"/sca/issues-releases?projectKey=\" +\n      UrlUtils.urlEncode(projectKey) +\n      \"&branchKey=\" +\n      UrlUtils.urlEncode(branchKey);\n\n    var allIssuesReleases = new ArrayList<GetIssuesReleasesResponse.IssuesRelease>();\n\n    if (serverApiHelper.isSonarCloud()) {\n      serverApiHelper.apiGetPaginated(\n        url,\n        response -> new Gson().fromJson(new InputStreamReader(response, StandardCharsets.UTF_8), GetIssuesReleasesResponse.class),\n        r -> r.page().total(),\n        GetIssuesReleasesResponse::issuesReleases,\n        allIssuesReleases::add,\n        false,\n        cancelMonitor,\n        \"pageIndex\",\n        \"pageSize\");\n    } else {\n      serverApiHelper.getPaginated(\n        url,\n        response -> new Gson().fromJson(new InputStreamReader(response, StandardCharsets.UTF_8), GetIssuesReleasesResponse.class),\n        r -> r.page().total(),\n        GetIssuesReleasesResponse::issuesReleases,\n        allIssuesReleases::add,\n        false,\n        cancelMonitor,\n        \"pageIndex\",\n        \"pageSize\");\n    }\n    return new GetIssuesReleasesResponse(allIssuesReleases, new GetIssuesReleasesResponse.Page(allIssuesReleases.size()));\n  }\n\n  public void changeStatus(UUID issueReleaseKey, String transitionKey, @Nullable String comment, SonarLintCancelMonitor cancelMonitor) {\n    var body = new ChangeStatusRequestBody(issueReleaseKey.toString(), transitionKey, comment);\n    var urlPrefix = serverApiHelper.isSonarCloud() ? \"\" : \"/api/v2\";\n    var url = urlPrefix + \"/sca/issues-releases/change-status\";\n\n    if (serverApiHelper.isSonarCloud()) {\n      serverApiHelper.apiPostJson(url, body, cancelMonitor);\n    } else {\n      serverApiHelper.postJson(url, body, cancelMonitor);\n    }\n  }\n\n  private record ChangeStatusRequestBody(String issueReleaseKey, String transitionKey, @Nullable String comment) {\n  }\n\n  public GetScaEnablementResponse isScaEnabled(SonarLintCancelMonitor cancelMonitor) {\n    var organizationKey = serverApiHelper.getOrganizationKey();\n    if (organizationKey.isEmpty()) {\n      return new GetScaEnablementResponse(false);\n    }\n    try {\n      return serverApiHelper.apiGetJson(\"/sca/feature-enabled?organization=\" + UrlUtils.urlEncode(organizationKey.get()),\n        GetScaEnablementResponse.class, cancelMonitor);\n    } catch (Exception e) {\n      return new GetScaEnablementResponse(false);\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/sca/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.sca;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/settings/SettingsApi.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.settings;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.function.BiConsumer;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\nimport org.sonarsource.sonarlint.core.serverapi.UrlUtils;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Settings;\n\npublic class SettingsApi {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final String API_SETTINGS_PATH = \"/api/settings/values.protobuf\";\n\n  private final ServerApiHelper helper;\n\n  public SettingsApi(ServerApiHelper helper) {\n    this.helper = helper;\n  }\n\n  public Map<String, String> getGlobalSettings(SonarLintCancelMonitor cancelMonitor) {\n    return getSettings(\"\", cancelMonitor);\n  }\n\n  public Map<String, String> getProjectSettings(String projectKey, SonarLintCancelMonitor cancelMonitor) {\n    return getSettings(\"?component=\" + UrlUtils.urlEncode(projectKey), cancelMonitor);\n  }\n\n  private Map<String, String> getSettings(String queryParameters, SonarLintCancelMonitor cancelMonitor) {\n    var settings = new HashMap<String, String>();\n    var url = API_SETTINGS_PATH + queryParameters;\n    ServerApiHelper.consumeTimed(\n      () -> helper.get(url, cancelMonitor),\n      response -> {\n        try (var is = response.bodyAsStream()) {\n          var values = Settings.ValuesWsResponse.parseFrom(is);\n          for (Settings.Setting s : values.getSettingsList()) {\n            processSetting(settings::put, s);\n          }\n        } catch (IOException e) {\n          throw new IllegalStateException(\"Unable to parse properties from: \" + response.bodyAsString(), e);\n        }\n      },\n      duration -> LOG.info(\"Downloaded settings in {}ms\", duration));\n    return settings;\n  }\n\n  private static void processSetting(BiConsumer<String, String> consumer, Settings.Setting s) {\n    switch (s.getValueOneOfCase()) {\n      case VALUE:\n        consumer.accept(s.getKey(), s.getValue());\n        break;\n      case VALUES:\n        consumer.accept(s.getKey(), String.join(\",\", s.getValues().getValuesList()));\n        break;\n      case FIELDVALUES:\n        processPropertySet(s, consumer);\n        break;\n      default:\n        throw new IllegalStateException(\"Unknown property value for \" + s.getKey());\n    }\n  }\n\n  private static void processPropertySet(Settings.Setting s, BiConsumer<String, String> consumer) {\n    var ids = new ArrayList<String>();\n    var id = 1;\n    for (Settings.FieldValues.Value v : s.getFieldValues().getFieldValuesList()) {\n      for (Map.Entry<String, String> entry : v.getValueMap().entrySet()) {\n        consumer.accept(s.getKey() + \".\" + id + \".\" + entry.getKey(), entry.getValue());\n      }\n      ids.add(String.valueOf(id));\n      id++;\n    }\n    consumer.accept(s.getKey(), String.join(\",\", ids));\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/settings/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.settings;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/source/SourceApi.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.source;\n\nimport java.util.Optional;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\n\nimport static org.sonarsource.sonarlint.core.serverapi.UrlUtils.urlEncode;\n\npublic class SourceApi {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final ServerApiHelper serverApiHelper;\n\n  public SourceApi(ServerApiHelper serverApiHelper) {\n    this.serverApiHelper = serverApiHelper;\n  }\n\n  /**\n   * Fetch source code of the file with specified key.\n   * If the component doesn't exist or it exists but has no source, an empty String is returned.\n   *\n   * @param key project key, or file key.\n   */\n  public Optional<String> getRawSourceCode(String fileKey, SonarLintCancelMonitor cancelMonitor) {\n    try (var r = serverApiHelper.get(\"/api/sources/raw?key=\" + urlEncode(fileKey), cancelMonitor)) {\n      return Optional.of(r.bodyAsString());\n    } catch (Exception e) {\n      LOG.debug(\"Unable to fetch source code of '\" + fileKey + \"'\", e);\n      return Optional.empty();\n    }\n  }\n\n  public Optional<String> getRawSourceCodeForBranchAndPullRequest(String fileKey, String branch, @Nullable String pullRequest, SonarLintCancelMonitor cancelMonitor) {\n    var url = \"/api/sources/raw?key=\" + urlEncode(fileKey);\n    if (pullRequest != null && !pullRequest.isEmpty()) {\n      url = url.concat(\"&pullRequest=\").concat(urlEncode(pullRequest));\n    } else if (!branch.isEmpty()) {\n      url = url.concat(\"&branch=\").concat(urlEncode(branch));\n    }\n    try (var r = serverApiHelper.get(url, cancelMonitor)) {\n      return Optional.of(r.bodyAsString());\n    } catch (Exception e) {\n      LOG.debug(\"Unable to fetch source code of '\" + fileKey + \"'\", e);\n      return Optional.empty();\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/source/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.source;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/stream/Event.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.stream;\n\npublic class Event {\n  private final String type;\n  private final String data;\n\n  public Event(String type, String data) {\n    this.type = type;\n    this.data = data;\n  }\n\n  public String getType() {\n    return type;\n  }\n\n  public String getData() {\n    return data;\n  }\n\n  @Override\n  public String toString() {\n    return \"[type: \" + type + \", \" +\n      \"data: \" + data + \"]\";\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/stream/EventBuffer.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.stream;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class EventBuffer {\n  private final StringBuilder buffer = new StringBuilder();\n\n  EventBuffer append(String data) {\n    buffer.append(data);\n    return this;\n  }\n\n  List<String> drainCompleteEvents() {\n    List<String> completeEvents = new ArrayList<>();\n    int firstEventEndIndex;\n    do {\n      firstEventEndIndex = buffer.indexOf(\"\\n\\n\");\n      if (firstEventEndIndex == -1) {\n        break;\n      }\n      var completeEvent = buffer.substring(0, firstEventEndIndex).trim();\n      buffer.delete(0, firstEventEndIndex + 2);\n      if (!completeEvent.isEmpty()) {\n        completeEvents.add(completeEvent);\n      }\n    } while (true);\n    return completeEvents;\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/stream/EventParser.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.stream;\n\nimport java.util.List;\n\npublic class EventParser {\n  private static final String EVENT_TYPE_PREFIX = \"event: \";\n  private static final String DATA_PREFIX = \"data: \";\n\n  static Event parse(String eventPayload) {\n    var fields = List.of(eventPayload.split(\"\\\\n\"));\n    var type = \"\";\n    var data = new StringBuilder();\n    for (String field : fields) {\n      if (field.startsWith(EVENT_TYPE_PREFIX)) {\n        type = field.substring(EVENT_TYPE_PREFIX.length());\n      } else if (field.startsWith(DATA_PREFIX)) {\n        data.append(field.substring(DATA_PREFIX.length()));\n      }\n    }\n    return new Event(type, data.toString());\n  }\n\n  private EventParser() {\n    // static only\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/stream/EventStream.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.stream;\n\nimport com.google.common.util.concurrent.MoreExecutors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.ScheduledFuture;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Consumer;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.util.FailSafeExecutors;\nimport org.sonarsource.sonarlint.core.http.HttpClient;\nimport org.sonarsource.sonarlint.core.http.HttpConnectionListener;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\n\nimport static java.util.concurrent.TimeUnit.SECONDS;\n\npublic class EventStream {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private static final Integer UNAUTHORIZED = 401;\n  private static final Integer FORBIDDEN = 403;\n  private static final Integer NOT_FOUND = 404;\n  private static final long HEART_BEAT_PERIOD = 60;\n\n  private final ServerApiHelper helper;\n  private final ScheduledExecutorService executor;\n  private final AtomicReference<HttpClient.AsyncRequest> currentRequest = new AtomicReference<>();\n  private final AtomicReference<ScheduledFuture<?>> pendingFuture = new AtomicReference<>();\n  private final Consumer<Event> eventConsumer;\n\n  public EventStream(ServerApiHelper helper, Consumer<Event> eventConsumer) {\n    this(helper, eventConsumer, FailSafeExecutors.newSingleThreadScheduledExecutor(\"sonarlint-event-stream-consumer\"));\n  }\n\n  EventStream(ServerApiHelper helper, Consumer<Event> eventConsumer, ScheduledExecutorService executor) {\n    this.helper = helper;\n    this.eventConsumer = eventConsumer;\n    this.executor = executor;\n  }\n\n  public EventStream connect(String wsPath) {\n    return connect(wsPath, new Attempt());\n  }\n\n  private EventStream connect(String wsPath, Attempt currentAttempt) {\n    LOG.debug(\"Connecting to server event-stream at '\" + wsPath + \"'...\");\n    var eventBuffer = new EventBuffer();\n    currentRequest.set(helper.getEventStream(wsPath,\n      new HttpConnectionListener() {\n        @Override\n        public void onConnected() {\n          LOG.debug(\"Connected to server event-stream\");\n          schedule(() -> connect(wsPath), HEART_BEAT_PERIOD * 3);\n        }\n\n        @Override\n        public void onError(@Nullable Integer responseCode) {\n          handleError(wsPath, currentAttempt, responseCode);\n        }\n\n        @Override\n        public void onClosed() {\n          cancelPendingFutureIfAny();\n          // reconnect instantly (will also reset attempt parameters)\n          LOG.debug(\"Disconnected from server event-stream, reconnecting now\");\n          connect(wsPath);\n        }\n      },\n      message -> {\n        cancelPendingFutureIfAny();\n        eventBuffer.append(message)\n          .drainCompleteEvents()\n          .forEach(stringEvent -> {\n            LOG.debug(\"Received event: \" + stringEvent);\n            eventConsumer.accept(EventParser.parse(stringEvent));\n          });\n      }));\n    return this;\n  }\n\n  private void handleError(String wsPath, Attempt currentAttempt, @Nullable Integer responseCode) {\n    if (shouldRetry(responseCode)) {\n      if (!currentAttempt.isMax()) {\n        var retryDelay = currentAttempt.delay;\n        var msgBuilder = new StringBuilder();\n        msgBuilder.append(\"Cannot connect to server event-stream\");\n        if (responseCode != null) {\n          msgBuilder.append(\" (\").append(responseCode).append(\")\");\n        }\n        msgBuilder.append(\", retrying in \").append(retryDelay).append(\"s\");\n        LOG.debug(msgBuilder.toString());\n        schedule(() -> connect(wsPath, currentAttempt.next()), retryDelay);\n      } else {\n        LOG.debug(\"Cannot connect to server event-stream, stop retrying\");\n      }\n    }\n  }\n\n  private static boolean shouldRetry(@Nullable Integer responseCode) {\n    if (UNAUTHORIZED.equals(responseCode)) {\n      LOG.debug(\"Cannot connect to server event-stream, unauthorized\");\n      return false;\n    }\n    if (FORBIDDEN.equals(responseCode)) {\n      LOG.debug(\"Cannot connect to server event-stream, forbidden\");\n      return false;\n    }\n    if (NOT_FOUND.equals(responseCode)) {\n      // the API is not supported (probably an old SQ or SC)\n      LOG.debug(\"Server events not supported by the server\");\n      return false;\n    }\n    return true;\n  }\n\n  private void schedule(Runnable task, long delayInSeconds) {\n    if (!executor.isShutdown()) {\n      pendingFuture.set(executor.schedule(task, delayInSeconds, SECONDS));\n    }\n  }\n\n  public void close() {\n    cancelPendingFutureIfAny();\n    var currentRequestOrNull = currentRequest.getAndSet(null);\n    if (currentRequestOrNull != null) {\n      currentRequestOrNull.cancel();\n    }\n    if (!MoreExecutors.shutdownAndAwaitTermination(executor, 5, TimeUnit.SECONDS)) {\n      LOG.warn(\"Unable to stop event stream executor service in a timely manner\");\n    }\n  }\n\n  private void cancelPendingFutureIfAny() {\n    var pendingFutureOrNull = pendingFuture.getAndSet(null);\n    if (pendingFutureOrNull != null) {\n      pendingFutureOrNull.cancel(true);\n    }\n  }\n\n  private static class Attempt {\n    private static final int DEFAULT_DELAY_S = 60;\n    private static final int BACK_OFF_MULTIPLIER = 2;\n    private static final int MAX_ATTEMPTS = 10;\n\n    private final long delay;\n    private final int attemptNumber;\n\n    public Attempt() {\n      this(DEFAULT_DELAY_S, 1);\n    }\n\n    public Attempt(long delay, int attemptNumber) {\n      this.delay = delay;\n      this.attemptNumber = attemptNumber;\n    }\n\n    public Attempt next() {\n      return new Attempt(delay * BACK_OFF_MULTIPLIER, attemptNumber + 1);\n    }\n\n    public boolean isMax() {\n      return attemptNumber == MAX_ATTEMPTS;\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/stream/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.stream;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/system/ServerStatusInfo.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.system;\n\npublic record ServerStatusInfo(String id, String status, String version) {\n  public boolean isUp() {\n    return \"UP\".equals(status);\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/system/SystemApi.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.system;\n\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\n\npublic class SystemApi {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final ServerApiHelper helper;\n\n  public SystemApi(ServerApiHelper helper) {\n    this.helper = helper;\n  }\n\n  public ServerStatusInfo getStatus(SonarLintCancelMonitor cancelMonitor) {\n    var start = System.currentTimeMillis();\n    var status = helper.getAnonymousJson(\"api/system/status\", SystemStatusDto.class, cancelMonitor);\n    var duration = System.currentTimeMillis() - start;\n    LOG.debug(\"Downloaded server infos in {}ms\", duration);\n    return new ServerStatusInfo(status.id(), status.status(), status.version());\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/system/SystemStatusDto.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.system;\n\npublic record SystemStatusDto(String id, String version, String status) {\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/system/ValidationResult.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.system;\n\npublic class ValidationResult {\n\n  private final boolean success;\n  private final String message;\n\n  public ValidationResult(boolean success, String message) {\n    this.success = success;\n    this.message = message;\n  }\n\n  public boolean success() {\n    return success;\n  }\n\n  public String message() {\n    return message;\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/system/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.system;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/users/CurrentUserResponseDto.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.users;\n\npublic record CurrentUserResponseDto(String id) {\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/users/UsersApi.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.users;\n\nimport javax.annotation.CheckForNull;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\n\npublic class UsersApi {\n  private final ServerApiHelper helper;\n\n  public UsersApi(ServerApiHelper helper) {\n    this.helper = helper;\n  }\n\n  /**\n   * Fetch the current user info using api/users/current.\n   * Returns null if the response cannot be parsed or if the id field is not present.\n   * Note: The id field is available on SonarQube Cloud and SonarQube Server 2025.6+.\n   */\n  @CheckForNull\n  public String getCurrentUserId(SonarLintCancelMonitor cancelMonitor) {\n    var userResponse = helper.getJson(\"/api/users/current\", CurrentUserResponseDto.class, cancelMonitor);\n    return userResponse == null ? null : userResponse.id();\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/users/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.users;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/util/ProtobufUtil.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.util;\n\nimport com.google.protobuf.InvalidProtocolBufferException;\nimport com.google.protobuf.Message;\nimport com.google.protobuf.Parser;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class ProtobufUtil {\n  private ProtobufUtil() {\n    // only static stuff\n  }\n\n  public static <T extends Message> List<T> readMessages(InputStream input, Parser<T> parser) {\n    List<T> list = new ArrayList<>();\n    while (true) {\n      T message;\n      try {\n        message = parser.parseDelimitedFrom(input);\n      } catch (InvalidProtocolBufferException e) {\n        throw new IllegalStateException(\"failed to parse protobuf message\", e);\n      }\n      if (message == null) {\n        break;\n      }\n      list.add(message);\n    }\n    return list;\n  }\n\n  public static <T extends Message> void writeMessages(OutputStream output, Iterable<T> messages) {\n    for (Message message : messages) {\n      writeMessage(output, message);\n    }\n  }\n\n  static <T extends Message> void writeMessage(OutputStream output, T message) {\n    try {\n      message.writeDelimitedTo(output);\n    } catch (IOException e) {\n      throw new IllegalStateException(\"failed to write message: \" + message, e);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/util/ServerApiUtils.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.util;\n\nimport java.io.File;\nimport java.nio.file.Path;\nimport java.time.OffsetDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.time.format.DateTimeParseException;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.regex.Pattern;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common.TextRange;\n\npublic class ServerApiUtils {\n\n  public static final String DATETIME_FORMAT = \"yyyy-MM-dd'T'HH:mm:ssZ\";\n\n  private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern(DATETIME_FORMAT);\n\n  public static String extractCodeSnippet(String sourceCode, TextRange textRange) {\n    return extractCodeSnippet(sourceCode.split(\"\\\\r?\\\\n\"), textRange);\n  }\n\n  private static String extractCodeSnippet(String[] sourceCodeLines, TextRange textRange) {\n    if (textRange.getStartLine() == 0 && textRange.getEndLine() == 0) {\n      // SLCORE-593 this is a file-level issue\n      return String.join(\"\\n\", sourceCodeLines);\n    } else if (textRange.getStartLine() == textRange.getEndLine()) {\n      var fullLine = sourceCodeLines[textRange.getStartLine() - 1];\n      return fullLine.substring(textRange.getStartOffset(), textRange.getEndOffset());\n    } else {\n      var linesOfTextRange = Arrays.copyOfRange(sourceCodeLines, textRange.getStartLine() - 1, textRange.getEndLine());\n      linesOfTextRange[0] = linesOfTextRange[0].substring(textRange.getStartOffset());\n      linesOfTextRange[linesOfTextRange.length - 1] = linesOfTextRange[linesOfTextRange.length - 1].substring(0, textRange.getEndOffset());\n      return String.join(\"\\n\", linesOfTextRange);\n    }\n  }\n\n  public static boolean isBlank(@Nullable Collection<?> collection) {\n    return collection == null || collection.isEmpty();\n  }\n\n  public static boolean isBlank(@Nullable String s) {\n    return s == null || s.isEmpty();\n  }\n\n  public static boolean areBlank(List<?>... lists) {\n    return Arrays.stream(lists).allMatch(ServerApiUtils::isBlank);\n  }\n\n  public static OffsetDateTime parseOffsetDateTime(String s) {\n    try {\n      return OffsetDateTime.parse(s, DATETIME_FORMATTER);\n    } catch (DateTimeParseException e) {\n      throw new IllegalStateException(\"The date '\" + s + \"' does not respect format '\" + DATETIME_FORMAT + \"'\", e);\n    }\n  }\n\n  /**\n   * Converts path to format used by SonarQube\n   *\n   * @param path path string in the local OS\n   * @return SonarQube path\n   */\n  public static String toSonarQubePath(Path path) {\n    var pathAsString = path.toString();\n    var sonarQubeSeparatorChar = '/';\n    if (File.separatorChar != sonarQubeSeparatorChar) {\n      return pathAsString.replaceAll(Pattern.quote(File.separator), String.valueOf(sonarQubeSeparatorChar));\n    }\n    return pathAsString;\n  }\n\n  private ServerApiUtils() {\n    // utility class\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/util/package-info.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverapi.util;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-api/src/main/proto/sonarcloud/ws-organizations.proto",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\nsyntax = \"proto2\";\n\npackage sonarcloud.ws.organizations;\n\nimport \"sonarqube/ws-commons.proto\";\n\noption java_package = \"org.sonarsource.sonarlint.core.serverapi.proto.sonarcloud.ws\";\noption java_outer_classname = \"Organizations\";\noption optimize_for = SPEED;\n\n// WS api/organizations/search\nmessage SearchWsResponse {\n  repeated Organization organizations = 1;\n  optional sonarqube.ws.commons.Paging paging = 2;\n}\n\nmessage Organization {\n  optional string key = 1;\n  optional string name = 2;\n  optional string description = 3;\n}\n"
  },
  {
    "path": "backend/server-api/src/main/proto/sonarqube/ws-commons.proto",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\nsyntax = \"proto2\";\n\npackage sonarqube.ws.commons;\n\noption java_package = \"org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws\";\noption java_outer_classname = \"Common\";\noption optimize_for = SPEED;\n\nmessage Paging {\n  optional int32 pageIndex = 1;\n  optional int32 pageSize = 2;\n  optional int32 total = 3;\n}\n\nenum Severity {\n  INFO = 0;\n  MINOR = 1;\n  MAJOR = 2;\n  CRITICAL = 3;\n  BLOCKER = 4;\n}\n\nmessage Rule {\n  optional string key = 1;\n  optional string name = 2;\n  optional string lang = 3;\n  optional string langName = 5;\n}\n\nmessage Rules {\n  repeated Rule rules = 1;\n}\n\nenum CleanCodeAttribute {\n  UNKNOWN_ATTRIBUTE = 0;\n  CONVENTIONAL = 1;\n  FORMATTED = 2;\n  IDENTIFIABLE = 3;\n  CLEAR = 4;\n  COMPLETE = 5;\n  EFFICIENT = 6;\n  LOGICAL = 7;\n  DISTINCT = 8;\n  FOCUSED = 9;\n  MODULAR = 10;\n  TESTED = 11;\n  LAWFUL = 12;\n  RESPECTFUL = 13;\n  TRUSTWORTHY = 14;\n}\n\nenum CleanCodeAttributeCategory {\n  UNKNOWN_CATEGORY = 0;\n  ADAPTABLE = 1;\n  CONSISTENT = 2;\n  INTENTIONAL = 3;\n  RESPONSIBLE = 4;\n}\n\nmessage Impact {\n  required SoftwareQuality softwareQuality = 1;\n  required ImpactSeverity severity = 2;\n}\n\nenum SoftwareQuality {\n  UNKNOWN_IMPACT_QUALITY = 0;\n  MAINTAINABILITY = 1;\n  RELIABILITY = 2;\n  SECURITY = 3;\n}\n\nenum ImpactSeverity {\n  UNKNOWN_IMPACT_SEVERITY = 0;\n  LOW = 1;\n  MEDIUM = 2;\n  HIGH = 3;\n  // INFO and BLOCKER conflicts with Severity enum, so we use different values prefixed with enum name\n  ImpactSeverity_INFO = 4;\n  ImpactSeverity_BLOCKER = 5;\n}\n\n// Lines start at 1 and line offsets start at 0\nmessage TextRange {\n  // Start line. Should never be absent\n  optional int32 startLine = 1;\n\n  // End line (inclusive). Absent means it is same as start line\n  optional int32 endLine = 2;\n\n  // If absent it means range starts at the first offset of start line\n  optional int32 startOffset = 3;\n\n  // If absent it means range ends at the last offset of end line\n  optional int32 endOffset = 4;\n}\n\nmessage Flow {\n  repeated Location locations = 1;\n}\n\nmessage Location {\n  optional string component = 4;\n  optional string unusedComponentId = 1;\n  // Only when component is a file. Can be empty for a file if this is an issue global to the file.\n  optional sonarqube.ws.commons.TextRange textRange = 2;\n  optional string msg = 3;\n}\n\nenum RuleType {\n  // Zero is required in order to not get CODE_SMELL as default value\n  // See http://androiddevblog.com/protocol-buffers-pitfall-adding-enum-values/\n  UNKNOWN = 0;\n\n  // same name as in Java enum IssueType,\n  // same index values as in database (see column ISSUES.ISSUE_TYPE)\n  CODE_SMELL = 1;\n  BUG = 2;\n  VULNERABILITY = 3;\n  SECURITY_HOTSPOT = 4;\n}\n\nenum BranchType {\n  UNKNOWN_BRANCH_TYPE = 0;\n  LONG = 1;\n  SHORT = 2;\n  PULL_REQUEST = 3;\n  BRANCH = 4;\n}\n\nmessage Metric {\n  optional string key = 1;\n  optional string name = 2;\n  optional string description = 3;\n  optional string domain = 4;\n  optional string type = 5;\n  optional bool higherValuesAreBetter = 6;\n  optional bool qualitative = 7;\n  optional bool hidden = 8;\n  optional bool custom = 9;\n  optional int32 decimalScale = 10;\n  optional string bestValue = 11;\n  optional string worstValue = 12;\n}\n"
  },
  {
    "path": "backend/server-api/src/main/proto/sonarqube/ws-components.proto",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\nsyntax = \"proto2\";\n\npackage sonarqube.ws.component;\n\nimport \"sonarqube/ws-commons.proto\";\n\noption java_package = \"org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws\";\noption java_outer_classname = \"Components\";\noption optimize_for = SPEED;\n\n// WS api/components/search\nmessage SearchWsResponse {\n  optional sonarqube.ws.commons.Paging paging = 1;\n  repeated Component components = 2;\n}\n\n// WS api/components/tree\nmessage TreeWsResponse {\n  optional sonarqube.ws.commons.Paging paging = 1;\n  optional Component baseComponent = 3;\n  repeated Component components = 4;\n}\n\n// WS api/components/show\nmessage ShowWsResponse {\n  optional sonarqube.ws.commons.Paging paging = 1;\n  optional Component component = 2;\n  repeated Component ancestors = 3;\n}\n\nmessage Component {\n  optional string key = 2;\n  optional string name = 6;\n  optional bool isAiCodeFixEnabled = 23;\n}\n"
  },
  {
    "path": "backend/server-api/src/main/proto/sonarqube/ws-hotspots.proto",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\nsyntax = \"proto2\";\n\npackage sonarqube.ws.hotspots;\n\nimport \"sonarqube/ws-commons.proto\";\n\noption java_package = \"org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws\";\noption java_outer_classname = \"Hotspots\";\noption optimize_for = SPEED;\n\n// Response of GET api/hotspots/search\nmessage SearchWsResponse {\n    optional sonarqube.ws.commons.Paging paging = 1;\n    repeated Hotspot hotspots = 2;\n    repeated Component components = 3;\n\n    message Hotspot {\n        optional string key = 1;\n        optional string component = 2;\n        optional string project = 3;\n        optional string securityCategory = 4;\n        optional string vulnerabilityProbability = 5;\n        optional string status = 6;\n        optional string resolution = 7;\n        optional int32 line = 8;\n        optional string message = 9;\n        optional string assignee = 10;\n        optional string author = 11;\n        optional string creationDate = 12;\n        optional string updateDate = 13;\n        optional sonarqube.ws.commons.TextRange textRange = 14;\n        repeated sonarqube.ws.commons.Flow flows = 15;\n        optional string ruleKey = 16;\n    }\n}\n\n// Response of GET api/hotspots/show\nmessage ShowWsResponse {\n  optional string key = 1;\n  optional Component component = 2;\n  optional Component project = 3;\n  optional Rule rule = 4;\n  optional string status = 5;\n  optional string resolution = 6;\n  optional string message = 8;\n  optional string author = 10;\n  optional sonarqube.ws.commons.TextRange textRange = 13;\n  optional bool canChangeStatus = 17;\n}\n\nmessage Component {\n  optional string key = 2;\n  optional string path = 6;\n}\n\nmessage Rule {\n  optional string key = 1;\n  optional string name = 2;\n  optional string securityCategory = 3;\n  optional string vulnerabilityProbability = 4;\n  optional string riskDescription = 5 [deprecated=true];\n  optional string vulnerabilityDescription = 6 [deprecated=true];\n  optional string fixRecommendations = 7 [deprecated=true];\n}\n\n// Response of GET api/hotspots/pull\nmessage HotspotPullQueryTimestamp {\n  required int64 queryTimestamp = 1;\n}\n\nmessage HotspotLite {\n  optional string key = 1;\n  optional string filePath = 2;\n  optional string vulnerabilityProbability = 3;\n  optional string status = 4;\n  optional string resolution = 5;\n  optional string message = 6;\n  optional int64 creationDate = 7;\n  optional TextRange textRange = 8;\n  optional string ruleKey = 9;\n  optional bool closed = 10;\n  optional string assignee = 11;\n}\n\nmessage TextRange {\n  optional int32 startLine = 1;\n  optional int32 startLineOffset = 2;\n  optional int32 endLine = 3;\n  optional int32 endLineOffset = 4;\n  optional string hash = 5;\n}\n"
  },
  {
    "path": "backend/server-api/src/main/proto/sonarqube/ws-issues.proto",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\nsyntax = \"proto2\";\n\npackage sonarqube.ws.issues;\n\nimport \"sonarqube/ws-commons.proto\";\n\noption java_package = \"org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws\";\noption java_outer_classname = \"Issues\";\noption optimize_for = SPEED;\n\n// Response of GET api/issues/search\nmessage SearchWsResponse {\n  optional sonarqube.ws.commons.Paging paging = 4;\n\n  repeated Issue issues = 6;\n  repeated Component components = 7;\n  optional sonarqube.ws.commons.Rules rules = 8;\n}\n\nmessage Issue {\n  optional string key = 1;\n  optional string rule = 2;\n  optional sonarqube.ws.commons.Severity severity = 3;\n  optional string component = 4;\n  optional int32 line = 8;\n  optional string hash = 31;\n  optional sonarqube.ws.commons.TextRange textRange = 9;\n  repeated sonarqube.ws.commons.Flow flows = 10;\n  optional string resolution = 11;\n  optional string status = 12;\n  optional string message = 13;\n  optional string assignee = 15;\n  // the transitions allowed for the requesting user.\n  optional Transitions transitions = 20;\n  optional string creationDate = 23;\n  optional sonarqube.ws.commons.RuleType type = 27;\n  optional string ruleDescriptionContextKey = 37;\n  // skipping unused fields 38-39\n  optional sonarqube.ws.commons.CleanCodeAttribute cleanCodeAttribute = 40;\n  // skipping unused field cleanCodeAttributeCategory = 41;\n  repeated sonarqube.ws.commons.Impact impacts = 42;\n\n}\n\nmessage Transitions {\n  repeated string transitions = 1;\n}\n\nmessage Component {\n  optional string key = 2;\n  optional string path = 8;\n}\n\n// Response of GET api/issues/pull\nmessage IssuesPullQueryTimestamp {\n  required int64 queryTimestamp = 1;\n}\n\nmessage TextRange {\n  optional int32 startLine = 1;\n  optional int32 startLineOffset = 2;\n  optional int32 endLine = 3;\n  optional int32 endLineOffset = 4;\n  optional string hash = 5;\n}\n\nmessage Location {\n  optional string filePath = 1;\n  optional string message = 2;\n  optional TextRange textRange = 3;\n}\n\nmessage IssueLite {\n  required string key = 1;\n  optional int64 creationDate = 2;\n  optional bool resolved = 3;\n  optional string ruleKey = 4;\n  optional sonarqube.ws.commons.Severity userSeverity = 5;\n  optional sonarqube.ws.commons.RuleType type = 6;\n  optional Location mainLocation = 7;\n  optional bool closed = 8;\n  repeated sonarqube.ws.commons.Impact impacts = 9;\n}\n\n// Response of GET api/issues/pull_taint\nmessage TaintVulnerabilityPullQueryTimestamp {\n  required int64 queryTimestamp = 1;\n}\n\n\nmessage TaintVulnerabilityLite {\n  required string key = 1;\n  optional int64 creationDate = 2;\n  optional bool resolved = 3;\n  optional string ruleKey = 4;\n  optional sonarqube.ws.commons.Severity severity = 5;\n  optional sonarqube.ws.commons.RuleType type = 6;\n  optional Location mainLocation = 7;\n  optional bool closed = 8;\n  repeated Flow flows = 9;\n  optional bool assignedToSubscribedUser = 10;\n  optional string ruleDescriptionContextKey = 11;\n  optional sonarqube.ws.commons.CleanCodeAttribute cleanCodeAttribute = 12;\n  // skipping unused field cleanCodeAttributeCategory = 13;\n  repeated sonarqube.ws.commons.Impact impacts = 14;\n}\n\nmessage Flow {\n  repeated Location locations = 1;\n}\n"
  },
  {
    "path": "backend/server-api/src/main/proto/sonarqube/ws-measures.proto",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\nsyntax = \"proto2\";\n\npackage sonarqube.ws.measures;\n\nimport \"sonarqube/ws-commons.proto\";\n\noption java_package = \"org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws\";\noption java_outer_classname = \"Measures\";\noption optimize_for = SPEED;\n\n\n// WS api/measures/component\nmessage ComponentWsResponse {\n  optional Component component = 1;\n  optional Metrics metrics = 2;\n  optional Periods periods = 3;\n  optional Period period = 4;\n}\n\n\nmessage Component {\n  reserved 1,3;\n  optional string key = 2;\n  optional string refKey = 4;\n  optional string projectId = 5;\n  optional string name = 6;\n  optional string description = 7;\n  optional string qualifier = 8;\n  optional string path = 9;\n  optional string language = 10;\n  repeated Measure measures = 11;\n  optional string branch = 12;\n  optional string pullRequest = 13;\n}\n\nmessage Period {\n  //deprecated since 8.1\n  optional int32 index = 1;\n  optional string mode = 2;\n  optional string date = 3;\n  optional string parameter = 4;\n}\n\nmessage Periods {\n  repeated Period periods = 1;\n}\n\nmessage Metrics {\n  repeated sonarqube.ws.commons.Metric metrics = 1;\n}\n\nmessage Measure {\n  optional string metric = 1;\n  optional string value = 2;\n  reserved 3; // periods\n  optional string component = 4;\n  optional bool bestValue = 5;\n  optional PeriodValue period = 6;\n}\n\n\nmessage PeriodValue {\n  //deprecated since 8.1\n  optional int32 index = 1;\n  optional string value = 2;\n  optional bool bestValue = 3;\n}\n"
  },
  {
    "path": "backend/server-api/src/main/proto/sonarqube/ws-projectbranches.proto",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\nsyntax = \"proto2\";\n\npackage sonarqube.ws.projectbranch;\n\noption java_package = \"org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws\";\noption java_outer_classname = \"ProjectBranches\";\noption optimize_for = SPEED;\n\nimport \"sonarqube/ws-commons.proto\";\n\n// WS api/project_branches/list\nmessage ListWsResponse {\n  repeated Branch branches = 1;\n}\n\nmessage Branch {\n  optional string name = 1;\n  optional bool isMain = 2;\n  optional sonarqube.ws.commons.BranchType type = 3;\n}\n"
  },
  {
    "path": "backend/server-api/src/main/proto/sonarqube/ws-qualityprofiles.proto",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\nsyntax = \"proto2\";\n\npackage sonarqube.ws.qualityprofiles;\n\noption java_package = \"org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws\";\noption java_outer_classname = \"Qualityprofiles\";\noption optimize_for = SPEED;\n\n// WS api/qualityprofiles/search\nmessage SearchWsResponse {\n  repeated QualityProfile profiles = 1;\n\n  message QualityProfile {\n    optional string key = 1;\n    optional string name = 2;\n    optional string language = 3;\n    optional string languageName = 4;\n    optional bool isDefault = 8;\n    optional int64 activeRuleCount = 9;\n    optional string rulesUpdatedAt = 11;\n    optional string userUpdatedAt = 14;\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/main/proto/sonarqube/ws-rules.proto",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\nsyntax = \"proto2\";\n\npackage sonarqube.ws.rules;\n\nimport \"sonarqube/ws-commons.proto\";\n\noption java_package = \"org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws\";\noption java_outer_classname = \"Rules\";\noption optimize_for = SPEED;\n\n// WS api/rules/search\nmessage SearchResponse {\n  optional int64 total = 1 [deprecated = true]; // Deprecated since 9.8\n  optional int32 p = 2 [deprecated = true]; // Deprecated since 9.8\n  optional int64 ps = 3 [deprecated = true]; // Deprecated since 9.8\n  repeated Rule rules = 4;\n  optional Actives actives = 5;\n  optional sonarqube.ws.commons.Paging paging = 8; // Added in 9.8\n}\n\n//WS api/rules/show\nmessage ShowResponse {\n  optional Rule rule = 1;\n}\n\nmessage Rule {\n  optional string key = 1;\n  optional string repo = 2;\n  optional string name = 3;\n  optional string htmlDesc = 5 [deprecated=true];\n  optional string htmlNote = 6;\n  optional string severity = 10 [deprecated=true];\n  optional string templateKey = 14;\n  optional string lang = 19;\n\n  optional sonarqube.ws.commons.RuleType type = 37 [deprecated=true];\n  optional DescriptionSections descriptionSections = 49;\n  optional EducationPrinciples educationPrinciples = 50;\n  optional sonarqube.ws.commons.CleanCodeAttribute cleanCodeAttribute = 52;\n  optional Impacts impacts = 54;\n\n  message Impacts {\n    repeated sonarqube.ws.commons.Impact impacts = 1;\n  }\n\n  message DescriptionSections {\n    repeated DescriptionSection descriptionSections = 1;\n  }\n\n  message DescriptionSection {\n    required string key = 1;\n    required string content = 2;\n    optional Context context = 3;\n\n    message Context {\n      required string displayName = 1;\n      required string key = 2;\n    }\n  }\n\n  message EducationPrinciples {\n    repeated string educationPrinciples = 1;\n  }\n\n}\n\nmessage Actives {\n  map<string, ActiveList> actives = 1;\n}\n\nmessage ActiveList {\n  repeated Active activeList = 1;\n}\n\nmessage Active {\n  optional string qProfile = 1;\n  optional string severity = 3;\n  repeated Param params = 5;\n  optional Impacts impacts = 9;\n\n  message Param {\n    optional string key = 1;\n    optional string value = 2;\n  }\n\n  message Impacts {\n    repeated sonarqube.ws.commons.Impact impacts = 1;\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/main/proto/sonarqube/ws-settings.proto",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\nsyntax = \"proto3\";\n\npackage sonarqube.ws.settings;\n\noption java_package = \"org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws\";\noption java_outer_classname = \"Settings\";\noption optimize_for = SPEED;\n\n// Response of GET api/settings/values\nmessage ValuesWsResponse {\n  repeated Setting settings = 1;\n}\n\nmessage Setting {\n  string key = 1;\n  oneof valueOneOf {\n    string value = 2;\n    Values values = 3;\n    FieldValues fieldValues = 4;\n  }\n}\n\nmessage Values {\n  repeated string values = 1;\n}\n\nmessage FieldValues {\n  repeated Value fieldValues = 1;\n\n  message Value {\n    map<string, string> value = 1;\n  }\n}\n\n\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/MockWebServerExtensionWithProtobuf.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi;\n\nimport com.google.protobuf.Message;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.Arrays;\nimport java.util.Iterator;\nimport javax.annotation.Nullable;\nimport mockwebserver3.MockResponse;\nimport okio.Buffer;\nimport org.sonarsource.sonarlint.core.commons.testutils.MockWebServerExtension;\nimport org.sonarsource.sonarlint.core.http.HttpClientProvider;\n\nimport static org.junit.jupiter.api.Assertions.fail;\n\npublic class MockWebServerExtensionWithProtobuf extends MockWebServerExtension {\n\n  public void addProtobufResponse(String path, Message m) {\n    try (var b = new Buffer()) {\n      m.writeTo(b.outputStream());\n      responsesByPath.put(path, new MockResponse.Builder().body(b).build());\n    } catch (IOException e) {\n      fail(e);\n    }\n  }\n\n  public void addProtobufResponseDelimited(String path, Message... m) {\n    try (var b = new Buffer()) {\n      writeMessages(b.outputStream(), Arrays.asList(m).iterator());\n      responsesByPath.put(path, new MockResponse.Builder().body(b).build());\n    }\n  }\n\n  public static <T extends Message> void writeMessages(OutputStream output, Iterator<T> messages) {\n    while (messages.hasNext()) {\n      writeMessage(output, messages.next());\n    }\n  }\n\n  public static <T extends Message> void writeMessage(OutputStream output, T message) {\n    try {\n      message.writeDelimitedTo(output);\n    } catch (IOException e) {\n      throw new IllegalStateException(\"failed to write message: \" + message, e);\n    }\n  }\n\n  public ServerApiHelper serverApiHelper() {\n    return serverApiHelper(null);\n  }\n\n  public ServerApiHelper serverApiHelper(@Nullable String organizationKey) {\n    return new ServerApiHelper(endpointParams(organizationKey), HttpClientProvider.forTesting().getHttpClientWithoutAuth());\n  }\n\n  public EndpointParams endpointParams() {\n    return endpointParams(null);\n  }\n\n  public EndpointParams endpointParams(@Nullable String organizationKey) {\n    return new EndpointParams(url(\"/\"), url(\"/\"), organizationKey != null, organizationKey);\n  }\n\n\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/ServerApiHelperTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi;\n\nimport java.net.HttpURLConnection;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.http.HttpClient;\nimport org.sonarsource.sonarlint.core.serverapi.exception.ForbiddenException;\nimport org.sonarsource.sonarlint.core.serverapi.exception.NotFoundException;\nimport org.sonarsource.sonarlint.core.serverapi.exception.ServerErrorException;\nimport org.sonarsource.sonarlint.core.serverapi.exception.TooManyRequestsException;\nimport org.sonarsource.sonarlint.core.serverapi.exception.UnauthorizedException;\nimport org.sonarsource.sonarlint.core.serverapi.exception.UnexpectedServerResponseException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass ServerApiHelperTests {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @Test\n  void concat_should_handle_base_url_with_trailing_slash() {\n    var result = ServerApiHelper.concat(\"http://localhost:9000/\", \"/api/test\");\n    \n    assertThat(result).isEqualTo(\"http://localhost:9000/api/test\");\n  }\n\n  @Test\n  void concat_should_handle_base_url_without_trailing_slash() {\n    var result = ServerApiHelper.concat(\"http://localhost:9000\", \"/api/test\");\n    \n    assertThat(result).isEqualTo(\"http://localhost:9000/api/test\");\n  }\n\n  @Test\n  void concat_should_handle_relative_path_without_leading_slash() {\n    var result = ServerApiHelper.concat(\"http://localhost:9000\", \"api/test\");\n    \n    assertThat(result).isEqualTo(\"http://localhost:9000/api/test\");\n  }\n\n  @Test\n  void concat_should_handle_empty_relative_path() {\n    var result = ServerApiHelper.concat(\"http://localhost:9000\", \"\");\n    \n    assertThat(result).isEqualTo(\"http://localhost:9000/\");\n  }\n\n  @Test\n  void handleError_should_throw_unauthorized_exception() {\n    var response = mock(HttpClient.Response.class);\n    when(response.code()).thenReturn(HttpURLConnection.HTTP_UNAUTHORIZED);\n\n    var error = ServerApiHelper.handleError(response);\n    \n    assertThat(error)\n      .isInstanceOf(UnauthorizedException.class)\n      .hasMessage(\"Not authorized. Please check server credentials.\");\n  }\n\n  @Test\n  void handleError_should_throw_forbidden_exception() {\n    var response = mock(HttpClient.Response.class);\n    when(response.code()).thenReturn(HttpURLConnection.HTTP_FORBIDDEN);\n    when(response.bodyAsString()).thenReturn(\"{\\\"errors\\\":[{\\\"msg\\\":\\\"Access denied\\\"}]}\");\n\n    var error = ServerApiHelper.handleError(response);\n\n    assertThat(error)\n      .isInstanceOf(ForbiddenException.class)\n      .hasMessage(\"Access denied\");\n  }\n\n  @Test\n  void handleError_should_throw_forbidden_exception_with_default_message() {\n    var response = mock(HttpClient.Response.class);\n    when(response.code()).thenReturn(HttpURLConnection.HTTP_FORBIDDEN);\n    when(response.bodyAsString()).thenReturn(\"{}\");\n\n    var error = ServerApiHelper.handleError(response);\n    \n    assertThat(error)\n      .isInstanceOf(ForbiddenException.class)\n      .hasMessage(\"Access denied\");\n  }\n\n  @Test\n  void handleError_should_throw_not_found_exception() {\n    var response = mock(HttpClient.Response.class);\n    when(response.code()).thenReturn(HttpURLConnection.HTTP_NOT_FOUND);\n    when(response.url()).thenReturn(\"http://localhost:9000/api/test\");\n\n    var error = ServerApiHelper.handleError(response);\n    \n    assertThat(error).isInstanceOf(NotFoundException.class);\n  }\n\n  @Test\n  void handleError_should_throw_server_error_exception() {\n    var response = mock(HttpClient.Response.class);\n    when(response.code()).thenReturn(HttpURLConnection.HTTP_INTERNAL_ERROR);\n    when(response.url()).thenReturn(\"http://localhost:9000/api/test\");\n\n    var error = ServerApiHelper.handleError(response);\n    \n    assertThat(error).isInstanceOf(ServerErrorException.class);\n  }\n\n  @Test\n  void handleError_should_throw_too_many_requests_exception() {\n    var response = mock(HttpClient.Response.class);\n    when(response.code()).thenReturn(ServerApiHelper.HTTP_TOO_MANY_REQUESTS);\n\n    var error = ServerApiHelper.handleError(response);\n    \n    assertThat(error)\n      .isInstanceOf(TooManyRequestsException.class)\n      .hasMessage(\"Too many requests have been made.\");\n  }\n\n  @Test\n  void handleError_should_throw_illegal_state_exception_for_other_codes() {\n    var response = mock(HttpClient.Response.class);\n    when(response.code()).thenReturn(HttpURLConnection.HTTP_BAD_REQUEST);\n    when(response.url()).thenReturn(\"http://localhost:9000/api/test\");\n    when(response.bodyAsString()).thenReturn(\"{\\\"errors\\\":[{\\\"msg\\\":\\\"Bad request\\\"}]}\");\n\n    var error = ServerApiHelper.handleError(response);\n    \n    assertThat(error)\n      .isInstanceOf(UnexpectedServerResponseException.class)\n      .hasMessageContaining(\"Error 400 on http://localhost:9000/api/test: Bad request\");\n  }\n\n  @Test\n  void handleError_should_throw_unexpected_response_body_exception_when_error_body_unexpected() {\n    var response = mock(HttpClient.Response.class);\n    when(response.code()).thenReturn(HttpURLConnection.HTTP_BAD_REQUEST);\n    when(response.url()).thenReturn(\"http://localhost:9000/api/test\");\n    when(response.bodyAsString()).thenReturn(\"not json\");\n\n    var error = ServerApiHelper.handleError(response);\n\n    assertThat(error)\n      .isInstanceOf(UnexpectedServerResponseException.class)\n      .hasMessageContaining(\"Error 400 on http://localhost:9000/api/test\");\n  }\n\n} \n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/authentication/AuthenticationApiTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.authentication;\n\nimport mockwebserver3.MockResponse;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.MockWebServerExtensionWithProtobuf;\nimport org.sonarsource.sonarlint.core.serverapi.exception.ServerErrorException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\n\nclass AuthenticationApiTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n  private AuthenticationApi underTest;\n\n  @BeforeEach\n  void setUp() {\n    underTest = new AuthenticationApi(mockServer.serverApiHelper());\n  }\n\n  @Test\n  void test_authentication_ok() {\n    mockServer.addStringResponse(\"/api/authentication/validate?format=json\", \"{\\\"valid\\\": true}\");\n\n    var validationResult = underTest.validate(new SonarLintCancelMonitor());\n\n    assertThat(validationResult.success()).isTrue();\n    assertThat(validationResult.message()).isEqualTo(\"Authentication successful\");\n  }\n\n  @Test\n  void test_authentication_ko() {\n    mockServer.addStringResponse(\"/api/authentication/validate?format=json\", \"{\\\"valid\\\": false}\");\n\n    var validationResult = underTest.validate(new SonarLintCancelMonitor());\n\n    assertThat(validationResult.success()).isFalse();\n    assertThat(validationResult.message()).isEqualTo(\"Authentication failed\");\n  }\n\n  @Test\n  void test_connection_issue() {\n    mockServer.addResponse(\"/api/authentication/validate?format=json\", new MockResponse.Builder().code(500).body(\"Foo\").build());\n\n    var throwable = catchThrowable(() -> underTest.validate(new SonarLintCancelMonitor()));\n\n    assertThat(throwable).isInstanceOf(ServerErrorException.class);\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/branches/ProjectBranchesApiTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.branches;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.MockWebServerExtensionWithProtobuf;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common.BranchType;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.ProjectBranches;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\n\nclass ProjectBranchesApiTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n\n  private final static String PROJECT_KEY = \"project1\";\n\n  private ProjectBranchesApi underTest;\n\n  @BeforeEach\n  void setUp() {\n    underTest = new ProjectBranchesApi(mockServer.serverApiHelper());\n  }\n\n  @Test\n  void shouldDownloadBranches() {\n    mockServer.addProtobufResponse(\"/api/project_branches/list.protobuf?project=\" + PROJECT_KEY, ProjectBranches.ListWsResponse.newBuilder()\n      .addBranches(ProjectBranches.Branch.newBuilder().setName(\"feature/foo\").setIsMain(false).setType(BranchType.BRANCH))\n      .addBranches(ProjectBranches.Branch.newBuilder().setName(\"master\").setIsMain(true).setType(BranchType.BRANCH)).build());\n\n    var branches = underTest.getAllBranches(PROJECT_KEY, new SonarLintCancelMonitor());\n\n    assertThat(branches).extracting(ServerBranch::getName, ServerBranch::isMain).containsExactlyInAnyOrder(tuple(\"master\", true), tuple(\"feature/foo\", false));\n  }\n\n  @Test\n  void shouldSkipShortLivingBranches() {\n    var branchListResponseBuilder = ProjectBranches.ListWsResponse.newBuilder();\n    branchListResponseBuilder.addBranches(ProjectBranches.Branch.newBuilder().setName(\"branch-1.x\").setIsMain(false).setType(BranchType.BRANCH));\n    branchListResponseBuilder.addBranches(ProjectBranches.Branch.newBuilder().setName(\"master\").setIsMain(true).setType(BranchType.BRANCH));\n    branchListResponseBuilder.addBranches(ProjectBranches.Branch.newBuilder().setName(\"feature/my-long-branch\").setIsMain(false).setType(BranchType.LONG));\n    branchListResponseBuilder.addBranches(ProjectBranches.Branch.newBuilder().setName(\"feature/my-short-branch\").setIsMain(false).setType(BranchType.SHORT));\n\n    mockServer.addProtobufResponse(\"/api/project_branches/list.protobuf?project=\" + PROJECT_KEY, branchListResponseBuilder.build());\n\n    var branches = underTest.getAllBranches(PROJECT_KEY, new SonarLintCancelMonitor());\n\n    assertThat(branches).extracting(ServerBranch::getName, ServerBranch::isMain)\n      .containsExactlyInAnyOrder(tuple(\"master\", true), tuple(\"branch-1.x\", false),\n        tuple(\"feature/my-long-branch\", false));\n  }\n\n  @Test\n  void shouldReturnEmptyListOnMalformedResponse() {\n    mockServer.addStringResponse(\"/api/project_branches/list.protobuf?project=project1\",\n      \"\"\"\n        {\n          \"branches\": [\n            { }\\\n          ]\n        }\"\"\");\n\n    var branches = underTest.getAllBranches(PROJECT_KEY, new SonarLintCancelMonitor());\n\n    assertThat(branches).isEmpty();\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/branches/ServerBranchTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.branches;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThat;\n\nclass ServerBranchTests {\n\n  @Test\n  void serverBranchTest() {\n    ServerBranch branch = new ServerBranch(\"foo\", true);\n\n    assertThat(branch.getName()).isEqualTo(\"foo\");\n    assertThat(branch.isMain()).isTrue();\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/component/ComponentApiTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.component;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.MockWebServerExtensionWithProtobuf;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Components;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\n\nclass ComponentApiTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n\n  private static final String PROJECT_KEY = \"project1\";\n\n  private ComponentApi underTest;\n\n  @BeforeEach\n  void setUp() {\n    underTest = new ComponentApi(mockServer.serverApiHelper());\n  }\n\n  @Test\n  void should_return_empty_when_no_components_returned() {\n    mockServer.addStringResponse(\"/api/components/search_projects?projectIds=project%3Akey\",\n      \"{\\\"components\\\":[]}\");\n\n    var result = underTest.searchProjects(\"project:key\", new SonarLintCancelMonitor());\n\n    assertThat(result).isNull();\n  }\n\n  @Test\n  void should_return_empty_when_response_is_invalid_json() {\n    mockServer.addStringResponse(\"/api/components/search_projects?projectIds=project%3Akey\",\n      \"invalid json\");\n\n    var result = underTest.searchProjects(\"project:key\", new SonarLintCancelMonitor());\n\n    assertThat(result).isNull();\n  }\n\n  @Test\n  void should_get_project_key_by_project_id() {\n    var projectId = \"project:key\";\n    var encodedProjectId = \"project%3Akey\";\n    var organization = \"my-org\";\n    underTest = new ComponentApi(mockServer.serverApiHelper(organization));\n\n    mockServer.addStringResponse(\"/api/components/search_projects?projectIds=\" + encodedProjectId + \"&organization=\" + organization,\n      \"{\\\"components\\\":[{\\\"key\\\":\\\"projectKey\\\",\\\"name\\\":\\\"projectName\\\"}]}\\n\");\n\n    var result = underTest.searchProjects(projectId, new SonarLintCancelMonitor());\n\n    assertThat(result.projectKey()).isEqualTo(\"projectKey\");\n    assertThat(result.projectName()).isEqualTo(\"projectName\");\n  }\n\n  @Test\n  void should_return_empty_if_project_not_found() {\n    var result = underTest.searchProjects(\"project:key\", new SonarLintCancelMonitor());\n\n    assertThat(result).isNull();\n  }\n\n  @Test\n  void should_get_files() {\n    mockServer.addResponseFromResource(\"/api/components/tree.protobuf?qualifiers=FIL,UTS&component=project1&ps=500&p=1\", \"/update/component_tree.pb\");\n\n    var files = underTest.getAllFileKeys(PROJECT_KEY, new SonarLintCancelMonitor());\n\n    assertThat(files).hasSize(187);\n    assertThat(files.get(0)).isEqualTo(\"org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/AbstractIssuesPanel.java\");\n  }\n\n  @Test\n  void should_get_files_with_organization() {\n    underTest = new ComponentApi(mockServer.serverApiHelper(\"myorg\"));\n    mockServer.addResponseFromResource(\"/api/components/tree.protobuf?qualifiers=FIL,UTS&component=project1&organization=myorg&ps=500&p=1\", \"/update/component_tree.pb\");\n\n    var files = underTest.getAllFileKeys(PROJECT_KEY, new SonarLintCancelMonitor());\n\n    assertThat(files).hasSize(187);\n    assertThat(files.get(0)).isEqualTo(\"org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/AbstractIssuesPanel.java\");\n  }\n\n  @Test\n  void should_get_empty_files_if_tree_is_empty() {\n    mockServer.addResponseFromResource(\"/api/components/tree.protobuf?qualifiers=FIL,UTS&component=project1&ps=500&p=1\", \"/update/empty_component_tree.pb\");\n\n    var files = underTest.getAllFileKeys(PROJECT_KEY, new SonarLintCancelMonitor());\n\n    assertThat(files).isEmpty();\n  }\n\n  @Test\n  void should_get_all_projects() {\n    mockServer.addProtobufResponse(\"/api/components/search.protobuf?qualifiers=TRK&ps=500&p=1\", Components.SearchWsResponse.newBuilder()\n      .addComponents(Components.Component.newBuilder().setKey(\"projectKey\").setName(\"projectName\").build()).build());\n    mockServer.addProtobufResponse(\"/api/components/search.protobuf?qualifiers=TRK&ps=500&p=2\", Components.SearchWsResponse.newBuilder().build());\n\n    var projects = underTest.getAllProjects(new SonarLintCancelMonitor());\n\n    assertThat(projects)\n      .extracting(\"key\", \"name\")\n      .containsOnly(tuple(\"projectKey\", \"projectName\"));\n  }\n\n  @Test\n  void should_get_all_projects_with_organization() {\n    mockServer.addProtobufResponse(\"/api/components/search.protobuf?qualifiers=TRK&organization=org%3Akey&ps=500&p=1\", Components.SearchWsResponse.newBuilder()\n      .addComponents(Components.Component.newBuilder().setKey(\"projectKey\").setName(\"projectName\").build()).build());\n    mockServer.addProtobufResponse(\"/api/components/search.protobuf?qualifiers=TRK&organization=org%3Akey&ps=500&p=2\", Components.SearchWsResponse.newBuilder().build());\n    var componentApi = new ComponentApi(mockServer.serverApiHelper(\"org:key\"));\n\n    var projects = componentApi.getAllProjects(new SonarLintCancelMonitor());\n\n    assertThat(projects)\n      .extracting(\"key\", \"name\")\n      .containsOnly(tuple(\"projectKey\", \"projectName\"));\n  }\n\n  @Test\n  void should_get_project_details() {\n    mockServer.addProtobufResponse(\"/api/components/show.protobuf?component=project%3Akey\", Components.ShowWsResponse.newBuilder()\n      .setComponent(Components.Component.newBuilder().setKey(\"projectKey\").setName(\"projectName\").build()).build());\n\n    var project = underTest.getProject(\"project:key\", new SonarLintCancelMonitor());\n\n    assertThat(project).hasValueSatisfying(p -> {\n      assertThat(p.key()).isEqualTo(\"projectKey\");\n      assertThat(p.name()).isEqualTo(\"projectName\");\n    });\n  }\n\n  @Test\n  void should_get_empty_project_details_if_request_fails() {\n    var project = underTest.getProject(\"project:key\", new SonarLintCancelMonitor());\n\n    assertThat(project).isEmpty();\n  }\n\n  @Test\n  void should_get_ancestor_key() {\n    mockServer.addProtobufResponse(\"/api/components/show.protobuf?component=project%3Akey\", Components.ShowWsResponse.newBuilder()\n      .addAncestors(Components.Component.newBuilder().setKey(\"ancestorKey\").build()).build());\n\n    var project = underTest.fetchFirstAncestorKey(\"project:key\", new SonarLintCancelMonitor());\n\n    assertThat(project).contains(\"ancestorKey\");\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/component/ServerProjectTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.component;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ServerProjectTests {\n  @Test\n  void testGetters() {\n    ServerProject project = new ServerProject(\"key\", \"name\", false);\n\n    assertThat(project.key()).isEqualTo(\"key\");\n    assertThat(project.name()).isEqualTo(\"name\");\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/developers/DevelopersApiTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.developers;\n\nimport java.time.ZonedDateTime;\nimport java.util.Map;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.MockWebServerExtensionWithProtobuf;\nimport org.sonarsource.sonarlint.core.serverapi.exception.NotFoundException;\nimport org.sonarsource.sonarlint.core.serverapi.exception.UnexpectedBodyException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\nimport static org.assertj.core.api.Assertions.tuple;\n\nclass DevelopersApiTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n\n  private DevelopersApi underTest;\n\n  @BeforeEach\n  void setUp() {\n    underTest = new DevelopersApi(mockServer.serverApiHelper());\n  }\n\n  @Test\n  void should_return_events_for_a_given_project_key() {\n    mockServer.addStringResponse(\"/api/developers/search_events?projects=projectKey&from=2022-01-01T12%3A00%3A00%2B0000\", \"{\\\"events\\\": [\" +\n      \"{\" +\n      \"\\\"category\\\": \\\"cat\\\",\" +\n      \"\\\"message\\\": \\\"msg\\\",\" +\n      \"\\\"link\\\": \\\"lnk\\\",\" +\n      \"\\\"project\\\": \\\"projectKey\\\",\" +\n      \"\\\"date\\\": \\\"2022-01-01T08:00:00+0000\\\"\" +\n      \"}\" +\n      \"]\" +\n      \"}\");\n\n    var response = underTest.searchEvents(Map.of(\"projectKey\", ZonedDateTime.parse(\"2022-01-01T12:00:00Z\")), new SonarLintCancelMonitor());\n\n    assertThat(response.events())\n      .extracting(\"category\", \"message\", \"link\", \"project\", \"date\")\n      .containsOnly(tuple(\"cat\", \"msg\", \"lnk\", \"projectKey\", ZonedDateTime.parse(\"2022-01-01T08:00:00Z\")));\n  }\n\n  @Test\n  void should_throw_if_a_field_is_missing_in_one_of_them() {\n    mockServer.addStringResponse(\"/api/developers/search_events?projects=projectKey&from=2022-01-01T12%3A00%3A00%2B0000\", \"{\\\"events\\\": [\" +\n      \"{\" +\n      \"\\\"message\\\": \\\"msg\\\",\" +\n      \"\\\"link\\\": \\\"lnk\\\",\" +\n      \"\\\"project\\\": \\\"projectKey\\\",\" +\n      \"\\\"date\\\": \\\"2022-01-01T08:00:00+0000\\\"\" +\n      \"}\" +\n      \"]\" +\n      \"}\");\n\n    var throwable = catchThrowable(() -> underTest.searchEvents(Map.of(\"projectKey\", ZonedDateTime.parse(\"2022-01-01T12:00:00Z\")), new SonarLintCancelMonitor()));\n\n    assertThat(throwable).isInstanceOf(UnexpectedBodyException.class);\n  }\n\n  @Test\n  void should_throw_if_the_request_fails() {\n    var throwable = catchThrowable(() -> underTest.searchEvents(Map.of(\"projectKey\", ZonedDateTime.parse(\"2022-01-01T12:00:00Z\")), new SonarLintCancelMonitor()));\n\n    assertThat(throwable).isInstanceOf(NotFoundException.class);\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/exception/ProjectNotFoundExceptionTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.exception;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ProjectNotFoundExceptionTests {\n\n  @Test\n  void show_organization_key() {\n    var ex = new ProjectNotFoundException(\"module\", \"organization\");\n    assertThat(ex.getMessage()).isEqualTo(\"Project with key 'module' in organization 'organization' not found on SonarQube Cloud (was it deleted?)\");\n  }\n\n  @Test\n  void organization_key_missing() {\n    var ex = new ProjectNotFoundException(\"module\", null);\n    assertThat(ex.getMessage()).isEqualTo(\"Project with key 'module' not found on your SonarQube Server instance (was it deleted?)\");\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/fixsuggestions/FixSuggestionsApiTest.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.fixsuggestions;\n\nimport java.util.List;\nimport java.util.Set;\nimport java.util.UUID;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.MockWebServerExtensionWithProtobuf;\nimport org.sonarsource.sonarlint.core.serverapi.exception.UnexpectedBodyException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\n\nclass FixSuggestionsApiTest {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n\n  private FixSuggestionsApi underTest;\n\n  @BeforeEach\n  void setUp() {\n    underTest = new FixSuggestionsApi(mockServer.serverApiHelper());\n  }\n\n  @Nested\n  class GetAiSuggestion {\n\n    @Test\n    void it_should_throw_an_exception_if_the_body_is_malformed() {\n      mockServer.addStringResponse(\"/api/v2/fix-suggestions/ai-suggestions\", \"\"\"\n        {\n          \"id\": \"XXX\n        }\n        \"\"\");\n\n      var throwable = catchThrowable(() -> underTest.getAiSuggestion(\n        new AiSuggestionRequestBodyDto(\"orgKey\", \"projectKey\", new AiSuggestionRequestBodyDto.Issue(\"message\", 0, 0, \"rule:key\", \"source\")), new SonarLintCancelMonitor()));\n\n      assertThat(throwable).isInstanceOf(UnexpectedBodyException.class);\n    }\n\n    @Test\n    void it_should_return_the_generated_suggestion_for_sonarqube_cloud() {\n      mockServer.addStringResponse(\"/fix-suggestions/ai-suggestions\", \"\"\"\n        {\n          \"id\": \"9d4e18f6-f79f-41ad-a480-1c96bd58d58f\",\n          \"explanation\": \"This is the way\",\n          \"changes\": [\n            {\n              \"startLine\": 0,\n              \"endLine\": 0,\n              \"newCode\": \"This is the new code\"\n            }\n          ]\n        }\n        \"\"\");\n      underTest = new FixSuggestionsApi(mockServer.serverApiHelper(\"orgKey\"));\n\n      var response = underTest.getAiSuggestion(new AiSuggestionRequestBodyDto(\"orgKey\", \"projectKey\", new AiSuggestionRequestBodyDto.Issue(\"message\", 0, 0, \"rule:key\", \"source\")),\n        new SonarLintCancelMonitor());\n\n      assertThat(response)\n        .isEqualTo(new AiSuggestionResponseBodyDto(UUID.fromString(\"9d4e18f6-f79f-41ad-a480-1c96bd58d58f\"), \"This is the way\",\n          List.of(new AiSuggestionResponseBodyDto.ChangeDto(0, 0, \"This is the new code\"))));\n    }\n\n    @Test\n    void it_should_return_the_generated_suggestion_for_sonarqube_server() {\n      mockServer.addStringResponse(\"/api/v2/fix-suggestions/ai-suggestions\", \"\"\"\n        {\n          \"id\": \"9d4e18f6-f79f-41ad-a480-1c96bd58d58f\",\n          \"explanation\": \"This is the way\",\n          \"changes\": [\n            {\n              \"startLine\": 0,\n              \"endLine\": 0,\n              \"newCode\": \"This is the new code\"\n            }\n          ]\n        }\n        \"\"\");\n\n      var response = underTest.getAiSuggestion(new AiSuggestionRequestBodyDto(\"orgKey\", \"projectKey\", new AiSuggestionRequestBodyDto.Issue(\"message\", 0, 0, \"rule:key\", \"source\")),\n        new SonarLintCancelMonitor());\n\n      assertThat(response)\n        .isEqualTo(new AiSuggestionResponseBodyDto(UUID.fromString(\"9d4e18f6-f79f-41ad-a480-1c96bd58d58f\"), \"This is the way\",\n          List.of(new AiSuggestionResponseBodyDto.ChangeDto(0, 0, \"This is the new code\"))));\n    }\n  }\n\n  @Nested\n  class GetSupportedRules {\n\n    @Test\n    void it_should_throw_an_exception_if_the_body_is_malformed() {\n      mockServer.addStringResponse(\"/api/v2/fix-suggestions/supported-rules\", \"\"\"\n        [\n        \"\"\");\n\n      var throwable = catchThrowable(() -> underTest.getSupportedRules(new SonarLintCancelMonitor()));\n\n      assertThat(throwable).isInstanceOf(UnexpectedBodyException.class);\n    }\n\n    @Test\n    void it_should_return_the_list_of_supported_rules_for_sonarqube_cloud() {\n      mockServer.addStringResponse(\"/fix-suggestions/supported-rules\", \"\"\"\n        {\n          \"rules\": [\"repo:rule1\", \"repo:rule2\"]\n        }\n        \"\"\");\n      underTest = new FixSuggestionsApi(mockServer.serverApiHelper(\"orgKey\"));\n\n      var response = underTest.getSupportedRules(new SonarLintCancelMonitor());\n\n      assertThat(response)\n        .isEqualTo(new SupportedRulesResponseDto(Set.of(\"repo:rule1\", \"repo:rule2\")));\n    }\n\n    @Test\n    void it_should_return_the_list_of_supported_rules_for_sonarqube_server() {\n      mockServer.addStringResponse(\"/api/v2/fix-suggestions/supported-rules\", \"\"\"\n        {\n          \"rules\": [\"repo:rule1\", \"repo:rule2\"]\n        }\n        \"\"\");\n\n      var response = underTest.getSupportedRules(new SonarLintCancelMonitor());\n\n      assertThat(response)\n        .isEqualTo(new SupportedRulesResponseDto(Set.of(\"repo:rule1\", \"repo:rule2\")));\n    }\n  }\n\n  @Nested\n  class GetOrganizationConfigs {\n\n    @Test\n    void it_should_throw_an_exception_if_the_body_is_malformed() {\n      mockServer.addStringResponse(\"/fix-suggestions/organization-configs/orgId\", \"\"\"\n        [\n        \"\"\");\n\n      var throwable = catchThrowable(() -> underTest.getOrganizationConfigs(\"orgId\", new SonarLintCancelMonitor()));\n\n      assertThat(throwable).isInstanceOf(UnexpectedBodyException.class);\n    }\n\n    @Test\n    void it_should_return_the_organization_config() {\n      mockServer.addStringResponse(\"/fix-suggestions/organization-configs/orgId\", \"\"\"\n        {\n          \"organizationId\": \"orgId\",\n          \"enablement\": \"DISABLED\",\n          \"organizationEligible\": true,\n          \"aiCodeFix\": {\n            \"enablement\": \"DISABLED\",\n            \"organizationEligible\": true\n          }\n        }\n        \"\"\");\n\n      var response = underTest.getOrganizationConfigs(\"orgId\", new SonarLintCancelMonitor());\n\n      assertThat(response)\n        .isEqualTo(new OrganizationConfigsResponseDto(\"orgId\",\n          new AiCodeFixConfiguration(SuggestionFeatureEnablement.DISABLED, null, true)));\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/hotspot/HotspotApiTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.hotspot;\n\nimport java.nio.file.Path;\nimport java.time.LocalDateTime;\nimport java.time.ZoneId;\nimport java.time.ZoneOffset;\nimport java.time.ZonedDateTime;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.http.HttpClientProvider;\nimport org.sonarsource.sonarlint.core.serverapi.MockWebServerExtensionWithProtobuf;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.UrlUtils;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Hotspots;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass HotspotApiTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n\n  private HotspotApi underTest;\n\n  @BeforeEach\n  void setUp() {\n    underTest = new ServerApi(mockServer.endpointParams(), HttpClientProvider.forTesting().getHttpClientWithoutAuth()).hotspot();\n  }\n\n  @Test\n  void it_should_call_the_expected_api_endpoint_when_fetching_hotspot_details() {\n    underTest.fetch(\"h\", new SonarLintCancelMonitor());\n\n    var recordedRequest = mockServer.takeRequest();\n    assertThat(recordedRequest.getPath()).isEqualTo(\"/api/hotspots/show.protobuf?hotspot=h\");\n  }\n\n  @Test\n  void it_should_urlencode_the_hotspot_and_project_keys_when_fetching_hotspot_details() {\n    underTest.fetch(\"hot/spot\", new SonarLintCancelMonitor());\n\n    var recordedRequest = mockServer.takeRequest();\n    assertThat(recordedRequest.getPath()).isEqualTo(\"/api/hotspots/show.protobuf?hotspot=hot%2Fspot\");\n  }\n\n  @Test\n  void it_should_adapt_and_return_the_hotspot_details() {\n    mockServer.addProtobufResponse(\"/api/hotspots/show.protobuf?hotspot=h\", Hotspots.ShowWsResponse.newBuilder()\n      .setMessage(\"message\")\n      .setComponent(Hotspots.Component.newBuilder().setPath(\"path\").setKey(\"myproject:path\"))\n      .setTextRange(Common.TextRange.newBuilder().setStartLine(2).setStartOffset(7).setEndLine(4).setEndOffset(9).build())\n      .setAuthor(\"author\")\n      .setStatus(\"REVIEWED\")\n      .setResolution(\"SAFE\")\n      .setRule(Hotspots.Rule.newBuilder().setKey(\"key\")\n        .setName(\"name\")\n        .setSecurityCategory(\"category\")\n        .setVulnerabilityProbability(\"HIGH\")\n        .setRiskDescription(\"risk\")\n        .setVulnerabilityDescription(\"vulnerability\")\n        .setFixRecommendations(\"fix\")\n        .build())\n      .build());\n    mockServer.addStringResponse(\"/api/sources/raw?key=\" + UrlUtils.urlEncode(\"myproject:path\"), \"Even\\nBefore My\\n\\tCode\\n  Snippet And\\n After\");\n\n    var remoteHotspot = underTest.fetch(\"h\", new SonarLintCancelMonitor());\n\n    assertThat(remoteHotspot).isNotEmpty();\n    var hotspot = remoteHotspot.get();\n    assertThat(hotspot.message).isEqualTo(\"message\");\n    assertThat(hotspot.filePath).isEqualTo(Path.of(\"path\"));\n    assertThat(hotspot.textRange).usingRecursiveComparison().isEqualTo(new TextRangeWithHash(2, 7, 4, 9, \"\"));\n    assertThat(hotspot.author).isEqualTo(\"author\");\n    assertThat(hotspot.status).isEqualTo(ServerHotspotDetails.Status.REVIEWED);\n    assertThat(hotspot.resolution).isEqualTo(ServerHotspotDetails.Resolution.SAFE);\n    assertThat(hotspot.rule.key).isEqualTo(\"key\");\n    assertThat(hotspot.rule.name).isEqualTo(\"name\");\n    assertThat(hotspot.rule.securityCategory).isEqualTo(\"category\");\n    assertThat(hotspot.rule.vulnerabilityProbability).isEqualTo(VulnerabilityProbability.HIGH);\n    assertThat(hotspot.rule.riskDescription).isEqualTo(\"risk\");\n    assertThat(hotspot.rule.vulnerabilityDescription).isEqualTo(\"vulnerability\");\n    assertThat(hotspot.rule.fixRecommendations).isEqualTo(\"fix\");\n    assertThat(hotspot.codeSnippet).isEqualTo(\"My\\n\\tCode\\n  Snippet\");\n  }\n\n  @Test\n  void it_should_extract_single_line_snippet() {\n    mockServer.addProtobufResponse(\"/api/hotspots/show.protobuf?hotspot=h\", Hotspots.ShowWsResponse.newBuilder()\n      .setMessage(\"message\")\n      .setComponent(Hotspots.Component.newBuilder().setPath(\"path\").setKey(\"myproject:path\"))\n      .setTextRange(Common.TextRange.newBuilder().setStartLine(2).setStartOffset(7).setEndLine(2).setEndOffset(9).build())\n      .setAuthor(\"author\")\n      .setStatus(\"REVIEWED\")\n      .setResolution(\"SAFE\")\n      .setRule(Hotspots.Rule.newBuilder().setKey(\"key\")\n        .setName(\"name\")\n        .setSecurityCategory(\"category\")\n        .setVulnerabilityProbability(\"HIGH\")\n        .setRiskDescription(\"risk\")\n        .setVulnerabilityDescription(\"vulnerability\")\n        .setFixRecommendations(\"fix\")\n        .build())\n      .build());\n    mockServer.addStringResponse(\"/api/sources/raw?key=\" + UrlUtils.urlEncode(\"myproject:path\"), \"Even\\nBefore My\\n\\tCode\\n  Snippet And\\n After\");\n\n    var remoteHotspot = underTest.fetch(\"h\", new SonarLintCancelMonitor());\n\n    assertThat(remoteHotspot).isNotEmpty();\n    var hotspot = remoteHotspot.get();\n    assertThat(hotspot.codeSnippet).isEqualTo(\"My\");\n  }\n\n  @Test\n  void it_should_return_empty_optional_when_ws_client_throws_an_exception() {\n    var remoteHotspot = underTest.fetch(\"h\", new SonarLintCancelMonitor());\n    assertThat(remoteHotspot).isEmpty();\n  }\n\n  @Test\n  void it_should_throw_when_parser_throws_an_exception() {\n    mockServer.addProtobufResponse(\"/api/hotspots/show.protobuf?hotspot=h\", Issues.SearchWsResponse.newBuilder().build());\n\n    var cancelMonitor = new SonarLintCancelMonitor();\n    assertThrows(IllegalArgumentException.class, () -> underTest.fetch(\"h\", cancelMonitor));\n  }\n\n  @Test\n  void it_should_return_no_resolution_status_when_not_available() {\n    mockServer.addProtobufResponse(\"/api/hotspots/show.protobuf?hotspot=h\", Hotspots.ShowWsResponse.newBuilder()\n      .setComponent(Hotspots.Component.newBuilder().setPath(\"path\"))\n      .setTextRange(Common.TextRange.newBuilder().setStartLine(1).setStartOffset(2).setEndLine(3).setEndOffset(4).build())\n      .setStatus(\"TO_REVIEW\")\n      .setRule(Hotspots.Rule.newBuilder().setKey(\"key\")\n        .setName(\"name\")\n        .setSecurityCategory(\"category\")\n        .setVulnerabilityProbability(\"HIGH\")\n        .setRiskDescription(\"risk\")\n        .setVulnerabilityDescription(\"vulnerability\")\n        .setFixRecommendations(\"fix\")\n        .build())\n      .build());\n\n    var remoteHotspot = underTest.fetch(\"h\", new SonarLintCancelMonitor());\n\n    assertThat(remoteHotspot).isNotEmpty();\n    var hotspot = remoteHotspot.get();\n    assertThat(hotspot.resolution).isNull();\n  }\n\n  @Test\n  void it_should_map_acknowledged_status_for_show() {\n    mockServer.addProtobufResponse(\"/api/hotspots/show.protobuf?hotspot=h\", Hotspots.ShowWsResponse.newBuilder()\n      .setComponent(Hotspots.Component.newBuilder().setPath(\"path\"))\n      .setTextRange(Common.TextRange.newBuilder().setStartLine(1).setStartOffset(2).setEndLine(3).setEndOffset(4).build())\n      .setStatus(\"REVIEWED\")\n      .setResolution(\"ACKNOWLEDGED\")\n      .setRule(Hotspots.Rule.newBuilder().setKey(\"key\")\n        .setName(\"name\")\n        .setSecurityCategory(\"category\")\n        .setVulnerabilityProbability(\"HIGH\")\n        .setRiskDescription(\"risk\")\n        .setVulnerabilityDescription(\"vulnerability\")\n        .setFixRecommendations(\"fix\")\n        .build())\n      .build());\n\n    var remoteHotspot = underTest.fetch(\"h\", new SonarLintCancelMonitor());\n\n    assertThat(remoteHotspot).isNotEmpty();\n    var hotspot = remoteHotspot.get();\n    assertThat(hotspot.resolution).isEqualTo(ServerHotspotDetails.Resolution.ACKNOWLEDGED);\n  }\n\n  @Test\n  void it_should_fetch_project_hotspots() {\n    mockServer.addProtobufResponse(\"/api/hotspots/search.protobuf?projectKey=p&branch=branch&ps=500&p=1\", Hotspots.SearchWsResponse.newBuilder()\n      .setPaging(Common.Paging.newBuilder().setTotal(1).build())\n      .addHotspots(Hotspots.SearchWsResponse.Hotspot.newBuilder()\n        .setComponent(\"component:path1\")\n        .setTextRange(Common.TextRange.newBuilder().setStartLine(1).setStartOffset(2).setEndLine(3).setEndOffset(4).build())\n        .setStatus(\"TO_REVIEW\")\n        .setKey(\"hotspotKey1\")\n        .setCreationDate(\"2020-09-21T12:46:39+0000\")\n        .setRuleKey(\"ruleKey1\")\n        .setMessage(\"message1\")\n        .setVulnerabilityProbability(\"HIGH\")\n        .build())\n      .addHotspots(Hotspots.SearchWsResponse.Hotspot.newBuilder()\n        .setComponent(\"component:path2\")\n        .setTextRange(Common.TextRange.newBuilder().setStartLine(5).setStartOffset(6).setEndLine(7).setEndOffset(8).build())\n        .setStatus(\"REVIEWED\")\n        .setResolution(\"SAFE\")\n        .setKey(\"hotspotKey2\")\n        .setCreationDate(\"2020-09-22T12:46:39+0000\")\n        .setRuleKey(\"ruleKey2\")\n        .setMessage(\"message2\")\n        .setVulnerabilityProbability(\"LOW\")\n        .build())\n      .addHotspots(Hotspots.SearchWsResponse.Hotspot.newBuilder()\n        .setComponent(\"component:path3\")\n        .setTextRange(Common.TextRange.newBuilder().setStartLine(9).setStartOffset(10).setEndLine(11).setEndOffset(12).build())\n        .setStatus(\"REVIEWED\")\n        .setResolution(\"ACKNOWLEDGED\")\n        .setKey(\"hotspotKey3\")\n        .setCreationDate(\"2020-09-23T12:46:39+0000\")\n        .setRuleKey(\"ruleKey3\")\n        .setMessage(\"message3\")\n        .setVulnerabilityProbability(\"LOW\")\n        .build())\n      .addHotspots(Hotspots.SearchWsResponse.Hotspot.newBuilder()\n        .setComponent(\"component:path4\")\n        .setTextRange(Common.TextRange.newBuilder().setStartLine(13).setStartOffset(14).setEndLine(15).setEndOffset(16).build())\n        .setStatus(\"REVIEWED\")\n        .setResolution(\"FIXED\")\n        .setKey(\"hotspotKey4\")\n        .setCreationDate(\"2020-09-24T12:46:39+0000\")\n        .setRuleKey(\"ruleKey4\")\n        .setMessage(\"message4\")\n        .setVulnerabilityProbability(\"MEDIUM\")\n        .build())\n      .addHotspots(Hotspots.SearchWsResponse.Hotspot.newBuilder()\n        .setComponent(\"component:path5\")\n        .setTextRange(Common.TextRange.newBuilder().setStartLine(17).setStartOffset(18).setEndLine(19).setEndOffset(20).build())\n        .setStatus(\"REVIEWED\")\n        .setKey(\"hotspotKey5\")\n        .setCreationDate(\"2020-09-25T12:46:39+0000\")\n        .setRuleKey(\"ruleKey5\")\n        .setMessage(\"message5\")\n        .setVulnerabilityProbability(\"LOW\")\n        .build())\n      .addHotspots(Hotspots.SearchWsResponse.Hotspot.newBuilder()\n        .setComponent(\"component:path6\")\n        .setTextRange(Common.TextRange.newBuilder().setStartLine(21).setStartOffset(22).setEndLine(23).setEndOffset(24).build())\n        .setStatus(\"REVIEWED\")\n        .setResolution(\"UNKNOWN\")\n        .setKey(\"hotspotKey6\")\n        .setCreationDate(\"2020-09-25T12:46:39+0000\")\n        .setRuleKey(\"ruleKey6\")\n        .setMessage(\"message6\")\n        .setVulnerabilityProbability(\"LOW\")\n        .build())\n      .addComponents(Hotspots.Component.newBuilder().setKey(\"component:path1\").setPath(\"path1\").build())\n      .addComponents(Hotspots.Component.newBuilder().setKey(\"component:path2\").setPath(\"path2\").build())\n      .addComponents(Hotspots.Component.newBuilder().setKey(\"component:path3\").setPath(\"path3\").build())\n      .addComponents(Hotspots.Component.newBuilder().setKey(\"component:path4\").setPath(\"path4\").build())\n      .addComponents(Hotspots.Component.newBuilder().setKey(\"component:path5\").setPath(\"path5\").build())\n      .addComponents(Hotspots.Component.newBuilder().setKey(\"component:path6\").setPath(\"path6\").build())\n      .build());\n\n    var hotspots = underTest.getAll(\"p\", \"branch\", new SonarLintCancelMonitor());\n\n    assertThat(hotspots)\n      .extracting(\"key\", \"ruleKey\", \"message\", \"filePath\", \"textRange.startLine\", \"textRange.startLineOffset\", \"textRange.endLine\", \"textRange.endLineOffset\", \"creationDate\",\n        \"status\")\n      .containsExactly(\n        tuple(\"hotspotKey1\", \"ruleKey1\", \"message1\", Path.of(\"path1\"), 1, 2, 3, 4, LocalDateTime.of(2020, 9, 21, 12, 46, 39).toInstant(ZoneOffset.UTC), HotspotReviewStatus.TO_REVIEW),\n        tuple(\"hotspotKey2\", \"ruleKey2\", \"message2\", Path.of(\"path2\"), 5, 6, 7, 8, LocalDateTime.of(2020, 9, 22, 12, 46, 39).toInstant(ZoneOffset.UTC), HotspotReviewStatus.SAFE),\n        tuple(\"hotspotKey3\", \"ruleKey3\", \"message3\", Path.of(\"path3\"), 9, 10, 11, 12, LocalDateTime.of(2020, 9, 23, 12, 46, 39).toInstant(ZoneOffset.UTC), HotspotReviewStatus.ACKNOWLEDGED),\n        tuple(\"hotspotKey4\", \"ruleKey4\", \"message4\", Path.of(\"path4\"), 13, 14, 15, 16, LocalDateTime.of(2020, 9, 24, 12, 46, 39).toInstant(ZoneOffset.UTC), HotspotReviewStatus.FIXED),\n        tuple(\"hotspotKey5\", \"ruleKey5\", \"message5\", Path.of(\"path5\"), 17, 18, 19, 20, LocalDateTime.of(2020, 9, 25, 12, 46, 39).toInstant(ZoneOffset.UTC), HotspotReviewStatus.SAFE),\n        tuple(\"hotspotKey6\", \"ruleKey6\", \"message6\", Path.of(\"path6\"), 21, 22, 23, 24, LocalDateTime.of(2020, 9, 25, 12, 46, 39).toInstant(ZoneOffset.UTC), HotspotReviewStatus.TO_REVIEW));\n  }\n\n  @Test\n  void it_should_fetch_file_hotspots() {\n    mockServer.addProtobufResponse(\"/api/hotspots/search.protobuf?projectKey=p&files=path%2Fto%2Ffile.ext&branch=branch&ps=500&p=1\", Hotspots.SearchWsResponse.newBuilder()\n      .setPaging(Common.Paging.newBuilder().setTotal(1).build())\n      .addHotspots(Hotspots.SearchWsResponse.Hotspot.newBuilder()\n        .setComponent(\"component:path/to/file.ext\")\n        .setTextRange(Common.TextRange.newBuilder().setStartLine(1).setStartOffset(2).setEndLine(3).setEndOffset(4).build())\n        .setStatus(\"TO_REVIEW\")\n        .setKey(\"hotspotKey1\")\n        .setCreationDate(\"2020-09-21T12:46:39+0000\")\n        .setRuleKey(\"ruleKey1\")\n        .setMessage(\"message1\")\n        .setVulnerabilityProbability(\"HIGH\")\n        .build())\n      .addComponents(Hotspots.Component.newBuilder().setKey(\"component:path/to/file.ext\").setPath(\"path/to/file.ext\").build())\n      .build());\n\n    var hotspots = underTest.getFromFile(\"p\", Path.of(\"path/to/file.ext\"), \"branch\", new SonarLintCancelMonitor());\n\n    assertThat(hotspots)\n      .extracting(\"key\", \"ruleKey\", \"message\", \"filePath\", \"textRange.startLine\", \"textRange.startLineOffset\", \"textRange.endLine\", \"textRange.endLineOffset\", \"creationDate\",\n        \"status\")\n      .containsExactly(\n        tuple(\"hotspotKey1\", \"ruleKey1\", \"message1\", Path.of(\"path/to/file.ext\"), 1, 2, 3, 4, ZonedDateTime.of(2020, 9, 21, 12, 46, 39, 0, ZoneId.of(\"UTC\")).toInstant(),\n          HotspotReviewStatus.TO_REVIEW));\n  }\n\n  @Test\n  void it_should_log_when_hotspot_component_is_missing() {\n    mockServer.addProtobufResponse(\"/api/hotspots/search.protobuf?projectKey=p&branch=branch&ps=500&p=1\", Hotspots.SearchWsResponse.newBuilder()\n      .setPaging(Common.Paging.newBuilder().setTotal(1).build())\n      .addHotspots(Hotspots.SearchWsResponse.Hotspot.newBuilder()\n        .setComponent(\"component:path\")\n        .build())\n      .build());\n\n    underTest.getAll(\"p\", \"branch\", new SonarLintCancelMonitor());\n\n    assertThat(logTester.logs())\n      .contains(\"Error while fetching security hotspots, the component 'component:path' is missing\");\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/hotspot/ServerHotspotDetailsTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.hotspot;\n\nimport java.nio.file.Path;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ServerHotspotDetailsTests {\n  @Test\n  void it_should_populate_fields_with_constructor_parameters() {\n    var hotspot = new ServerHotspotDetails(\"message\",\n      Path.of(\"path\"),\n      new TextRange(0, 1, 2, 3),\n      \"author\",\n      ServerHotspotDetails.Status.TO_REVIEW,\n      ServerHotspotDetails.Resolution.FIXED, new ServerHotspotDetails.Rule(\n        \"key\",\n        \"name\",\n        \"category\",\n        VulnerabilityProbability.HIGH,\n        \"risk\",\n        \"vulnerability\",\n        \"fix\"),\n      \"some code \\n content\", true);\n\n    assertThat(hotspot.message).isEqualTo(\"message\");\n    assertThat(hotspot.filePath).isEqualTo(Path.of(\"path\"));\n    assertThat(hotspot.textRange.getStartLine()).isZero();\n    assertThat(hotspot.textRange.getStartLineOffset()).isEqualTo(1);\n    assertThat(hotspot.textRange.getEndLine()).isEqualTo(2);\n    assertThat(hotspot.textRange.getEndLineOffset()).isEqualTo(3);\n    assertThat(hotspot.author).isEqualTo(\"author\");\n    assertThat(hotspot.status).isEqualTo(ServerHotspotDetails.Status.TO_REVIEW);\n    assertThat(hotspot.resolution).isEqualTo(ServerHotspotDetails.Resolution.FIXED);\n    assertThat(hotspot.rule.key).isEqualTo(\"key\");\n    assertThat(hotspot.rule.name).isEqualTo(\"name\");\n    assertThat(hotspot.rule.securityCategory).isEqualTo(\"category\");\n    assertThat(hotspot.rule.vulnerabilityProbability).isEqualTo(VulnerabilityProbability.HIGH);\n    assertThat(hotspot.rule.riskDescription).isEqualTo(\"risk\");\n    assertThat(hotspot.rule.vulnerabilityDescription).isEqualTo(\"vulnerability\");\n    assertThat(hotspot.rule.fixRecommendations).isEqualTo(\"fix\");\n    assertThat(hotspot.codeSnippet).isEqualTo(\"some code \\n content\");\n    assertThat(hotspot.canChangeStatus).isTrue();\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/issue/IssueApiTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.issue;\n\nimport java.nio.file.Path;\nimport java.util.Set;\nimport mockwebserver3.MockResponse;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonar.scanner.protocol.input.ScannerInput;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.MockWebServerExtensionWithProtobuf;\nimport org.sonarsource.sonarlint.core.serverapi.exception.ServerErrorException;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\nimport static org.assertj.core.api.Assertions.entry;\nimport static org.sonarsource.sonarlint.core.serverapi.UrlUtils.urlEncode;\n\nclass IssueApiTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n\n  private IssueApi underTest;\n\n  @BeforeEach\n  void setUp() {\n    underTest = new IssueApi(mockServer.serverApiHelper());\n  }\n\n  @Test\n  void should_download_all_issues_as_batch() {\n    mockServer.addProtobufResponseDelimited(\"/batch/issues?key=keyyy\", ScannerInput.ServerIssue.newBuilder().setRuleKey(\"ruleKey\").build());\n\n    var issues = underTest.downloadAllFromBatchIssues(\"keyyy\", null, new SonarLintCancelMonitor());\n\n    assertThat(issues)\n      .extracting(\"ruleKey\")\n      .containsOnly(\"ruleKey\");\n  }\n\n  @Test\n  void should_download_all_issues_as_batch_from_branch() {\n    mockServer.addProtobufResponseDelimited(\"/batch/issues?key=keyyy&branch=branchName\", ScannerInput.ServerIssue.newBuilder().setRuleKey(\"ruleKey\").build());\n\n    var issues = underTest.downloadAllFromBatchIssues(\"keyyy\", \"branchName\", new SonarLintCancelMonitor());\n\n    assertThat(issues)\n      .extracting(\"ruleKey\")\n      .containsOnly(\"ruleKey\");\n  }\n\n  @Test\n  void should_return_no_batch_issue_if_download_is_forbidden() {\n    mockServer.addResponse(\"/batch/issues?key=keyyy\", new MockResponse.Builder().code(403).build());\n\n    var issues = underTest.downloadAllFromBatchIssues(\"keyyy\", null, new SonarLintCancelMonitor());\n\n    assertThat(issues).isEmpty();\n  }\n\n  @Test\n  void should_return_no_batch_issue_if_endpoint_is_not_found() {\n    mockServer.addResponse(\"/batch/issues?key=keyyy\", new MockResponse.Builder().code(404).build());\n\n    var issues = underTest.downloadAllFromBatchIssues(\"keyyy\", null, new SonarLintCancelMonitor());\n\n    assertThat(issues).isEmpty();\n  }\n\n  @Test\n  void should_throw_an_error_if_batch_issue_download_fails() {\n    mockServer.addResponse(\"/batch/issues?key=keyyy\", new MockResponse.Builder().code(500).build());\n\n    var throwable = catchThrowable(() -> underTest.downloadAllFromBatchIssues(\"keyyy\", null, new SonarLintCancelMonitor()));\n\n    assertThat(throwable).isInstanceOf(ServerErrorException.class);\n  }\n\n  @Test\n  void should_throw_an_error_if_batch_issue_body__format_is_unexpected() {\n    mockServer.addStringResponse(\"/batch/issues?key=keyyy\", \"nope\");\n\n    var throwable = catchThrowable(() -> underTest.downloadAllFromBatchIssues(\"keyyy\", null, new SonarLintCancelMonitor()));\n\n    assertThat(throwable).isInstanceOf(IllegalStateException.class);\n  }\n\n  @Test\n  void should_download_all_vulnerabilities() {\n    mockServer.addProtobufResponse(\"/api/issues/search.protobuf?statuses=OPEN,CONFIRMED,REOPENED,RESOLVED&types=VULNERABILITY&componentKeys=keyyy&components=keyyy&rules=ruleKey&ps=500&p=1\",\n      Issues.SearchWsResponse.newBuilder().addIssues(Issues.Issue.newBuilder().setKey(\"issueKey\").build()).build());\n    mockServer.addProtobufResponse(\"/api/issues/search.protobuf?statuses=OPEN,CONFIRMED,REOPENED,RESOLVED&types=VULNERABILITY&componentKeys=keyyy&components=keyyy&rules=ruleKey&ps=500&p=2\",\n      Issues.SearchWsResponse.newBuilder().addComponents(Issues.Component.newBuilder().setKey(\"componentKey\").setPath(\"componentPath\").build()).build());\n\n    var result = underTest.downloadVulnerabilitiesForRules(\"keyyy\", Set.of(\"ruleKey\"), null, new SonarLintCancelMonitor());\n\n    assertThat(result.getIssues())\n      .extracting(\"key\")\n      .containsOnly(\"issueKey\");\n    assertThat(result.getComponentPathsByKey())\n      .containsOnly(entry(\"componentKey\", Path.of(\"componentPath\")));\n  }\n\n  @Test\n  void should_fetch_server_issue_by_key() {\n    var issueKey = \"issueKey\";\n    var path = \"/home/file.java\";\n    var projectKey = \"projectKey\";\n    mockServer.addProtobufResponse(\"/api/issues/search.protobuf?issues=\".concat(urlEncode(issueKey)).concat(\"&componentKeys=\").concat(projectKey).concat(\"&components=\").concat(projectKey).concat(\"&ps=1&p=1\"),\n      Issues.SearchWsResponse.newBuilder()\n        .addIssues(Issues.Issue.newBuilder().setKey(issueKey).build())\n        .addComponents(Issues.Component.newBuilder().setPath(path).build())\n        .setRules(Issues.SearchWsResponse.newBuilder().getRulesBuilder().addRules(Common.Rule.newBuilder().setKey(\"ruleKey\").build()))\n        .build());\n    var serverIssueDetails = underTest.fetchServerIssue(issueKey, projectKey, \"\", \"\", new SonarLintCancelMonitor());\n    assertThat(serverIssueDetails).isPresent();\n    assertThat(serverIssueDetails.get().key).isEqualTo(issueKey);\n    assertThat(serverIssueDetails.get().path).isEqualTo(Path.of(path));\n  }\n\n  @Test\n  void should_not_fail_when_no_issue_found_by_key() {\n    mockServer.addProtobufResponse(\"/api/issues/search.protobuf?issues=\".concat(urlEncode(\"qwert\")).concat(\"&componentKeys=myProject\").concat(\"&components=myProject\").concat(\"&ps=1&p=1\"),\n      Issues.SearchWsResponse.newBuilder().addIssues(Issues.Issue.newBuilder().build()).build());\n    var serverIssueDetails = underTest.fetchServerIssue(\"non-existent\", \"myProject\", \"\", \"\", new SonarLintCancelMonitor());\n    assertThat(serverIssueDetails).isEmpty();\n    assertThat(logTester.logs()).contains(\"Error while fetching issue\");\n  }\n\n  @Test\n  void should_not_fetch_server_issue_by_key_with_no_matching_component() {\n    var issueKey = \"issueKey\";\n    var path = \"/home/file.java\";\n    mockServer.addProtobufResponse(\"/api/issues/search.protobuf?issues=\".concat(urlEncode(issueKey)).concat(\"&componentKeys=differentIssueComponent\").concat(\"&components=differentIssueComponent\").concat(\"&ps=1&p=1\"),\n      Issues.SearchWsResponse.newBuilder()\n        .addIssues(Issues.Issue.newBuilder().setKey(issueKey).setComponent(\"issueComponent\").build())\n        .addComponents(Issues.Component.newBuilder().setPath(path).setKey(\"differentIssueComponent\").build())\n        .setRules(Issues.SearchWsResponse.newBuilder().getRulesBuilder().addRules(Common.Rule.newBuilder().setKey(\"ruleKey\").build()))\n        .build());\n    var serverIssueDetails = underTest.fetchServerIssue(issueKey, \"differentIssueComponent\", \"\", \"\", new SonarLintCancelMonitor());\n    assertThat(serverIssueDetails).isEmpty();\n    assertThat(logTester.logs()).contains(\"No path found in components for the issue with key 'issueKey'\");\n  }\n\n  @Test\n  void should_fetch_branch_issue() {\n    var issueKey = \"issueKey\";\n    var path = \"/home/file.java\";\n    var projectKey = \"projectKey\";\n    var branch = \"branch\";\n    mockServer.addProtobufResponse(\"/api/issues/search.protobuf?issues=\".concat(urlEncode(issueKey))\n        .concat(\"&componentKeys=\").concat(projectKey)\n        .concat(\"&components=\").concat(projectKey)\n        .concat(\"&ps=1&p=1\")\n        .concat(\"&branch=\").concat(branch),\n      Issues.SearchWsResponse.newBuilder()\n        .addIssues(Issues.Issue.newBuilder().setKey(issueKey).build())\n        .addComponents(Issues.Component.newBuilder().setPath(path).build())\n        .setRules(Issues.SearchWsResponse.newBuilder().getRulesBuilder().addRules(Common.Rule.newBuilder().setKey(\"ruleKey\").build()))\n        .build());\n    var serverIssueDetails = underTest.fetchServerIssue(issueKey, projectKey, branch, \"\", new SonarLintCancelMonitor());\n    assertThat(serverIssueDetails).isPresent();\n    assertThat(serverIssueDetails.get().key).isEqualTo(issueKey);\n    assertThat(serverIssueDetails.get().path).isEqualTo(Path.of(path));\n  }\n\n  @Test\n  void should_fetch_pull_request_issue() {\n    var issueKey = \"issueKey\";\n    var path = \"/home/file.java\";\n    var projectKey = \"projectKey\";\n    var pullRequest = \"1234\";\n    mockServer.addProtobufResponse(\"/api/issues/search.protobuf?issues=\".concat(urlEncode(issueKey))\n        .concat(\"&componentKeys=\").concat(projectKey)\n        .concat(\"&components=\").concat(projectKey)\n        .concat(\"&ps=1&p=1\")\n        .concat(\"&pullRequest=\").concat(pullRequest),\n      Issues.SearchWsResponse.newBuilder()\n        .addIssues(Issues.Issue.newBuilder().setKey(issueKey).build())\n        .addComponents(Issues.Component.newBuilder().setPath(path).build())\n        .setRules(Issues.SearchWsResponse.newBuilder().getRulesBuilder().addRules(Common.Rule.newBuilder().setKey(\"ruleKey\").build()))\n        .build());\n    var serverIssueDetails = underTest.fetchServerIssue(issueKey, projectKey, \"prbranch\", pullRequest, new SonarLintCancelMonitor());\n    assertThat(serverIssueDetails).isPresent();\n    assertThat(serverIssueDetails.get().key).isEqualTo(issueKey);\n    assertThat(serverIssueDetails.get().path).isEqualTo(Path.of(path));\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/newcode/NewCodeApiTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.newcode;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.NewCodeDefinition;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.http.HttpClient;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Measures;\nimport org.sonarsource.sonarlint.core.serverapi.util.ServerApiUtils;\n\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\nimport static org.sonarsource.sonarlint.core.serverapi.newcode.NewCodeApi.getPeriodForServer;\nimport static org.sonarsource.sonarlint.core.serverapi.newcode.NewCodeApi.getPeriodFromWs;\n\nclass NewCodeApiTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private static final String PROJECT = \"project\";\n  private static final String BRANCH = \"branch\";\n  private static final Version RECENT_SQ_VERSION = Version.create(\"10.2\");\n  private static final Version SC_VERSION = Version.create(\"8.0.0.46314\");\n  private static final String SOME_DATE = \"2023-08-29T09:37:59+0000\";\n  private static final long SOME_DATE_EPOCH_MILLIS = ServerApiUtils.parseOffsetDateTime(SOME_DATE).toInstant().toEpochMilli();\n\n  private ServerApiHelper mockApiHelper;\n\n  private NewCodeApi underTest;\n\n  @BeforeEach\n  void setup() {\n    mockApiHelper = mock(ServerApiHelper.class);\n    underTest = new NewCodeApi(mockApiHelper);\n  }\n\n  @Test\n  void getPeriodForNewSonarQube() {\n    var response = Measures.ComponentWsResponse\n      .newBuilder().setPeriod(Measures.Period.newBuilder()\n        .setDate(SOME_DATE).build())\n      .build();\n\n    var period = getPeriodFromWs(response);\n\n    assertThat(period.getDate()).isEqualTo(SOME_DATE);\n  }\n\n  @Test\n  void getPeriodsForOldSonarQubeOrSonarCloud() {\n    var response = Measures.ComponentWsResponse\n      .newBuilder().setPeriods(Measures.Periods.newBuilder().addPeriods(Measures.Period.newBuilder()\n        .setDate(SOME_DATE).build()).build())\n      .build();\n\n    var period = getPeriodFromWs(response);\n\n    assertThat(period.getDate()).isEqualTo(SOME_DATE);\n  }\n\n  @Test\n  void getPeriodFromServer() {\n    var serverApiHelper = mock(ServerApiHelper.class);\n    when(serverApiHelper.isSonarCloud()).thenReturn(true);\n\n    var sonarCloud = getPeriodForServer(serverApiHelper, Version.create(\"9.2\"));\n    when(serverApiHelper.isSonarCloud()).thenReturn(false);\n    var sonarQubeOld = getPeriodForServer(serverApiHelper, Version.create(\"8.0\"));\n    var sonarQubeNew = getPeriodForServer(serverApiHelper, Version.create(\"8.1\"));\n\n    assertThat(sonarCloud).isEqualTo(\"periods\");\n    assertThat(sonarQubeOld).isEqualTo(\"periods\");\n    assertThat(sonarQubeNew).isEqualTo(\"period\");\n  }\n\n  @Test\n  void parseReferenceBranchPeriod() {\n    prepareSqWsResponseWithPeriod(Measures.Period.newBuilder()\n      .setMode(\"REFERENCE_BRANCH\")\n      .setParameter(\"referenceBranch\")\n      .build());\n\n    var newCodeDefinition = underTest.getNewCodeDefinition(PROJECT, BRANCH, RECENT_SQ_VERSION, new SonarLintCancelMonitor()).orElseThrow();\n\n    assertThat(newCodeDefinition).isInstanceOf(NewCodeDefinition.NewCodeReferenceBranch.class)\n      .hasToString(\"Current new code definition (reference branch) is not supported\");\n    assertThat(newCodeDefinition.isOnNewCode(0)).isTrue();\n    assertThat(newCodeDefinition.isSupported()).isFalse();\n\n  }\n\n  @Test\n  void parseNumberOfDaysPeriodFromSq() {\n    prepareSqWsResponseWithPeriod(Measures.Period.newBuilder()\n      .setMode(\"NUMBER_OF_DAYS\")\n      .setParameter(\"42\")\n      .setDate(SOME_DATE)\n      .build());\n\n    var newCodeDefinition = underTest.getNewCodeDefinition(PROJECT, BRANCH, RECENT_SQ_VERSION, new SonarLintCancelMonitor()).orElseThrow();\n\n    assertThat(newCodeDefinition).isInstanceOf(NewCodeDefinition.NewCodeNumberOfDaysWithDate.class)\n      .hasToString(\"From last 42 days\");\n    assertThat(newCodeDefinition.isOnNewCode(SOME_DATE_EPOCH_MILLIS + 1)).isTrue();\n    assertThat(newCodeDefinition.isOnNewCode(SOME_DATE_EPOCH_MILLIS - 1)).isFalse();\n    assertThat(newCodeDefinition.isSupported()).isTrue();\n  }\n\n  @Test\n  void parseNumberOfDaysPeriodFromSc() {\n    prepareScWsResponseWithPeriods(Measures.Period.newBuilder()\n      .setMode(\"days\")\n      .setParameter(\"42\")\n      .setDate(SOME_DATE)\n      .build());\n\n    var newCodeDefinition = underTest.getNewCodeDefinition(PROJECT, BRANCH, SC_VERSION, new SonarLintCancelMonitor()).orElseThrow();\n\n    assertThat(newCodeDefinition).isInstanceOf(NewCodeDefinition.NewCodeNumberOfDaysWithDate.class)\n      .hasToString(\"From last 42 days\");\n    assertThat(newCodeDefinition.isOnNewCode(SOME_DATE_EPOCH_MILLIS + 1)).isTrue();\n    assertThat(newCodeDefinition.isOnNewCode(SOME_DATE_EPOCH_MILLIS - 1)).isFalse();\n    assertThat(newCodeDefinition.isSupported()).isTrue();\n  }\n\n  @Test\n  void parsePreviousVersionPeriodFromSq() {\n    prepareSqWsResponseWithPeriod(Measures.Period.newBuilder()\n      .setMode(\"PREVIOUS_VERSION\")\n      .setParameter(\"version\")\n      .setDate(SOME_DATE)\n      .build());\n\n    var newCodeDefinition = underTest.getNewCodeDefinition(PROJECT, BRANCH, RECENT_SQ_VERSION, new SonarLintCancelMonitor()).orElseThrow();\n\n    assertThat(newCodeDefinition).isInstanceOf(NewCodeDefinition.NewCodePreviousVersion.class)\n      .hasToString(\"Since version version\");\n    assertThat(newCodeDefinition.isOnNewCode(SOME_DATE_EPOCH_MILLIS + 1)).isTrue();\n    assertThat(newCodeDefinition.isOnNewCode(SOME_DATE_EPOCH_MILLIS - 1)).isFalse();\n    assertThat(newCodeDefinition.isSupported()).isTrue();\n  }\n\n  @Test\n  void parsePreviousVersionPeriodWithoutVersionFromSq() {\n    prepareSqWsResponseWithPeriod(Measures.Period.newBuilder()\n      .setMode(\"PREVIOUS_VERSION\")\n      .setDate(SOME_DATE)\n      .build());\n\n    var newCodeDefinition = underTest.getNewCodeDefinition(PROJECT, BRANCH, RECENT_SQ_VERSION, new SonarLintCancelMonitor()).orElseThrow();\n\n    assertThat(newCodeDefinition).isInstanceOf(NewCodeDefinition.NewCodePreviousVersion.class)\n      .hasToString(\"Since \" + NewCodeDefinition.formatEpochToDate(SOME_DATE_EPOCH_MILLIS));\n    assertThat(newCodeDefinition.isOnNewCode(SOME_DATE_EPOCH_MILLIS + 1)).isTrue();\n    assertThat(newCodeDefinition.isOnNewCode(SOME_DATE_EPOCH_MILLIS - 1)).isFalse();\n    assertThat(newCodeDefinition.isSupported()).isTrue();\n  }\n\n  @Test\n  void parsePreviousVersionPeriodFromSc() {\n    prepareScWsResponseWithPeriods(Measures.Period.newBuilder()\n      .setMode(\"previous_version\")\n      .setParameter(\"version\")\n      .setDate(SOME_DATE)\n      .build());\n\n    var newCodeDefinition = underTest.getNewCodeDefinition(PROJECT, BRANCH, SC_VERSION, new SonarLintCancelMonitor()).orElseThrow();\n\n    assertThat(newCodeDefinition).isInstanceOf(NewCodeDefinition.NewCodePreviousVersion.class)\n      .hasToString(\"Since version version\");\n    assertThat(newCodeDefinition.isOnNewCode(SOME_DATE_EPOCH_MILLIS + 1)).isTrue();\n    assertThat(newCodeDefinition.isOnNewCode(SOME_DATE_EPOCH_MILLIS - 1)).isFalse();\n    assertThat(newCodeDefinition.isSupported()).isTrue();\n  }\n\n  @Test\n  void parseSpecificAnalysisPeriodFromSq() {\n    prepareSqWsResponseWithPeriod(Measures.Period.newBuilder()\n      .setMode(\"SPECIFIC_ANALYSIS\")\n      .setParameter(\"someAnalysisKey\")\n      .setDate(SOME_DATE)\n      .build());\n\n    var newCodeDefinition = underTest.getNewCodeDefinition(PROJECT, BRANCH, RECENT_SQ_VERSION, new SonarLintCancelMonitor()).orElseThrow();\n\n    var date = NewCodeDefinition.formatEpochToDate(SOME_DATE_EPOCH_MILLIS);\n    assertThat(newCodeDefinition).isInstanceOf(NewCodeDefinition.NewCodeSpecificAnalysis.class)\n      .hasToString(\"Since analysis from \" + date);\n    assertThat(newCodeDefinition.isOnNewCode(SOME_DATE_EPOCH_MILLIS + 1)).isTrue();\n    assertThat(newCodeDefinition.isOnNewCode(SOME_DATE_EPOCH_MILLIS - 1)).isFalse();\n    assertThat(newCodeDefinition.isSupported()).isTrue();\n  }\n\n  @Test\n  void parseSpecificVersionPeriodFromSc() {\n    prepareScWsResponseWithPeriods(Measures.Period.newBuilder()\n      .setMode(\"version\")\n      .setParameter(\"X.Y.Z\")\n      .setDate(SOME_DATE)\n      .build());\n\n    var newCodeDefinition = underTest.getNewCodeDefinition(PROJECT, BRANCH, SC_VERSION, new SonarLintCancelMonitor()).orElseThrow();\n\n    var date = NewCodeDefinition.formatEpochToDate(SOME_DATE_EPOCH_MILLIS);\n    assertThat(newCodeDefinition).isInstanceOf(NewCodeDefinition.NewCodeSpecificAnalysis.class)\n      .hasToString(\"Since analysis from \" + date);\n    assertThat(newCodeDefinition.isOnNewCode(SOME_DATE_EPOCH_MILLIS + 1)).isTrue();\n    assertThat(newCodeDefinition.isOnNewCode(SOME_DATE_EPOCH_MILLIS - 1)).isFalse();\n    assertThat(newCodeDefinition.isSupported()).isTrue();\n  }\n\n  @Test\n  void parseSpecificDatePeriodFromSc() {\n    prepareScWsResponseWithPeriods(Measures.Period.newBuilder()\n      .setMode(\"date\")\n      .setDate(SOME_DATE)\n      .build());\n\n    var newCodeDefinition = underTest.getNewCodeDefinition(PROJECT, BRANCH, SC_VERSION, new SonarLintCancelMonitor()).orElseThrow();\n\n    var date = NewCodeDefinition.formatEpochToDate(SOME_DATE_EPOCH_MILLIS);\n    assertThat(newCodeDefinition).isInstanceOf(NewCodeDefinition.NewCodeSpecificAnalysis.class)\n      .hasToString(\"Since analysis from \" + date);\n    assertThat(newCodeDefinition.isOnNewCode(SOME_DATE_EPOCH_MILLIS + 1)).isTrue();\n    assertThat(newCodeDefinition.isOnNewCode(SOME_DATE_EPOCH_MILLIS - 1)).isFalse();\n    assertThat(newCodeDefinition.isSupported()).isTrue();\n  }\n\n  @Test\n  void parseUnknownModePeriod() {\n    prepareSqWsResponseWithPeriod(Measures.Period.newBuilder()\n      .setMode(\"Definitely not a supported mode\")\n      .setParameter(\"Whatever\")\n      .build());\n    assertThat(underTest.getNewCodeDefinition(PROJECT, BRANCH, RECENT_SQ_VERSION, new SonarLintCancelMonitor())).isEmpty();\n  }\n\n  @Test\n  void failHttpCall() {\n    when(mockApiHelper.get(anyString(), any(SonarLintCancelMonitor.class)))\n      .thenThrow(new RuntimeException(\"Not good\"));\n    assertThat(underTest.getNewCodeDefinition(PROJECT, BRANCH, RECENT_SQ_VERSION, new SonarLintCancelMonitor())).isEmpty();\n  }\n\n  void prepareSqWsResponseWithPeriod(Measures.Period period) {\n    when(mockApiHelper.isSonarCloud()).thenReturn(false);\n    var httpResponse = mock(HttpClient.Response.class);\n    when(httpResponse.bodyAsStream()).thenReturn(Measures.ComponentWsResponse.newBuilder()\n      .setPeriod(period)\n      .build().toByteString().newInput());\n    when(mockApiHelper.get(eq(\"/api/measures/component.protobuf?additionalFields=period&metricKeys=projects&component=\" + PROJECT + \"&branch=\" + BRANCH), any(SonarLintCancelMonitor.class)))\n      .thenReturn(httpResponse);\n  }\n\n  void prepareScWsResponseWithPeriods(Measures.Period period) {\n    when(mockApiHelper.isSonarCloud()).thenReturn(true);\n    var httpResponse = mock(HttpClient.Response.class);\n    when(httpResponse.bodyAsStream()).thenReturn(Measures.ComponentWsResponse.newBuilder()\n      .setPeriods(Measures.Periods.newBuilder()\n        .addPeriods(period)\n        .build())\n      .build().toByteString().newInput());\n    when(mockApiHelper.get(eq(\"/api/measures/component.protobuf?additionalFields=periods&metricKeys=projects&component=\" + PROJECT + \"&branch=\" + BRANCH), any(SonarLintCancelMonitor.class)))\n      .thenReturn(httpResponse);\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/organization/OrganizationApiTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.organization;\n\nimport java.util.List;\nimport java.util.UUID;\nimport java.util.stream.IntStream;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.http.HttpClientProvider;\nimport org.sonarsource.sonarlint.core.serverapi.MockWebServerExtensionWithProtobuf;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\nimport org.sonarsource.sonarlint.core.serverapi.exception.UnexpectedBodyException;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarcloud.ws.Organizations;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarcloud.ws.Organizations.Organization;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarcloud.ws.Organizations.SearchWsResponse;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common.Paging;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\n\nclass OrganizationApiTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n\n  @Test\n  void testListUserOrganizationWithMoreThan20Pages() {\n    var underTest = new OrganizationApi(new ServerApiHelper(mockServer.endpointParams(\"myOrg\"), HttpClientProvider.forTesting().getHttpClientWithoutAuth()));\n\n    for (var i = 0; i < 21; i++) {\n      mockOrganizationsPage(i + 1, 10500);\n    }\n\n    var orgs = underTest.listUserOrganizations(new SonarLintCancelMonitor());\n\n    assertThat(orgs).hasSize(10500);\n  }\n\n  @Test\n  void should_search_organization_details() {\n    mockServer.addProtobufResponse(\"/api/organizations/search.protobuf?organizations=org%3Akey&ps=500&p=1\", SearchWsResponse.newBuilder()\n      .addOrganizations(Organization.newBuilder()\n        .setKey(\"orgKey\")\n        .setName(\"orgName\")\n        .setDescription(\"orgDesc\")\n        .build())\n      .build());\n    mockServer.addProtobufResponse(\"/api/organizations/search.protobuf?organizations=org%3Akey&ps=500&p=2\", SearchWsResponse.newBuilder().build());\n    var underTest = new OrganizationApi(new ServerApiHelper(mockServer.endpointParams(), HttpClientProvider.forTesting().getHttpClientWithoutAuth()));\n\n    var organization = underTest.searchOrganization(\"org:key\", new SonarLintCancelMonitor());\n\n    assertThat(organization).hasValueSatisfying(org -> {\n      assertThat(org.getKey()).isEqualTo(\"orgKey\");\n      assertThat(org.getName()).isEqualTo(\"orgName\");\n      assertThat(org.getDescription()).isEqualTo(\"orgDesc\");\n    });\n  }\n\n  @Test\n  void should_get_organization_by_key() {\n    mockServer.addStringResponse(\"/organizations/organizations?organizationKey=org%3Akey&excludeEligibility=true\", \"\"\"\n      [{\n        \"id\": \"orgId\",\n        \"uuidV4\": \"f9cb252d-9f81-4e40-8b77-99fa13190b74\"\n      }]\n      \"\"\");\n    var underTest = new OrganizationApi(new ServerApiHelper(mockServer.endpointParams(\"org:key\"), HttpClientProvider.forTesting().getHttpClientWithoutAuth()));\n\n    var organization = underTest.getOrganizationByKey(new SonarLintCancelMonitor());\n\n    assertThat(organization)\n      .isEqualTo(new GetOrganizationsResponseDto(\"orgId\", UUID.fromString(\"f9cb252d-9f81-4e40-8b77-99fa13190b74\")));\n  }\n\n  @Test\n  void should_throw_if_get_organization_by_key_is_malformed() {\n    mockServer.addStringResponse(\"/organizations/organizations?organizationKey=org%3Akey&excludeEligibility=true\", \"\"\"\n      [{\n        \"id\": \"orgId\",\n        \"uuidV4\": \"f9cb252d-\n      \"\"\");\n    var underTest = new OrganizationApi(new ServerApiHelper(mockServer.endpointParams(\"org:key\"), HttpClientProvider.forTesting().getHttpClientWithoutAuth()));\n\n    var throwable = catchThrowable(() -> underTest.getOrganizationByKey(new SonarLintCancelMonitor()));\n\n    assertThat(throwable).isInstanceOf(UnexpectedBodyException.class);\n  }\n\n  private void mockOrganizationsPage(int page, int total) {\n    List<Organization> orgs = IntStream.rangeClosed(1, 500)\n      .mapToObj(i -> Organization.newBuilder().setKey(\"org_page\" + page + \"number\" + i).build())\n      .toList();\n\n    var paging = Paging.newBuilder()\n      .setPageSize(500)\n      .setTotal(total)\n      .setPageIndex(page)\n      .build();\n    var response = Organizations.SearchWsResponse.newBuilder()\n      .setPaging(paging)\n      .addAllOrganizations(orgs)\n      .build();\n    mockServer.addProtobufResponse(\"/api/organizations/search.protobuf?member=true&ps=500&p=\" + page, response);\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/organization/ServerOrganizationTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.organization;\n\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarcloud.ws.Organizations.Organization;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ServerOrganizationTests {\n  @Test\n  void testRoundTrip() {\n    var org = Organization.newBuilder()\n      .setName(\"name\")\n      .setKey(\"key\")\n      .setDescription(\"desc\")\n      .build();\n    ServerOrganization remoteOrg = new ServerOrganization(org);\n    assertThat(remoteOrg.getKey()).isEqualTo(\"key\");\n    assertThat(remoteOrg.getName()).isEqualTo(\"name\");\n    assertThat(remoteOrg.getDescription()).isEqualTo(\"desc\");\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/plugins/PluginsApiTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.plugins;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.MockWebServerExtensionWithProtobuf;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\n\nclass PluginsApiTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n\n  @Test\n  void should_return_installed_plugins() {\n    var underTest = new PluginsApi(mockServer.serverApiHelper());\n    mockServer.addStringResponse(\"/api/plugins/installed\", \"{\\\"plugins\\\": [\" +\n      \"{\\\"key\\\": \\\"pluginKey\\\", \\\"hash\\\": \\\"de5308f43260d357acc97712ce4c5475\\\", \\\"filename\\\": \\\"plugin-1.0.0.1234.jar\\\", \\\"sonarLintSupported\\\": true}\" +\n      \"]}\");\n\n    var serverPlugins = underTest.getInstalled(new SonarLintCancelMonitor());\n\n    assertThat(serverPlugins)\n      .extracting(\"key\", \"hash\", \"filename\", \"sonarLintSupported\")\n      .containsOnly(tuple(\"pluginKey\", \"de5308f43260d357acc97712ce4c5475\", \"plugin-1.0.0.1234.jar\", true));\n  }\n\n  @Test\n  void should_return_plugin_content() {\n    var underTest = new PluginsApi(mockServer.serverApiHelper());\n    mockServer.addStringResponse(\"/api/plugins/download?plugin=pluginKey\", \"content\");\n\n    underTest.getPlugin(\"pluginKey\", stream -> assertThat(stream).hasContent(\"content\"), new SonarLintCancelMonitor());\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/projectbindings/ProjectBindingsApiTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.projectbindings;\n\nimport java.net.URLEncoder;\nimport java.nio.charset.StandardCharsets;\nimport mockwebserver3.MockResponse;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.MockWebServerExtensionWithProtobuf;\nimport org.sonarsource.sonarlint.core.serverapi.exception.ServerErrorException;\nimport org.sonarsource.sonarlint.core.serverapi.exception.UnexpectedBodyException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\n\nclass ProjectBindingsApiTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n\n  private ProjectBindingsApi underTest;\n\n  @BeforeEach\n  void setUp() {\n    underTest = new ProjectBindingsApi(mockServer.serverApiHelper());\n  }\n\n  @Nested\n  class SonarQubeCloud {\n    @Test\n    void should_return_project_id_by_url() {\n      var url = \"https://github.com/foo/bar\";\n      var encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8);\n      mockServer.addStringResponse(\"/dop-translation/project-bindings?url=\" + encodedUrl,\n        \"{\\\"bindings\\\":[{\\\"projectId\\\":\\\"proj:123\\\"}]}\");\n\n      var result = underTest.getSQCProjectBindings(url, new SonarLintCancelMonitor());\n\n      assertThat(result).isEqualTo(new SQCProjectBindingsResponse(\"proj:123\"));\n    }\n\n    @Test\n    void should_return_empty_when_no_bindings() {\n      var url = \"https://github.com/foo/bar\";\n      var encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8);\n      mockServer.addStringResponse(\"/dop-translation/project-bindings?url=\" + encodedUrl,\n        \"{\\\"bindings\\\":[]}\");\n\n      var result = underTest.getSQCProjectBindings(url, new SonarLintCancelMonitor());\n\n      assertThat(result).isNull();\n    }\n\n    @Test\n    void should_return_empty_when_invalid_json() {\n      var url = \"https://github.com/foo/bar\";\n      var encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8);\n      mockServer.addStringResponse(\"/dop-translation/project-bindings?url=\" + encodedUrl,\n        \"this is not json\");\n\n      var result = underTest.getSQCProjectBindings(url, new SonarLintCancelMonitor());\n\n      assertThat(result).isNull();\n    }\n\n    @Test\n    void should_return_empty_when_request_fails() {\n      var url = \"https://github.com/foo/bar\";\n      var encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8);\n      mockServer.addResponse(\"/dop-translation/project-bindings?url=\" + encodedUrl,\n        new MockResponse.Builder().code(500).body(\"Internal error\").build());\n\n      var result = underTest.getSQCProjectBindings(url, new SonarLintCancelMonitor());\n\n      assertThat(result).isNull();\n    }\n  }\n\n  @Nested\n  class SonarQubeServer {\n    @Test\n    void should_return_project_key_by_url() {\n      var url = \"https://github.com/foo/bar\";\n      var encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8);\n      mockServer.addStringResponse(\"/api/v2/dop-translation/project-bindings?repositoryUrl=\" + encodedUrl,\n        \"{\\\"projectBindings\\\":[{\\\"projectId\\\":\\\"proj:123\\\",\\\"projectKey\\\":\\\"my-project-key\\\"}]}\");\n\n      var result = underTest.getSQSProjectBindings(url, new SonarLintCancelMonitor());\n\n      assertThat(result).isEqualTo(new SQSProjectBindingsResponse(\"proj:123\", \"my-project-key\"));\n    }\n\n    @Test\n    void should_return_empty_when_no_bindings() {\n      var url = \"https://github.com/foo/bar\";\n      var encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8);\n      mockServer.addStringResponse(\"/api/v2/dop-translation/project-bindings?repositoryUrl=\" + encodedUrl,\n        \"{\\\"projectBindings\\\":[]}\");\n\n      var result = underTest.getSQSProjectBindings(url, new SonarLintCancelMonitor());\n\n      assertThat(result).isNull();\n    }\n\n    @Test\n    void should_throw_when_invalid_json() {\n      var url = \"https://github.com/foo/bar\";\n      var encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8);\n      mockServer.addStringResponse(\"/api/v2/dop-translation/project-bindings?repositoryUrl=\" + encodedUrl,\n        \"this is not json\");\n\n      var throwable = catchThrowable(() -> underTest.getSQSProjectBindings(url, new SonarLintCancelMonitor()));\n\n      assertThat(throwable).isInstanceOf(UnexpectedBodyException.class);\n    }\n\n    @Test\n    void should_throw_when_request_fails() {\n      var url = \"https://github.com/foo/bar\";\n      var encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8);\n      mockServer.addResponse(\"/api/v2/dop-translation/project-bindings?repositoryUrl=\" + encodedUrl,\n        new MockResponse.Builder().code(500).body(\"Internal error\").build());\n\n      var throwable = catchThrowable(() -> underTest.getSQSProjectBindings(url, new SonarLintCancelMonitor()));\n\n      assertThat(throwable).isInstanceOf(ServerErrorException.class);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/push/PushApiTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.concurrent.TimeUnit;\nimport mockwebserver3.MockResponse;\nimport org.assertj.core.api.InstanceOfAssertFactories;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.serverapi.MockWebServerExtensionWithProtobuf;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\n\nclass PushApiTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester(true);\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n\n  private PushApi underTest;\n\n  @BeforeEach\n  void setUp() {\n    underTest = new PushApi(mockServer.serverApiHelper());\n  }\n\n  @Test\n  @Disabled(\"Settings will be supported later\")\n  void should_notify_setting_changed_event_for_simple_setting() {\n    var mockResponse = new MockResponse.Builder()\n      .body(\"\"\"\n        event: SettingChanged\n        data: {\n          \"projects\": [\"projectKey1\", \"projectKey2\"],\n          \"key\": \"key1\",\n          \"value\": \"value1\"\n        }\n    \n        \"\"\")\n      .build();\n    mockServer.addResponse(\"/api/push/sonarlint_events?projectKeys=projectKey1,projectKey2&languages=java,py\", mockResponse);\n\n    List<SonarServerEvent> receivedEvents = new CopyOnWriteArrayList<>();\n    underTest.subscribe(new LinkedHashSet<>(List.of(\"projectKey1\", \"projectKey2\")), new LinkedHashSet<>(List.of(SonarLanguage.JAVA, SonarLanguage.PYTHON)), receivedEvents::add);\n\n    assertThat(receivedEvents)\n      .extracting(\"projectKeys\", \"keyValues\")\n      .containsOnly(tuple(List.of(\"projectKey1\", \"projectKey2\"), Map.of(\"key1\", \"value1\")));\n  }\n\n  @Test\n  @Disabled(\"Settings will be supported later\")\n  void should_notify_setting_changed_event_for_multi_values_setting() {\n    var mockResponse = new MockResponse.Builder()\n      .body(\"\"\"\n        event: SettingChanged\n        data: {\n          \"projects\": [\"projectKey1\", \"projectKey2\"],\n          \"key\": \"key1\",\n          \"values\": [\"value1\",\"value2\"]\n        }\n    \n        \"\"\")\n      .build();\n    mockServer.addResponse(\"/api/push/sonarlint_events?projectKeys=projectKey1,projectKey2&languages=java,py\", mockResponse);\n\n    List<SonarServerEvent> receivedEvents = new CopyOnWriteArrayList<>();\n    underTest.subscribe(new LinkedHashSet<>(List.of(\"projectKey1\", \"projectKey2\")), new LinkedHashSet<>(List.of(SonarLanguage.JAVA, SonarLanguage.PYTHON)), receivedEvents::add);\n\n    assertThat(receivedEvents)\n      .extracting(\"projectKeys\", \"keyValues\")\n      .containsOnly(tuple(List.of(\"projectKey1\", \"projectKey2\"), Map.of(\"key1\", \"value1,value2\")));\n  }\n\n  @Test\n  @Disabled(\"Settings will be supported later\")\n  void should_notify_setting_changed_event_for_field_values_setting() {\n    var mockResponse = new MockResponse.Builder()\n      .body(\"\"\"\n        event: SettingChanged\n        data: {\n          \"projects\": [\"projectKey1\", \"projectKey2\"],\n          \"key\": \"key1\",\n          \"fieldValues\": [\n            {\n              \"key2\": \"value2\",\n              \"key3\": \"value3\"\n            },\n            {\n              \"key4\": \"value4\",\n              \"key5\": \"value5\"\n            }\n          ]\n        }\n    \n        \"\"\")\n      .build();\n    mockServer.addResponse(\"/api/push/sonarlint_events?projectKeys=projectKey1,projectKey2&languages=java,py\", mockResponse);\n\n    List<SonarServerEvent> receivedEvents = new CopyOnWriteArrayList<>();\n    underTest.subscribe(new LinkedHashSet<>(List.of(\"projectKey1\", \"projectKey2\")), new LinkedHashSet<>(List.of(SonarLanguage.JAVA, SonarLanguage.PYTHON)), receivedEvents::add);\n\n    assertThat(receivedEvents)\n      .extracting(\"projectKeys\", \"keyValues\")\n      .containsOnly(tuple(\n        List.of(\"projectKey1\", \"projectKey2\"),\n        Map.of(\"key1\", \"1,2\", \"key1.1.key2\", \"value2\", \"key1.1.key3\", \"value3\", \"key1.2.key4\", \"value4\", \"key1.2.key5\", \"value5\")));\n  }\n\n  @Test\n  void should_notify_rule_set_changed_event_without_impacts() {\n    var mockResponse = new MockResponse.Builder()\n      .body(\"\"\"\n        event: RuleSetChanged\n        data: {\\\n          \"projects\": [\"projectKey1\", \"projectKey2\"],\\\n          \"activatedRules\": [{\\\n            \"key\": \"java:S0000\",\\\n            \"severity\": \"MAJOR\",\\\n            \"params\": [{\\\n              \"key\": \"key1\",\\\n              \"value\": \"value1\"\\\n            }]\\\n          }],\\\n          \"deactivatedRules\": [\"java:S4321\"]\\\n        }\n        \n        \"\"\")\n      .build();\n    mockServer.addResponse(\"/api/push/sonarlint_events?projectKeys=projectKey1,projectKey2&languages=java,py\", mockResponse);\n\n    List<SonarServerEvent> receivedEvents = new CopyOnWriteArrayList<>();\n    underTest.subscribe(new LinkedHashSet<>(List.of(\"projectKey1\", \"projectKey2\")), new LinkedHashSet<>(List.of(SonarLanguage.JAVA, SonarLanguage.PYTHON)), receivedEvents::add);\n\n    await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(receivedEvents)\n      .extracting(\"projectKeys\", \"deactivatedRules\")\n      .containsOnly(tuple(List.of(\"projectKey1\", \"projectKey2\"), List.of(\"java:S4321\"))));\n    assertThat(receivedEvents)\n      .flatExtracting(\"activatedRules\")\n      .extracting(\"key\", \"severity\")\n      .containsOnly(tuple(\"java:S0000\", IssueSeverity.MAJOR));\n    assertThat(receivedEvents)\n      .flatExtracting(\"activatedRules\")\n      .extracting(\"parameters\")\n      .containsOnly(Map.of(\"key1\", \"value1\"));\n  }\n\n  @Test\n  void should_notify_rule_set_changed_event() {\n    var mockResponse = new MockResponse.Builder()\n      .body(\"\"\"\n        event: RuleSetChanged\n        data: {\\\n          \"projects\": [\"projectKey1\", \"projectKey2\"],\\\n          \"activatedRules\": [{\\\n            \"key\": \"java:S0000\",\\\n            \"severity\": \"MAJOR\",\\\n            \"params\": [{\\\n              \"key\": \"key1\",\\\n              \"value\": \"value1\"\\\n            }],\\\n            \"templateKey\": \"templateKey\",\\\n            \"impacts\": [{\\\n              \"softwareQuality\": \"SECURITY\",\\\n              \"severity\": \"HIGH\"\\\n            }]\\\n          }],\\\n          \"deactivatedRules\": [\"java:S4321\"]\\\n        }\n        \n        \"\"\")\n      .build();\n    mockServer.addResponse(\"/api/push/sonarlint_events?projectKeys=projectKey1,projectKey2&languages=java,py\", mockResponse);\n\n    List<SonarServerEvent> receivedEvents = new CopyOnWriteArrayList<>();\n    underTest.subscribe(new LinkedHashSet<>(List.of(\"projectKey1\", \"projectKey2\")), new LinkedHashSet<>(List.of(SonarLanguage.JAVA, SonarLanguage.PYTHON)), receivedEvents::add);\n\n    await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(receivedEvents)\n      .extracting(\"projectKeys\", \"deactivatedRules\")\n      .containsOnly(tuple(List.of(\"projectKey1\", \"projectKey2\"), List.of(\"java:S4321\"))));\n    assertThat(receivedEvents)\n      .flatExtracting(\"activatedRules\")\n      .extracting(\"key\", \"severity\")\n      .containsOnly(tuple(\"java:S0000\", IssueSeverity.MAJOR));\n    assertThat(receivedEvents)\n      .flatExtracting(\"activatedRules\")\n      .extracting(\"parameters\")\n      .containsOnly(Map.of(\"key1\", \"value1\"));\n  }\n\n  @Test\n  void should_not_notify_while_event_is_incomplete() {\n    var mockResponse = new MockResponse.Builder()\n      .body(\"\"\"\n        event: RuleSetChanged\n        data: {\\\n          \"projects\": [\"projectKey1\", \"projectKey2\"],\\\n          \"activatedRules\":\n        \"\"\")\n      .build();\n    mockServer.addResponse(\"/api/push/sonarlint_events?projectKeys=projectKey1,projectKey2&languages=java,py\", mockResponse);\n\n    List<SonarServerEvent> receivedEvents = new CopyOnWriteArrayList<>();\n    underTest.subscribe(new LinkedHashSet<>(List.of(\"projectKey1\", \"projectKey2\")), new LinkedHashSet<>(List.of(SonarLanguage.JAVA, SonarLanguage.PYTHON)), receivedEvents::add);\n\n    assertThat(receivedEvents).isEmpty();\n  }\n\n  @Test\n  void should_ignore_events_without_project_keys() {\n    var mockResponse = new MockResponse.Builder()\n      .body(\"\"\"\n        event: RuleSetChanged\n        data: {\\\n          \"activatedRules\": [\"java:S1234\"],\\\n          \"deactivatedRules\": [\"java:S4321\"],\\\n          \"changedRules\": [{\\\n            \"key\": \"java:S0000\",\\\n            \"overriddenSeverity\": \"MAJOR\",\\\n            \"params\": [{\\\n              \"key\": \"key1\",\\\n              \"value\": \"value1\"\\\n            }]\\\n          }]\\\n        }\n        \"\"\")\n      .build();\n    mockServer.addResponse(\"/api/push/sonarlint_events?projectKeys=projectKey1,projectKey2&languages=java,py\", mockResponse);\n\n    List<SonarServerEvent> receivedEvents = new CopyOnWriteArrayList<>();\n    underTest.subscribe(new LinkedHashSet<>(List.of(\"projectKey1\", \"projectKey2\")), new LinkedHashSet<>(List.of(SonarLanguage.JAVA, SonarLanguage.PYTHON)), receivedEvents::add);\n\n    assertThat(receivedEvents).isEmpty();\n  }\n\n  @Test\n  void should_ignore_unknown_events() {\n    var mockResponse = new MockResponse.Builder()\n      .body(\"\"\"\n        event: UnknownEvent\n        data: \"plop\"\n        \n        \"\"\")\n      .build();\n    mockServer.addResponse(\"/api/push/sonarlint_events?projectKeys=projectKey1,projectKey2&languages=java,py\", mockResponse);\n\n    List<SonarServerEvent> receivedEvents = new CopyOnWriteArrayList<>();\n    underTest.subscribe(new LinkedHashSet<>(List.of(\"projectKey1\", \"projectKey2\")), new LinkedHashSet<>(List.of(SonarLanguage.JAVA, SonarLanguage.PYTHON)), receivedEvents::add);\n\n    assertThat(receivedEvents).isEmpty();\n  }\n\n  @Test\n  void should_ignore_ruleset_changed_events_with_invalid_json() {\n    var mockResponse = new MockResponse.Builder()\n      .body(\"\"\"\n        event: RuleSetChanged\n        data: {]\n        \n        \"\"\")\n      .build();\n    mockServer.addResponse(\"/api/push/sonarlint_events?projectKeys=projectKey1,projectKey2&languages=java,py\", mockResponse);\n\n    List<SonarServerEvent> receivedEvents = new CopyOnWriteArrayList<>();\n    underTest.subscribe(new LinkedHashSet<>(List.of(\"projectKey1\", \"projectKey2\")), new LinkedHashSet<>(List.of(SonarLanguage.JAVA, SonarLanguage.PYTHON)), receivedEvents::add);\n\n    assertThat(receivedEvents).isEmpty();\n  }\n\n  @Test\n  void should_ignore_setting_changed_events_with_invalid_json() {\n    var mockResponse = new MockResponse.Builder()\n      .body(\"\"\"\n        event: SettingChanged\n        data: {]\n        \n        \"\"\")\n      .build();\n    mockServer.addResponse(\"/api/push/sonarlint_events?projectKeys=projectKey1,projectKey2&languages=java,py\", mockResponse);\n\n    List<SonarServerEvent> receivedEvents = new CopyOnWriteArrayList<>();\n    underTest.subscribe(new LinkedHashSet<>(List.of(\"projectKey1\", \"projectKey2\")), new LinkedHashSet<>(List.of(SonarLanguage.JAVA, SonarLanguage.PYTHON)), receivedEvents::add);\n\n    assertThat(receivedEvents).isEmpty();\n  }\n\n  @Test\n  void should_ignore_invalid_setting_changed_events() {\n    var mockResponse = new MockResponse.Builder()\n      .body(\"\"\"\n        event: SettingChanged\n        data: {\\\n          \"projects\": [\"projectKey1\", \"projectKey2\"],\\\n          \"key\": \"key1\",\\\n          \"value\": \"\",\\\n          \"values\": [],\\\n          \"fieldValues\": []\\\n        }\n        \n        \"\"\")\n      .build();\n    mockServer.addResponse(\"/api/push/sonarlint_events?projectKeys=projectKey1,projectKey2&languages=java,py\", mockResponse);\n\n    List<SonarServerEvent> receivedEvents = new CopyOnWriteArrayList<>();\n    underTest.subscribe(new LinkedHashSet<>(List.of(\"projectKey1\", \"projectKey2\")), new LinkedHashSet<>(List.of(SonarLanguage.JAVA, SonarLanguage.PYTHON)), receivedEvents::add);\n\n    assertThat(receivedEvents).isEmpty();\n  }\n\n  @Test\n  void should_notify_issue_changed_event_when_resolved_status_changed() {\n    var mockResponse = new MockResponse.Builder()\n      .body(\"\"\"\n        event: IssueChanged\n        data: {\\\n          \"projectKey\": \"projectKey1\",\\\n          \"issues\": [{\\\n            \"issueKey\": \"key1\",\\\n            \"branchName\": \"master\",\\\n            \"impacts\": [ { \"softwareQuality\": \"MAINTAINABILITY\", \"severity\": \"HIGH\" } ]\\\n          }],\\\n          \"resolved\": true\\\n        }\n        \n        \"\"\")\n      .build();\n    mockServer.addResponse(\"/api/push/sonarlint_events?projectKeys=projectKey1&languages=java,py\", mockResponse);\n\n    List<SonarServerEvent> receivedEvents = new CopyOnWriteArrayList<>();\n    underTest.subscribe(new LinkedHashSet<>(List.of(\"projectKey1\")), new LinkedHashSet<>(List.of(SonarLanguage.JAVA, SonarLanguage.PYTHON)), receivedEvents::add);\n\n    await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> {\n      assertThat(receivedEvents)\n        .asInstanceOf(InstanceOfAssertFactories.list(IssueChangedEvent.class))\n        .extracting(IssueChangedEvent::getResolved, IssueChangedEvent::getUserSeverity, IssueChangedEvent::getUserType)\n        .containsOnly(tuple(true, null, null));\n\n      assertThat(receivedEvents).isNotEmpty();\n      assertThat(((IssueChangedEvent) receivedEvents.get(0)).getImpactedIssues()).hasSize(1);\n      assertThat(((IssueChangedEvent) receivedEvents.get(0)).getImpactedIssues().get(0).getIssueKey()).isEqualTo(\"key1\");\n    });\n  }\n\n  @Test\n  void should_notify_issue_changed_event_when_severity_changed() {\n    var mockResponse = new MockResponse.Builder()\n      .body(\"\"\"\n        event: IssueChanged\n        data: {\\\n          \"projectKey\": \"projectKey1\",\\\n          \"issues\": [{\\\n            \"issueKey\": \"key1\",\\\n            \"branchName\": \"master\"\\\n          }],\\\n          \"userSeverity\": \"MAJOR\"\\\n        }\n        \n        \"\"\")\n      .build();\n    mockServer.addResponse(\"/api/push/sonarlint_events?projectKeys=projectKey1&languages=java,py\", mockResponse);\n\n    List<SonarServerEvent> receivedEvents = new CopyOnWriteArrayList<>();\n    underTest.subscribe(new LinkedHashSet<>(List.of(\"projectKey1\")), new LinkedHashSet<>(List.of(SonarLanguage.JAVA, SonarLanguage.PYTHON)), receivedEvents::add);\n\n    await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> {\n      assertThat(receivedEvents)\n        .asInstanceOf(InstanceOfAssertFactories.list(IssueChangedEvent.class))\n        .extracting(IssueChangedEvent::getResolved, IssueChangedEvent::getUserSeverity, IssueChangedEvent::getUserType)\n        .containsOnly(tuple(null, IssueSeverity.MAJOR, null));\n\n      assertThat(receivedEvents).isNotEmpty();\n      assertThat(((IssueChangedEvent) receivedEvents.get(0)).getImpactedIssues()).hasSize(1);\n      assertThat(((IssueChangedEvent) receivedEvents.get(0)).getImpactedIssues().get(0).getIssueKey()).isEqualTo(\"key1\");\n    });\n  }\n\n  @Test\n  void should_notify_issue_changed_event_when_type_changed() {\n    var mockResponse = new MockResponse.Builder()\n      .body(\"\"\"\n        event: IssueChanged\n        data: {\\\n          \"projectKey\": \"projectKey1\",\\\n          \"issues\": [{\\\n            \"issueKey\": \"key1\",\\\n            \"branchName\": \"master\"\\\n          }],\\\n          \"userType\": \"BUG\"\\\n        }\n        \n        \"\"\")\n      .build();\n    mockServer.addResponse(\"/api/push/sonarlint_events?projectKeys=projectKey1&languages=java,py\", mockResponse);\n\n    List<SonarServerEvent> receivedEvents = new CopyOnWriteArrayList<>();\n    underTest.subscribe(new LinkedHashSet<>(List.of(\"projectKey1\")), new LinkedHashSet<>(List.of(SonarLanguage.JAVA, SonarLanguage.PYTHON)), receivedEvents::add);\n\n    await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> {\n      assertThat(receivedEvents)\n        .asInstanceOf(InstanceOfAssertFactories.list(IssueChangedEvent.class))\n        .extracting(IssueChangedEvent::getResolved, IssueChangedEvent::getUserSeverity, IssueChangedEvent::getUserType)\n        .containsOnly(tuple(null, null, RuleType.BUG));\n\n      assertThat(receivedEvents).isNotEmpty();\n      assertThat(((IssueChangedEvent) receivedEvents.get(0)).getImpactedIssues()).hasSize(1);\n      assertThat(((IssueChangedEvent) receivedEvents.get(0)).getImpactedIssues().get(0).getIssueKey()).isEqualTo(\"key1\");\n    });\n  }\n\n  @Test\n  void should_not_notify_issue_changed_event_when_no_change_is_present() {\n    var mockResponse = new MockResponse.Builder()\n      .body(\"\"\"\n        event: IssueChangedEvent\n        data: {\\\n          \"projectKey\": \"projectKey1\",\\\n          \"issues\": [{\\\n            \"issueKey\": \"key1\",\\\n            \"branchName\": \"master\"\\\n          }]\\\n        }\n        \n        \"\"\")\n      .build();\n    mockServer.addResponse(\"/api/push/sonarlint_events?projectKeys=projectKey1&languages=java,py\", mockResponse);\n\n    List<SonarServerEvent> receivedEvents = new CopyOnWriteArrayList<>();\n    underTest.subscribe(new LinkedHashSet<>(List.of(\"projectKey1\")), new LinkedHashSet<>(List.of(SonarLanguage.JAVA, SonarLanguage.PYTHON)), receivedEvents::add);\n\n    assertThat(receivedEvents).isEmpty();\n  }\n\n  @Test\n  void should_notify_taint_vulnerability_raised_event() {\n    var mockResponse = new MockResponse.Builder()\n      .body(\"\"\"\n        event: TaintVulnerabilityRaised\n        data: {\\\n          \"key\": \"taintKey\",\\\n          \"projectKey\": \"projectKey1\",\\\n          \"branch\": \"branch\",\\\n          \"creationDate\": 123456789,\\\n          \"ruleKey\": \"javasecurity:S123\",\\\n          \"severity\": \"MAJOR\",\\\n          \"type\": \"VULNERABILITY\",\\\n          \"mainLocation\": {\\\n            \"filePath\": \"functions/taint.js\",\\\n            \"message\": \"blah blah\",\\\n            \"textRange\": {\\\n              \"startLine\": 17,\\\n              \"startLineOffset\": 10,\\\n              \"endLine\": 3,\\\n              \"endLineOffset\": 2,\\\n              \"hash\": \"hash\"\\\n            }\\\n          },\\\n          \"flows\": [{\\\n            \"locations\": [{\\\n              \"filePath\": \"functions/taint.js\",\\\n              \"message\": \"sink: tainted value is used to perform a security-sensitive operation\",\\\n              \"textRange\": {\\\n                \"startLine\": 17,\\\n                \"startLineOffset\": 10,\\\n                \"endLine\": 3,\\\n                \"endLineOffset\": 2,\\\n                \"hash\": \"hash1\"\\\n              }\\\n            },\\\n            {\\\n              \"filePath\": \"functions/taint2.js\",\\\n              \"message\": \"sink: tainted value is used to perform a security-sensitive operation\",\\\n              \"textRange\": {\\\n                \"startLine\": 18,\\\n                \"startLineOffset\": 11,\\\n                \"endLine\": 4,\\\n                \"endLineOffset\": 3,\\\n                \"hash\": \"hash2\"\\\n              }\\\n            }]\\\n          }]\\\n        }\n        \n        \"\"\")\n      .build();\n    mockServer.addResponse(\"/api/push/sonarlint_events?projectKeys=projectKey&languages=java,py\", mockResponse);\n\n    List<SonarServerEvent> receivedEvents = new CopyOnWriteArrayList<>();\n    underTest.subscribe(new LinkedHashSet<>(List.of(\"projectKey\")), new LinkedHashSet<>(List.of(SonarLanguage.JAVA, SonarLanguage.PYTHON)), receivedEvents::add);\n\n    await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(receivedEvents)\n      .extracting(\"key\", \"projectKey\", \"branchName\", \"creationDate\", \"ruleKey\", \"severity\", \"type\")\n      .containsOnly(tuple(\"taintKey\", \"projectKey1\", \"branch\", Instant.parse(\"1970-01-02T10:17:36.789Z\"), \"javasecurity:S123\", IssueSeverity.MAJOR, RuleType.VULNERABILITY)));\n    assertThat(receivedEvents)\n      .extracting(\"mainLocation\")\n      .extracting(\"filePath\", \"message\", \"textRange.startLine\", \"textRange.startLineOffset\", \"textRange.endLine\", \"textRange.endLineOffset\", \"textRange.hash\")\n      .containsOnly(tuple(Path.of(\"functions/taint.js\"), \"blah blah\", 17, 10, 3, 2, \"hash\"));\n    assertThat(receivedEvents)\n      .flatExtracting(\"flows\")\n      .flatExtracting(\"locations\")\n      .extracting(\"filePath\", \"message\", \"textRange.startLine\", \"textRange.startLineOffset\", \"textRange.endLine\", \"textRange.endLineOffset\", \"textRange.hash\")\n      .containsOnly(\n        tuple(Path.of(\"functions/taint.js\"), \"sink: tainted value is used to perform a security-sensitive operation\", 17, 10, 3, 2, \"hash1\"),\n        tuple(Path.of(\"functions/taint2.js\"), \"sink: tainted value is used to perform a security-sensitive operation\", 18, 11, 4, 3, \"hash2\"));\n  }\n\n  @Test\n  void should_notify_taint_vulnerability_raised_event_with_cct() {\n    var mockResponse = new MockResponse.Builder()\n      .body(\"\"\"\n        event: TaintVulnerabilityRaised\n        data: {\\\n          \"key\": \"taintKey\",\\\n          \"projectKey\": \"projectKey1\",\\\n          \"branch\": \"branch\",\\\n          \"creationDate\": 123456789,\\\n          \"ruleKey\": \"javasecurity:S123\",\\\n          \"severity\": \"MAJOR\",\\\n          \"type\": \"VULNERABILITY\",\\\n          \"cleanCodeAttribute\": \"TRUSTWORTHY\",\\\n          \"impacts\": [ { \"softwareQuality\": \"SECURITY\", \"severity\": \"HIGH\" } ],\\\n          \"type\": \"VULNERABILITY\",\\\n          \"mainLocation\": {\\\n            \"filePath\": \"functions/taint.js\",\\\n            \"message\": \"blah blah\",\\\n            \"textRange\": {\\\n              \"startLine\": 17,\\\n              \"startLineOffset\": 10,\\\n              \"endLine\": 3,\\\n              \"endLineOffset\": 2,\\\n              \"hash\": \"hash\"\\\n            }\\\n          },\\\n          \"flows\": [{\\\n            \"locations\": [{\\\n              \"filePath\": \"functions/taint.js\",\\\n              \"message\": \"sink: tainted value is used to perform a security-sensitive operation\",\\\n              \"textRange\": {\\\n                \"startLine\": 17,\\\n                \"startLineOffset\": 10,\\\n                \"endLine\": 3,\\\n                \"endLineOffset\": 2,\\\n                \"hash\": \"hash1\"\\\n              }\\\n            },\\\n            {\\\n              \"filePath\": \"functions/taint2.js\",\\\n              \"message\": \"sink: tainted value is used to perform a security-sensitive operation\",\\\n              \"textRange\": {\\\n                \"startLine\": 18,\\\n                \"startLineOffset\": 11,\\\n                \"endLine\": 4,\\\n                \"endLineOffset\": 3,\\\n                \"hash\": \"hash2\"\\\n              }\\\n            }]\\\n          }]\\\n        }\n        \n        \"\"\")\n      .build();\n    mockServer.addResponse(\"/api/push/sonarlint_events?projectKeys=projectKey&languages=java,py\", mockResponse);\n\n    List<SonarServerEvent> receivedEvents = new CopyOnWriteArrayList<>();\n    underTest.subscribe(new LinkedHashSet<>(List.of(\"projectKey\")), new LinkedHashSet<>(List.of(SonarLanguage.JAVA, SonarLanguage.PYTHON)), receivedEvents::add);\n\n    await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(receivedEvents)\n      .extracting(\"key\", \"projectKey\", \"branchName\", \"creationDate\", \"ruleKey\", \"severity\", \"type\", \"cleanCodeAttribute\", \"impacts\")\n      .containsOnly(tuple(\"taintKey\", \"projectKey1\", \"branch\", Instant.parse(\"1970-01-02T10:17:36.789Z\"), \"javasecurity:S123\", IssueSeverity.MAJOR, RuleType.VULNERABILITY, Optional.of(CleanCodeAttribute.TRUSTWORTHY), Map.of(SoftwareQuality.SECURITY, ImpactSeverity.HIGH))));\n  }\n\n  @Test\n  void should_notify_taint_vulnerability_closed_event() {\n    var mockResponse = new MockResponse.Builder()\n      .body(\"\"\"\n        event: TaintVulnerabilityClosed\n        data: {\\\n          \"projectKey\": \"projectKey1\",\\\n          \"key\": \"taintKey\"\\\n        }\n        \n        \"\"\")\n      .build();\n    mockServer.addResponse(\"/api/push/sonarlint_events?projectKeys=projectKey&languages=java,py\", mockResponse);\n\n    List<SonarServerEvent> receivedEvents = new CopyOnWriteArrayList<>();\n    underTest.subscribe(new LinkedHashSet<>(List.of(\"projectKey\")), new LinkedHashSet<>(List.of(SonarLanguage.JAVA, SonarLanguage.PYTHON)), receivedEvents::add);\n\n    await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(receivedEvents)\n      .extracting(\"taintIssueKey\")\n      .containsOnly(\"taintKey\"));\n  }\n\n  @Test\n  void should_notify_issue_changed_event_when_software_impacts_changed() {\n    var mockResponse = new MockResponse.Builder()\n      .body(\"\"\"\n        event: IssueChanged\n        data: {\\\n          \"projectKey\": \"projectKey1\",\\\n          \"issues\": [{\\\n            \"issueKey\": \"key1\",\\\n            \"branchName\": \"master\",\\\n            \"impacts\": [ { \"softwareQuality\": \"MAINTAINABILITY\", \"severity\": \"HIGH\" } ]\\\n          }],\\\n          \"resolved\": true\\\n        }\n        \n        \"\"\")\n      .build();\n    mockServer.addResponse(\"/api/push/sonarlint_events?projectKeys=projectKey1&languages=java,py\", mockResponse);\n\n    List<SonarServerEvent> receivedEvents = new CopyOnWriteArrayList<>();\n    underTest.subscribe(new LinkedHashSet<>(List.of(\"projectKey1\")), new LinkedHashSet<>(List.of(SonarLanguage.JAVA, SonarLanguage.PYTHON)), receivedEvents::add);\n\n    await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> {\n      assertThat(receivedEvents)\n        .asInstanceOf(InstanceOfAssertFactories.list(IssueChangedEvent.class))\n        .extracting(IssueChangedEvent::getResolved, IssueChangedEvent::getUserSeverity, IssueChangedEvent::getUserType)\n        .containsOnly(tuple(true, null, null));\n\n      assertThat(receivedEvents).isNotEmpty();\n      assertThat(((IssueChangedEvent) receivedEvents.get(0)).getImpactedIssues()).hasSize(1);\n      assertThat(((IssueChangedEvent) receivedEvents.get(0)).getImpactedIssues().get(0).getIssueKey()).isEqualTo(\"key1\");\n      assertThat(((IssueChangedEvent) receivedEvents.get(0)).getImpactedIssues().get(0).getBranchName()).isEqualTo(\"master\");\n      assertThat(((IssueChangedEvent) receivedEvents.get(0)).getImpactedIssues().get(0).getImpacts()).isNotEmpty();\n      assertThat(((IssueChangedEvent) receivedEvents.get(0)).getImpactedIssues().get(0).getImpacts()).containsKey(SoftwareQuality.MAINTAINABILITY);\n      assertThat(((IssueChangedEvent) receivedEvents.get(0)).getImpactedIssues().get(0).getImpacts()).containsValue(ImpactSeverity.HIGH);\n    });\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/push/parsing/SecurityHotspotChangedEventParserTest.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push.parsing;\n\nimport java.nio.file.Path;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.sonarsource.sonarlint.core.commons.HotspotReviewStatus.ACKNOWLEDGED;\nimport static org.sonarsource.sonarlint.core.commons.HotspotReviewStatus.SAFE;\nimport static org.sonarsource.sonarlint.core.commons.HotspotReviewStatus.TO_REVIEW;\n\nclass SecurityHotspotChangedEventParserTest {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  SecurityHotspotChangedEventParser parser = new SecurityHotspotChangedEventParser();\n  private static final String TEST_PAYLOAD_WITHOUT_KEY = \"\"\"\n    {\n      \"projectKey\": \"test\",\n      \"updateDate\": 1685007187000,\n      \"status\": \"REVIEWED\",\n      \"assignee\": \"AYfcq2moStCcBwCPm0uK\",\n      \"resolution\": \"ACKNOWLEDGED\",\n      \"filePath\": \"/project/path/to/file\"\n    }\"\"\";\n\n  private static final String TEST_PAYLOAD_WITHOUT_PROJECT_KEY = \"\"\"\n    {\n      \"key\": \"AYhSN6mVrRF_krvNbHl1\",\n      \"updateDate\": 1685007187000,\n      \"status\": \"REVIEWED\",\n      \"assignee\": \"AYfcq2moStCcBwCPm0uK\",\n      \"resolution\": \"ACKNOWLEDGED\",\n      \"filePath\": \"/project/path/to/file\"\n    }\"\"\";\n\n  private static final String TEST_PAYLOAD_WITHOUT_FILE_PATH = \"\"\"\n    {\n      \"key\": \"AYhSN6mVrRF_krvNbHl1\",\n      \"projectKey\": \"test\",\n      \"updateDate\": 1685007187000,\n      \"status\": \"REVIEWED\",\n      \"assignee\": \"AYfcq2moStCcBwCPm0uK\",\n      \"resolution\": \"ACKNOWLEDGED\"\n    }\"\"\";\n\n  private static final String VALID_PAYLOAD = \"\"\"\n    {\n      \"key\": \"AYhSN6mVrRF_krvNbHl1\",\n      \"projectKey\": \"test\",\n      \"updateDate\": 1685007187000,\n      \"status\": \"REVIEWED\",\n      \"assignee\": \"assigneeEmail\",\n      \"resolution\": \"ACKNOWLEDGED\",\n      \"filePath\": \"/project/path/to/file\"\n    }\"\"\";\n\n  @ParameterizedTest\n  @ValueSource(strings = {TEST_PAYLOAD_WITHOUT_KEY, TEST_PAYLOAD_WITHOUT_PROJECT_KEY, TEST_PAYLOAD_WITHOUT_FILE_PATH})\n  void shouldReturnEmptyOptionalWhenPayloadIsInvalid(String invalidPayload) {\n    var parseResult = parser.parse(invalidPayload);\n    assertThat(parseResult).isEmpty();\n  }\n\n  @Test\n  void shouldReturnChangeEventWhenPayloadIsValid() {\n    var parsedResult = parser.parse(VALID_PAYLOAD);\n    assertThat(parsedResult).isPresent();\n    assertThat(parsedResult.get().getAssignee()).isEqualTo(\"assigneeEmail\");\n    assertThat(parsedResult.get().getHotspotKey()).isEqualTo(\"AYhSN6mVrRF_krvNbHl1\");\n    assertThat(parsedResult.get().getStatus()).isEqualTo(ACKNOWLEDGED);\n    assertThat(parsedResult.get().getProjectKey()).isEqualTo(\"test\");\n    assertThat(parsedResult.get().getFilePath()).isEqualTo(Path.of(\"/project/path/to/file\"));\n  }\n\n  @Test\n  void shouldCorrectlyMapStatus() {\n    var payloadNoResolution = \"\"\"\n      {\n        \"key\": \"AYhSN6mVrRF_krvNbHl1\",\n        \"projectKey\": \"test\",\n        \"updateDate\": 1685007187000,\n        \"status\": \"TO_REVIEW\",\n        \"assignee\": \"assigneeEmail\",\n        \"filePath\": \"/project/path/to/file\"\n      }\"\"\";\n\n    var parsedResult = parser.parse(payloadNoResolution);\n    assertThat(parsedResult).isPresent();\n    assertThat(parsedResult.get().getStatus()).isEqualTo(TO_REVIEW);\n\n    var payloadSafe = \"\"\"\n      {\n        \"key\": \"AYhSN6mVrRF_krvNbHl1\",\n        \"projectKey\": \"test\",\n        \"updateDate\": 1685007187000,\n        \"status\": \"REVIEWED\",\n        \"assignee\": \"assigneeEmail\",\n        \"resolution\": \"SAFE\",\n        \"filePath\": \"/project/path/to/file\"\n      }\"\"\";\n\n    var parsedResult2 = parser.parse(payloadSafe);\n    assertThat(parsedResult2).isPresent();\n    assertThat(parsedResult2.get().getStatus()).isEqualTo(SAFE);\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/push/parsing/SecurityHotspotClosedEventParserTest.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push.parsing;\n\nimport java.nio.file.Path;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SecurityHotspotClosedEventParserTest {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  SecurityHotspotClosedEventParser parser = new SecurityHotspotClosedEventParser();\n  private static final String TEST_PAYLOAD_WITHOUT_KEY = \"\"\"\n    {\n      \"projectKey\": \"test\",\n      \"filePath\": \"/project/path/to/file\"\n    }\"\"\";\n\n  private static final String TEST_PAYLOAD_WITHOUT_PROJECT_KEY = \"\"\"\n    {\n      \"key\": \"AYhSN6mVrRF_krvNbHl1\",\n      \"filePath\": \"/project/path/to/file\"\n    }\"\"\";\n\n  private static final String VALID_PAYLOAD = \"\"\"\n    {\n      \"key\": \"AYhSN6mVrRF_krvNbHl1\",\n      \"projectKey\": \"test\",\n      \"filePath\": \"/project/path/to/file\"\n    }\"\"\";\n\n  @ParameterizedTest\n  @ValueSource(strings = {TEST_PAYLOAD_WITHOUT_KEY, TEST_PAYLOAD_WITHOUT_PROJECT_KEY})\n  void shouldReturnEmptyOptionalWhenPayloadIsInvalid(String invalidPayload) {\n    var parseResult = parser.parse(invalidPayload);\n    assertThat(parseResult).isEmpty();\n  }\n\n  @Test\n  void shouldReturnChangeEventWhenPayloadIsValid() {\n    var parsedResult = parser.parse(VALID_PAYLOAD);\n    assertThat(parsedResult).isPresent();\n    assertThat(parsedResult.get().getHotspotKey()).isEqualTo(\"AYhSN6mVrRF_krvNbHl1\");\n    assertThat(parsedResult.get().getProjectKey()).isEqualTo(\"test\");\n    assertThat(parsedResult.get().getFilePath()).isEqualTo(Path.of(\"/project/path/to/file\"));\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/push/parsing/SecurityHotspotRaisedEventParserTest.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.push.parsing;\n\nimport java.nio.file.Path;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SecurityHotspotRaisedEventParserTest {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  SecurityHotspotRaisedEventParser parser = new SecurityHotspotRaisedEventParser();\n  private static final String TEST_PAYLOAD_WITHOUT_KEY = \"\"\"\n    {\n      \"status\": \"TO_REVIEW\",\n      \"vulnerabilityProbability\": \"MEDIUM\",\n      \"creationDate\": 1685006550000,\n      \"mainLocation\": {\n        \"filePath\": \"src/main/java/org/example/Main.java\",\n        \"message\": \"Make sure that using this pseudorandom number generator is safe here.\",\n        \"textRange\": {\n          \"startLine\": 12,\n          \"startLineOffset\": 29,\n          \"endLine\": 12,\n          \"endLineOffset\": 36,\n          \"hash\": \"43b5c9175984c071f30b873fdce0a000\"\n        }\n      },\n      \"ruleKey\": \"java:S2245\",\n      \"projectKey\": \"test\",\n      \"branch\": \"some-branch\"\n    }\"\"\";\n\n  private static final String TEST_PAYLOAD_WITHOUT_BRANCH = \"\"\"\n    {\n      \"status\": \"TO_REVIEW\",\n      \"vulnerabilityProbability\": \"MEDIUM\",\n      \"creationDate\": 1685006550000,\n      \"mainLocation\": {\n        \"filePath\": \"src/main/java/org/example/Main.java\",\n        \"message\": \"Make sure that using this pseudorandom number generator is safe here.\",\n        \"textRange\": {\n          \"startLine\": 12,\n          \"startLineOffset\": 29,\n          \"endLine\": 12,\n          \"endLineOffset\": 36,\n          \"hash\": \"43b5c9175984c071f30b873fdce0a000\"\n        }\n      },\n      \"ruleKey\": \"java:S2245\",\n      \"key\": \"AYhSN6mVrRF_krvNbHl1\",\n      \"projectKey\": \"test\"\n    }\"\"\";\n\n  private static final String TEST_PAYLOAD_WITHOUT_PROJECT_KEY = \"\"\"\n    {\n      \"status\": \"TO_REVIEW\",\n      \"vulnerabilityProbability\": \"MEDIUM\",\n      \"creationDate\": 1685006550000,\n      \"mainLocation\": {\n        \"filePath\": \"src/main/java/org/example/Main.java\",\n        \"message\": \"Make sure that using this pseudorandom number generator is safe here.\",\n        \"textRange\": {\n          \"startLine\": 12,\n          \"startLineOffset\": 29,\n          \"endLine\": 12,\n          \"endLineOffset\": 36,\n          \"hash\": \"43b5c9175984c071f30b873fdce0a000\"\n        }\n      },\n      \"ruleKey\": \"java:S2245\",\n      \"key\": \"AYhSN6mVrRF_krvNbHl1\",\n      \"branch\": \"some-branch\"\n    }\"\"\";\n\n  private static final String TEST_PAYLOAD_WITHOUT_FILE_PATH = \"\"\"\n    {\n      \"status\": \"TO_REVIEW\",\n      \"vulnerabilityProbability\": \"MEDIUM\",\n      \"creationDate\": 1685006550000,\n      \"mainLocation\": {\n        \"message\": \"Make sure that using this pseudorandom number generator is safe here.\",\n        \"textRange\": {\n          \"startLine\": 12,\n          \"startLineOffset\": 29,\n          \"endLine\": 12,\n          \"endLineOffset\": 36,\n          \"hash\": \"43b5c9175984c071f30b873fdce0a000\"\n        }\n      },\n      \"ruleKey\": \"java:S2245\",\n      \"key\": \"AYhSN6mVrRF_krvNbHl1\",\n      \"projectKey\": \"test\",\n      \"branch\": \"some-branch\"\n    }\"\"\";\n\n  private static final String VALID_PAYLOAD = \"\"\"\n    {\n      \"status\": \"TO_REVIEW\",\n      \"vulnerabilityProbability\": \"MEDIUM\",\n      \"creationDate\": 1685006550000,\n      \"mainLocation\": {\n        \"filePath\": \"src/main/java/org/example/Main.java\",\n        \"message\": \"Make sure that using this pseudorandom number generator is safe here.\",\n        \"textRange\": {\n          \"startLine\": 12,\n          \"startLineOffset\": 29,\n          \"endLine\": 12,\n          \"endLineOffset\": 36,\n          \"hash\": \"43b5c9175984c071f30b873fdce0a000\"\n        }\n      },\n      \"ruleKey\": \"java:S2245\",\n      \"key\": \"AYhSN6mVrRF_krvNbHl1\",\n      \"projectKey\": \"test\",\n      \"branch\": \"some-branch\"\n    }\"\"\";\n\n  private static final String VALID_PAYLOAD_REVIEWED = \"\"\"\n    {\n      \"status\": \"REVIEWED\",\n      \"vulnerabilityProbability\": \"MEDIUM\",\n      \"creationDate\": 1685006550000,\n      \"mainLocation\": {\n        \"filePath\": \"src/main/java/org/example/Main.java\",\n        \"message\": \"Make sure that using this pseudorandom number generator is safe here.\",\n        \"textRange\": {\n          \"startLine\": 12,\n          \"startLineOffset\": 29,\n          \"endLine\": 12,\n          \"endLineOffset\": 36,\n          \"hash\": \"43b5c9175984c071f30b873fdce0a000\"\n        }\n      },\n      \"ruleKey\": \"java:S2245\",\n      \"key\": \"AYhSN6mVrRF_krvNbHl1\",\n      \"projectKey\": \"test\",\n      \"branch\": \"some-branch\"\n    }\"\"\";\n\n  @ParameterizedTest\n  @ValueSource(strings = {TEST_PAYLOAD_WITHOUT_KEY, TEST_PAYLOAD_WITHOUT_PROJECT_KEY, TEST_PAYLOAD_WITHOUT_FILE_PATH, TEST_PAYLOAD_WITHOUT_BRANCH})\n  void shouldReturnEmptyOptionalWhenPayloadIsInvalid(String invalidPayload) {\n    var parseResult = parser.parse(invalidPayload);\n    assertThat(parseResult).isEmpty();\n  }\n\n  @Test\n  void shouldReturnChangeEventWhenPayloadIsValid() {\n    var parsedResult = parser.parse(VALID_PAYLOAD);\n    assertThat(parsedResult).isPresent();\n    assertThat(parsedResult.get().getHotspotKey()).isEqualTo(\"AYhSN6mVrRF_krvNbHl1\");\n    assertThat(parsedResult.get().getStatus()).isEqualTo(HotspotReviewStatus.TO_REVIEW);\n    assertThat(parsedResult.get().getProjectKey()).isEqualTo(\"test\");\n    assertThat(parsedResult.get().getMainLocation().getFilePath()).isEqualTo(Path.of(\"src/main/java/org/example/Main.java\"));\n    assertThat(parsedResult.get().getBranch()).isEqualTo(\"some-branch\");\n    assertThat(parsedResult.get().getRuleKey()).isEqualTo(\"java:S2245\");\n    assertThat(parsedResult.get().getMainLocation().getMessage()).isEqualTo(\"Make sure that using this pseudorandom number generator is safe here.\");\n  }\n\n  @Test\n  void shouldReturnChangeEventWhenPayloadIsValidAndHotspotIsReviewed() {\n    var parsedResult = parser.parse(VALID_PAYLOAD_REVIEWED);\n    assertThat(parsedResult).isPresent();\n    assertThat(parsedResult.get().getHotspotKey()).isEqualTo(\"AYhSN6mVrRF_krvNbHl1\");\n    assertThat(parsedResult.get().getStatus()).isEqualTo(HotspotReviewStatus.SAFE);\n    assertThat(parsedResult.get().getProjectKey()).isEqualTo(\"test\");\n    assertThat(parsedResult.get().getMainLocation().getFilePath()).isEqualTo(Path.of(\"src/main/java/org/example/Main.java\"));\n    assertThat(parsedResult.get().getBranch()).isEqualTo(\"some-branch\");\n    assertThat(parsedResult.get().getRuleKey()).isEqualTo(\"java:S2245\");\n    assertThat(parsedResult.get().getMainLocation().getMessage()).isEqualTo(\"Make sure that using this pseudorandom number generator is safe here.\");\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/qualityprofile/QualityProfileApiTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.qualityprofile;\n\nimport mockwebserver3.MockResponse;\nimport okhttp3.Headers;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.MockWebServerExtensionWithProtobuf;\nimport org.sonarsource.sonarlint.core.serverapi.exception.ProjectNotFoundException;\nimport org.sonarsource.sonarlint.core.serverapi.exception.ServerErrorException;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Qualityprofiles;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass QualityProfileApiTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n\n  @Test\n  void should_throw_when_the_endpoint_is_not_found() {\n    var underTest = new QualityProfileApi(mockServer.serverApiHelper());\n\n    mockServer.addResponse(\"/api/qualityprofiles/search.protobuf?project=projectKey\", new MockResponse(404, Headers.EMPTY, \"\"));\n\n    var cancelMonitor = new SonarLintCancelMonitor();\n    assertThrows(ProjectNotFoundException.class, () -> underTest.getQualityProfiles(\"projectKey\", cancelMonitor));\n  }\n\n  @Test\n  void should_throw_when_a_server_error_occurs() {\n    var underTest = new QualityProfileApi(mockServer.serverApiHelper());\n\n    mockServer.addResponse(\"/api/qualityprofiles/search.protobuf?project=projectKey\", new MockResponse(503, Headers.EMPTY, \"\"));\n\n    var cancelMonitor = new SonarLintCancelMonitor();\n    assertThrows(ServerErrorException.class, () -> underTest.getQualityProfiles(\"projectKey\", cancelMonitor));\n  }\n\n  @Test\n  void should_return_the_quality_profiles_of_a_given_project() {\n    var underTest = new QualityProfileApi(mockServer.serverApiHelper());\n\n    mockServer.addProtobufResponse(\"/api/qualityprofiles/search.protobuf?project=projectKey\", Qualityprofiles.SearchWsResponse.newBuilder()\n      .addProfiles(Qualityprofiles.SearchWsResponse.QualityProfile.newBuilder()\n        .setIsDefault(true)\n        .setKey(\"profileKey\")\n        .setName(\"profileName\")\n        .setLanguage(\"lang\")\n        .setLanguageName(\"langName\")\n        .setActiveRuleCount(12)\n        .setRulesUpdatedAt(\"rulesUpdatedAt\")\n        .setUserUpdatedAt(\"userUpdatedAt\")\n        .build())\n      .build());\n\n    var qualityProfiles = underTest.getQualityProfiles(\"projectKey\", new SonarLintCancelMonitor());\n\n    assertThat(qualityProfiles)\n      .extracting(\"default\", \"key\", \"name\", \"language\", \"languageName\", \"activeRuleCount\", \"rulesUpdatedAt\", \"userUpdatedAt\")\n      .containsOnly(tuple(true, \"profileKey\", \"profileName\", \"lang\", \"langName\", 12L, \"rulesUpdatedAt\", \"userUpdatedAt\"));\n\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/rules/RulesApiTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.rules;\n\nimport java.util.Map;\nimport java.util.Optional;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.MockWebServerExtensionWithProtobuf;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Rules;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\n\nclass RulesApiTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n\n  @Test\n  void logErrorParsingRuleDescription() {\n    mockServer.addStringResponse(\"/api/rules/show.protobuf?key=java:S1234\", \"trash\");\n\n    var rulesApi = new RulesApi(mockServer.serverApiHelper());\n\n    assertThat(rulesApi.getRule(\"java:S1234\", new SonarLintCancelMonitor())).isEmpty();\n\n    assertThat(logTester.logs()).contains(\"Error when fetching rule 'java:S1234'\");\n  }\n\n  @Test\n  void should_get_rule() {\n    mockServer.addProtobufResponse(\"/api/rules/show.protobuf?key=java:S1234\",\n      Rules.ShowResponse.newBuilder().setRule(\n          Rules.Rule.newBuilder()\n            .setName(\"name\")\n            .setSeverity(\"MINOR\")\n            .setType(Common.RuleType.VULNERABILITY)\n            .setLang(SonarLanguage.PYTHON.getSonarLanguageKey())\n            .setHtmlNote(\"htmlNote\")\n            .setDescriptionSections(Rules.Rule.DescriptionSections.newBuilder()\n              .addDescriptionSections(Rules.Rule.DescriptionSection.newBuilder()\n                .setKey(\"default\")\n                .setContent(\"desc\")\n                .build())\n              .build())\n            .setCleanCodeAttribute(Common.CleanCodeAttribute.COMPLETE)\n            .setImpacts(Rules.Rule.Impacts.newBuilder().addImpacts(Common.Impact.newBuilder().setSeverity(Common.ImpactSeverity.HIGH).setSoftwareQuality(Common.SoftwareQuality.MAINTAINABILITY).build()).build())\n            .build())\n        .build());\n\n    var rulesApi = new RulesApi(mockServer.serverApiHelper());\n\n    var rule = rulesApi.getRule(\"java:S1234\", new SonarLintCancelMonitor()).get();\n\n    assertThat(rule).extracting(\"name\", \"severity\", \"type\", \"language\", \"htmlNote\", \"cleanCodeAttribute\", \"impacts\")\n      .contains(\"name\", IssueSeverity.MINOR, RuleType.VULNERABILITY, SonarLanguage.PYTHON, \"htmlNote\", CleanCodeAttribute.COMPLETE, Map.of(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.HIGH));\n    assertThat(rule.getDescriptionSections().get(0).getHtmlContent()).isEqualTo(\"desc\");\n  }\n\n  @Test\n  void should_get_rule_with_description_sections() {\n    mockServer.addProtobufResponse(\"/api/rules/show.protobuf?key=java:S1234\",\n      Rules.ShowResponse.newBuilder().setRule(\n          Rules.Rule.newBuilder()\n            .setName(\"name\")\n            .setSeverity(\"MINOR\")\n            .setType(Common.RuleType.VULNERABILITY)\n            .setLang(SonarLanguage.PYTHON.getSonarLanguageKey())\n            .setDescriptionSections(Rules.Rule.DescriptionSections.newBuilder()\n              .addDescriptionSections(Rules.Rule.DescriptionSection.newBuilder().setKey(\"sectionKey\").setContent(\"htmlContent\").build())\n              .addDescriptionSections(\n                Rules.Rule.DescriptionSection.newBuilder().setKey(\"sectionKey2\").setContent(\"htmlContent2\").setContext(Rules.Rule.DescriptionSection.Context.newBuilder()\n                  .setKey(\"contextKey\").setDisplayName(\"displayName\").build()).build())\n              .build())\n            .setHtmlNote(\"htmlNote\")\n            .setCleanCodeAttribute(Common.CleanCodeAttribute.CONVENTIONAL)\n            .setImpacts(Rules.Rule.Impacts.newBuilder().addImpacts(Common.Impact.newBuilder().setSeverity(Common.ImpactSeverity.LOW).setSoftwareQuality(Common.SoftwareQuality.RELIABILITY).build()).build())\n            .build())\n        .build());\n\n    var rulesApi = new RulesApi(mockServer.serverApiHelper());\n\n    var rule = rulesApi.getRule(\"java:S1234\", new SonarLintCancelMonitor()).get();\n\n    assertThat(rule).extracting(\"name\", \"severity\", \"type\", \"language\",\"htmlNote\", \"cleanCodeAttribute\", \"impacts\")\n      .contains(\"name\", IssueSeverity.MINOR, RuleType.VULNERABILITY, SonarLanguage.PYTHON, \"htmlNote\", CleanCodeAttribute.CONVENTIONAL, Map.of(SoftwareQuality.RELIABILITY, ImpactSeverity.LOW));\n\n    var sections = rule.getDescriptionSections();\n    assertThat(sections).hasSize(2);\n    assertThat(sections.get(0)).extracting(\"key\", \"htmlContent\", \"context\")\n      .containsExactly(\"sectionKey\", \"htmlContent\", Optional.empty());\n    assertThat(sections.get(1)).extracting(\"key\", \"htmlContent\")\n      .containsExactly(\"sectionKey2\", \"htmlContent2\");\n    assertThat(sections.get(1).getContext()).hasValueSatisfying(context -> {\n      assertThat(context.getKey()).isEqualTo(\"contextKey\");\n      assertThat(context.getDisplayName()).isEqualTo(\"displayName\");\n    });\n\n  }\n\n  @Test\n  void should_get_rule_from_organization() {\n    mockServer.addProtobufResponse(\"/api/rules/show.protobuf?key=java:S1234&organization=orgKey\",\n      Rules.ShowResponse.newBuilder().setRule(\n          Rules.Rule.newBuilder()\n            .setName(\"name\")\n            .setSeverity(\"MAJOR\")\n            .setType(Common.RuleType.VULNERABILITY)\n            .setLang(SonarLanguage.PYTHON.getSonarLanguageKey())\n            .setHtmlNote(\"htmlNote\")\n            .setDescriptionSections(Rules.Rule.DescriptionSections.newBuilder()\n              .addDescriptionSections(Rules.Rule.DescriptionSection.newBuilder()\n                .setKey(\"default\")\n                .setContent(\"desc\")\n                  .build())\n              .build())\n            .build())\n        .build());\n\n    var rulesApi = new RulesApi(mockServer.serverApiHelper(\"orgKey\"));\n\n    var rule = rulesApi.getRule(\"java:S1234\", new SonarLintCancelMonitor()).get();\n\n    assertThat(rule).extracting(\"name\", \"severity\", \"type\", \"language\", \"htmlNote\")\n      .contains(\"name\", IssueSeverity.MAJOR, RuleType.VULNERABILITY, SonarLanguage.PYTHON, \"htmlNote\");\n    assertThat(rule.getDescriptionSections().get(0).getHtmlContent()).isEqualTo(\"desc\");\n  }\n\n  @Test\n  void should_get_active_rules_of_a_given_quality_profile() {\n    mockServer.addProtobufResponse(\n      \"/api/rules/search.protobuf?qprofile=QPKEY%2B&organization=orgKey&activation=true&f=templateKey,actives&types=CODE_SMELL,BUG,VULNERABILITY,SECURITY_HOTSPOT&s=key&ps=500&p=1\",\n      Rules.SearchResponse.newBuilder()\n        .setPaging(Common.Paging.newBuilder().setTotal(2).build())\n        .addRules(Rules.Rule.newBuilder().setKey(\"repo:key_with_template\").setTemplateKey(\"template\").build())\n        .addRules(Rules.Rule.newBuilder().setKey(\"repo:key\").build())\n        .setActives(\n          Rules.Actives.newBuilder()\n            .putActives(\"repo:key_with_template\", Rules.ActiveList.newBuilder().addActiveList(\n                Rules.Active.newBuilder()\n                  .setSeverity(\"MAJOR\")\n                  .addParams(Rules.Active.Param.newBuilder().setKey(\"paramKey\").setValue(\"paramValue\").build())\n                  .build())\n              .build())\n            .putActives(\"repo:key\", Rules.ActiveList.newBuilder().addActiveList(\n                Rules.Active.newBuilder()\n                  .setSeverity(\"MINOR\")\n                  .build())\n              .build())\n            .build())\n        .build());\n\n    var rulesApi = new RulesApi(mockServer.serverApiHelper(\"orgKey\"));\n\n    var activeRules = rulesApi.getAllActiveRules(\"QPKEY+\", new SonarLintCancelMonitor());\n\n    assertThat(activeRules)\n      .extracting(\"ruleKey\", \"severity\", \"templateKey\", \"params\")\n      .containsOnly(tuple(\"repo:key\", IssueSeverity.MINOR, \"\", Map.of()),\n        tuple(\"repo:key_with_template\", IssueSeverity.MAJOR, \"template\", Map.of(\"paramKey\", \"paramValue\")));\n  }\n\n  @Test\n  void should_fallback_on_deprecated_pagination_for_sonarqube_older_than_9_8() {\n    mockServer.addProtobufResponse(\n      \"/api/rules/search.protobuf?qprofile=QPKEY%2B&organization=orgKey&activation=true&f=templateKey,actives&types=CODE_SMELL,BUG,VULNERABILITY,SECURITY_HOTSPOT&s=key&ps=500&p=1\",\n      Rules.SearchResponse.newBuilder()\n        .setTotal(501)\n        .addRules(Rules.Rule.newBuilder().setKey(\"repo:key1\").build())\n        .setActives(\n          Rules.Actives.newBuilder()\n            .putActives(\"repo:key1\", Rules.ActiveList.newBuilder().addActiveList(\n                Rules.Active.newBuilder()\n                  .setSeverity(\"MINOR\")\n                  .build())\n              .build())\n            .build())\n        .build());\n    mockServer.addProtobufResponse(\n      \"/api/rules/search.protobuf?qprofile=QPKEY%2B&organization=orgKey&activation=true&f=templateKey,actives&types=CODE_SMELL,BUG,VULNERABILITY,SECURITY_HOTSPOT&s=key&ps=500&p=2\",\n      Rules.SearchResponse.newBuilder()\n        .setTotal(501)\n        .addRules(Rules.Rule.newBuilder().setKey(\"repo:key2\").build())\n        .setActives(\n          Rules.Actives.newBuilder()\n            .putActives(\"repo:key2\", Rules.ActiveList.newBuilder().addActiveList(\n                Rules.Active.newBuilder()\n                  .setSeverity(\"MAJOR\")\n                  .build())\n              .build())\n            .build())\n        .build());\n\n    var rulesApi = new RulesApi(mockServer.serverApiHelper(\"orgKey\"));\n\n    var activeRules = rulesApi.getAllActiveRules(\"QPKEY+\", new SonarLintCancelMonitor());\n\n    assertThat(activeRules).extracting(ServerActiveRule::getRuleKey).containsExactlyInAnyOrder(\"repo:key1\", \"repo:key2\");\n  }\n\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/sca/ScaApiTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.sca;\n\nimport java.util.UUID;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.MockWebServerExtensionWithProtobuf;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass ScaApiTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n\n  private static final String EMPTY_ISSUES_RELEASES_JSON = \"\"\"\n    {\n      \"issuesReleases\": [],\n      \"page\": {\n        \"pageIndex\": 1,\n        \"pageSize\": 100,\n        \"total\": 0\n      }\n    }\n    \"\"\";\n\n  private ScaApi scaApi;\n\n  @Nested\n  class SonarQubeServer {\n\n    @BeforeEach\n    void prepare() {\n      scaApi = new ScaApi(mockServer.serverApiHelper());\n    }\n\n    @Test\n    void should_get_issues_releases_with_empty_response() {\n      mockServer.addStringResponse(\"/api/v2/sca/issues-releases?projectKey=my-project&branchKey=main&pageSize=500&pageIndex=1\", EMPTY_ISSUES_RELEASES_JSON);\n\n      var response = scaApi.getIssuesReleases(\"my-project\", \"main\", new SonarLintCancelMonitor());\n\n      assertThat(response.issuesReleases()).isEmpty();\n    }\n\n    @Test\n    void should_get_issues_releases_of_vulnerability_type() {\n      var uuid = UUID.randomUUID();\n      var jsonResponse = String.format(\"\"\"\n      {\n        \"issuesReleases\": [\n          {\n            \"key\": \"%s\",\n            \"type\": \"VULNERABILITY\",\n            \"severity\": \"HIGH\",\n            \"quality\": \"MAINTAINABILITY\",\n            \"vulnerabilityId\": \"CVE-2023-12345\",\n            \"cvssScore\": \"7.5\",\n            \"release\": {\n              \"packageName\": \"com.example.vulnerable\",\n              \"version\": \"1.0.0\"\n            },\n            \"transitions\": [\"CONFIRM\", \"REOPEN\"]\n          }\n        ],\n        \"page\": {\n          \"pageIndex\": 1,\n          \"pageSize\": 100,\n          \"total\": 1\n        }\n      }\n      \"\"\", uuid);\n      mockServer.addStringResponse(\"/api/v2/sca/issues-releases?projectKey=test-project&branchKey=feature%2Fmy-branch&pageSize=500&pageIndex=1\", jsonResponse);\n\n      var response = scaApi.getIssuesReleases(\"test-project\", \"feature/my-branch\", new SonarLintCancelMonitor());\n\n      assertThat(response.issuesReleases()).hasSize(1);\n      var issueRelease = response.issuesReleases().get(0);\n      assertThat(issueRelease.key()).isEqualTo(uuid);\n      assertThat(issueRelease.type()).isEqualTo(GetIssuesReleasesResponse.IssuesRelease.Type.VULNERABILITY);\n      assertThat(issueRelease.severity()).isEqualTo(GetIssuesReleasesResponse.IssuesRelease.Severity.HIGH);\n      assertThat(issueRelease.quality()).isEqualTo(GetIssuesReleasesResponse.IssuesRelease.SoftwareQuality.MAINTAINABILITY);\n      assertThat(issueRelease.vulnerabilityId()).isEqualTo(\"CVE-2023-12345\");\n      assertThat(issueRelease.cvssScore()).isEqualTo(\"7.5\");\n      assertThat(issueRelease.release().packageName()).isEqualTo(\"com.example.vulnerable\");\n      assertThat(issueRelease.release().version()).isEqualTo(\"1.0.0\");\n      assertThat(issueRelease.transitions()).containsExactly(\n        GetIssuesReleasesResponse.IssuesRelease.Transition.CONFIRM,\n        GetIssuesReleasesResponse.IssuesRelease.Transition.REOPEN);\n    }\n\n    @Test\n    void should_get_issues_releases_of_prohibited_license_type() {\n      var uuid = UUID.randomUUID();\n      var jsonResponse = String.format(\"\"\"\n      {\n        \"issuesReleases\": [\n          {\n            \"key\": \"%s\",\n            \"type\": \"PROHIBITED_LICENSE\",\n            \"severity\": \"BLOCKER\",\n            \"quality\": \"SECURITY\",\n            \"vulnerabilityId\": null,\n            \"cvssScore\": null,\n            \"release\": {\n              \"packageName\": \"com.example.prohibited\",\n              \"version\": \"2.1.0\"\n            },\n            \"transitions\": [\"ACCEPT\", \"SAFE\"]\n          }\n        ],\n        \"page\": {\n          \"pageIndex\": 1,\n          \"pageSize\": 100,\n          \"total\": 1\n        }\n      }\n      \"\"\", uuid);\n      mockServer.addStringResponse(\"/api/v2/sca/issues-releases?projectKey=license-project&branchKey=develop&pageSize=500&pageIndex=1\", jsonResponse);\n\n      var response = scaApi.getIssuesReleases(\"license-project\", \"develop\", new SonarLintCancelMonitor());\n\n      assertThat(response.issuesReleases()).hasSize(1);\n      var issueRelease = response.issuesReleases().get(0);\n      assertThat(issueRelease.key()).isEqualTo(uuid);\n      assertThat(issueRelease.type()).isEqualTo(GetIssuesReleasesResponse.IssuesRelease.Type.PROHIBITED_LICENSE);\n      assertThat(issueRelease.severity()).isEqualTo(GetIssuesReleasesResponse.IssuesRelease.Severity.BLOCKER);\n      assertThat(issueRelease.quality()).isEqualTo(GetIssuesReleasesResponse.IssuesRelease.SoftwareQuality.SECURITY);\n      assertThat(issueRelease.vulnerabilityId()).isNull();\n      assertThat(issueRelease.cvssScore()).isNull();\n      assertThat(issueRelease.release().packageName()).isEqualTo(\"com.example.prohibited\");\n      assertThat(issueRelease.release().version()).isEqualTo(\"2.1.0\");\n      assertThat(issueRelease.transitions()).containsExactly(\n        GetIssuesReleasesResponse.IssuesRelease.Transition.ACCEPT,\n        GetIssuesReleasesResponse.IssuesRelease.Transition.SAFE);\n    }\n\n    @Test\n    void should_get_issues_releases_with_multiple_issues() {\n      var uuid1 = UUID.randomUUID();\n      var uuid2 = UUID.randomUUID();\n      var jsonResponse = String.format(\"\"\"\n      {\n        \"issuesReleases\": [\n          {\n            \"key\": \"%s\",\n            \"type\": \"VULNERABILITY\",\n            \"severity\": \"MEDIUM\",\n            \"quality\": \"RELIABILITY\",\n            \"vulnerabilityId\": \"CVE-2023-12345\",\n            \"cvssScore\": \"7.5\",\n            \"release\": {\n              \"packageName\": \"com.example.first\",\n              \"version\": \"1.0.0\"\n            },\n            \"transitions\": [\"CONFIRM\"]\n          },\n          {\n            \"key\": \"%s\",\n            \"type\": \"PROHIBITED_LICENSE\",\n            \"severity\": \"LOW\",\n            \"quality\": \"MAINTAINABILITY\",\n            \"vulnerabilityId\": null,\n            \"cvssScore\": null,\n            \"release\": {\n              \"packageName\": \"com.example.second\",\n              \"version\": \"2.0.0\"\n            },\n            \"transitions\": [\"ACCEPT\", \"SAFE\", \"FIXED\"]\n          }\n        ],\n        \"page\": {\n          \"pageIndex\": 1,\n          \"pageSize\": 100,\n          \"total\": 2\n        }\n      }\n      \"\"\", uuid1, uuid2);\n      mockServer.addStringResponse(\"/api/v2/sca/issues-releases?projectKey=multi-project&branchKey=master&pageSize=500&pageIndex=1\", jsonResponse);\n\n      var response = scaApi.getIssuesReleases(\"multi-project\", \"master\", new SonarLintCancelMonitor());\n\n      assertThat(response.issuesReleases()).hasSize(2);\n\n      var firstIssue = response.issuesReleases().get(0);\n      assertThat(firstIssue.key()).isEqualTo(uuid1);\n      assertThat(firstIssue.type()).isEqualTo(GetIssuesReleasesResponse.IssuesRelease.Type.VULNERABILITY);\n      assertThat(firstIssue.severity()).isEqualTo(GetIssuesReleasesResponse.IssuesRelease.Severity.MEDIUM);\n      assertThat(firstIssue.quality()).isEqualTo(GetIssuesReleasesResponse.IssuesRelease.SoftwareQuality.RELIABILITY);\n      assertThat(firstIssue.vulnerabilityId()).isEqualTo(\"CVE-2023-12345\");\n      assertThat(firstIssue.cvssScore()).isEqualTo(\"7.5\");\n      assertThat(firstIssue.release().packageName()).isEqualTo(\"com.example.first\");\n      assertThat(firstIssue.release().version()).isEqualTo(\"1.0.0\");\n      assertThat(firstIssue.transitions()).containsExactly(GetIssuesReleasesResponse.IssuesRelease.Transition.CONFIRM);\n\n      var secondIssue = response.issuesReleases().get(1);\n      assertThat(secondIssue.key()).isEqualTo(uuid2);\n      assertThat(secondIssue.type()).isEqualTo(GetIssuesReleasesResponse.IssuesRelease.Type.PROHIBITED_LICENSE);\n      assertThat(secondIssue.severity()).isEqualTo(GetIssuesReleasesResponse.IssuesRelease.Severity.LOW);\n      assertThat(secondIssue.quality()).isEqualTo(GetIssuesReleasesResponse.IssuesRelease.SoftwareQuality.MAINTAINABILITY);\n      assertThat(secondIssue.vulnerabilityId()).isNull();\n      assertThat(secondIssue.cvssScore()).isNull();\n      assertThat(secondIssue.release().packageName()).isEqualTo(\"com.example.second\");\n      assertThat(secondIssue.release().version()).isEqualTo(\"2.0.0\");\n      assertThat(secondIssue.transitions()).containsExactly(\n        GetIssuesReleasesResponse.IssuesRelease.Transition.ACCEPT,\n        GetIssuesReleasesResponse.IssuesRelease.Transition.SAFE,\n        GetIssuesReleasesResponse.IssuesRelease.Transition.FIXED);\n    }\n\n    @Test\n    void should_handle_special_characters_in_project_key_and_branch_name() {\n      mockServer.addStringResponse(\"/api/v2/sca/issues-releases?projectKey=my%3Aproject%2Bkey&branchKey=feature%2Fmy-branch%3Atest&pageSize=500&pageIndex=1\", EMPTY_ISSUES_RELEASES_JSON);\n\n      var response = scaApi.getIssuesReleases(\"my:project+key\", \"feature/my-branch:test\", new SonarLintCancelMonitor());\n\n      assertThat(response.issuesReleases()).isEmpty();\n    }\n\n    @Test\n    void should_handle_malformed_json_response() {\n      mockServer.addStringResponse(\"/api/v2/sca/issues-releases?projectKey=test&branchName=main&pageSize=500&pageIndex=1\", \"invalid json\");\n\n      assertThatThrownBy(() -> scaApi.getIssuesReleases(\"test\", \"main\", new SonarLintCancelMonitor()))\n        .isInstanceOf(Exception.class);\n    }\n\n    @Test\n    void should_handle_empty_transitions() {\n      var uuid = UUID.randomUUID();\n      var jsonResponse = String.format(\"\"\"\n      {\n        \"issuesReleases\": [\n          {\n            \"key\": \"%s\",\n            \"type\": \"VULNERABILITY\",\n            \"severity\": \"INFO\",\n            \"release\": {\n              \"packageName\": \"com.example.minimal\",\n              \"version\": \"0.1.0\"\n            },\n            \"transitions\": []\n          }\n        ],\n        \"page\": {\n          \"pageIndex\": 1,\n          \"pageSize\": 100,\n          \"total\": 1\n        }\n      }\n      \"\"\", uuid);\n      mockServer.addStringResponse(\"/api/v2/sca/issues-releases?projectKey=minimal-project&branchKey=main&pageSize=500&pageIndex=1\", jsonResponse);\n\n      var response = scaApi.getIssuesReleases(\"minimal-project\", \"main\", new SonarLintCancelMonitor());\n\n      assertThat(response.issuesReleases()).hasSize(1);\n      var issueRelease = response.issuesReleases().get(0);\n      assertThat(issueRelease.key()).isEqualTo(uuid);\n      assertThat(issueRelease.type()).isEqualTo(GetIssuesReleasesResponse.IssuesRelease.Type.VULNERABILITY);\n      assertThat(issueRelease.severity()).isEqualTo(GetIssuesReleasesResponse.IssuesRelease.Severity.INFO);\n      assertThat(issueRelease.transitions()).isEmpty();\n    }\n  }\n\n  @Nested\n  class SonarQubeCloud {\n\n    @BeforeEach\n    void prepare() {\n      scaApi = new ScaApi(mockServer.serverApiHelper(\"orgKey\"));\n    }\n\n    @Test\n    void should_get_issues_releases_with_empty_response() {\n      mockServer.addStringResponse(\"/sca/issues-releases?projectKey=my-project&branchKey=main&pageSize=500&pageIndex=1\", EMPTY_ISSUES_RELEASES_JSON);\n\n      var response = scaApi.getIssuesReleases(\"my-project\", \"main\", new SonarLintCancelMonitor());\n\n      assertThat(response.issuesReleases()).isEmpty();\n    }\n\n    @Test\n    void should_get_issues_releases_of_vulnerability_type() {\n      var uuid = UUID.randomUUID();\n      var jsonResponse = String.format(\"\"\"\n      {\n        \"issuesReleases\": [\n          {\n            \"key\": \"%s\",\n            \"type\": \"VULNERABILITY\",\n            \"severity\": \"HIGH\",\n            \"quality\": \"MAINTAINABILITY\",\n            \"vulnerabilityId\": \"CVE-2023-12345\",\n            \"cvssScore\": \"7.5\",\n            \"release\": {\n              \"packageName\": \"com.example.vulnerable\",\n              \"version\": \"1.0.0\"\n            },\n            \"transitions\": [\"CONFIRM\", \"REOPEN\"]\n          }\n        ],\n        \"page\": {\n          \"pageIndex\": 1,\n          \"pageSize\": 100,\n          \"total\": 1\n        }\n      }\n      \"\"\", uuid);\n      mockServer.addStringResponse(\"/sca/issues-releases?projectKey=test-project&branchKey=feature%2Fmy-branch&pageSize=500&pageIndex=1\", jsonResponse);\n\n      var response = scaApi.getIssuesReleases(\"test-project\", \"feature/my-branch\", new SonarLintCancelMonitor());\n\n      assertThat(response.issuesReleases()).hasSize(1);\n      var issueRelease = response.issuesReleases().get(0);\n      assertThat(issueRelease.key()).isEqualTo(uuid);\n      assertThat(issueRelease.type()).isEqualTo(GetIssuesReleasesResponse.IssuesRelease.Type.VULNERABILITY);\n      assertThat(issueRelease.severity()).isEqualTo(GetIssuesReleasesResponse.IssuesRelease.Severity.HIGH);\n      assertThat(issueRelease.quality()).isEqualTo(GetIssuesReleasesResponse.IssuesRelease.SoftwareQuality.MAINTAINABILITY);\n      assertThat(issueRelease.vulnerabilityId()).isEqualTo(\"CVE-2023-12345\");\n      assertThat(issueRelease.cvssScore()).isEqualTo(\"7.5\");\n      assertThat(issueRelease.release().packageName()).isEqualTo(\"com.example.vulnerable\");\n      assertThat(issueRelease.release().version()).isEqualTo(\"1.0.0\");\n      assertThat(issueRelease.transitions()).containsExactly(\n        GetIssuesReleasesResponse.IssuesRelease.Transition.CONFIRM,\n        GetIssuesReleasesResponse.IssuesRelease.Transition.REOPEN);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/settings/SettingsApiTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.settings;\n\nimport java.util.Map;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.MockWebServerExtensionWithProtobuf;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Settings;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.entry;\n\nclass SettingsApiTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n\n  @Test\n  void test_fetch_project_settings() {\n    var valuesBuilder = Settings.FieldValues.Value.newBuilder();\n    valuesBuilder.putValue(\"filepattern\", \"**/*.xml\");\n    valuesBuilder.putValue(\"rulepattern\", \"*:S12345\");\n    var value1 = valuesBuilder.build();\n    valuesBuilder.clear();\n    valuesBuilder.putValue(\"filepattern\", \"**/*.java\");\n    valuesBuilder.putValue(\"rulepattern\", \"*:S456\");\n    var value2 = valuesBuilder.build();\n\n    var response = Settings.ValuesWsResponse.newBuilder()\n      .addSettings(Settings.Setting.newBuilder()\n        .setKey(\"sonar.inclusions\")\n        .setValues(Settings.Values.newBuilder().addValues(\"**/*.java\")))\n      .addSettings(Settings.Setting.newBuilder()\n        .setKey(\"sonar.java.fileSuffixes\")\n        .setValue(\"*.java\"))\n      .addSettings(Settings.Setting.newBuilder()\n        .setKey(\"sonar.issue.exclusions.multicriteria\")\n        .setFieldValues(Settings.FieldValues.newBuilder().addFieldValues(value1).addFieldValues(value2)).build())\n      .build();\n    mockServer.addProtobufResponse(\"/api/settings/values.protobuf?component=foo\", response);\n\n    var projectSettings = new SettingsApi(mockServer.serverApiHelper()).getProjectSettings(\"foo\", new SonarLintCancelMonitor());\n\n    assertThat(projectSettings).containsOnly(\n      entry(\"sonar.inclusions\", \"**/*.java\"),\n      entry(\"sonar.java.fileSuffixes\", \"*.java\"),\n      entry(\"sonar.issue.exclusions.multicriteria\", \"1,2\"),\n      entry(\"sonar.issue.exclusions.multicriteria.1.filepattern\", \"**/*.xml\"),\n      entry(\"sonar.issue.exclusions.multicriteria.1.rulepattern\", \"*:S12345\"),\n      entry(\"sonar.issue.exclusions.multicriteria.2.filepattern\", \"**/*.java\"),\n      entry(\"sonar.issue.exclusions.multicriteria.2.rulepattern\", \"*:S456\"));\n  }\n\n  @Test\n  void test_fetch_global_setting() {\n    var response = Settings.ValuesWsResponse.newBuilder()\n      .addSettings(Settings.Setting.newBuilder()\n        .setKey(\"sonar.multi-quality-mode.enabled\")\n        .setValue(\"true\"))\n      .addSettings(Settings.Setting.newBuilder()\n        .setKey(\"fake.property\")\n        .setValue(\"false\"))\n      .build();\n    mockServer.addProtobufResponse(\"/api/settings/values.protobuf\", response);\n\n    var globalSettings = new SettingsApi(mockServer.serverApiHelper()).getGlobalSettings(new SonarLintCancelMonitor());\n\n    assertThat(globalSettings).isEqualTo(Map.of(\"sonar.multi-quality-mode.enabled\", \"true\", \"fake.property\", \"false\"));\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/stream/EventStreamTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.stream;\n\nimport java.util.ArrayList;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.ScheduledFuture;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Consumer;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.mockito.ArgumentCaptor;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.http.HttpClient;\nimport org.sonarsource.sonarlint.core.http.HttpConnectionListener;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyLong;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.clearInvocations;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.when;\n\nclass EventStreamTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private final ServerApiHelper apiHelper = mock(ServerApiHelper.class);\n  private final ScheduledExecutorService executor = mock(ScheduledExecutorService.class);\n  private final ArgumentCaptor<HttpConnectionListener> listenerCaptor = ArgumentCaptor.forClass(HttpConnectionListener.class);\n  private final ArgumentCaptor<Consumer<String>> consumerCaptor = ArgumentCaptor.forClass(Consumer.class);\n  private final ArrayList<Event> receivedEvents = new ArrayList<>();\n  private EventStream stream;\n\n  @BeforeEach\n  void setUp() {\n    stream = new EventStream(apiHelper, receivedEvents::add, executor);\n  }\n\n  @Test\n  void should_log_when_connected() {\n    stream.connect(\"wsPath\");\n    verify(apiHelper).getEventStream(eq(\"wsPath\"), listenerCaptor.capture(), any());\n\n    listenerCaptor.getValue().onConnected();\n\n    assertThat(logTester.logs())\n      .containsOnly(\n        \"Connecting to server event-stream at 'wsPath'...\",\n        \"Connected to server event-stream\");\n  }\n\n  @Test\n  void should_notify_consumer_when_event_received() {\n    stream.connect(\"wsPath\");\n    verify(apiHelper).getEventStream(eq(\"wsPath\"), listenerCaptor.capture(), consumerCaptor.capture());\n    var scheduledFuture = mock(ScheduledFuture.class);\n    when(executor.schedule(any(Runnable.class), anyLong(), any())).thenReturn(scheduledFuture);\n    listenerCaptor.getValue().onConnected();\n    consumerCaptor.getValue().accept(\"event: type\\ndata: data\\n\\n\");\n\n    assertThat(receivedEvents)\n      .extracting(\"type\", \"data\")\n      .containsOnly(tuple(\"type\", \"data\"));\n  }\n\n  @Test\n  void should_not_retry_when_unauthorized() {\n    stream.connect(\"wsPath\");\n    verify(apiHelper).getEventStream(eq(\"wsPath\"), listenerCaptor.capture(), any());\n\n    listenerCaptor.getValue().onError(401);\n\n    verifyNoInteractions(executor);\n    assertThat(logTester.logs())\n      .containsOnly(\n        \"Connecting to server event-stream at 'wsPath'...\",\n        \"Cannot connect to server event-stream, unauthorized\");\n  }\n\n  @Test\n  void should_not_retry_when_forbidden() {\n    stream.connect(\"wsPath\");\n    verify(apiHelper).getEventStream(eq(\"wsPath\"), listenerCaptor.capture(), any());\n\n    listenerCaptor.getValue().onError(403);\n\n    verifyNoInteractions(executor);\n    assertThat(logTester.logs())\n      .containsOnly(\n        \"Connecting to server event-stream at 'wsPath'...\",\n        \"Cannot connect to server event-stream, forbidden\");\n  }\n\n  @Test\n  void should_not_retry_when_api_not_found() {\n    stream.connect(\"wsPath\");\n    verify(apiHelper).getEventStream(eq(\"wsPath\"), listenerCaptor.capture(), any());\n\n    listenerCaptor.getValue().onError(404);\n\n    verifyNoInteractions(executor);\n    assertThat(logTester.logs())\n      .containsOnly(\n        \"Connecting to server event-stream at 'wsPath'...\",\n        \"Server events not supported by the server\");\n  }\n\n  @Test\n  void should_retry_when_server_error() {\n    stream.connect(\"wsPath\");\n    verify(apiHelper).getEventStream(eq(\"wsPath\"), listenerCaptor.capture(), any());\n\n    listenerCaptor.getValue().onError(500);\n\n    verify(executor).schedule(any(Runnable.class), eq(60L), eq(TimeUnit.SECONDS));\n    assertThat(logTester.logs())\n      .containsOnly(\n        \"Connecting to server event-stream at 'wsPath'...\",\n        \"Cannot connect to server event-stream (500), retrying in 60s\");\n  }\n\n  @Test\n  void should_reconnect_when_disconnected() {\n    stream.connect(\"wsPath\");\n    verify(apiHelper).getEventStream(eq(\"wsPath\"), listenerCaptor.capture(), any());\n    var listener = listenerCaptor.getValue();\n    var scheduledFuture = mock(ScheduledFuture.class);\n    when(executor.schedule(any(Runnable.class), anyLong(), any())).thenReturn(scheduledFuture);\n    listener.onConnected();\n\n    listener.onClosed();\n\n    verify(apiHelper).getEventStream(eq(\"wsPath\"), eq(listener), any());\n    assertThat(logTester.logs())\n      .containsOnly(\n        \"Connecting to server event-stream at 'wsPath'...\",\n        \"Connected to server event-stream\",\n        \"Disconnected from server event-stream, reconnecting now\",\n        \"Connecting to server event-stream at 'wsPath'...\");\n  }\n\n  @Test\n  void should_stop_retrying_after_failed_attempts() {\n    stream.connect(\"wsPath\");\n\n    for (int attemptNumber = 0; attemptNumber < 9; attemptNumber++) {\n      verify(apiHelper).getEventStream(eq(\"wsPath\"), listenerCaptor.capture(), any());\n      listenerCaptor.getValue().onError(null);\n      ArgumentCaptor<Runnable> callableCaptor = ArgumentCaptor.forClass(Runnable.class);\n      verify(executor).schedule(callableCaptor.capture(), anyLong(), any());\n      clearInvocations(executor);\n      clearInvocations(apiHelper);\n      callableCaptor.getValue().run();\n    }\n    verify(apiHelper).getEventStream(eq(\"wsPath\"), listenerCaptor.capture(), any());\n    listenerCaptor.getValue().onError(null);\n    verifyNoInteractions(executor);\n\n    assertThat(logTester.logs())\n      .contains(\n        \"Connecting to server event-stream at 'wsPath'...\",\n        \"Cannot connect to server event-stream, retrying in 15360s\",\n        \"Cannot connect to server event-stream, stop retrying\");\n  }\n\n  @Test\n  void should_reconnect_when_no_heart_beat_received_for_a_while() {\n    var scheduledFuture = mock(ScheduledFuture.class);\n    ArgumentCaptor<Runnable> callableCaptor = ArgumentCaptor.forClass(Runnable.class);\n    when(executor.schedule(callableCaptor.capture(), anyLong(), any())).thenReturn(scheduledFuture);\n    stream.connect(\"wsPath\");\n    verify(apiHelper).getEventStream(eq(\"wsPath\"), listenerCaptor.capture(), any());\n    var listener = listenerCaptor.getValue();\n\n    listener.onConnected();\n\n    callableCaptor.getValue().run();\n\n    verify(apiHelper).getEventStream(eq(\"wsPath\"), eq(listener), any());\n  }\n\n  @Test\n  void should_cancel_request_when_closing_stream() {\n    var asyncRequest = mock(HttpClient.AsyncRequest.class);\n    when(apiHelper.getEventStream(eq(\"wsPath\"), any(), any())).thenReturn(asyncRequest);\n    stream.connect(\"wsPath\");\n\n    stream.close();\n\n    verify(asyncRequest).cancel();\n  }\n\n  @Test\n  void should_cancel_delayed_retry_when_closing_stream() {\n    var scheduledFuture = mock(ScheduledFuture.class);\n    when(executor.schedule(any(Runnable.class), anyLong(), any())).thenReturn(scheduledFuture);\n    stream.connect(\"wsPath\");\n    verify(apiHelper).getEventStream(eq(\"wsPath\"), listenerCaptor.capture(), any());\n    listenerCaptor.getValue().onError(null);\n\n    stream.close();\n\n    verify(scheduledFuture).cancel(true);\n  }\n\n  @Test\n  void should_close_executor_when_closing_stream() {\n    stream.close();\n\n    verify(executor).shutdownNow();\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/users/UsersApiTests.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.users;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.MockWebServerExtensionWithProtobuf;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass UsersApiTests {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n\n  private UsersApi underTest;\n\n  @BeforeEach\n  void setUp() {\n    // default with SonarCloud organization to trigger api base URL and isSonarCloud = true\n    ServerApiHelper helper = mockServer.serverApiHelper(\"orgKey\");\n    underTest = new UsersApi(helper);\n  }\n\n  @Test\n  void should_return_user_id_on_sonarcloud() {\n    mockServer.addStringResponse(\"/api/users/current\", \"\"\"\n      {\n        \"isLoggedIn\": true,\n        \"id\": \"16c9b3b3-3f7e-4d61-91fe-31d731456c08\",\n        \"login\": \"obiwan.kenobi\"\n      }\"\"\");\n\n    var id = underTest.getCurrentUserId(new SonarLintCancelMonitor());\n\n    assertThat(id).isEqualTo(\"16c9b3b3-3f7e-4d61-91fe-31d731456c08\");\n  }\n\n  @Test\n  void should_return_user_id_on_sonarqube_server() {\n    var helperSqs = mockServer.serverApiHelper(null); // isSonarCloud = false\n    var api = new UsersApi(helperSqs);\n    mockServer.addStringResponse(\"/api/users/current\", \"\"\"\n      {\n        \"isLoggedIn\": true,\n        \"id\": \"00000000-0000-0000-0000-000000000001\",\n        \"login\": \"obiwan.kenobi\"\n      }\"\"\");\n\n    var id = api.getCurrentUserId(new SonarLintCancelMonitor());\n\n    assertThat(id).isEqualTo(\"00000000-0000-0000-0000-000000000001\");\n  }\n\n  @Test\n  void should_return_null_on_malformed_response() {\n    mockServer.addStringResponse(\"/api/users/current\", \"{}\");\n\n    var id = underTest.getCurrentUserId(new SonarLintCancelMonitor());\n\n    assertThat(id).isNull();\n  }\n\n}\n\n\n"
  },
  {
    "path": "backend/server-api/src/test/java/org/sonarsource/sonarlint/core/serverapi/util/ProtobufUtilTest.java",
    "content": "/*\n * SonarLint Core - Server API\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverapi.util;\n\nimport com.google.protobuf.Message;\nimport com.google.protobuf.Parser;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.nio.charset.StandardCharsets;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentMatchers;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.mock;\nimport static org.sonarsource.sonarlint.core.serverapi.util.ProtobufUtil.readMessages;\n\npublic class ProtobufUtilTest {\n\n  private static final Common.Paging SOME_MESSAGE = Common.Paging.newBuilder().build();\n\n  private static final Parser<Common.Paging> SOME_PARSER = Common.Paging.parser();\n\n  @Test\n  void test_readMessages_empty() throws IOException {\n    try (InputStream inputStream = newEmptyStream()) {\n      assertThat(readMessages(inputStream, SOME_PARSER)).isEmpty();\n    }\n  }\n\n  @Test\n  void test_readMessages_multiple() throws IOException {\n    var paging1 = SOME_MESSAGE;\n    var paging2 = SOME_MESSAGE;\n\n    try (InputStream inputStream = new ByteArrayInputStream(toByteArray(paging1, paging2))) {\n      assertThat(readMessages(inputStream, paging1.getParserForType())).containsOnly(paging1, paging2);\n    }\n  }\n\n  @Test\n  void test_readMessages_error() {\n    InputStream inputStream = new ByteArrayInputStream(\"trash\".getBytes(StandardCharsets.UTF_8));\n\n    var thrown = assertThrows(IllegalStateException.class, () -> readMessages(inputStream, SOME_PARSER));\n    assertThat(thrown).hasMessage(\"failed to parse protobuf message\");\n  }\n\n  @Test\n  void test_writeMessage_error() throws IOException {\n    var out = mock(OutputStream.class);\n    doThrow(IOException.class).when(out).write(any(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt());\n\n    var thrown = assertThrows(IllegalStateException.class, () -> ProtobufUtil.writeMessage(out, SOME_MESSAGE));\n    assertThat(thrown).hasMessageStartingWith(\"failed to write message\");\n  }\n\n  public static byte[] toByteArray(Message... messages) throws IOException {\n    try (var byteStream = new ByteArrayOutputStream()) {\n      for (Message msg : messages) {\n        msg.writeDelimitedTo(byteStream);\n      }\n      return byteStream.toByteArray();\n    }\n  }\n\n  public static ByteArrayInputStream newEmptyStream() {\n    return new ByteArrayInputStream(new byte[0]);\n  }\n}\n"
  },
  {
    "path": "backend/server-api/src/test/resources/logback-test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE configuration>\n\n<configuration>\n  <include resource=\"logback-shared.xml\"/>\n</configuration>"
  },
  {
    "path": "backend/server-api/src/test/resources/update/component_tree.pb",
    "content": "\n\b\b\u0001\u0010\u0003\u0018\u0001\u001a\u0001\n$8b745480-b598-4e34-af4a-cb2de1808e50\u00125org.sonarsource.sonarlint.intellij:sonarlint-intellij2\u001bSonarLint for IntelliJ IDEA:\u001bSonarLint for IntelliJ IDEAB\u0003TRKb\u0014default-organizationr\u000b\n\tsonarlintz\u0006public\"\u0002\n\u0014AViSPajkqePDAZzqZ8n1\u0012vorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/AbstractIssuesPanel.java2\u0018AbstractIssuesPanel.javaB\u0003FILJ@src/main/java/org/sonarlint/intellij/ui/AbstractIssuesPanel.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVI_VGQoMaJn9Xn8TGmM\u0012uorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/nodes/AbstractNode.java2\u0011AbstractNode.javaB\u0003FILJ?src/main/java/org/sonarlint/intellij/ui/nodes/AbstractNode.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVGSAZk6ShtJBwDkIzI7\u0012{org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/actions/AbstractSonarAction.java2\u0018AbstractSonarAction.javaB\u0003FILJEsrc/main/java/org/sonarlint/intellij/actions/AbstractSonarAction.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVGSAZk8ShtJBwDkIzJK\u0012org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/editor/AccumulatorIssueListener.java2\u001dAccumulatorIssueListener.javaB\u0003FILJIsrc/main/java/org/sonarlint/intellij/editor/AccumulatorIssueListener.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWChh-u4_Q-0dz4JAcNl\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/project/AddEditExclusionDialog.java2\u001bAddEditExclusionDialog.javaB\u0003FILJOsrc/main/java/org/sonarlint/intellij/config/project/AddEditExclusionDialog.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVjz54nG7h1V61hFWcxM\u0012yorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/analysis/AnalysisCallback.java2\u0015AnalysisCallback.javaB\u0003FILJCsrc/main/java/org/sonarlint/intellij/analysis/AnalysisCallback.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVYIST4At19XVl6V76Ct\u0012}org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/analysis/AnalysisConfigurator.java2\u0019AnalysisConfigurator.javaB\u0003FILJGsrc/main/java/org/sonarlint/intellij/analysis/AnalysisConfigurator.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWJEZZkOlHOQQAsrvh5L\u0012zorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/AnalysisResultIssues.java2\u0019AnalysisResultIssues.javaB\u0003FILJDsrc/main/java/org/sonarlint/intellij/issue/AnalysisResultIssues.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AWJEZZkPlHOQQAsrvh5O\u0012rorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/AnalysisResults.java2\u0014AnalysisResults.javaB\u0003FILJ<src/main/java/org/sonarlint/intellij/ui/AnalysisResults.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVI_VGQoMaJn9Xn8TGmK\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/messages/AnalysisResultsListener.java2\u001cAnalysisResultsListener.javaB\u0003FILJJsrc/main/java/org/sonarlint/intellij/messages/AnalysisResultsListener.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVkB8uDONJs7f7U30wq0\u0012~org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/analysis/AnalysisThreadFactory.java2\u001aAnalysisThreadFactory.javaB\u0003FILJHsrc/main/java/org/sonarlint/intellij/analysis/AnalysisThreadFactory.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVzAhrSdRLymfFZDbbDI\u0012}org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/wizard/AuthStep.java2\rAuthStep.javaB\u0003FILJGsrc/main/java/org/sonarlint/intellij/config/global/wizard/AuthStep.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AViM6p0PcMUueO7VmA_f\u0012yorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/AutoTriggerStatusPanel.java2\u001bAutoTriggerStatusPanel.javaB\u0003FILJCsrc/main/java/org/sonarlint/intellij/ui/AutoTriggerStatusPanel.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVfiyjqAdiWs2KokOuZk\u0012|org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/editor/CodeAnalyzerRestarter.java2\u001aCodeAnalyzerRestarter.javaB\u0003FILJFsrc/main/java/org/sonarlint/intellij/editor/CodeAnalyzerRestarter.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWEswAfEr8h_fVHZ4UsV\u0012vorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/CodeAnalyzersDialog.java2\u0018CodeAnalyzersDialog.javaB\u0003FILJ@src/main/java/org/sonarlint/intellij/ui/CodeAnalyzersDialog.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVo9epjCR1_D6kvz7Yay\u0012qorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/util/CompoundIcon.java2\u0011CompoundIcon.javaB\u0003FILJ;src/main/java/org/sonarlint/intellij/util/CompoundIcon.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWChh-u3_Q-0dz4JAcNg\u0012yorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/ConfigurationPanel.java2\u0017ConfigurationPanel.javaB\u0003FILJCsrc/main/java/org/sonarlint/intellij/config/ConfigurationPanel.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVzAhrSdRLymfFZDbbDJ\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/wizard/ConfirmStep.java2\u0010ConfirmStep.javaB\u0003FILJJsrc/main/java/org/sonarlint/intellij/config/global/wizard/ConfirmStep.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVPR7c4B9txRcDXxXQ9b\u0012}org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ConnectedSonarLintFacade.java2\u001dConnectedSonarLintFacade.javaB\u0003FILJGsrc/main/java/org/sonarlint/intellij/core/ConnectedSonarLintFacade.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVoeoD-jXQg2W5SJWYi2\u0012xorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/tasks/ConnectionTestTask.java2\u0017ConnectionTestTask.javaB\u0003FILJBsrc/main/java/org/sonarlint/intellij/tasks/ConnectionTestTask.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWJEZZkPlHOQQAsrvh5P\u0012xorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/CurrentFileController.java2\u001aCurrentFileController.javaB\u0003FILJBsrc/main/java/org/sonarlint/intellij/ui/CurrentFileController.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVgKnQBP2QCgEeL25lnF\u0012org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/analysis/DefaultClientInputFile.java2\u001bDefaultClientInputFile.javaB\u0003FILJIsrc/main/java/org/sonarlint/intellij/analysis/DefaultClientInputFile.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWSPMTkt7gmWmqwF9F4Z\u0012yorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/actions/DisableRuleAction.java2\u0016DisableRuleAction.javaB\u0003FILJCsrc/main/java/org/sonarlint/intellij/actions/DisableRuleAction.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWR-SuIei6PwnvJ9RAeR\u0012zorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/editor/DisableRuleQuickFix.java2\u0018DisableRuleQuickFix.javaB\u0003FILJDsrc/main/java/org/sonarlint/intellij/editor/DisableRuleQuickFix.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWChh-u3_Q-0dz4JAcNi\u0012}org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/component/EditableList.java2\u0011EditableList.javaB\u0003FILJGsrc/main/java/org/sonarlint/intellij/config/component/EditableList.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWJEZZkPlHOQQAsrvh5M\u0012{org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/trigger/EditorChangeTrigger.java2\u0018EditorChangeTrigger.javaB\u0003FILJEsrc/main/java/org/sonarlint/intellij/trigger/EditorChangeTrigger.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWJEZZkPlHOQQAsrvh5N\u0012yorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/trigger/EditorOpenTrigger.java2\u0016EditorOpenTrigger.javaB\u0003FILJCsrc/main/java/org/sonarlint/intellij/trigger/EditorOpenTrigger.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVzEiCZRiE0jT5TR4qcV\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/wizard/ErrorPainter.java2\u0011ErrorPainter.javaB\u0003FILJKsrc/main/java/org/sonarlint/intellij/config/global/wizard/ErrorPainter.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVoZFzCGh-gplmkNkOeK\u0012torg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/editor/EscapeHandler.java2\u0012EscapeHandler.javaB\u0003FILJ>src/main/java/org/sonarlint/intellij/editor/EscapeHandler.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWChh-u3_Q-0dz4JAcNc\u0012yorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/actions/ExcludeFileAction.java2\u0016ExcludeFileAction.javaB\u0003FILJCsrc/main/java/org/sonarlint/intellij/actions/ExcludeFileAction.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWChh-u4_Q-0dz4JAcNm\u0012|org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/project/ExclusionItem.java2\u0012ExclusionItem.javaB\u0003FILJFsrc/main/java/org/sonarlint/intellij/config/project/ExclusionItem.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWChh-u4_Q-0dz4JAcNn\u0012}org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/project/ExclusionTable.java2\u0013ExclusionTable.javaB\u0003FILJGsrc/main/java/org/sonarlint/intellij/config/project/ExclusionTable.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVI_VGQoMaJn9Xn8TGmN\u0012qorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/nodes/FileNode.java2\rFileNode.javaB\u0003FILJ;src/main/java/org/sonarlint/intellij/ui/nodes/FileNode.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVlBCACVDdGu0TUEM4_U\u0012qorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/tree/FlowsTree.java2\u000eFlowsTree.javaB\u0003FILJ;src/main/java/org/sonarlint/intellij/ui/tree/FlowsTree.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVlBCACVDdGu0TUEM4_V\u0012}org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/tree/FlowsTreeModelBuilder.java2\u001aFlowsTreeModelBuilder.javaB\u0003FILJGsrc/main/java/org/sonarlint/intellij/ui/tree/FlowsTreeModelBuilder.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWOSUxTp_oWprfWtnuSn\u0012yorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/tasks/GetOrganizationTask.java2\u0018GetOrganizationTask.javaB\u0003FILJCsrc/main/java/org/sonarlint/intellij/tasks/GetOrganizationTask.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVPR7c4C9txRcDXxXQ9k\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/messages/GlobalConfigurationListener.java2 GlobalConfigurationListener.javaB\u0003FILJNsrc/main/java/org/sonarlint/intellij/messages/GlobalConfigurationListener.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWChh-u3_Q-0dz4JAcNk\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/GlobalExclusionsPanel.java2\u001aGlobalExclusionsPanel.javaB\u0003FILJMsrc/main/java/org/sonarlint/intellij/config/global/GlobalExclusionsPanel.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVPR7c4C9txRcDXxXQ9l\u0012torg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/util/GlobalLogOutput.java2\u0014GlobalLogOutput.javaB\u0003FILJ>src/main/java/org/sonarlint/intellij/util/GlobalLogOutput.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVfcmCn7J96w3vssdKg4\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/persistence/IndexedObjectStore.java2\u0017IndexedObjectStore.javaB\u0003FILJNsrc/main/java/org/sonarlint/intellij/issue/persistence/IndexedObjectStore.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AV1RVebIo6Qmn8dFiYtI\u0012zorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/tasks/InformationFetchTask.java2\u0019InformationFetchTask.javaB\u0003FILJDsrc/main/java/org/sonarlint/intellij/tasks/InformationFetchTask.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVLvZ9LbSacnzMyR1mlu\u0012torg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/tracking/Input.java2\nInput.javaB\u0003FILJ>src/main/java/org/sonarlint/intellij/issue/tracking/Input.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVjd6xIgFr_J5Dpp7BbD\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/exception/InvalidBindingException.java2\u001cInvalidBindingException.javaB\u0003FILJKsrc/main/java/org/sonarlint/intellij/exception/InvalidBindingException.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVfcmCn7J96w3vssdKgz\u0012rorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/IssueManager.java2\u0011IssueManager.javaB\u0003FILJ<src/main/java/org/sonarlint/intellij/issue/IssueManager.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVGSAZk8ShtJBwDkIzJQ\u0012rorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/IssueMatcher.java2\u0011IssueMatcher.javaB\u0003FILJ<src/main/java/org/sonarlint/intellij/issue/IssueMatcher.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVI_VGQoMaJn9Xn8TGmO\u0012rorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/nodes/IssueNode.java2\u000eIssueNode.javaB\u0003FILJ<src/main/java/org/sonarlint/intellij/ui/nodes/IssueNode.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVfcmCn7J96w3vssdKg5\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/persistence/IssuePersistence.java2\u0015IssuePersistence.javaB\u0003FILJLsrc/main/java/org/sonarlint/intellij/issue/persistence/IssuePersistence.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVGSAZk8ShtJBwDkIzJR\u0012torg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/IssueProcessor.java2\u0013IssueProcessor.javaB\u0003FILJ>src/main/java/org/sonarlint/intellij/issue/IssueProcessor.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVGSAZk8ShtJBwDkIzJS\u0012porg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/IssueStore.java2\u000fIssueStore.javaB\u0003FILJ:src/main/java/org/sonarlint/intellij/issue/IssueStore.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVRxXmKqeOKlMxH0tQUt\u0012{org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/messages/IssueStoreListener.java2\u0017IssueStoreListener.javaB\u0003FILJEsrc/main/java/org/sonarlint/intellij/messages/IssueStoreListener.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVo9epi9R1_D6kvz7Yax\u0012{org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/actions/IssuesViewTabOpener.java2\u0018IssuesViewTabOpener.javaB\u0003FILJEsrc/main/java/org/sonarlint/intellij/actions/IssuesViewTabOpener.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVI_VGQoMaJn9Xn8TGmY\u0012qorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/tree/IssueTree.java2\u000eIssueTree.javaB\u0003FILJ;src/main/java/org/sonarlint/intellij/ui/tree/IssueTree.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVI_VGQoMaJn9Xn8TGma\u0012vorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/tree/IssueTreeIndex.java2\u0013IssueTreeIndex.javaB\u0003FILJ@src/main/java/org/sonarlint/intellij/ui/tree/IssueTreeIndex.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVlBCACWDdGu0TUEM4_W\u0012}org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/tree/IssueTreeModelBuilder.java2\u001aIssueTreeModelBuilder.javaB\u0003FILJGsrc/main/java/org/sonarlint/intellij/ui/tree/IssueTreeModelBuilder.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVYIST4At19XVl6V76Cu\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/analysis/JavaAnalysisConfigurator.java2\u001dJavaAnalysisConfigurator.javaB\u0003FILJKsrc/main/java/org/sonarlint/intellij/analysis/JavaAnalysisConfigurator.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVlBCACUDdGu0TUEM4_S\u0012rorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/nodes/LabelNode.java2\u000eLabelNode.javaB\u0003FILJ<src/main/java/org/sonarlint/intellij/ui/nodes/LabelNode.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AViM6p0PcMUueO7VmA_g\u0012torg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/LastAnalysisPanel.java2\u0016LastAnalysisPanel.javaB\u0003FILJ>src/main/java/org/sonarlint/intellij/ui/LastAnalysisPanel.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVzAhrSdRLymfFZDbbDK\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/wizard/LengthRestrictedDocument.java2\u001dLengthRestrictedDocument.javaB\u0003FILJWsrc/main/java/org/sonarlint/intellij/config/global/wizard/LengthRestrictedDocument.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVfcmCn7J96w3vssdKg0\u0012oorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/LiveIssue.java2\u000eLiveIssue.javaB\u0003FILJ9src/main/java/org/sonarlint/intellij/issue/LiveIssue.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVfcmCn7J96w3vssdKg6\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/persistence/LiveIssueCache.java2\u0013LiveIssueCache.javaB\u0003FILJJsrc/main/java/org/sonarlint/intellij/issue/persistence/LiveIssueCache.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWChh-u3_Q-0dz4JAcNe\u0012|org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/analysis/LocalFileExclusions.java2\u0018LocalFileExclusions.javaB\u0003FILJFsrc/main/java/org/sonarlint/intellij/analysis/LocalFileExclusions.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVfcmCn7J96w3vssdKg1\u0012yorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/LocalIssueTrackable.java2\u0018LocalIssueTrackable.javaB\u0003FILJCsrc/main/java/org/sonarlint/intellij/issue/LocalIssueTrackable.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVlBCACUDdGu0TUEM4_T\u0012uorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/nodes/LocationNode.java2\u0011LocationNode.javaB\u0003FILJ?src/main/java/org/sonarlint/intellij/ui/nodes/LocationNode.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVRxtLJXeOKlMxH0tR9H\u0012sorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/trigger/MakeTrigger.java2\u0010MakeTrigger.javaB\u0003FILJ=src/main/java/org/sonarlint/intellij/trigger/MakeTrigger.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVzAhrSdRLymfFZDbbDL\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/wizard/OrganizationStep.java2\u0015OrganizationStep.javaB\u0003FILJOsrc/main/java/org/sonarlint/intellij/config/global/wizard/OrganizationStep.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWChh-u3_Q-0dz4JAcNj\u0012}org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/component/package-info.java2\u0011package-info.javaB\u0003FILJGsrc/main/java/org/sonarlint/intellij/config/component/package-info.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVzAhrSdRLymfFZDbbDP\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/wizard/package-info.java2\u0011package-info.javaB\u0003FILJKsrc/main/java/org/sonarlint/intellij/config/global/wizard/package-info.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWR-SuIci6PwnvJ9RAeQ\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/rules/package-info.java2\u0011package-info.javaB\u0003FILJJsrc/main/java/org/sonarlint/intellij/config/global/rules/package-info.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVoeoD-jXQg2W5SJWYi4\u0012rorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/tasks/package-info.java2\u0011package-info.javaB\u0003FILJ<src/main/java/org/sonarlint/intellij/tasks/package-info.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVqo6rkFmafQcz5ZZbCp\u0012vorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/telemetry/package-info.java2\u0011package-info.javaB\u0003FILJ@src/main/java/org/sonarlint/intellij/telemetry/package-info.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVjd6xIgFr_J5Dpp7BbE\u0012vorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/exception/package-info.java2\u0011package-info.javaB\u0003FILJ@src/main/java/org/sonarlint/intellij/exception/package-info.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVjjZHb_jW8OMIIyRCKA\u0012[org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/icons/package-info.java2\u0011package-info.javaB\u0003FILJ%src/main/java/icons/package-info.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVBBpXODintm2-s7WOQ-\u0012qorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/util/package-info.java2\u0011package-info.javaB\u0003FILJ;src/main/java/org/sonarlint/intellij/util/package-info.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVBBpXOCintm2-s7WOQs\u0012sorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/package-info.java2\u0011package-info.javaB\u0003FILJ=src/main/java/org/sonarlint/intellij/config/package-info.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVBBw1vGintm2-s7WOZm\u0012torg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/actions/package-info.java2\u0011package-info.javaB\u0003FILJ>src/main/java/org/sonarlint/intellij/actions/package-info.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVGSAZk6ShtJBwDkIzI5\u0012lorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/package-info.java2\u0011package-info.javaB\u0003FILJ6src/main/java/org/sonarlint/intellij/package-info.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVGSAZk7ShtJBwDkIzJI\u0012uorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/analysis/package-info.java2\u0011package-info.javaB\u0003FILJ?src/main/java/org/sonarlint/intellij/analysis/package-info.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVGSAZk8ShtJBwDkIzJO\u0012sorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/editor/package-info.java2\u0011package-info.javaB\u0003FILJ=src/main/java/org/sonarlint/intellij/editor/package-info.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVGSAZk8ShtJBwDkIzJT\u0012rorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/package-info.java2\u0011package-info.javaB\u0003FILJ<src/main/java/org/sonarlint/intellij/issue/package-info.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVGSAZk9ShtJBwDkIzJZ\u0012torg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/trigger/package-info.java2\u0011package-info.javaB\u0003FILJ>src/main/java/org/sonarlint/intellij/trigger/package-info.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVGSAZk_ShtJBwDkIzJe\u0012oorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/package-info.java2\u0011package-info.javaB\u0003FILJ9src/main/java/org/sonarlint/intellij/ui/package-info.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVI7z0IbaQmCg8KqUyNB\u0012zorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/package-info.java2\u0011package-info.javaB\u0003FILJDsrc/main/java/org/sonarlint/intellij/config/global/package-info.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVI7z0IbaQmCg8KqUyNG\u0012{org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/project/package-info.java2\u0011package-info.javaB\u0003FILJEsrc/main/java/org/sonarlint/intellij/config/project/package-info.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVI_VGQoMaJn9Xn8TGmR\u0012uorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/nodes/package-info.java2\u0011package-info.javaB\u0003FILJ?src/main/java/org/sonarlint/intellij/ui/nodes/package-info.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVI_VGQoMaJn9Xn8TGmc\u0012torg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/tree/package-info.java2\u0011package-info.javaB\u0003FILJ>src/main/java/org/sonarlint/intellij/ui/tree/package-info.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVJAh5MfnavCjZLaBRPk\u0012uorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/messages/package-info.java2\u0011package-info.javaB\u0003FILJ?src/main/java/org/sonarlint/intellij/messages/package-info.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVLvZ9LbSacnzMyR1mly\u0012{org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/tracking/package-info.java2\u0011package-info.javaB\u0003FILJEsrc/main/java/org/sonarlint/intellij/issue/tracking/package-info.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVPR7c4C9txRcDXxXQ9i\u0012qorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/package-info.java2\u0011package-info.javaB\u0003FILJ;src/main/java/org/sonarlint/intellij/core/package-info.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVfcmCn7J96w3vssdKg_\u0012~org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/persistence/package-info.java2\u0011package-info.javaB\u0003FILJHsrc/main/java/org/sonarlint/intellij/issue/persistence/package-info.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVfdcixoJ96w3vssdN0j\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/persistence/PathStoreKeyValidator.java2\u001aPathStoreKeyValidator.javaB\u0003FILJQsrc/main/java/org/sonarlint/intellij/issue/persistence/PathStoreKeyValidator.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVfdcixoJ96w3vssdN0g\u0012zorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ProjectBindingManager.java2\u001aProjectBindingManager.javaB\u0003FILJDsrc/main/java/org/sonarlint/intellij/core/ProjectBindingManager.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AV1RVebHo6Qmn8dFiYtH\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/messages/ProjectConfigurationListener.java2!ProjectConfigurationListener.javaB\u0003FILJOsrc/main/java/org/sonarlint/intellij/messages/ProjectConfigurationListener.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWChh-u4_Q-0dz4JAcNo\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/project/ProjectExclusionsPanel.java2\u001bProjectExclusionsPanel.javaB\u0003FILJOsrc/main/java/org/sonarlint/intellij/config/project/ProjectExclusionsPanel.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVPR7c4C9txRcDXxXQ9m\u0012uorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/util/ProjectLogOutput.java2\u0015ProjectLogOutput.javaB\u0003FILJ?src/main/java/org/sonarlint/intellij/util/ProjectLogOutput.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVlBCACNDdGu0TUEM4_P\u0012sorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/editor/RangeBlinker.java2\u0011RangeBlinker.javaB\u0003FILJ=src/main/java/org/sonarlint/intellij/editor/RangeBlinker.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWR-SuIci6PwnvJ9RAeL\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/rules/RuleConfigurationPanel.java2\u001bRuleConfigurationPanel.javaB\u0003FILJTsrc/main/java/org/sonarlint/intellij/config/global/rules/RuleConfigurationPanel.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWSDLlRRl658ZWmGCyVF\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/rules/RulesFilterAction.java2\u0016RulesFilterAction.javaB\u0003FILJOsrc/main/java/org/sonarlint/intellij/config/global/rules/RulesFilterAction.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWSDLlRRl658ZWmGCyVG\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/rules/RulesFilterModel.java2\u0015RulesFilterModel.javaB\u0003FILJNsrc/main/java/org/sonarlint/intellij/config/global/rules/RulesFilterModel.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWR-SuIci6PwnvJ9RAeM\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/rules/RulesTreeNode.java2\u0012RulesTreeNode.javaB\u0003FILJKsrc/main/java/org/sonarlint/intellij/config/global/rules/RulesTreeNode.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWR-SuIci6PwnvJ9RAeN\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/rules/RulesTreeTable.java2\u0013RulesTreeTable.javaB\u0003FILJLsrc/main/java/org/sonarlint/intellij/config/global/rules/RulesTreeTable.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWR-SuIci6PwnvJ9RAeO\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/rules/RulesTreeTableModel.java2\u0018RulesTreeTableModel.javaB\u0003FILJQsrc/main/java/org/sonarlint/intellij/config/global/rules/RulesTreeTableModel.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWR-SuIci6PwnvJ9RAeP\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/rules/RulesTreeTableRenderer.java2\u001bRulesTreeTableRenderer.javaB\u0003FILJTsrc/main/java/org/sonarlint/intellij/config/global/rules/RulesTreeTableRenderer.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWJTMlNaorNz2_dLi6RR\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/project/SearchProjectKeyDialog.java2\u001bSearchProjectKeyDialog.javaB\u0003FILJOsrc/main/java/org/sonarlint/intellij/config/project/SearchProjectKeyDialog.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVoeoD-jXQg2W5SJWYi3\u0012org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/tasks/ServerDownloadProjectTask.java2\u001eServerDownloadProjectTask.javaB\u0003FILJIsrc/main/java/org/sonarlint/intellij/tasks/ServerDownloadProjectTask.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVfcmCn7J96w3vssdKg2\u0012zorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/ServerIssueTrackable.java2\u0019ServerIssueTrackable.javaB\u0003FILJDsrc/main/java/org/sonarlint/intellij/issue/ServerIssueTrackable.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVfTIlxMwczdZ2UaLhnt\u0012worg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java2\u0017ServerIssueUpdater.javaB\u0003FILJAsrc/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVzAhrSdRLymfFZDbbDN\u0012org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/wizard/ServerStep.java2\u000fServerStep.javaB\u0003FILJIsrc/main/java/org/sonarlint/intellij/config/global/wizard/ServerStep.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVoeoD-jXQg2W5SJWYi5\u0012vorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/tasks/ServerUpdateTask.java2\u0015ServerUpdateTask.javaB\u0003FILJ@src/main/java/org/sonarlint/intellij/tasks/ServerUpdateTask.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWJEZZkNlHOQQAsrvh5G\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/actions/ShowAnalysisResultsCallable.java2 ShowAnalysisResultsCallable.javaB\u0003FILJMsrc/main/java/org/sonarlint/intellij/actions/ShowAnalysisResultsCallable.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWJTMlNYorNz2_dLi6RQ\u0012org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/actions/ShowCurrentFileCallable.java2\u001cShowCurrentFileCallable.javaB\u0003FILJIsrc/main/java/org/sonarlint/intellij/actions/ShowCurrentFileCallable.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVmIAJwxG7g4kk4HeclZ\u0012}org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/editor/ShowLocationsIntention.java2\u001bShowLocationsIntention.javaB\u0003FILJGsrc/main/java/org/sonarlint/intellij/editor/ShowLocationsIntention.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVoZFzCFh-gplmkNkOeJ\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/actions/SonarAnalyzeAllFilesAction.java2\u001fSonarAnalyzeAllFilesAction.javaB\u0003FILJLsrc/main/java/org/sonarlint/intellij/actions/SonarAnalyzeAllFilesAction.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AViM6p0NcMUueO7VmA_W\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/actions/SonarAnalyzeChangedFilesAction.java2#SonarAnalyzeChangedFilesAction.javaB\u0003FILJPsrc/main/java/org/sonarlint/intellij/actions/SonarAnalyzeChangedFilesAction.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWChh-u3_Q-0dz4JAcNd\u0012org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/actions/SonarAnalyzeFilesAction.java2\u001cSonarAnalyzeFilesAction.javaB\u0003FILJIsrc/main/java/org/sonarlint/intellij/actions/SonarAnalyzeFilesAction.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVGSAZk5ShtJBwDkIzI4\u0012porg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/SonarApplication.java2\u0015SonarApplication.javaB\u0003FILJ:src/main/java/org/sonarlint/intellij/SonarApplication.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWJEZZkNlHOQQAsrvh5H\u0012yorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/actions/SonarCancelAction.java2\u0016SonarCancelAction.javaB\u0003FILJCsrc/main/java/org/sonarlint/intellij/actions/SonarCancelAction.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWJEZZkNlHOQQAsrvh5I\u0012org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/actions/SonarCleanConsoleAction.java2\u001cSonarCleanConsoleAction.javaB\u0003FILJIsrc/main/java/org/sonarlint/intellij/actions/SonarCleanConsoleAction.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWJEZZkNlHOQQAsrvh5J\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/actions/SonarClearAnalysisResultsAction.java2$SonarClearAnalysisResultsAction.javaB\u0003FILJQsrc/main/java/org/sonarlint/intellij/actions/SonarClearAnalysisResultsAction.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWJEZZkNlHOQQAsrvh5K\u0012~org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/actions/SonarClearIssuesAction.java2\u001bSonarClearIssuesAction.javaB\u0003FILJHsrc/main/java/org/sonarlint/intellij/actions/SonarClearIssuesAction.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVGSAZk6ShtJBwDkIzI_\u0012}org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/actions/SonarConfigureProject.java2\u001aSonarConfigureProject.javaB\u0003FILJGsrc/main/java/org/sonarlint/intellij/actions/SonarConfigureProject.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVGSAZk8ShtJBwDkIzJL\u0012}org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/editor/SonarExternalAnnotator.java2\u001bSonarExternalAnnotator.javaB\u0003FILJGsrc/main/java/org/sonarlint/intellij/editor/SonarExternalAnnotator.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVGSAZk8ShtJBwDkIzJN\u0012worg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/editor/SonarLinkHandler.java2\u0015SonarLinkHandler.javaB\u0003FILJAsrc/main/java/org/sonarlint/intellij/editor/SonarLinkHandler.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVrWXSHCXDyvoKp0u12F\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/SonarLintAboutPanel.java2\u0018SonarLintAboutPanel.javaB\u0003FILJKsrc/main/java/org/sonarlint/intellij/config/global/SonarLintAboutPanel.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVpMbFbfVFbuW6S1_-dy\u0012uorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/util/SonarLintActions.java2\u0015SonarLintActions.javaB\u0003FILJ?src/main/java/org/sonarlint/intellij/util/SonarLintActions.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVpHA0JpG3VlJy9H2ntk\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/SonarLintAnalysisResultsPanel.java2\"SonarLintAnalysisResultsPanel.javaB\u0003FILJJsrc/main/java/org/sonarlint/intellij/ui/SonarLintAnalysisResultsPanel.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVI7z0IbaQmCg8KqUyM7\u0012zorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/analysis/SonarLintAnalyzer.java2\u0016SonarLintAnalyzer.javaB\u0003FILJDsrc/main/java/org/sonarlint/intellij/analysis/SonarLintAnalyzer.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVf2Vn5xidHM9RGl4f66\u0012vorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/util/SonarLintAppUtils.java2\u0016SonarLintAppUtils.javaB\u0003FILJ@src/main/java/org/sonarlint/intellij/util/SonarLintAppUtils.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVBBpXODintm2-s7WOQ8\u0012torg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/util/SonarLintBundle.java2\u0014SonarLintBundle.javaB\u0003FILJ>src/main/java/org/sonarlint/intellij/util/SonarLintBundle.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AViM6p0PcMUueO7VmA_c\u0012org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/trigger/SonarLintCheckinHandler.java2\u001cSonarLintCheckinHandler.javaB\u0003FILJIsrc/main/java/org/sonarlint/intellij/trigger/SonarLintCheckinHandler.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AViM6p0PcMUueO7VmA_d\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/trigger/SonarLintCheckinHandlerFactory.java2#SonarLintCheckinHandlerFactory.javaB\u0003FILJPsrc/main/java/org/sonarlint/intellij/trigger/SonarLintCheckinHandlerFactory.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVI7z0IbaQmCg8KqUyM8\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/SonarLintColorSettingsPage.java2\u001fSonarLintColorSettingsPage.javaB\u0003FILJKsrc/main/java/org/sonarlint/intellij/config/SonarLintColorSettingsPage.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVGSAZk9ShtJBwDkIzJb\u0012sorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/SonarLintConsole.java2\u0015SonarLintConsole.javaB\u0003FILJ=src/main/java/org/sonarlint/intellij/ui/SonarLintConsole.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVfdcixoJ96w3vssdN0h\u0012{org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/SonarLintEngineFactory.java2\u001bSonarLintEngineFactory.javaB\u0003FILJEsrc/main/java/org/sonarlint/intellij/core/SonarLintEngineFactory.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVfdcixoJ96w3vssdN0i\u0012{org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/SonarLintEngineManager.java2\u001bSonarLintEngineManager.javaB\u0003FILJEsrc/main/java/org/sonarlint/intellij/core/SonarLintEngineManager.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVPR7c4B9txRcDXxXQ9e\u0012torg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/SonarLintFacade.java2\u0014SonarLintFacade.javaB\u0003FILJ>src/main/java/org/sonarlint/intellij/core/SonarLintFacade.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVI7z0IbaQmCg8KqUyM-\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/SonarLintGlobalConfigurable.java2 SonarLintGlobalConfigurable.javaB\u0003FILJSsrc/main/java/org/sonarlint/intellij/config/global/SonarLintGlobalConfigurable.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVrWXSHCXDyvoKp0u12G\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/SonarLintGlobalOptionsPanel.java2 SonarLintGlobalOptionsPanel.javaB\u0003FILJSsrc/main/java/org/sonarlint/intellij/config/global/SonarLintGlobalOptionsPanel.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVI7z0IbaQmCg8KqUyM_\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/SonarLintGlobalSettings.java2\u001cSonarLintGlobalSettings.javaB\u0003FILJOsrc/main/java/org/sonarlint/intellij/config/global/SonarLintGlobalSettings.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVlBCACNDdGu0TUEM4_Q\u0012|org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/editor/SonarLintHighlighting.java2\u001aSonarLintHighlighting.javaB\u0003FILJFsrc/main/java/org/sonarlint/intellij/editor/SonarLintHighlighting.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVjjZHb_jW8OMIIyRCJ_\u0012]org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/icons/SonarLintIcons.java2\u0013SonarLintIcons.javaB\u0003FILJ'src/main/java/icons/SonarLintIcons.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVI_VGQoMaJn9Xn8TGmL\u0012worg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/SonarLintIssuesPanel.java2\u0019SonarLintIssuesPanel.javaB\u0003FILJAsrc/main/java/org/sonarlint/intellij/ui/SonarLintIssuesPanel.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVfr4xyZl8r7s5DFVXXd\u0012uorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/analysis/SonarLintJob.java2\u0011SonarLintJob.javaB\u0003FILJ?src/main/java/org/sonarlint/intellij/analysis/SonarLintJob.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVYIST4At19XVl6V76Cv\u0012|org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/analysis/SonarLintJobManager.java2\u0018SonarLintJobManager.javaB\u0003FILJFsrc/main/java/org/sonarlint/intellij/analysis/SonarLintJobManager.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVRxtLJYeOKlMxH0tR9I\u0012torg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/SonarLintLogPanel.java2\u0016SonarLintLogPanel.javaB\u0003FILJ>src/main/java/org/sonarlint/intellij/ui/SonarLintLogPanel.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWEswAfEr8h_fVHZ4UsW\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/SonarLintProjectAnalyzersPanel.java2#SonarLintProjectAnalyzersPanel.javaB\u0003FILJKsrc/main/java/org/sonarlint/intellij/ui/SonarLintProjectAnalyzersPanel.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVPR7c4B9txRcDXxXQ9Z\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectBindPanel.java2\u001eSonarLintProjectBindPanel.javaB\u0003FILJRsrc/main/java/org/sonarlint/intellij/config/project/SonarLintProjectBindPanel.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVI7z0IbaQmCg8KqUyND\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectConfigurable.java2!SonarLintProjectConfigurable.javaB\u0003FILJUsrc/main/java/org/sonarlint/intellij/config/project/SonarLintProjectConfigurable.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVPR7c4B9txRcDXxXQ9f\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/SonarLintProjectNotifications.java2\"SonarLintProjectNotifications.javaB\u0003FILJLsrc/main/java/org/sonarlint/intellij/core/SonarLintProjectNotifications.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVPR7c4B9txRcDXxXQ9a\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectPropertiesPanel.java2$SonarLintProjectPropertiesPanel.javaB\u0003FILJXsrc/main/java/org/sonarlint/intellij/config/project/SonarLintProjectPropertiesPanel.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVI7z0IbaQmCg8KqUyNE\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectSettings.java2\u001dSonarLintProjectSettings.javaB\u0003FILJQsrc/main/java/org/sonarlint/intellij/config/project/SonarLintProjectSettings.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVI7z0IbaQmCg8KqUyNF\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectSettingsPanel.java2\"SonarLintProjectSettingsPanel.javaB\u0003FILJVsrc/main/java/org/sonarlint/intellij/config/project/SonarLintProjectSettingsPanel.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AV1RVebXo6Qmn8dFiYtK\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectState.java2\u001aSonarLintProjectState.javaB\u0003FILJNsrc/main/java/org/sonarlint/intellij/config/project/SonarLintProjectState.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVKsh-RkwW7gmwRPbWJ_\u0012uorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/SonarLintRulePanel.java2\u0017SonarLintRulePanel.javaB\u0003FILJ?src/main/java/org/sonarlint/intellij/ui/SonarLintRulePanel.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVI_VGQpMaJn9Xn8TGme\u0012vorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/util/SonarLintSeverity.java2\u0016SonarLintSeverity.javaB\u0003FILJ@src/main/java/org/sonarlint/intellij/util/SonarLintSeverity.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVGSAZk7ShtJBwDkIzJE\u0012xorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/analysis/SonarLintStatus.java2\u0014SonarLintStatus.javaB\u0003FILJBsrc/main/java/org/sonarlint/intellij/analysis/SonarLintStatus.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AViM6p0PcMUueO7VmA_e\u0012zorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/trigger/SonarLintSubmitter.java2\u0017SonarLintSubmitter.javaB\u0003FILJDsrc/main/java/org/sonarlint/intellij/trigger/SonarLintSubmitter.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVGSAZk7ShtJBwDkIzJF\u0012vorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/analysis/SonarLintTask.java2\u0012SonarLintTask.javaB\u0003FILJ@src/main/java/org/sonarlint/intellij/analysis/SonarLintTask.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVjz0PKA7h1V61hFWcCu\u0012}org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/analysis/SonarLintTaskFactory.java2\u0019SonarLintTaskFactory.javaB\u0003FILJGsrc/main/java/org/sonarlint/intellij/analysis/SonarLintTaskFactory.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVqo6rkFmafQcz5ZZbCo\u0012|org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/telemetry/SonarLintTelemetry.java2\u0017SonarLintTelemetry.javaB\u0003FILJFsrc/main/java/org/sonarlint/intellij/telemetry/SonarLintTelemetry.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVI7z0IbaQmCg8KqUyM9\u0012~org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/SonarLintTextAttributes.java2\u001cSonarLintTextAttributes.javaB\u0003FILJHsrc/main/java/org/sonarlint/intellij/config/SonarLintTextAttributes.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVGSAZk_ShtJBwDkIzJd\u0012}org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/SonarLintToolWindowFactory.java2\u001fSonarLintToolWindowFactory.javaB\u0003FILJGsrc/main/java/org/sonarlint/intellij/ui/SonarLintToolWindowFactory.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AViM6p0NcMUueO7VmA_Z\u0012zorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/analysis/SonarLintUserTask.java2\u0016SonarLintUserTask.javaB\u0003FILJDsrc/main/java/org/sonarlint/intellij/analysis/SonarLintUserTask.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVGSAZlAShtJBwDkIzJh\u0012sorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/util/SonarLintUtils.java2\u0013SonarLintUtils.javaB\u0003FILJ=src/main/java/org/sonarlint/intellij/util/SonarLintUtils.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AV1RVebOo6Qmn8dFiYtJ\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/SonarQubeEventNotifications.java2 SonarQubeEventNotifications.javaB\u0003FILJJsrc/main/java/org/sonarlint/intellij/core/SonarQubeEventNotifications.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVPR7c4B9txRcDXxXQ9V\u0012}org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/SonarQubeServer.java2\u0014SonarQubeServer.javaB\u0003FILJGsrc/main/java/org/sonarlint/intellij/config/global/SonarQubeServer.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVPR7c4B9txRcDXxXQ9X\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/SonarQubeServerMgmtPanel.java2\u001dSonarQubeServerMgmtPanel.javaB\u0003FILJPsrc/main/java/org/sonarlint/intellij/config/global/SonarQubeServerMgmtPanel.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWEswAfBr8h_fVHZ4UsU\u0012~org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/actions/SonarShowCodeAnalyzers.java2\u001bSonarShowCodeAnalyzers.javaB\u0003FILJHsrc/main/java/org/sonarlint/intellij/actions/SonarShowCodeAnalyzers.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVzAhrSdRLymfFZDbbDM\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/wizard/SQServerWizard.java2\u0013SQServerWizard.javaB\u0003FILJMsrc/main/java/org/sonarlint/intellij/config/global/wizard/SQServerWizard.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVPR7c4B9txRcDXxXQ9h\u0012~org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/StandaloneSonarLintFacade.java2\u001eStandaloneSonarLintFacade.javaB\u0003FILJHsrc/main/java/org/sonarlint/intellij/core/StandaloneSonarLintFacade.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVI7z0IcaQmCg8KqUyNI\u0012worg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/messages/StatusListener.java2\u0013StatusListener.javaB\u0003FILJAsrc/main/java/org/sonarlint/intellij/messages/StatusListener.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVfcmCn7J96w3vssdKg8\u0012|org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/persistence/StoreIndex.java2\u000fStoreIndex.javaB\u0003FILJFsrc/main/java/org/sonarlint/intellij/issue/persistence/StoreIndex.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVfcmCn7J96w3vssdKg9\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/persistence/StoreKeyValidator.java2\u0016StoreKeyValidator.javaB\u0003FILJMsrc/main/java/org/sonarlint/intellij/issue/persistence/StoreKeyValidator.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVfcmCn7J96w3vssdKg-\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/persistence/StringStoreIndex.java2\u0015StringStoreIndex.javaB\u0003FILJLsrc/main/java/org/sonarlint/intellij/issue/persistence/StringStoreIndex.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVI_VGQoMaJn9Xn8TGmP\u0012torg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/nodes/SummaryNode.java2\u0010SummaryNode.javaB\u0003FILJ>src/main/java/org/sonarlint/intellij/ui/nodes/SummaryNode.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVI7z0IcaQmCg8KqUyNJ\u0012uorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/messages/TaskListener.java2\u0011TaskListener.javaB\u0003FILJ?src/main/java/org/sonarlint/intellij/messages/TaskListener.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVP2V6LzJSJ5Fb--AuHK\u0012xorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/util/TaskProgressMonitor.java2\u0018TaskProgressMonitor.javaB\u0003FILJBsrc/main/java/org/sonarlint/intellij/util/TaskProgressMonitor.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVrWXSHDXDyvoKp0u12H\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/telemetry/TelemetryEngineProvider.java2\u001cTelemetryEngineProvider.javaB\u0003FILJKsrc/main/java/org/sonarlint/intellij/telemetry/TelemetryEngineProvider.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVP2V6LyJSJ5Fb--AuHH\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/actions/ToolWindowLogAnalysisAction.java2 ToolWindowLogAnalysisAction.javaB\u0003FILJMsrc/main/java/org/sonarlint/intellij/actions/ToolWindowLogAnalysisAction.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVGSAZk6ShtJBwDkIzJB\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/actions/ToolWindowVerboseModeAction.java2 ToolWindowVerboseModeAction.javaB\u0003FILJMsrc/main/java/org/sonarlint/intellij/actions/ToolWindowVerboseModeAction.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVLvZ9LbSacnzMyR1mlv\u0012xorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/tracking/Trackable.java2\u000eTrackable.javaB\u0003FILJBsrc/main/java/org/sonarlint/intellij/issue/tracking/Trackable.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVLvZ9LbSacnzMyR1mlw\u0012vorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/tracking/Tracker.java2\fTracker.javaB\u0003FILJ@src/main/java/org/sonarlint/intellij/issue/tracking/Tracker.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVLvZ9LbSacnzMyR1mlx\u0012worg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/issue/tracking/Tracking.java2\rTracking.javaB\u0003FILJAsrc/main/java/org/sonarlint/intellij/issue/tracking/Tracking.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVlPHdNrJfqetNCTTETW\u0012xorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/ui/tree/TreeCellRenderer.java2\u0015TreeCellRenderer.javaB\u0003FILJBsrc/main/java/org/sonarlint/intellij/ui/tree/TreeCellRenderer.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AVYDL9N7YM5w3R5Wcr8c\u0012sorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/trigger/TriggerType.java2\u0010TriggerType.javaB\u0003FILJ=src/main/java/org/sonarlint/intellij/trigger/TriggerType.javaR\u0004javab\u0014default-organization\"\u0001\n\u0014AViNHYcmcMUueO7VmCdv\u0012rorg.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/UpdateChecker.java2\u0012UpdateChecker.javaB\u0003FILJ<src/main/java/org/sonarlint/intellij/core/UpdateChecker.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AWChh-u3_Q-0dz4JAcNf\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/analysis/VirtualFileTestPredicate.java2\u001dVirtualFileTestPredicate.javaB\u0003FILJKsrc/main/java/org/sonarlint/intellij/analysis/VirtualFileTestPredicate.javaR\u0004javab\u0014default-organization\"\u0002\n\u0014AVzAhrSdRLymfFZDbbDO\u0012\u0001org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/config/global/wizard/WizardModel.java2\u0010WizardModel.javaB\u0003FILJJsrc/main/java/org/sonarlint/intellij/config/global/wizard/WizardModel.javaR\u0004javab\u0014default-organization"
  },
  {
    "path": "backend/server-connection/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-backend-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../pom.xml</relativePath>\n  </parent>\n  <artifactId>sonarlint-server-connection</artifactId>\n  <name>SonarLint Core - Server Connection</name>\n  <description>Manage connections with SonarQube or SonarCloud</description>\n\n  <properties>\n    <xodus.version>2.0.1</xodus.version>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.code.findbugs</groupId>\n      <artifactId>jsr305</artifactId>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-lang3</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>commons-io</groupId>\n      <artifactId>commons-io</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-compress</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>commons-codec</groupId>\n      <artifactId>commons-codec</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-commons</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-server-api</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>com.google.protobuf</groupId>\n      <artifactId>protobuf-java</artifactId>\n      <version>${protobuf.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.jetbrains.xodus</groupId>\n      <artifactId>xodus-entity-store</artifactId>\n      <version>${xodus.version}</version>\n      <exclusions>\n        <exclusion>\n          <groupId>org.slf4j</groupId>\n          <artifactId>slf4j-api</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>org.jetbrains.xodus</groupId>\n      <artifactId>xodus-environment</artifactId>\n      <version>${xodus.version}</version>\n      <exclusions>\n        <exclusion>\n          <groupId>org.slf4j</groupId>\n          <artifactId>slf4j-api</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>org.jetbrains.xodus</groupId>\n      <artifactId>xodus-vfs</artifactId>\n      <version>${xodus.version}</version>\n    </dependency>\n\n    <!-- unit tests -->\n    <dependency>\n      <groupId>com.squareup.okhttp3</groupId>\n      <artifactId>okhttp</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-engine</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.assertj</groupId>\n      <artifactId>assertj-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>com.squareup.okhttp3</groupId>\n      <artifactId>mockwebserver3</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-params</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>ch.qos.logback</groupId>\n      <artifactId>logback-classic</artifactId>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>kr.motd.maven</groupId>\n        <artifactId>os-maven-plugin</artifactId>\n        <executions>\n          <execution>\n            <phase>initialize</phase>\n            <goals>\n              <goal>detect</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.xolstice.maven.plugins</groupId>\n        <artifactId>protobuf-maven-plugin</artifactId>\n        <executions>\n          <execution>\n            <goals>\n              <goal>compile</goal>\n              <goal>test-compile</goal>\n            </goals>\n          </execution>\n        </executions>\n        <configuration>\n          <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n\n\n  <profiles>\n    <!-- Workaround for https://issues.apache.org/jira/projects/MJAR/issues/MJAR-138 -->\n    <profile>\n      <id>conditionally-add-commons-tests-if-tests-not-skipped</id>\n      <activation>\n        <property>\n          <name>maven.test.skip</name>\n          <value>!true</value>\n        </property>\n      </activation>\n      <dependencies>\n        <dependency>\n          <groupId>${project.groupId}</groupId>\n          <artifactId>sonarlint-commons</artifactId>\n          <version>${project.version}</version>\n          <classifier>tests</classifier>\n          <type>test-jar</type>\n          <scope>test</scope>\n        </dependency>\n      </dependencies>\n    </profile>\n  </profiles>\n</project>\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/AnalyzerConfiguration.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.util.Map;\n\npublic class AnalyzerConfiguration {\n  public static final int CURRENT_SCHEMA_VERSION = 1;\n  private final Settings settings;\n  private final Map<String, RuleSet> ruleSetByLanguageKey;\n\n  private final int schemaVersion;\n\n  public AnalyzerConfiguration(Settings settings, Map<String, RuleSet> ruleSetByLanguageKey, int schemaVersion) {\n    this.settings = settings;\n    this.ruleSetByLanguageKey = ruleSetByLanguageKey;\n    this.schemaVersion = schemaVersion;\n  }\n\n  public Settings getSettings() {\n    return settings;\n  }\n\n  public Map<String, RuleSet> getRuleSetByLanguageKey() {\n    return ruleSetByLanguageKey;\n  }\n\n  public int getSchemaVersion() {\n    return schemaVersion;\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/AnalyzerConfigurationStorage.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.function.UnaryOperator;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.serverapi.push.parsing.common.ImpactPayload;\nimport org.sonarsource.sonarlint.core.serverapi.rules.ServerActiveRule;\nimport org.sonarsource.sonarlint.core.serverconnection.proto.Sonarlint;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ProtobufFileUtil;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.RWLock;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.StorageException;\n\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ProtobufFileUtil.writeToFile;\n\npublic class AnalyzerConfigurationStorage {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final RWLock rwLock = new RWLock();\n  private final Path storageFilePath;\n\n  public AnalyzerConfigurationStorage(Path projectStorageRoot) {\n    this.storageFilePath = projectStorageRoot.resolve(\"analyzer_config.pb\");\n  }\n\n  public boolean isValid() {\n    if (!Files.exists(storageFilePath)) {\n      LOG.debug(\"Analyzer configuration storage doesn't exist: {}\", storageFilePath);\n      return false;\n    }\n    return tryRead().isPresent();\n  }\n\n  public void store(AnalyzerConfiguration analyzerConfiguration) {\n    FileUtils.mkdirs(storageFilePath.getParent());\n    var data = adapt(analyzerConfiguration);\n    LOG.debug(\"Storing project analyzer configuration in {}\", storageFilePath);\n    rwLock.write(() -> writeToFile(data, storageFilePath));\n    LOG.debug(\"Stored project analyzer configuration\");\n  }\n\n  private Optional<AnalyzerConfiguration> tryRead() {\n    try {\n      return Optional.of(read());\n    } catch (Exception e) {\n      LOG.debug(\"Could not load analyzer configuration storage\", e);\n      return Optional.empty();\n    }\n  }\n\n  public AnalyzerConfiguration read() {\n    return adapt(rwLock.read(() -> readConfiguration(storageFilePath)));\n  }\n\n  public void update(UnaryOperator<AnalyzerConfiguration> updater) {\n    FileUtils.mkdirs(storageFilePath.getParent());\n    rwLock.write(() -> {\n      Sonarlint.AnalyzerConfiguration config;\n      try {\n        config = readConfiguration(storageFilePath);\n      } catch (StorageException e) {\n        LOG.warn(\"Unable to read storage. Creating a new one.\", e);\n        config = Sonarlint.AnalyzerConfiguration.newBuilder().build();\n      }\n      writeToFile(adapt(updater.apply(adapt(config))), storageFilePath);\n      LOG.debug(\"Storing project data in {}\", storageFilePath);\n    });\n  }\n\n  private static Sonarlint.AnalyzerConfiguration readConfiguration(Path projectFilePath) {\n    return ProtobufFileUtil.readFile(projectFilePath, Sonarlint.AnalyzerConfiguration.parser());\n  }\n\n  private static AnalyzerConfiguration adapt(Sonarlint.AnalyzerConfiguration analyzerConfiguration) {\n    return new AnalyzerConfiguration(\n      new Settings(analyzerConfiguration.getSettingsMap()),\n      analyzerConfiguration.getRuleSetsByLanguageKeyMap().entrySet().stream().collect(Collectors.toMap(\n        Map.Entry::getKey,\n        e -> adapt(e.getValue()))),\n      analyzerConfiguration.getSchemaVersion());\n  }\n\n  private static Sonarlint.AnalyzerConfiguration adapt(AnalyzerConfiguration analyzerConfiguration) {\n    return Sonarlint.AnalyzerConfiguration.newBuilder()\n      .setSchemaVersion(analyzerConfiguration.getSchemaVersion())\n      .putAllSettings(analyzerConfiguration.getSettings().getAll())\n      .putAllRuleSetsByLanguageKey(analyzerConfiguration.getRuleSetByLanguageKey().entrySet().stream()\n        .collect(Collectors.toMap(Map.Entry::getKey, e -> adapt(e.getValue()))))\n      .build();\n  }\n\n  private static RuleSet adapt(Sonarlint.RuleSet ruleSet) {\n    return new RuleSet(\n      ruleSet.getRuleList().stream().map(AnalyzerConfigurationStorage::adapt).toList(),\n      ruleSet.getLastModified());\n  }\n\n  private static ServerActiveRule adapt(Sonarlint.RuleSet.ActiveRule rule) {\n    return new ServerActiveRule(\n      rule.getRuleKey(),\n      IssueSeverity.valueOf(rule.getSeverity()),\n      rule.getParamsMap(),\n      rule.getTemplateKey(),\n      rule.getOverriddenImpactsList().stream()\n        .map(impact -> new ImpactPayload(impact.getSoftwareQuality(), impact.getSeverity()))\n        .toList());\n  }\n\n  private static Sonarlint.RuleSet adapt(RuleSet ruleSet) {\n    return Sonarlint.RuleSet.newBuilder()\n      .setLastModified(ruleSet.getLastModified())\n      .addAllRule(ruleSet.getRules().stream().map(AnalyzerConfigurationStorage::adapt).toList()).build();\n  }\n\n  private static Sonarlint.RuleSet.ActiveRule adapt(ServerActiveRule rule) {\n    return Sonarlint.RuleSet.ActiveRule.newBuilder()\n      .setRuleKey(rule.getRuleKey())\n      .setSeverity(rule.getSeverity().name())\n      .setTemplateKey(rule.getTemplateKey())\n      .putAllParams(rule.getParams())\n      .addAllOverriddenImpacts(rule.getOverriddenImpacts().stream()\n        .map(impact -> Sonarlint.RuleSet.ActiveRule.newBuilder().addOverriddenImpactsBuilder()\n          .setSoftwareQuality(impact.getSoftwareQuality())\n          .setSeverity(impact.getSeverity())\n          .build())\n        .toList())\n      .build();\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/AnalyzerSettingsUpdateSummary.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.util.Map;\n\npublic class AnalyzerSettingsUpdateSummary {\n\n  private final Map<String, String> updatedSettingsValueByKey;\n\n  public AnalyzerSettingsUpdateSummary(Map<String, String> updatedSettingsValueByKey) {\n    this.updatedSettingsValueByKey = updatedSettingsValueByKey;\n  }\n\n  public Map<String, String> getUpdatedSettingsValueByKey() {\n    return updatedSettingsValueByKey;\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/ConnectionStorage.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.nio.file.Path;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabase;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.OrganizationStorage;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.PluginsStorage;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ServerInfoStorage;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ServerIssueStoresManager;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.UserStorage;\n\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ProjectStoragePaths.encodeForFs;\n\npublic class ConnectionStorage {\n  private final ServerIssueStoresManager serverIssueStoresManager;\n  private final ServerInfoStorage serverInfoStorage;\n  private final Map<String, SonarProjectStorage> sonarProjectStorageByKey = new ConcurrentHashMap<>();\n  private final Path projectsStorageRoot;\n  private final PluginsStorage pluginsStorage;\n  private final Path connectionStorageRoot;\n  private final OrganizationStorage organizationStorage;\n  private final String connectionId;\n  private final UserStorage userStorage;\n\n  public ConnectionStorage(Path globalStorageRoot, String connectionId, SonarLintDatabase database) {\n    this.connectionId = connectionId;\n    this.connectionStorageRoot = globalStorageRoot.resolve(encodeForFs(connectionId));\n    this.projectsStorageRoot = connectionStorageRoot.resolve(\"projects\");\n    this.serverIssueStoresManager = new ServerIssueStoresManager(connectionId, database);\n    this.serverInfoStorage = new ServerInfoStorage(connectionStorageRoot);\n    this.pluginsStorage = new PluginsStorage(connectionStorageRoot);\n    this.organizationStorage = new OrganizationStorage(connectionStorageRoot);\n    this.userStorage = new UserStorage(connectionStorageRoot);\n  }\n\n  public ServerInfoStorage serverInfo() {\n    return serverInfoStorage;\n  }\n\n  public SonarProjectStorage project(String sonarProjectKey) {\n    return sonarProjectStorageByKey.computeIfAbsent(sonarProjectKey,\n      k -> new SonarProjectStorage(projectsStorageRoot, serverIssueStoresManager, sonarProjectKey));\n  }\n\n  public PluginsStorage plugins() {\n    return pluginsStorage;\n  }\n\n  public OrganizationStorage organization() {\n    return organizationStorage;\n  }\n\n  public UserStorage user() {\n    return userStorage;\n  }\n\n  public String connectionId() {\n    return connectionId;\n  }\n\n  public void delete() {\n    FileUtils.deleteRecursively(connectionStorageRoot);\n    serverIssueStoresManager.delete();\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/DownloadException.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.SonarLintException;\n\npublic class DownloadException extends SonarLintException {\n  public DownloadException() {\n    super();\n  }\n\n  public DownloadException(String msg, @Nullable Throwable cause) {\n    super(msg, cause);\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/DownloaderUtils.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common;\n\npublic class DownloaderUtils {\n\n  private DownloaderUtils() {\n    // Utility class\n  }\n\n  public static SoftwareQuality parseProtoSoftwareQuality(Common.Impact protoImpact) {\n    if (!protoImpact.hasSoftwareQuality() || protoImpact.getSoftwareQuality() == Common.SoftwareQuality.UNKNOWN_IMPACT_QUALITY) {\n      throw new IllegalArgumentException(\"Unknown or missing software quality\");\n    }\n    return SoftwareQuality.valueOf(protoImpact.getSoftwareQuality().name());\n  }\n\n  public static ImpactSeverity parseProtoImpactSeverity(Common.Impact protoImpact) {\n    if (!protoImpact.hasSeverity() || protoImpact.getSeverity() == Common.ImpactSeverity.UNKNOWN_IMPACT_SEVERITY) {\n      throw new IllegalArgumentException(\"Unknown or missing impact severity\");\n    }\n    return ImpactSeverity.mapSeverity(protoImpact.getSeverity().name());\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/FileUtils.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.io.IOException;\nimport java.nio.file.AccessDeniedException;\nimport java.nio.file.AtomicMoveNotSupportedException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardCopyOption;\nimport java.util.function.Consumer;\nimport org.apache.commons.io.file.PathUtils;\n\npublic class FileUtils {\n\n  /**\n   * A simple representation of an IO operation.\n   * An internal interface necessary for the implementation of {@link #retry()}.\n   */\n  @FunctionalInterface\n  interface IORunnable {\n    void run() throws IOException;\n  }\n\n  private static final String OS_NAME_PROPERTY = \"os.name\";\n\n  /**\n   * A simple check whether the underlying operating system is Windows.\n   */\n  private static final boolean WINDOWS = System.getProperty(OS_NAME_PROPERTY) != null && System.getProperty(OS_NAME_PROPERTY).startsWith(\"Windows\");\n\n  /**\n   * How many times to retry a failing IO operation.\n   */\n  private static final int MAX_RETRIES = WINDOWS ? 20 : 0;\n\n  private FileUtils() {\n    // utility class, forbidden constructor\n  }\n\n  public static void moveDir(Path src, Path dest) {\n    try {\n      moveDirPreferAtomic(src, dest);\n    } catch (IOException e) {\n      throw new IllegalStateException(\"Unable to move \" + src + \" to \" + dest, e);\n    }\n  }\n\n  private static void moveDirPreferAtomic(Path src, Path dest) throws IOException {\n    try {\n      retry(() -> Files.move(src, dest, StandardCopyOption.ATOMIC_MOVE));\n    } catch (AtomicMoveNotSupportedException e) {\n      // Fallback to non atomic move\n      PathUtils.copyDirectory(src, dest);\n      deleteRecursively(src);\n    }\n  }\n\n  /**\n   * Deletes recursively the specified file or directory tree.\n   *\n   * @param path\n   */\n  public static void deleteRecursively(Path path) {\n    if (!path.toFile().exists()) {\n      return;\n    }\n    try {\n      PathUtils.deleteDirectory(path);\n    } catch (IOException e) {\n      throw new IllegalStateException(\"Unable to delete directory \" + path, e);\n    }\n  }\n\n  /**\n   * Creates a directory by creating all nonexistent parent directories first.\n   *\n   * @param path the directory to create\n   */\n  public static void mkdirs(Path path) {\n    try {\n      Files.createDirectories(path);\n    } catch (IOException e) {\n      throw new IllegalStateException(\"Unable to create directory: \" + path, e);\n    }\n  }\n\n  /**\n   * Populates a new temporary directory and when done, replace the target directory with it.\n   *\n   * @param dirContentUpdater function that will be called to create new content\n   * @param target target location to replace when content is ready\n   * @param work directory to populate with new content (typically a new empty temporary directory)\n   */\n  public static void replaceDir(Consumer<Path> dirContentUpdater, Path target, Path work) {\n    dirContentUpdater.accept(work);\n    FileUtils.deleteRecursively(target);\n    FileUtils.mkdirs(target.getParent());\n    FileUtils.moveDir(work, target);\n  }\n\n  /**\n   * On Windows, retries the provided IO operation a number of times, in an effort to make the operation succeed.\n   *\n   * Operations that might fail on Windows are file & directory move, as well as file deletion. These failures\n   * are typically caused by the virus scanner and/or the Windows Indexing Service. These services tend to open a file handle\n   * on newly created files in an effort to scan their content.\n   *\n   * @param runnable the runnable whose execution should be retried\n   */\n  static void retry(IORunnable runnable, int maxRetries) throws IOException {\n    for (var retry = 0; retry < maxRetries; retry++) {\n      try {\n        runnable.run();\n        return;\n      } catch (AccessDeniedException e) {\n        // Sleep a bit to give a chance to the virus scanner / Windows Indexing Service to release the opened file handle\n        try {\n          Thread.sleep(100);\n        } catch (InterruptedException ie) {\n          // Nothing else that meaningfully can be done here\n          Thread.currentThread().interrupt();\n        }\n      }\n    }\n\n    // Give it a one last chance, and this time do not swallow the exception\n    runnable.run();\n  }\n\n  static void retry(IORunnable runnable) throws IOException {\n    retry(runnable, MAX_RETRIES);\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/HotspotDownloader.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.hotspot.HotspotApi;\nimport org.sonarsource.sonarlint.core.serverapi.hotspot.ServerHotspot;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Hotspots;\n\nimport static java.util.function.Predicate.not;\n\npublic class HotspotDownloader {\n\n  private final Set<SonarLanguage> enabledLanguages;\n\n  public HotspotDownloader(Set<SonarLanguage> enabledLanguages) {\n    this.enabledLanguages = enabledLanguages;\n  }\n\n  /**\n   * Fetch all hotspots of the project with specified key, using new SQ 10.1 api/issues/pull\n   *\n   * @param projectKey project key\n   * @param branchName name of the branch.\n   * @return List of hotspots. It can be empty but never null.\n   */\n  public PullResult downloadFromPull(HotspotApi hotspotApi, String projectKey, String branchName, Optional<Instant> lastSync, SonarLintCancelMonitor cancelMonitor) {\n    var apiResult = hotspotApi.pullHotspots(projectKey, branchName, enabledLanguages, lastSync.map(Instant::toEpochMilli).orElse(null), cancelMonitor);\n    var changedHotspots = apiResult.getHotspots()\n      .stream()\n      .filter(not(Hotspots.HotspotLite::getClosed))\n      .map(HotspotDownloader::convertLiteHotspot)\n      .toList();\n    var closedIssueKeys = apiResult.getHotspots()\n      .stream()\n      .filter(Hotspots.HotspotLite::getClosed)\n      .map(Hotspots.HotspotLite::getKey)\n      .collect(Collectors.toSet());\n\n    return new PullResult(Instant.ofEpochMilli(apiResult.getTimestamp().getQueryTimestamp()), changedHotspots, closedIssueKeys);\n  }\n\n  private static ServerHotspot convertLiteHotspot(Hotspots.HotspotLite liteHotspotFromWs) {\n    var creationDate = Instant.ofEpochMilli(liteHotspotFromWs.getCreationDate());\n    return new ServerHotspot(\n      liteHotspotFromWs.getKey(),\n      liteHotspotFromWs.getRuleKey(),\n      liteHotspotFromWs.getMessage(),\n      Path.of(liteHotspotFromWs.getFilePath()),\n      toServerHotspotTextRange(liteHotspotFromWs.getTextRange()),\n      creationDate,\n      fromHotspotLite(liteHotspotFromWs),\n      VulnerabilityProbability.valueOf(liteHotspotFromWs.getVulnerabilityProbability()),\n      liteHotspotFromWs.getAssignee()\n    );\n  }\n\n  private static HotspotReviewStatus fromHotspotLite(Hotspots.HotspotLite hotspot) {\n    var status = hotspot.getStatus();\n    var resolution = hotspot.hasResolution() ? hotspot.getResolution() : null;\n    return HotspotReviewStatus.fromStatusAndResolution(status, resolution);\n  }\n\n  private static TextRangeWithHash toServerHotspotTextRange(Hotspots.TextRange textRange) {\n    return new TextRangeWithHash(\n      textRange.getStartLine(),\n      textRange.getStartLineOffset(),\n      textRange.getEndLine(),\n      textRange.getEndLineOffset(),\n      textRange.getHash()\n    );\n  }\n\n  public static class PullResult {\n    private final Instant queryTimestamp;\n    private final List<ServerHotspot> changedHotspots;\n    private final Set<String> closedHotspotKeys;\n\n    public PullResult(Instant queryTimestamp, List<ServerHotspot> changedHotspots, Set<String> closedHotspotKeys) {\n      this.queryTimestamp = queryTimestamp;\n      this.changedHotspots = changedHotspots;\n      this.closedHotspotKeys = closedHotspotKeys;\n    }\n\n    public Instant getQueryTimestamp() {\n      return queryTimestamp;\n    }\n\n    public List<ServerHotspot> getChangedHotspots() {\n      return changedHotspots;\n    }\n\n    public Set<String> getClosedHotspotKeys() {\n      return closedHotspotKeys;\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/IssueDownloader.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.sonar.scanner.protocol.input.ScannerInput;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues.IssueLite;\nimport org.sonarsource.sonarlint.core.serverapi.rules.RulesApi;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.FileLevelServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.LineLevelServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.RangeLevelServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerIssue;\n\nimport static java.util.function.Predicate.not;\nimport static org.sonarsource.sonarlint.core.serverconnection.DownloaderUtils.parseProtoImpactSeverity;\nimport static org.sonarsource.sonarlint.core.serverconnection.DownloaderUtils.parseProtoSoftwareQuality;\n\npublic class IssueDownloader {\n\n  private final Set<SonarLanguage> enabledLanguages;\n\n  public Set<SonarLanguage> getEnabledLanguages() {\n    return enabledLanguages;\n  }\n\n  public IssueDownloader(Set<SonarLanguage> enabledLanguages) {\n    this.enabledLanguages = enabledLanguages;\n  }\n\n  /**\n   * Fetch all issues of the component with specified key.\n   * If the component doesn't exist or it exists but has no issues, an empty iterator is returned.\n   *\n   * @param key           project key, or file key.\n   * @param branchName    name of the branch.\n   * @param cancelMonitor\n   * @return List of issues. It can be empty but never null.\n   */\n  public List<ServerIssue<?>> downloadFromBatch(ServerApi serverApi, String key, @Nullable String branchName, SonarLintCancelMonitor cancelMonitor) {\n    var issueApi = serverApi.issue();\n\n    List<ServerIssue<?>> result = new ArrayList<>();\n\n    var batchIssues = issueApi.downloadAllFromBatchIssues(key, branchName, cancelMonitor);\n\n    for (ScannerInput.ServerIssue batchIssue : batchIssues) {\n      // We ignore project level issues\n      if (!RulesApi.TAINT_REPOS.contains(batchIssue.getRuleRepository()) && batchIssue.hasPath()) {\n        result.add(convertBatchIssue(batchIssue));\n      }\n    }\n\n    return result;\n  }\n\n  /**\n   * Fetch all issues of the project with specified key, using new SQ 9.6 api/issues/pull\n   *\n   * @param projectKey project key\n   * @param branchName name of the branch.\n   * @return List of issues. It can be empty but never null.\n   */\n  public PullResult downloadFromPull(ServerApi serverApi, String projectKey, String branchName, Optional<Instant> lastSync, SonarLintCancelMonitor cancelMonitor) {\n    var issueApi = serverApi.issue();\n\n    var apiResult = issueApi.pullIssues(projectKey, branchName, enabledLanguages, lastSync.map(Instant::toEpochMilli).orElse(null), cancelMonitor);\n    // Ignore project level issues\n    List<ServerIssue<?>> changedIssues = apiResult.getIssues()\n      .stream()\n      // Ignore project level issues\n      .filter(i -> i.getMainLocation().hasFilePath())\n      .filter(not(IssueLite::getClosed))\n      .<ServerIssue<?>>map(IssueDownloader::convertLiteIssue)\n      .toList();\n    var closedIssueKeys = apiResult.getIssues()\n      .stream()\n      // Ignore project level issues\n      .filter(i -> i.getMainLocation().hasFilePath())\n      .filter(IssueLite::getClosed)\n      .map(IssueLite::getKey)\n      .collect(Collectors.toSet());\n\n    return new PullResult(Instant.ofEpochMilli(apiResult.getTimestamp().getQueryTimestamp()), changedIssues, closedIssueKeys);\n  }\n\n  private static ServerIssue<?> convertBatchIssue(ScannerInput.ServerIssue batchIssueFromWs) {\n    var ruleKey = batchIssueFromWs.getRuleRepository() + \":\" + batchIssueFromWs.getRuleKey();\n    // We have filtered out issues without file path earlier\n    var filePath = Path.of(batchIssueFromWs.getPath());\n    var creationDate = Instant.ofEpochMilli(batchIssueFromWs.getCreationDate());\n    var userSeverity = batchIssueFromWs.getManualSeverity() ? IssueSeverity.valueOf(batchIssueFromWs.getSeverity().name()) : null;\n    var ruleType = RuleType.valueOf(batchIssueFromWs.getType());\n    var impacts = Collections.<SoftwareQuality, ImpactSeverity>emptyMap();\n    var resolutionStatus = IssueStatus.parse(batchIssueFromWs.getResolution());\n    if (batchIssueFromWs.hasLine()) {\n      return new LineLevelServerIssue(batchIssueFromWs.getKey(), batchIssueFromWs.hasResolution(), resolutionStatus, ruleKey,\n        batchIssueFromWs.getMsg(), batchIssueFromWs.getChecksum(), filePath,\n        creationDate, userSeverity, ruleType, batchIssueFromWs.getLine(), impacts);\n    } else {\n      return new FileLevelServerIssue(batchIssueFromWs.getKey(), batchIssueFromWs.hasResolution(), resolutionStatus, ruleKey,\n        batchIssueFromWs.getMsg(), filePath, creationDate, userSeverity,\n        ruleType, impacts);\n    }\n  }\n\n  private static ServerIssue<?> convertLiteIssue(IssueLite liteIssueFromWs) {\n    var mainLocation = liteIssueFromWs.getMainLocation();\n    // We have filtered out issues without file path earlier\n    var filePath = Path.of(mainLocation.getFilePath());\n    var creationDate = Instant.ofEpochMilli(liteIssueFromWs.getCreationDate());\n    var userSeverity = liteIssueFromWs.hasUserSeverity() ? IssueSeverity.valueOf(liteIssueFromWs.getUserSeverity().name()) : null;\n    var ruleType = RuleType.valueOf(liteIssueFromWs.getType().name());\n    var impacts = liteIssueFromWs.getImpactsList().stream()\n      .map(i -> Map.entry(\n        parseProtoSoftwareQuality(i),\n        parseProtoImpactSeverity(i)))\n      .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\n    if (mainLocation.hasTextRange()) {\n      return new RangeLevelServerIssue(liteIssueFromWs.getKey(), liteIssueFromWs.getResolved(), null, liteIssueFromWs.getRuleKey(), mainLocation.getMessage(),\n        filePath, creationDate, userSeverity,\n        ruleType, toServerIssueTextRange(mainLocation.getTextRange()), impacts);\n    } else {\n      return new FileLevelServerIssue(liteIssueFromWs.getKey(), liteIssueFromWs.getResolved(), null, liteIssueFromWs.getRuleKey(), mainLocation.getMessage(),\n        filePath, creationDate, userSeverity, ruleType, impacts);\n    }\n  }\n\n  private static TextRangeWithHash toServerIssueTextRange(Issues.TextRange textRange) {\n    return new TextRangeWithHash(textRange.getStartLine(), textRange.getStartLineOffset(), textRange.getEndLine(), textRange.getEndLineOffset(), textRange.getHash());\n  }\n\n  public static class PullResult {\n    private final Instant queryTimestamp;\n    private final List<ServerIssue<?>> changedIssues;\n    private final Set<String> closedIssueKeys;\n\n    public PullResult(Instant queryTimestamp, List<ServerIssue<?>> changedIssues, Set<String> closedIssueKeys) {\n      this.queryTimestamp = queryTimestamp;\n      this.changedIssues = changedIssues;\n      this.closedIssueKeys = closedIssueKeys;\n    }\n\n    public Instant getQueryTimestamp() {\n      return queryTimestamp;\n    }\n\n    public List<ServerIssue<?>> getChangedIssues() {\n      return changedIssues;\n    }\n\n    public Set<String> getClosedIssueKeys() {\n      return closedIssueKeys;\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/IssueStorePaths.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport javax.annotation.CheckForNull;\n\nimport static org.sonarsource.sonarlint.core.serverapi.util.ServerApiUtils.toSonarQubePath;\n\npublic class IssueStorePaths {\n\n  private IssueStorePaths() {\n\n  }\n\n  @CheckForNull\n  public static String idePathToFileKey(ProjectBinding projectBinding, Path ideFilePath) {\n    var serverFilePath = idePathToServerPath(projectBinding, ideFilePath);\n\n    if (serverFilePath == null) {\n      return null;\n    }\n    return componentKey(projectBinding, serverFilePath);\n  }\n\n  public static String componentKey(ProjectBinding projectBinding, Path serverFilePath) {\n    return componentKey(projectBinding.projectKey(), serverFilePath);\n  }\n\n  public static String componentKey(String projectKey, Path serverFilePath) {\n    return projectKey + \":\" + toSonarQubePath(serverFilePath);\n  }\n\n  @CheckForNull\n  public static Path idePathToServerPath(ProjectBinding projectBinding, Path ideFilePath) {\n    return idePathToServerPath(Paths.get(projectBinding.idePathPrefix()), Paths.get(projectBinding.serverPathPrefix()), ideFilePath);\n  }\n\n  @CheckForNull\n  public static Path idePathToServerPath(Path idePathPrefix, Path serverPathPrefix, Path ideFilePath) {\n    Path commonPart;\n    if (!idePathPrefix.toString().isEmpty()) {\n      if (!ideFilePath.startsWith(idePathPrefix)) {\n        return null;\n      }\n      commonPart = idePathPrefix.relativize(ideFilePath);\n    } else {\n      commonPart = ideFilePath;\n    }\n    if (!serverPathPrefix.toString().isEmpty()) {\n      return serverPathPrefix.resolve(commonPart);\n    } else {\n      return commonPart;\n    }\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/LocalStorageSynchronizer.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport com.google.common.collect.Maps;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.qualityprofile.QualityProfile;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.StorageException;\n\nimport static java.util.stream.Collectors.toSet;\n\npublic class LocalStorageSynchronizer {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final Set<String> enabledLanguageKeys;\n  private final ConnectionStorage storage;\n  private final ServerInfoSynchronizer serverInfoSynchronizer;\n\n  public LocalStorageSynchronizer(Set<SonarLanguage> enabledLanguages, ServerInfoSynchronizer serverInfoSynchronizer, ConnectionStorage storage) {\n    this.enabledLanguageKeys = enabledLanguages.stream().map(SonarLanguage::getSonarLanguageKey).collect(toSet());\n    this.storage = storage;\n    this.serverInfoSynchronizer = serverInfoSynchronizer;\n  }\n\n  public Summary synchronizeServerInfosAndPlugins(ServerApi serverApi, SonarLintCancelMonitor cancelMonitor) {\n    serverInfoSynchronizer.synchronize(serverApi, cancelMonitor);\n    var version = storage.serverInfo().read().orElseThrow().version();\n    return new Summary(version);\n  }\n\n  private static AnalyzerSettingsUpdateSummary diffAnalyzerConfiguration(AnalyzerConfiguration original, AnalyzerConfiguration updated) {\n    var originalSettings = original.getSettings().getAll();\n    var updatedSettings = updated.getSettings().getAll();\n    var diff = Maps.difference(originalSettings, updatedSettings);\n    var updatedSettingsValueByKey = diff.entriesDiffering().entrySet().stream()\n      .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().rightValue()));\n    updatedSettingsValueByKey.putAll(diff.entriesOnlyOnRight());\n    updatedSettingsValueByKey.putAll(diff.entriesOnlyOnLeft().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> \"\")));\n    return new AnalyzerSettingsUpdateSummary(updatedSettingsValueByKey);\n  }\n\n  public AnalyzerSettingsUpdateSummary synchronizeAnalyzerConfig(ServerApi serverApi, String projectKey, SonarLintCancelMonitor cancelMonitor) {\n    var updatedAnalyzerConfiguration = downloadAnalyzerConfig(serverApi, projectKey, cancelMonitor);\n    AnalyzerSettingsUpdateSummary configUpdateSummary;\n    try {\n      var originalAnalyzerConfiguration = storage.project(projectKey).analyzerConfiguration().read();\n      configUpdateSummary = diffAnalyzerConfiguration(originalAnalyzerConfiguration, updatedAnalyzerConfiguration);\n    } catch (StorageException e) {\n      configUpdateSummary = new AnalyzerSettingsUpdateSummary(updatedAnalyzerConfiguration.getSettings().getAll());\n    }\n\n    storage.project(projectKey).analyzerConfiguration().store(updatedAnalyzerConfiguration);\n    var version = storage.serverInfo().read().orElseThrow().version();\n    serverApi.newCodeApi().getNewCodeDefinition(projectKey, null, version, cancelMonitor)\n      .ifPresent(ncd -> storage.project(projectKey).newCodeDefinition().store(ncd));\n    return configUpdateSummary;\n  }\n\n  private AnalyzerConfiguration downloadAnalyzerConfig(ServerApi serverApi, String projectKey, SonarLintCancelMonitor cancelMonitor) {\n    LOG.info(\"[SYNC] Synchronizing analyzer configuration for project '{}'\", projectKey);\n    LOG.info(\"[SYNC] Languages enabled for synchronization: {}\", enabledLanguageKeys);\n    Map<String, RuleSet> currentRuleSets;\n    int currentSchemaVersion;\n    try {\n      var analyzerConfiguration = storage.project(projectKey).analyzerConfiguration().read();\n      currentRuleSets = analyzerConfiguration.getRuleSetByLanguageKey();\n      currentSchemaVersion = analyzerConfiguration.getSchemaVersion();\n    } catch (StorageException e) {\n      currentRuleSets = Map.of();\n      currentSchemaVersion = 0;\n    }\n    var shouldForceRuleSetUpdate = outdatedSchema(currentSchemaVersion);\n    var currentRuleSetsFinal = currentRuleSets;\n    var settings = new Settings(serverApi.settings().getProjectSettings(projectKey, cancelMonitor));\n    var ruleSetsByLanguageKey = serverApi.qualityProfile().getQualityProfiles(projectKey, cancelMonitor).stream()\n      .filter(qualityProfile -> enabledLanguageKeys.contains(qualityProfile.getLanguage()))\n      .collect(Collectors.toMap(QualityProfile::getLanguage, profile -> toRuleSet(serverApi, currentRuleSetsFinal, profile, shouldForceRuleSetUpdate, cancelMonitor)));\n    return new AnalyzerConfiguration(settings, ruleSetsByLanguageKey, AnalyzerConfiguration.CURRENT_SCHEMA_VERSION);\n  }\n\n  private static RuleSet toRuleSet(ServerApi serverApi, Map<String, RuleSet> currentRuleSets, QualityProfile profile, boolean forceUpdate,\n    SonarLintCancelMonitor cancelMonitor) {\n    var language = profile.getLanguage();\n    if (forceUpdate ||\n      newlySupportedLanguage(currentRuleSets, language) ||\n      profileModifiedSinceLastSync(currentRuleSets, profile, language)) {\n      var profileKey = profile.getKey();\n      LOG.info(\"[SYNC] Fetching rule set for language '{}' from profile '{}'\", language, profileKey);\n      var profileActiveRules = serverApi.rules().getAllActiveRules(profileKey, cancelMonitor);\n      return new RuleSet(profileActiveRules, profile.getRulesUpdatedAt());\n    } else {\n      LOG.info(\"[SYNC] Active rules for '{}' are up-to-date\", language);\n      return currentRuleSets.get(language);\n    }\n  }\n\n  private static boolean profileModifiedSinceLastSync(Map<String, RuleSet> currentRuleSets, QualityProfile profile, String language) {\n    return !currentRuleSets.get(language).getLastModified().equals(profile.getRulesUpdatedAt());\n  }\n\n  private static boolean newlySupportedLanguage(Map<String, RuleSet> currentRuleSets, String language) {\n    return !currentRuleSets.containsKey(language);\n  }\n\n  private static boolean outdatedSchema(int currentSchemaVersion) {\n    return currentSchemaVersion < AnalyzerConfiguration.CURRENT_SCHEMA_VERSION;\n  }\n\n  public record Summary(Version version) {\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/Organization.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.util.UUID;\n\npublic record Organization(String id, UUID uuidV4) {\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/OrganizationSynchronizer.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\n\npublic class OrganizationSynchronizer {\n  private final ConnectionStorage storage;\n\n  public OrganizationSynchronizer(ConnectionStorage storage) {\n    this.storage = storage;\n  }\n\n  // should be called only in the context of SonarQube Cloud\n  public Organization readOrSynchronizeOrganization(ServerApi serverApi, SonarLintCancelMonitor cancelMonitor) {\n    return storage.organization().read()\n      .orElseGet(() -> synchronize(serverApi, cancelMonitor));\n  }\n\n  private Organization synchronize(ServerApi serverApi, SonarLintCancelMonitor cancelMonitor) {\n    var organizationDto = serverApi.organization().getOrganizationByKey(cancelMonitor);\n    var organization = new Organization(organizationDto.id(), organizationDto.uuidV4());\n    storage.organization().store(organization);\n    return organization;\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/ProjectBinding.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.util.Objects;\nimport java.util.Optional;\n\n/**\n * Describes the link between a project in the IDE and a project in SonarQube/SonarCloud.\n *\n */\npublic class ProjectBinding {\n  private final String projectKey;\n  private final String serverPathPrefix;\n  private final String idePathPrefix;\n\n  public ProjectBinding(String projectKey, String serverPathPrefix, String idePathPrefix) {\n    this.projectKey = projectKey;\n    this.serverPathPrefix = serverPathPrefix;\n    this.idePathPrefix = idePathPrefix;\n  }\n\n  public String projectKey() {\n    return projectKey;\n  }\n\n  public String serverPathPrefix() {\n    return serverPathPrefix;\n  }\n\n  public String idePathPrefix() {\n    return idePathPrefix;\n  }\n\n  public Optional<String> serverPathToIdePath(String serverPath) {\n    if (!serverPath.startsWith(serverPathPrefix())) {\n      return Optional.empty();\n    }\n    var localPrefixLen = serverPathPrefix().length();\n    if (localPrefixLen > 0) {\n      localPrefixLen++;\n    }\n    var actualLocalPrefix = idePathPrefix();\n    if (!actualLocalPrefix.isEmpty()) {\n      actualLocalPrefix = actualLocalPrefix + \"/\";\n    }\n    return Optional.of(actualLocalPrefix + serverPath.substring(localPrefixLen));\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) {\n      return true;\n    }\n    if (o == null || getClass() != o.getClass()) {\n      return false;\n    }\n    var that = (ProjectBinding) o;\n    return Objects.equals(projectKey, that.projectKey) &&\n      Objects.equals(serverPathPrefix, that.serverPathPrefix) &&\n      Objects.equals(idePathPrefix, that.idePathPrefix);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(projectKey, serverPathPrefix, idePathPrefix);\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/ProjectBranches.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.util.Set;\n\npublic class ProjectBranches {\n  private final Set<String> branchNames;\n  private final String mainBranchName;\n\n  public ProjectBranches(Set<String> branchNames, String mainBranchName) {\n    this.branchNames = branchNames;\n    this.mainBranchName = mainBranchName;\n  }\n\n  public Set<String> getBranchNames() {\n    return branchNames;\n  }\n\n  public String getMainBranchName() {\n    return mainBranchName;\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/ProjectBranchesStorage.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Set;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.serverconnection.proto.Sonarlint;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ProtobufFileUtil;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.RWLock;\n\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ProtobufFileUtil.writeToFile;\n\npublic class ProjectBranchesStorage {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final Path storageFilePath;\n  private final RWLock rwLock = new RWLock();\n\n  public ProjectBranchesStorage(Path projectStorageRoot) {\n    this.storageFilePath = projectStorageRoot.resolve(\"project_branches.pb\");\n  }\n\n  public boolean exists() {\n    return Files.exists(storageFilePath);\n  }\n\n  public void store(ProjectBranches projectBranches) {\n    FileUtils.mkdirs(storageFilePath.getParent());\n    var data = adapt(projectBranches);\n    LOG.debug(\"Storing project branches in {}\", storageFilePath);\n    rwLock.write(() -> writeToFile(data, storageFilePath));\n  }\n\n  public ProjectBranches read() {\n    return adapt(rwLock.read(() -> ProtobufFileUtil.readFile(storageFilePath, Sonarlint.ProjectBranches.parser())));\n  }\n\n  private static ProjectBranches adapt(Sonarlint.ProjectBranches projectBranches) {\n    return new ProjectBranches(Set.copyOf(projectBranches.getBranchNameList()), projectBranches.getMainBranchName());\n  }\n\n  private static Sonarlint.ProjectBranches adapt(ProjectBranches projectBranches) {\n    return Sonarlint.ProjectBranches.newBuilder()\n      .addAllBranchName(projectBranches.getBranchNames())\n      .setMainBranchName(projectBranches.getMainBranchName())\n      .build();\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/RuleSet.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.util.Collection;\nimport java.util.Map;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.serverapi.rules.ServerActiveRule;\n\npublic class RuleSet {\n  private final Collection<ServerActiveRule> rules;\n  private final Map<String, ServerActiveRule> rulesByKey;\n  private final String lastModified;\n\n  public RuleSet(Collection<ServerActiveRule> rules, String lastModified) {\n    this.rules = rules;\n    this.rulesByKey = rules.stream().collect(Collectors.toMap(ServerActiveRule::getRuleKey, Function.identity()));\n    this.lastModified = lastModified;\n  }\n\n  public Collection<ServerActiveRule> getRules() {\n    return rules;\n  }\n\n  public Map<String, ServerActiveRule> getRulesByKey() {\n    return rulesByKey;\n  }\n\n  public String getLastModified() {\n    return lastModified;\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/ServerHotspotUpdater.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.nio.file.Path;\nimport java.util.Set;\nimport java.util.function.Supplier;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.hotspot.HotspotApi;\n\nimport static org.sonarsource.sonarlint.core.serverconnection.ServerUpdaterUtils.computeLastSync;\n\npublic class ServerHotspotUpdater {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final ConnectionStorage storage;\n  private final HotspotDownloader hotspotDownloader;\n\n  public ServerHotspotUpdater(ConnectionStorage storage, HotspotDownloader hotspotDownloader) {\n    this.storage = storage;\n    this.hotspotDownloader = hotspotDownloader;\n  }\n\n  public void updateAll(HotspotApi hotspotApi, String projectKey, String branchName, Set<SonarLanguage> enabledLanguages, SonarLintCancelMonitor cancelMonitor) {\n    var projectHotspots = hotspotApi.getAll(projectKey, branchName, cancelMonitor);\n    storage.project(projectKey).findings().replaceAllHotspotsOfBranch(branchName, projectHotspots, enabledLanguages);\n  }\n\n  public void updateForFile(HotspotApi hotspotApi, String projectKey, Path serverFilePath, String branchName, Supplier<Version> serverVersionSupplier,\n    SonarLintCancelMonitor cancelMonitor) {\n    if (hotspotApi.supportHotspotsPull(serverVersionSupplier)) {\n      LOG.debug(\"Skip downloading file hotspots on SonarQube 10.1+\");\n      return;\n    }\n    var fileHotspots = hotspotApi.getFromFile(projectKey, serverFilePath, branchName, cancelMonitor);\n    storage.project(projectKey).findings().replaceAllHotspotsOfFile(branchName, serverFilePath, fileHotspots);\n  }\n\n  public void sync(HotspotApi hotspotApi, String projectKey, String branchName, Set<SonarLanguage> enabledLanguages, SonarLintCancelMonitor cancelMonitor) {\n    var lastSync = storage.project(projectKey).findings().getLastHotspotSyncTimestamp(branchName);\n\n    lastSync = computeLastSync(enabledLanguages, lastSync, storage.project(projectKey).findings().getLastHotspotEnabledLanguages(branchName));\n\n    var result = hotspotDownloader.downloadFromPull(hotspotApi, projectKey, branchName, lastSync, cancelMonitor);\n    storage.project(projectKey).findings().mergeHotspots(branchName, result.getChangedHotspots(), result.getClosedHotspotKeys(),\n      result.getQueryTimestamp(), enabledLanguages);\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/ServerInfoSynchronizer.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.util.Set;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.features.Feature;\n\npublic class ServerInfoSynchronizer {\n  private final ConnectionStorage storage;\n\n  public ServerInfoSynchronizer(ConnectionStorage storage) {\n    this.storage = storage;\n  }\n\n  public StoredServerInfo readOrSynchronizeServerInfo(ServerApi serverApi, SonarLintCancelMonitor cancelMonitor) {\n    return storage.serverInfo().read()\n      .orElseGet(() -> {\n        synchronize(serverApi, cancelMonitor);\n        return storage.serverInfo().read().get();\n      });\n  }\n\n  public void synchronize(ServerApi serverApi, SonarLintCancelMonitor cancelMonitor) {\n    var serverStatus = serverApi.system().getStatus(cancelMonitor);\n    var serverVersionAndStatusChecker = new ServerVersionAndStatusChecker(serverApi);\n    serverVersionAndStatusChecker.checkVersionAndStatus(cancelMonitor);\n    var globalSettings = serverApi.settings().getGlobalSettings(cancelMonitor);\n    var supportedFeatures = serverApi.isSonarCloud() ? getSupportedFeaturesForSonarQubeCloud(serverApi, cancelMonitor) : serverApi.features().list(cancelMonitor);\n    storage.serverInfo().store(serverStatus, supportedFeatures, globalSettings);\n  }\n\n  private static Set<Feature> getSupportedFeaturesForSonarQubeCloud(ServerApi serverApi, SonarLintCancelMonitor cancelMonitor) {\n    return serverApi.sca().isScaEnabled(cancelMonitor).enabled() ? Set.of(Feature.SCA) : Set.of();\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/ServerIssueUpdater.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Set;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerTaintIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.UpdateSummary;\n\nimport static java.util.stream.Collectors.toSet;\nimport static org.sonarsource.sonarlint.core.serverconnection.ServerUpdaterUtils.computeLastSync;\n\npublic class ServerIssueUpdater {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final ConnectionStorage storage;\n  private final IssueDownloader issueDownloader;\n  private final TaintIssueDownloader taintIssueDownloader;\n\n  public ServerIssueUpdater(ConnectionStorage storage, IssueDownloader issueDownloader, TaintIssueDownloader taintIssueDownloader) {\n    this.storage = storage;\n    this.issueDownloader = issueDownloader;\n    this.taintIssueDownloader = taintIssueDownloader;\n  }\n\n  public void update(ServerApi serverApi, String projectKey, String branchName, Set<SonarLanguage> enabledLanguages, SonarLintCancelMonitor cancelMonitor) {\n    if (serverApi.isSonarCloud()) {\n      var issues = issueDownloader.downloadFromBatch(serverApi, projectKey, branchName, cancelMonitor);\n      storage.project(projectKey).findings().replaceAllIssuesOfBranch(branchName, issues, enabledLanguages);\n    } else {\n      sync(serverApi, projectKey, branchName, issueDownloader.getEnabledLanguages(), cancelMonitor);\n    }\n  }\n\n  public void sync(ServerApi serverApi, String projectKey, String branchName, Set<SonarLanguage> enabledLanguages, SonarLintCancelMonitor cancelMonitor) {\n    var lastSync = storage.project(projectKey).findings().getLastIssueSyncTimestamp(branchName);\n\n    lastSync = computeLastSync(enabledLanguages, lastSync, storage.project(projectKey).findings().getLastIssueEnabledLanguages(branchName));\n\n    var result = issueDownloader.downloadFromPull(serverApi, projectKey, branchName, lastSync, cancelMonitor);\n    storage.project(projectKey).findings().mergeIssues(branchName, result.getChangedIssues(), result.getClosedIssueKeys(),\n      result.getQueryTimestamp(), enabledLanguages);\n  }\n\n  public UpdateSummary<ServerTaintIssue> syncTaints(ServerApi serverApi, String projectKey, String branchName, Set<SonarLanguage> enabledLanguages,\n    SonarLintCancelMonitor cancelMonitor) {\n    var serverIssueStore = storage.project(projectKey).findings();\n\n    var lastSync = serverIssueStore.getLastTaintSyncTimestamp(branchName);\n\n    lastSync = computeLastSync(enabledLanguages, lastSync, storage.project(projectKey).findings().getLastTaintEnabledLanguages(branchName));\n\n    var result = taintIssueDownloader.downloadTaintFromPull(serverApi, projectKey, branchName, lastSync, cancelMonitor);\n    var previousTaintIssues = serverIssueStore.loadTaint(branchName);\n    var previousTaintIssueKeys = previousTaintIssues.stream().map(ServerTaintIssue::getSonarServerKey).collect(toSet());\n    serverIssueStore.mergeTaintIssues(branchName, result.getChangedTaintIssues(), result.getClosedIssueKeys(), result.getQueryTimestamp(), enabledLanguages);\n    var deletedTaintVulnerabilityIds = previousTaintIssues.stream().filter(issue -> result.getClosedIssueKeys().contains(issue.getSonarServerKey())).map(ServerTaintIssue::getId)\n      .collect(toSet());\n    var addedTaintVulnerabilities = result.getChangedTaintIssues().stream().filter(issue -> !previousTaintIssueKeys.contains(issue.getSonarServerKey()))\n      .toList();\n    var updatedTaintVulnerabilities = result.getChangedTaintIssues().stream().filter(issue -> previousTaintIssueKeys.contains(issue.getSonarServerKey()))\n      .toList();\n    return new UpdateSummary<>(deletedTaintVulnerabilityIds, addedTaintVulnerabilities, updatedTaintVulnerabilities);\n  }\n\n  public void updateFileIssuesIfNeeded(ServerApi serverApi, String projectKey, Path serverFileRelativePath, String branchName, SonarLintCancelMonitor cancelMonitor) {\n    if (serverApi.isSonarCloud()) {\n      updateFileIssues(serverApi, projectKey, serverFileRelativePath, branchName, cancelMonitor);\n    } else {\n      LOG.debug(\"Skip downloading file issues on SonarQube \");\n    }\n  }\n\n  public void updateFileIssues(ServerApi serverApi, String projectKey, Path serverFileRelativePath, String branchName, SonarLintCancelMonitor cancelMonitor) {\n    var fileKey = IssueStorePaths.componentKey(projectKey, serverFileRelativePath);\n    List<ServerIssue<?>> issues = new ArrayList<>();\n    try {\n      issues.addAll(issueDownloader.downloadFromBatch(serverApi, fileKey, branchName, cancelMonitor));\n    } catch (Exception e) {\n      // null as cause so that it doesn't get wrapped\n      throw new DownloadException(\"Failed to update file issues: \" + e.getMessage(), null);\n    }\n    storage.project(projectKey).findings().replaceAllIssuesOfFile(branchName, serverFileRelativePath, issues);\n  }\n\n  public UpdateSummary<ServerTaintIssue> downloadProjectTaints(ServerApi serverApi, String projectKey, String branchName, Set<SonarLanguage> enabledLanguages,\n    SonarLintCancelMonitor cancelMonitor) {\n    List<ServerTaintIssue> newTaintIssues;\n    try {\n      newTaintIssues = new ArrayList<>(taintIssueDownloader.downloadTaintFromIssueSearch(serverApi, projectKey, branchName, cancelMonitor));\n    } catch (Exception e) {\n      // null as cause so that it doesn't get wrapped\n      throw new DownloadException(\"Failed to update file taint vulnerabilities: \" + e.getMessage(), null);\n    }\n    var findingsStorage = storage.project(projectKey).findings();\n    var previousTaintIssues = findingsStorage.loadTaint(branchName);\n    var previousTaintIssueKeys = previousTaintIssues.stream().map(ServerTaintIssue::getSonarServerKey).collect(toSet());\n    findingsStorage.replaceAllTaintsOfBranch(branchName, newTaintIssues, enabledLanguages);\n    var newTaintIssueKeys = newTaintIssues.stream().map(ServerTaintIssue::getSonarServerKey).collect(toSet());\n    var deletedTaintVulnerabilityIds = previousTaintIssues.stream().filter(issue -> !newTaintIssueKeys.contains(issue.getSonarServerKey())).map(ServerTaintIssue::getId)\n      .collect(toSet());\n    var addedTaintVulnerabilities = newTaintIssues.stream().filter(issue -> !previousTaintIssueKeys.contains(issue.getSonarServerKey()))\n      .toList();\n    var updatedTaintVulnerabilities = newTaintIssues.stream().filter(issue -> previousTaintIssueKeys.contains(issue.getSonarServerKey()))\n      .toList();\n    return new UpdateSummary<>(deletedTaintVulnerabilityIds, addedTaintVulnerabilities, updatedTaintVulnerabilities);\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/ServerSettings.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.util.Map;\nimport java.util.Optional;\n\npublic record ServerSettings(Map<String, String> globalSettings) {\n  public static final String MQR_MODE_SETTING = \"sonar.multi-quality-mode.enabled\";\n  public static final String EARLY_ACCESS_MISRA_ENABLED = \"sonar.earlyAccess.misra.enabled\";\n  public static final String MISRA_COMPLIANCE_ENABLED = \"sonar.misracompliance.enabled\";\n\n  public Optional<Boolean> getAsBoolean(String settingKey) {\n    return Optional.ofNullable(globalSettings.get(settingKey))\n      .map(Boolean::valueOf);\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/ServerUpdaterUtils.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.time.Instant;\nimport java.util.Optional;\nimport java.util.Set;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\n\npublic class ServerUpdaterUtils {\n  private ServerUpdaterUtils() {\n    // utility class\n  }\n\n\n  /**\n   * @return empty if there is no exact match for languages to indicate all issues must be fetched\n   */\n  public static Optional<Instant> computeLastSync(Set<SonarLanguage> enabledLanguages, Optional<Instant> lastSync,\n    Set<SonarLanguage> lastEnabledLanguages) {\n    if (lastEnabledLanguages.isEmpty() || (!lastEnabledLanguages.equals(enabledLanguages))) {\n      lastSync = Optional.empty();\n    }\n    return lastSync;\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/ServerVersionAndStatusChecker.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.exception.UnsupportedServerException;\nimport org.sonarsource.sonarlint.core.serverapi.system.ServerStatusInfo;\nimport org.sonarsource.sonarlint.core.serverapi.system.SystemApi;\n\npublic class ServerVersionAndStatusChecker {\n\n  private static final String MIN_SQ_VERSION = \"9.9\";\n  private static final String MIN_SQ_VERSION_SUPPORTING_BEARER = \"10.4\";\n  private final SystemApi systemApi;\n  private final boolean isSonarCloud;\n\n  public ServerVersionAndStatusChecker(ServerApi serverApi) {\n    this.systemApi = serverApi.system();\n    this.isSonarCloud = serverApi.isSonarCloud();\n  }\n\n  /**\n   * Checks SonarQube availability status and version against the minimum version supported by the core\n   * or only server availability status for SonarCloud\n   *\n   * @throws UnsupportedServerException if version &lt; minimum supported version\n   * @throws IllegalStateException      If server is not ready\n   */\n  public void checkVersionAndStatus(SonarLintCancelMonitor cancelMonitor) {\n    var serverStatus = systemApi.getStatus(cancelMonitor);\n    if (isSonarCloud) {\n      checkServerUp(serverStatus);\n    } else {\n      checkServerUpAndSupported(serverStatus);\n    }\n  }\n\n  public boolean isSupportingBearer(ServerStatusInfo serverStatus) {\n    if (isSonarCloud) {\n      return true;\n    } else {\n      var serverVersion = Version.create(serverStatus.version());\n      return serverVersion.compareToIgnoreQualifier(Version.create(MIN_SQ_VERSION_SUPPORTING_BEARER)) >= 0;\n    }\n  }\n\n  private static void checkServerUp(ServerStatusInfo serverStatus) {\n    if (!serverStatus.isUp()) {\n      throw new IllegalStateException(serverNotReady(serverStatus));\n    }\n  }\n\n  private static void checkServerUpAndSupported(ServerStatusInfo serverStatus) {\n    checkServerUp(serverStatus);\n    var serverVersion = Version.create(serverStatus.version());\n    if (serverVersion.compareToIgnoreQualifier(Version.create(MIN_SQ_VERSION)) < 0) {\n      throw new UnsupportedServerException(unsupportedVersion(serverStatus));\n    }\n  }\n\n  private static String unsupportedVersion(ServerStatusInfo serverStatus) {\n    return \"Your SonarQube Server instance has version \" + serverStatus.version() + \". Version should be greater or equal to \" + MIN_SQ_VERSION;\n  }\n\n  private static String serverNotReady(ServerStatusInfo serverStatus) {\n    return \"Server not ready (\" + serverStatus.status() + \")\";\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/Settings.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.util.Map;\n\npublic class Settings {\n  private final Map<String, String> settings;\n\n  public Settings(Map<String, String> settings) {\n    this.settings = settings;\n  }\n\n  public Map<String, String> getAll() {\n    return settings;\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/SonarProjectStorage.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.nio.file.Path;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.NewCodeDefinitionStorage;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ProjectServerIssueStore;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ServerIssueStoresManager;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.SmartNotificationsStorage;\n\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ProjectStoragePaths.encodeForFs;\n\npublic class SonarProjectStorage {\n\n  private final ServerIssueStoresManager serverIssueStoresManager;\n  private final String sonarProjectKey;\n  private final AnalyzerConfigurationStorage analyzerConfigurationStorage;\n  private final ProjectBranchesStorage projectBranchesStorage;\n  private final SmartNotificationsStorage smartNotificationsStorage;\n  private final NewCodeDefinitionStorage newCodeDefinitionStorage;\n  private final Path projectStorageRoot;\n\n  public SonarProjectStorage(Path projectsStorageRoot, ServerIssueStoresManager serverIssueStoresManager, String sonarProjectKey) {\n    this.projectStorageRoot = projectsStorageRoot.resolve(encodeForFs(sonarProjectKey));\n    this.serverIssueStoresManager = serverIssueStoresManager;\n    this.sonarProjectKey = sonarProjectKey;\n    this.analyzerConfigurationStorage = new AnalyzerConfigurationStorage(projectStorageRoot);\n    this.projectBranchesStorage = new ProjectBranchesStorage(projectStorageRoot);\n    this.smartNotificationsStorage = new SmartNotificationsStorage(projectStorageRoot);\n    this.newCodeDefinitionStorage = new NewCodeDefinitionStorage(projectStorageRoot);\n  }\n\n  public ProjectServerIssueStore findings() {\n    return serverIssueStoresManager.get(sonarProjectKey);\n  }\n\n  public AnalyzerConfigurationStorage analyzerConfiguration() {\n    return analyzerConfigurationStorage;\n  }\n\n  public ProjectBranchesStorage branches() {\n    return projectBranchesStorage;\n  }\n\n  public SmartNotificationsStorage smartNotifications() {\n    return smartNotificationsStorage;\n  }\n\n  public NewCodeDefinitionStorage newCodeDefinition() {\n    return newCodeDefinitionStorage;\n  }\n\n  public Path filePath() {\n    return projectStorageRoot;\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/SonarServerSettingsChangedEvent.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.util.Map;\nimport java.util.Set;\n\npublic record SonarServerSettingsChangedEvent(String connectionId, Set<String> configScopeIds, Map<String, String> updatedSettingsValueByKey) {\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/StoredPlugin.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.nio.file.Path;\nimport org.sonarsource.sonarlint.core.serverapi.plugins.ServerPlugin;\n\npublic class StoredPlugin {\n  private final String key;\n  private final String hash;\n  private final Path jarPath;\n\n  public StoredPlugin(String key, String hash, Path jarPath) {\n    this.key = key;\n    this.hash = hash;\n    this.jarPath = jarPath;\n  }\n\n  public String getKey() {\n    return key;\n  }\n\n  public String getHash() {\n    return hash;\n  }\n\n  public Path getJarPath() {\n    return jarPath;\n  }\n\n  public boolean hasSameHash(ServerPlugin serverPlugin) {\n    return getHash().equals(serverPlugin.getHash());\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/StoredServerInfo.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.util.Set;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.serverapi.features.Feature;\n\nimport static org.sonarsource.sonarlint.core.serverconnection.ServerSettings.MQR_MODE_SETTING;\n\npublic record StoredServerInfo(Version version, Set<Feature> features, ServerSettings globalSettings, String serverId) {\n  private static final String MIN_MQR_MODE_SUPPORT_VERSION = \"10.2\";\n  private static final String MQR_MODE_SETTING_MIN_VERSION = \"10.8\";\n\n  public boolean shouldConsiderMultiQualityModeEnabled() {\n    if (version.satisfiesMinRequirement(Version.create(MQR_MODE_SETTING_MIN_VERSION))) {\n      // starting 10.8, the sonar.multi-quality-mode.enabled setting was introduced. We honor this setting in priority\n      return globalSettings.getAsBoolean(MQR_MODE_SETTING).orElse(false);\n    }\n    // if no setting is present, MQR mode should be used for 10.2+, otherwise standard mode should be used\n    return version.satisfiesMinRequirement(Version.create(MIN_MQR_MODE_SUPPORT_VERSION));\n  }\n\n  public boolean hasFeature(Feature feature) {\n    return features.contains(feature);\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/SynchronizationResult.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\npublic class SynchronizationResult {\n  private final boolean analyzerUpdated;\n\n  public SynchronizationResult(boolean analyzerUpdated) {\n    this.analyzerUpdated = analyzerUpdated;\n  }\n\n  public boolean hasAnalyzerBeenUpdated() {\n    return analyzerUpdated;\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/TaintIssueDownloader.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.apache.commons.codec.digest.DigestUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.sonarsource.sonarlint.core.commons.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.RuleKey;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common.Flow;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common.TextRange;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues.Issue;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues.TaintVulnerabilityLite;\nimport org.sonarsource.sonarlint.core.serverapi.source.SourceApi;\nimport org.sonarsource.sonarlint.core.serverapi.util.ServerApiUtils;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerTaintIssue;\n\nimport static java.util.function.Predicate.not;\nimport static org.sonarsource.sonarlint.core.serverconnection.DownloaderUtils.parseProtoImpactSeverity;\nimport static org.sonarsource.sonarlint.core.serverconnection.DownloaderUtils.parseProtoSoftwareQuality;\n\npublic class TaintIssueDownloader {\n\n  private static final Pattern MATCH_ALL_WHITESPACES = Pattern.compile(\"\\\\s\");\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final Set<SonarLanguage> enabledLanguages;\n\n  public TaintIssueDownloader(Set<SonarLanguage> enabledLanguages) {\n    this.enabledLanguages = enabledLanguages;\n  }\n\n  public List<ServerTaintIssue> downloadTaintFromIssueSearch(ServerApi serverApi, String key, @Nullable String branchName, SonarLintCancelMonitor cancelMonitor) {\n    var issueApi = serverApi.issue();\n\n    List<ServerTaintIssue> result = new ArrayList<>();\n\n    Set<String> taintRuleKeys = serverApi.rules().getAllTaintRules(List.of(SonarLanguage.values()), cancelMonitor);\n    Map<String, String> sourceCodeByKey = new HashMap<>();\n    var downloadVulnerabilitiesForRules = issueApi.downloadVulnerabilitiesForRules(key, taintRuleKeys, branchName, cancelMonitor);\n    downloadVulnerabilitiesForRules.getIssues()\n      .stream()\n      .map(i -> convertTaintVulnerability(serverApi.source(), i, downloadVulnerabilitiesForRules.getComponentPathsByKey(), sourceCodeByKey, cancelMonitor))\n      .filter(Objects::nonNull)\n      .forEach(result::add);\n\n    return result;\n  }\n\n  /**\n   * Fetch all taint issues of the project with specified key, using new SQ 9.6 api/issues/pull_taint\n   *\n   * @param projectKey project key\n   * @param branchName name of the branch.\n   * @return List of issues. It can be empty but never null.\n   */\n  public PullTaintResult downloadTaintFromPull(ServerApi serverApi, String projectKey, String branchName, Optional<Instant> lastSync, SonarLintCancelMonitor cancelMonitor) {\n    var issueApi = serverApi.issue();\n\n    var apiResult = issueApi.pullTaintIssues(projectKey, branchName, enabledLanguages, lastSync.map(Instant::toEpochMilli).orElse(null), cancelMonitor);\n    var changedIssues = apiResult.getTaintIssues()\n      .stream()\n      // Ignore project level issues\n      .filter(i -> i.getMainLocation().hasFilePath())\n      .filter(not(TaintVulnerabilityLite::getClosed))\n      .map(TaintIssueDownloader::convertLiteTaintIssue)\n      .toList();\n    var closedIssueKeys = apiResult.getTaintIssues()\n      .stream()\n      // Ignore project level issues\n      .filter(i -> i.getMainLocation().hasFilePath())\n      .filter(TaintVulnerabilityLite::getClosed)\n      .map(TaintVulnerabilityLite::getKey)\n      .collect(Collectors.toSet());\n\n    return new PullTaintResult(Instant.ofEpochMilli(apiResult.getTimestamp().getQueryTimestamp()), changedIssues, closedIssueKeys);\n  }\n\n  @CheckForNull\n  private static ServerTaintIssue convertTaintVulnerability(SourceApi sourceApi, Issue taintVulnerabilityFromWs,\n    Map<String, Path> componentPathsByKey, Map<String, String> sourceCodeByKey, SonarLintCancelMonitor cancelMonitor) {\n    var ruleKey = RuleKey.parse(taintVulnerabilityFromWs.getRule());\n    var primaryLocation = convertPrimaryLocation(sourceApi, taintVulnerabilityFromWs, componentPathsByKey, sourceCodeByKey, cancelMonitor);\n    var filePath = primaryLocation.filePath();\n    if (filePath == null) {\n      // Ignore project level issues\n      return null;\n    }\n    var ruleDescriptionContextKey = taintVulnerabilityFromWs.hasRuleDescriptionContextKey() ? taintVulnerabilityFromWs.getRuleDescriptionContextKey() : null;\n    var cleanCodeAttribute = parseProtoCleanCodeAttribute(taintVulnerabilityFromWs);\n    var impacts = taintVulnerabilityFromWs.getImpactsList().stream()\n      .map(i -> Map.entry(parseProtoSoftwareQuality(i), parseProtoImpactSeverity(i)))\n      .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\n    var resolution = taintVulnerabilityFromWs.getResolution();\n    var resolutionStatus = IssueStatus.parse(resolution);\n    return new ServerTaintIssue(\n      UUID.randomUUID(),\n      taintVulnerabilityFromWs.getKey(),\n      !resolution.isEmpty(),\n      resolutionStatus,\n      ruleKey.toString(),\n      primaryLocation.message(),\n      filePath,\n      ServerApiUtils.parseOffsetDateTime(taintVulnerabilityFromWs.getCreationDate()).toInstant(),\n      IssueSeverity.valueOf(taintVulnerabilityFromWs.getSeverity().name()),\n      RuleType.valueOf(taintVulnerabilityFromWs.getType().name()),\n      primaryLocation.textRange(), ruleDescriptionContextKey,\n      cleanCodeAttribute, impacts,\n      convertFlows(sourceApi, taintVulnerabilityFromWs.getFlowsList(), componentPathsByKey, sourceCodeByKey, cancelMonitor));\n  }\n\n  @CheckForNull\n  @VisibleForTesting\n  static CleanCodeAttribute parseProtoCleanCodeAttribute(Issue taintVulnerabilityFromWs) {\n    if (!taintVulnerabilityFromWs.hasCleanCodeAttribute() || taintVulnerabilityFromWs.getCleanCodeAttribute() == Common.CleanCodeAttribute.UNKNOWN_ATTRIBUTE) {\n      return null;\n    }\n    return CleanCodeAttribute.valueOf(taintVulnerabilityFromWs.getCleanCodeAttribute().name());\n  }\n\n  @CheckForNull\n  @VisibleForTesting\n  static CleanCodeAttribute parseProtoCleanCodeAttribute(TaintVulnerabilityLite taintVulnerabilityFromWs) {\n    if (!taintVulnerabilityFromWs.hasCleanCodeAttribute() || taintVulnerabilityFromWs.getCleanCodeAttribute() == Common.CleanCodeAttribute.UNKNOWN_ATTRIBUTE) {\n      return null;\n    }\n    return CleanCodeAttribute.valueOf(taintVulnerabilityFromWs.getCleanCodeAttribute().name());\n  }\n\n  private static List<ServerTaintIssue.Flow> convertFlows(SourceApi sourceApi, List<Flow> flowsList, Map<String, Path> componentPathsByKey,\n    Map<String, String> sourceCodeByKey, SonarLintCancelMonitor cancelMonitor) {\n    return flowsList.stream()\n      .map(flowFromWs -> new ServerTaintIssue.Flow(flowFromWs.getLocationsList().stream().map(locationFromWs -> {\n        var componentPath = componentPathsByKey.get(locationFromWs.getComponent());\n        if (locationFromWs.hasTextRange()) {\n          var codeSnippet = getCodeSnippet(sourceApi, locationFromWs.getComponent(), locationFromWs.getTextRange(), sourceCodeByKey, cancelMonitor);\n          String textRangeHash;\n          if (codeSnippet != null) {\n            textRangeHash = hash(codeSnippet);\n          } else {\n            // Use empty String, the client will detect a mismatch with real hash and apply UX for mismatched locations\n            textRangeHash = \"\";\n          }\n          return new ServerTaintIssue.ServerIssueLocation(componentPath, convertTextRangeFromWs(locationFromWs.getTextRange(), textRangeHash), locationFromWs.getMsg());\n        }\n        return new ServerTaintIssue.ServerIssueLocation(componentPath, null, locationFromWs.getMsg());\n      }).toList()))\n      .toList();\n  }\n\n  private static TextRangeWithHash toServerTaintIssueTextRange(Issues.TextRange textRange) {\n    return new TextRangeWithHash(textRange.getStartLine(), textRange.getStartLineOffset(), textRange.getEndLine(), textRange.getEndLineOffset(), textRange.getHash());\n  }\n\n  private static ServerTaintIssue convertLiteTaintIssue(TaintVulnerabilityLite liteTaintIssueFromWs) {\n    var mainLocation = liteTaintIssueFromWs.getMainLocation();\n    // We have filtered out issues without file path earlier\n    var filePath = Path.of(mainLocation.getFilePath());\n    var creationDate = Instant.ofEpochMilli(liteTaintIssueFromWs.getCreationDate());\n    ServerTaintIssue taintIssue;\n    var severity = IssueSeverity.valueOf(liteTaintIssueFromWs.getSeverity().name());\n    var type = RuleType.valueOf(liteTaintIssueFromWs.getType().name());\n    var ruleDescriptionContextKey = liteTaintIssueFromWs.hasRuleDescriptionContextKey() ? liteTaintIssueFromWs.getRuleDescriptionContextKey() : null;\n    var cleanCodeAttribute = parseProtoCleanCodeAttribute(liteTaintIssueFromWs);\n    var impacts = liteTaintIssueFromWs.getImpactsList().stream()\n      .map(i -> Map.entry(\n        parseProtoSoftwareQuality(i),\n        parseProtoImpactSeverity(i)))\n      .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\n    var flows = liteTaintIssueFromWs.getFlowsList().stream().map(TaintIssueDownloader::convertFlows).toList();\n    if (mainLocation.hasTextRange()) {\n      taintIssue = new ServerTaintIssue(UUID.randomUUID(), liteTaintIssueFromWs.getKey(), liteTaintIssueFromWs.getResolved(), null, liteTaintIssueFromWs.getRuleKey(),\n        mainLocation.getMessage(),\n        filePath, creationDate, severity,\n        type, toServerTaintIssueTextRange(mainLocation.getTextRange()), ruleDescriptionContextKey, cleanCodeAttribute, impacts, flows);\n    } else {\n      taintIssue = new ServerTaintIssue(UUID.randomUUID(), liteTaintIssueFromWs.getKey(), liteTaintIssueFromWs.getResolved(), null, liteTaintIssueFromWs.getRuleKey(),\n        mainLocation.getMessage(),\n        filePath, creationDate, severity, type, null, ruleDescriptionContextKey, cleanCodeAttribute, impacts, flows);\n    }\n    return taintIssue;\n  }\n\n  private static ServerTaintIssue.Flow convertFlows(Issues.Flow flowFromWs) {\n    return new ServerTaintIssue.Flow(flowFromWs.getLocationsList().stream().map(locationFromWs -> {\n      var filePath = locationFromWs.hasFilePath() ? Path.of(locationFromWs.getFilePath()) : null;\n      if (locationFromWs.hasTextRange()) {\n        return new ServerTaintIssue.ServerIssueLocation(filePath, toServerTaintIssueTextRange(locationFromWs.getTextRange()), locationFromWs.getMessage());\n      } else {\n        return new ServerTaintIssue.ServerIssueLocation(filePath, null, locationFromWs.getMessage());\n      }\n    }).toList());\n  }\n\n  private static ServerTaintIssue.ServerIssueLocation convertPrimaryLocation(SourceApi sourceApi, Issue issueFromWs, Map<String, Path> componentPathsByKey,\n    Map<String, String> sourceCodeByKey, SonarLintCancelMonitor cancelMonitor) {\n    var componentPath = componentPathsByKey.get(issueFromWs.getComponent());\n    if (issueFromWs.hasTextRange()) {\n      var codeSnippet = getCodeSnippet(sourceApi, issueFromWs.getComponent(), issueFromWs.getTextRange(), sourceCodeByKey, cancelMonitor);\n      String textRangeHash;\n      if (codeSnippet != null) {\n        textRangeHash = hash(codeSnippet);\n      } else {\n        // Use empty String, the client will detect a mismatch with real hash and apply UX for mismatched locations\n        textRangeHash = \"\";\n      }\n      return new ServerTaintIssue.ServerIssueLocation(componentPath, convertTextRangeFromWs(issueFromWs.getTextRange(), textRangeHash), issueFromWs.getMessage());\n    }\n    return new ServerTaintIssue.ServerIssueLocation(componentPath, null, issueFromWs.getMessage());\n  }\n\n  static String hash(String codeSnippet) {\n    String codeSnippetWithoutWhitespaces = MATCH_ALL_WHITESPACES.matcher(codeSnippet).replaceAll(\"\");\n    return DigestUtils.md5Hex(codeSnippetWithoutWhitespaces);\n  }\n\n  private static TextRangeWithHash convertTextRangeFromWs(TextRange textRange, String hash) {\n    return new TextRangeWithHash(textRange.getStartLine(), textRange.getStartOffset(), textRange.getEndLine(), textRange.getEndOffset(), hash);\n  }\n\n  @CheckForNull\n  private static String getCodeSnippet(SourceApi sourceApi, String fileKey, TextRange textRange, Map<String, String> sourceCodeByKey, SonarLintCancelMonitor cancelMonitor) {\n    var sourceCode = getOrFetchSourceCode(sourceApi, fileKey, sourceCodeByKey, cancelMonitor);\n    if (StringUtils.isEmpty(sourceCode)) {\n      return null;\n    }\n    try {\n      return ServerApiUtils.extractCodeSnippet(sourceCode, textRange);\n    } catch (Exception e) {\n      LOG.debug(\"Unable to compute code snippet of '\" + fileKey + \"' for text range: \" + textRange, e);\n    }\n    return null;\n  }\n\n  private static String getOrFetchSourceCode(SourceApi sourceApi, String fileKey, Map<String, String> sourceCodeByKey, SonarLintCancelMonitor cancelMonitor) {\n    return sourceCodeByKey.computeIfAbsent(fileKey, k -> sourceApi\n      .getRawSourceCode(fileKey, cancelMonitor)\n      .orElse(\"\"));\n  }\n\n  public static class PullTaintResult {\n    private final Instant queryTimestamp;\n    private final List<ServerTaintIssue> changedIssues;\n    private final Set<String> closedIssueKeys;\n\n    public PullTaintResult(Instant queryTimestamp, List<ServerTaintIssue> changedIssues, Set<String> closedIssueKeys) {\n      this.queryTimestamp = queryTimestamp;\n      this.changedIssues = changedIssues;\n      this.closedIssueKeys = closedIssueKeys;\n    }\n\n    public Instant getQueryTimestamp() {\n      return queryTimestamp;\n    }\n\n    public List<ServerTaintIssue> getChangedTaintIssues() {\n      return changedIssues;\n    }\n\n    public Set<String> getClosedIssueKeys() {\n      return closedIssueKeys;\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/UserSynchronizer.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\n\npublic class UserSynchronizer {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final ConnectionStorage storage;\n\n  public UserSynchronizer(ConnectionStorage storage) {\n    this.storage = storage;\n  }\n\n  /**\n   * Fetches and stores the user id from the server.\n   * Available on SonarQube Cloud and SonarQube Server 2025.6+.\n   */\n  public void synchronize(ServerApi serverApi, SonarLintCancelMonitor cancelMonitor) {\n    try {\n      var userId = serverApi.users().getCurrentUserId(cancelMonitor);\n      if (userId != null && !userId.trim().isEmpty()) {\n        storage.user().store(userId.trim());\n      }\n    } catch (Exception e) {\n      LOG.warn(\"Failed to synchronize user id from server: {}\", e.getMessage());\n    }\n  }\n}\n\n\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/VersionUtils.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport org.sonarsource.sonarlint.core.commons.Version;\n\npublic class VersionUtils {\n\n  private static final Version CURRENT_LTS = Version.create(\"9.9\");\n  private static final Version MINIMAL_SUPPORTED_VERSION = Version.create(\"9.9\");\n\n  private VersionUtils() {\n  }\n\n  /**\n   * Right now since minimal supported version is equal to current LTS (9.9) this method will always return false.\n   * But it's important to keep it for the future when next LTS will be released, and we will have a grace period again.\n   */\n  public static boolean isVersionSupportedDuringGracePeriod(Version currentVersion) {\n    return currentVersion.compareTo(CURRENT_LTS) < 0 &&\n      currentVersion.compareToIgnoreQualifier(MINIMAL_SUPPORTED_VERSION) >= 0;\n  }\n\n  public static Version getCurrentLts() {\n    return CURRENT_LTS;\n  }\n\n  public static Version getMinimalSupportedVersion() {\n    return MINIMAL_SUPPORTED_VERSION;\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/aicodefix/AiCodeFix.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.aicodefix;\n\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Objects;\n\n/**\n * Entity representing AI CodeFix settings persisted in the local storage (H2).\n * This mirrors org.sonarsource.sonarlint.core.serverconnection.AiCodeFixSettings but\n * lives in commons to avoid cross-module dependencies.\n */\npublic record AiCodeFix(\n  String connectionId,\n  String[] supportedRules,\n  boolean organizationEligible,\n  Enablement enablement,\n  String[] enabledProjectKeys\n) {\n\n  public AiCodeFix(String connectionId, Collection<String> supportedRules, boolean organizationEligible, Enablement enablement, Collection<String> enabledProjectKeys) {\n    this(connectionId, supportedRules.toArray(String[]::new), organizationEligible, enablement, enabledProjectKeys.toArray(String[]::new));\n  }\n\n  public enum Enablement {\n    DISABLED,\n    ENABLED_FOR_ALL_PROJECTS,\n    ENABLED_FOR_SOME_PROJECTS\n  }\n\n  public AiCodeFix {\n    Objects.requireNonNull(connectionId, \"connectionId\");\n    Objects.requireNonNull(supportedRules, \"supportedRules\");\n    Objects.requireNonNull(enablement, \"enablement\");\n    Objects.requireNonNull(enabledProjectKeys, \"enabledProjectKeys\");\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o == null || getClass() != o.getClass()) return false;\n    var aiCodeFix = (AiCodeFix) o;\n    return organizationEligible == aiCodeFix.organizationEligible && Objects.equals(connectionId, aiCodeFix.connectionId) && enablement == aiCodeFix.enablement\n      && Objects.deepEquals(supportedRules, aiCodeFix.supportedRules) && Objects.deepEquals(enabledProjectKeys, aiCodeFix.enabledProjectKeys);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(connectionId, Arrays.hashCode(supportedRules), organizationEligible, enablement, Arrays.hashCode(enabledProjectKeys));\n  }\n\n  @Override\n  public String toString() {\n    return \"AiCodeFix{\" +\n      \"connectionId='\" + connectionId + '\\'' +\n      \", supportedRules=\" + Arrays.toString(supportedRules) +\n      \", organizationEligible=\" + organizationEligible +\n      \", enablement=\" + enablement +\n      \", enabledProjectKeys=\" + Arrays.toString(enabledProjectKeys) +\n      '}';\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/aicodefix/AiCodeFixFeatureEnablement.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.aicodefix;\n\npublic enum AiCodeFixFeatureEnablement {\n  DISABLED,\n  ENABLED_FOR_ALL_PROJECTS,\n  ENABLED_FOR_SOME_PROJECTS\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/aicodefix/AiCodeFixRepository.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.aicodefix;\n\nimport java.util.Optional;\nimport java.util.Set;\nimport org.jooq.DSLContext;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\n\nimport static org.sonarsource.sonarlint.core.commons.storage.model.Tables.AI_CODEFIX_SETTINGS;\n\n/**\n * Repository for persisting and retrieving AiCodeFix entity using the local H2 database.\n * Settings are stored per server connection, addressed by connectionId.\n */\npublic class AiCodeFixRepository {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final DSLContext database;\n\n  public AiCodeFixRepository(DSLContext dslContext) {\n    this.database = dslContext;\n  }\n\n  public Optional<AiCodeFix> get(String connectionId) {\n    var rec = database\n      .select(AI_CODEFIX_SETTINGS.SUPPORTED_RULES, AI_CODEFIX_SETTINGS.ORGANIZATION_ELIGIBLE, AI_CODEFIX_SETTINGS.ENABLEMENT, AI_CODEFIX_SETTINGS.ENABLED_PROJECT_KEYS)\n      .from(AI_CODEFIX_SETTINGS)\n      .where(AI_CODEFIX_SETTINGS.CONNECTION_ID.eq(connectionId))\n      .fetchOne();\n    if (rec == null) {\n      return Optional.empty();\n    }\n    var supportedRules = rec.get(AI_CODEFIX_SETTINGS.SUPPORTED_RULES);\n    var organizationEligible = Boolean.TRUE.equals(rec.get(AI_CODEFIX_SETTINGS.ORGANIZATION_ELIGIBLE));\n    var enablement = AiCodeFix.Enablement.valueOf(rec.get(AI_CODEFIX_SETTINGS.ENABLEMENT));\n    var enabledProjectKeys = rec.get(AI_CODEFIX_SETTINGS.ENABLED_PROJECT_KEYS);\n    return Optional.of(new AiCodeFix(connectionId, supportedRules, organizationEligible, enablement, enabledProjectKeys));\n  }\n\n  public void upsert(AiCodeFix entity) {\n    database\n      .insertInto(AI_CODEFIX_SETTINGS, AI_CODEFIX_SETTINGS.CONNECTION_ID, AI_CODEFIX_SETTINGS.SUPPORTED_RULES, AI_CODEFIX_SETTINGS.ORGANIZATION_ELIGIBLE,\n        AI_CODEFIX_SETTINGS.ENABLEMENT, AI_CODEFIX_SETTINGS.ENABLED_PROJECT_KEYS)\n      .values(entity.connectionId(), entity.supportedRules(), entity.organizationEligible(), entity.enablement().name(), entity.enabledProjectKeys())\n      .onDuplicateKeyUpdate()\n      .set(AI_CODEFIX_SETTINGS.SUPPORTED_RULES, entity.supportedRules())\n      .set(AI_CODEFIX_SETTINGS.ORGANIZATION_ELIGIBLE, entity.organizationEligible())\n      .set(AI_CODEFIX_SETTINGS.ENABLEMENT, entity.enablement().name())\n      .set(AI_CODEFIX_SETTINGS.ENABLED_PROJECT_KEYS, entity.enabledProjectKeys())\n      .execute();\n  }\n\n  public void deleteUnknownConnections(Set<String> knownConnectionIds) {\n    database.dsl().deleteFrom(AI_CODEFIX_SETTINGS)\n      .where(AI_CODEFIX_SETTINGS.CONNECTION_ID.notIn(knownConnectionIds))\n      .execute();\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/aicodefix/AiCodeFixSettings.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.aicodefix;\n\nimport java.util.Set;\n\npublic record AiCodeFixSettings(Set<String> supportedRules, boolean isOrganizationEligible, AiCodeFixFeatureEnablement enablement, Set<String> enabledProjectKeys) {\n  public boolean isFeatureEnabled(String projectKey) {\n    return isOrganizationEligible && (enablement.equals(AiCodeFixFeatureEnablement.ENABLED_FOR_ALL_PROJECTS)\n      || (enablement.equals(AiCodeFixFeatureEnablement.ENABLED_FOR_SOME_PROJECTS) && enabledProjectKeys.contains(projectKey)));\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/aicodefix/AiCodeFixSettingsSynchronizer.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.aicodefix;\n\nimport java.util.List;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.component.ServerProject;\nimport org.sonarsource.sonarlint.core.serverapi.features.Feature;\nimport org.sonarsource.sonarlint.core.serverapi.organization.ServerOrganization;\nimport org.sonarsource.sonarlint.core.serverconnection.ConnectionStorage;\nimport org.sonarsource.sonarlint.core.serverconnection.OrganizationSynchronizer;\n\npublic class AiCodeFixSettingsSynchronizer {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  public static final Version MIN_SQS_VERSION_SUPPORTING_AI_CODEFIX = Version.create(\"2025.3\");\n\n  private final ConnectionStorage storage;\n  private final OrganizationSynchronizer organizationSynchronizer;\n  private final AiCodeFixRepository aiCodeFixRepository;\n\n  public AiCodeFixSettingsSynchronizer(ConnectionStorage storage, OrganizationSynchronizer organizationSynchronizer,\n    AiCodeFixRepository aiCodeFixRepository) {\n    this.storage = storage;\n    this.organizationSynchronizer = organizationSynchronizer;\n    this.aiCodeFixRepository = aiCodeFixRepository;\n  }\n\n  public void synchronize(ServerApi serverApi, Version serverVersion, Set<String> projectKeys, SonarLintCancelMonitor cancelMonitor) {\n    if (serverApi.isSonarCloud()) {\n      synchronizeForSonarQubeCloud(serverApi, cancelMonitor);\n    } else {\n      synchronizeForSonarQubeServer(serverApi, serverVersion, projectKeys, cancelMonitor);\n    }\n  }\n\n  private void synchronizeForSonarQubeCloud(ServerApi serverApi, SonarLintCancelMonitor cancelMonitor) {\n    var userOrganizations = serverApi.isSonarCloud() ? serverApi.organization().listUserOrganizations(cancelMonitor) : List.<ServerOrganization>of();\n    if (userBelongsToOrganization(serverApi, userOrganizations)) {\n      try {\n        var supportedRules = serverApi.fixSuggestions().getSupportedRules(cancelMonitor);\n        var organization = organizationSynchronizer.readOrSynchronizeOrganization(serverApi, cancelMonitor);\n        var organizationConfig = serverApi.fixSuggestions().getOrganizationConfigs(organization.id(), cancelMonitor);\n        var aiCodeFixConfiguration = organizationConfig.aiCodeFix();\n        var enabledProjectKeys = aiCodeFixConfiguration.enabledProjectKeys();\n        var enabled = enabledProjectKeys == null ? Set.<String>of() : enabledProjectKeys;\n        var entity = new AiCodeFix(\n          storage.connectionId(),\n          supportedRules.rules(),\n          aiCodeFixConfiguration.organizationEligible(),\n          AiCodeFix.Enablement.valueOf(aiCodeFixConfiguration.enablement().name()),\n          enabled);\n        aiCodeFixRepository.upsert(entity);\n      } catch (Exception e) {\n        LOG.error(\"Error synchronizing AI CodeFix settings for SonarQube Cloud\", e);\n      }\n    }\n  }\n\n  private void synchronizeForSonarQubeServer(ServerApi serverApi, Version serverVersion, Set<String> projectKeys, SonarLintCancelMonitor cancelMonitor) {\n    try {\n      if (serverVersion.satisfiesMinRequirement(MIN_SQS_VERSION_SUPPORTING_AI_CODEFIX) && serverApi.features().list(cancelMonitor).contains(Feature.AI_CODE_FIX)) {\n        var supportedRules = serverApi.fixSuggestions().getSupportedRules(cancelMonitor);\n        var enabledProjectKeys = projectKeys.stream()\n          .filter(projectKey -> serverApi.component().getProject(projectKey, cancelMonitor).filter(ServerProject::isAiCodeFixEnabled).isPresent()).collect(Collectors.toSet());\n        var entity = new AiCodeFix(\n          storage.connectionId(),\n          supportedRules.rules(),\n          true,\n          AiCodeFix.Enablement.ENABLED_FOR_SOME_PROJECTS,\n          enabledProjectKeys);\n        aiCodeFixRepository.upsert(entity);\n      }\n    } catch (Exception e) {\n      LOG.error(\"Error synchronizing AI CodeFix settings for SonarQube Server\", e);\n    }\n  }\n\n  private static boolean userBelongsToOrganization(ServerApi serverApi, List<ServerOrganization> userOrganizations) {\n    return serverApi.getOrganizationKey().filter(orgKey -> userOrganizations.stream().anyMatch(org -> org.getKey().equals(orgKey))).isPresent();\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/aicodefix/package-info.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverconnection.aicodefix;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/issues/FileLevelServerIssue.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.issues;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.Map;\nimport java.util.UUID;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\n\n/**\n * Issues reported at file level.\n */\npublic class FileLevelServerIssue extends ServerIssue<FileLevelServerIssue> {\n\n  public FileLevelServerIssue(@Nullable UUID id, String key, boolean resolved, @Nullable IssueStatus resolutionStatus, String ruleKey,\n    String message, Path filePath, Instant creationDate, @Nullable IssueSeverity userSeverity,\n    RuleType type, Map<SoftwareQuality, ImpactSeverity> impacts) {\n    super(id, key, resolved, resolutionStatus, ruleKey, message, filePath, creationDate, userSeverity, type, impacts);\n  }\n\n  /**\n   * constructor for backward compatibility, after finalization of migration from Xodus to H2 should not be used\n   * when using with H2 UUID should always be set\n   */\n  public FileLevelServerIssue(String key, boolean resolved, @Nullable IssueStatus resolutionStatus, String ruleKey,\n    String message, Path filePath, Instant creationDate, @Nullable IssueSeverity userSeverity,\n    RuleType type, Map<SoftwareQuality, ImpactSeverity> impacts) {\n    this(null, key, resolved, resolutionStatus, ruleKey, message, filePath, creationDate, userSeverity, type, impacts);\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/issues/Findings.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.issues;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.sonarsource.sonarlint.core.commons.KnownFinding;\n\npublic record Findings(List<KnownFinding> issues, List<KnownFinding> hotspots) {\n  public Findings mergeWith(Findings other) {\n    var mergedIssues = new ArrayList<>(issues);\n    mergedIssues.addAll(other.issues);\n    var mergedHotspots = new ArrayList<KnownFinding>(hotspots);\n    mergedHotspots.addAll(other.hotspots);\n    return new Findings(mergedIssues, mergedHotspots);\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/issues/KnownFindingsRepository.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.issues;\n\nimport java.nio.file.Path;\nimport java.time.LocalDateTime;\nimport java.time.ZoneOffset;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Stream;\nimport org.jooq.Configuration;\nimport org.jooq.Record;\nimport org.sonarsource.sonarlint.core.commons.KnownFinding;\nimport org.sonarsource.sonarlint.core.commons.KnownFindingType;\nimport org.sonarsource.sonarlint.core.commons.LineWithHash;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabase;\nimport org.sonarsource.sonarlint.core.commons.storage.model.Tables;\nimport org.sonarsource.sonarlint.core.commons.storage.model.tables.records.KnownFindingsRecord;\n\nimport static org.sonarsource.sonarlint.core.commons.storage.model.Tables.KNOWN_FINDINGS;\n\npublic class KnownFindingsRepository {\n\n  private final SonarLintDatabase database;\n\n  public KnownFindingsRepository(SonarLintDatabase database) {\n    this.database = database;\n  }\n\n  public void storeFindings(Map<String, Map<Path, Findings>> findingsPerFilePerConfigScopeId) {\n    var records = findingsPerFilePerConfigScopeId.entrySet().stream()\n      .flatMap(KnownFindingsRepository::expandConfigScope)\n      .toList();\n    database.dsl().deleteFrom(Tables.KNOWN_FINDINGS).execute();\n    database.dsl().batchInsert(records).execute();\n  }\n\n  private static Stream<KnownFindingsRecord> expandConfigScope(Map.Entry<String, Map<Path, Findings>> configScopeEntry) {\n    var configScopeId = configScopeEntry.getKey();\n    return configScopeEntry.getValue().entrySet().stream()\n      .flatMap(fileEntry -> expandFileFindings(configScopeId, fileEntry));\n  }\n\n  private static Stream<KnownFindingsRecord> expandFileFindings(String configScopeId, Map.Entry<Path, Findings> fileEntry) {\n    var filePath = fileEntry.getKey();\n    var findings = fileEntry.getValue();\n\n    return Stream.concat(\n      findings.issues().stream()\n        .map(f -> createRecord(f, configScopeId, filePath, KnownFindingType.ISSUE)),\n      findings.hotspots().stream()\n        .map(f -> createRecord(f, configScopeId, filePath, KnownFindingType.HOTSPOT))\n    );\n  }\n\n  private static KnownFindingsRecord createRecord(KnownFinding finding, String configScopeId, Path filePath, KnownFindingType type) {\n    var textRangeWithHash = finding.getTextRangeWithHash();\n    var lineWithHash = finding.getLineWithHash();\n    var introductionDate = LocalDateTime.ofInstant(finding.getIntroductionDate(), ZoneOffset.UTC);\n    return new KnownFindingsRecord(\n      finding.getId(),\n      configScopeId,\n      filePath.toString(),\n      finding.getServerKey(),\n      finding.getRuleKey(),\n      finding.getMessage(),\n      introductionDate,\n      type.name(),\n      textRangeWithHash == null ? null : textRangeWithHash.getStartLine(),\n      textRangeWithHash == null ? null : textRangeWithHash.getStartLineOffset(),\n      textRangeWithHash == null ? null : textRangeWithHash.getEndLine(),\n      textRangeWithHash == null ? null : textRangeWithHash.getEndLineOffset(),\n      textRangeWithHash == null ? null : textRangeWithHash.getHash(),\n      lineWithHash == null ? null : lineWithHash.getNumber(),\n      lineWithHash == null ? null : lineWithHash.getHash());\n  }\n\n  public void storeKnownIssues(String configurationScopeId, Path clientRelativePath, List<KnownFinding> newKnownIssues) {\n    storeKnownFindings(configurationScopeId, clientRelativePath, newKnownIssues, KnownFindingType.ISSUE);\n  }\n\n  public void storeKnownSecurityHotspots(String configurationScopeId, Path clientRelativePath, List<KnownFinding> newKnownSecurityHotspots) {\n    storeKnownFindings(configurationScopeId, clientRelativePath, newKnownSecurityHotspots, KnownFindingType.HOTSPOT);\n  }\n\n  public List<KnownFinding> loadSecurityHotspotsForFile(String configurationScopeId, Path filePath) {\n    return getKnownFindingsForFile(configurationScopeId, filePath, KnownFindingType.HOTSPOT);\n  }\n\n  public List<KnownFinding> loadIssuesForFile(String configurationScopeId, Path filePath) {\n    return getKnownFindingsForFile(configurationScopeId, filePath, KnownFindingType.ISSUE);\n  }\n\n  private void storeKnownFindings(String configurationScopeId, Path clientRelativePath, List<KnownFinding> newKnownFindings, KnownFindingType type) {\n    database.dsl().transaction((Configuration trx) -> newKnownFindings.forEach(finding -> {\n      var textRangeWithHash = finding.getTextRangeWithHash();\n      var startLine = textRangeWithHash == null ? null : textRangeWithHash.getStartLine();\n      var startLineOffset = textRangeWithHash == null ? null : textRangeWithHash.getStartLineOffset();\n      var endLine = textRangeWithHash == null ? null : textRangeWithHash.getEndLine();\n      var endLineOffset = textRangeWithHash == null ? null : textRangeWithHash.getEndLineOffset();\n      var textRangeHash = textRangeWithHash == null ? null : textRangeWithHash.getHash();\n\n      var lineWithHash = finding.getLineWithHash();\n      var line = lineWithHash == null ? null : lineWithHash.getNumber();\n      var lineHash = lineWithHash == null ? null : lineWithHash.getHash();\n      var introDate = LocalDateTime.ofInstant(finding.getIntroductionDate(), ZoneOffset.UTC);\n      trx.dsl().mergeInto(KNOWN_FINDINGS)\n        .using(trx.dsl().selectOne())\n        .on(KNOWN_FINDINGS.ID.eq(finding.getId()))\n        .whenMatchedThenUpdate()\n        .set(KNOWN_FINDINGS.CONFIGURATION_SCOPE_ID, configurationScopeId)\n        .set(KNOWN_FINDINGS.IDE_RELATIVE_FILE_PATH, clientRelativePath.toString())\n        .set(KNOWN_FINDINGS.SERVER_KEY, finding.getServerKey())\n        .set(KNOWN_FINDINGS.RULE_KEY, finding.getRuleKey())\n        .set(KNOWN_FINDINGS.MESSAGE, finding.getMessage())\n        .set(KNOWN_FINDINGS.INTRODUCTION_DATE, introDate)\n        .set(KNOWN_FINDINGS.FINDING_TYPE, type.name())\n        .set(KNOWN_FINDINGS.START_LINE, startLine)\n        .set(KNOWN_FINDINGS.START_LINE_OFFSET, startLineOffset)\n        .set(KNOWN_FINDINGS.END_LINE, endLine)\n        .set(KNOWN_FINDINGS.END_LINE_OFFSET, endLineOffset)\n        .set(KNOWN_FINDINGS.TEXT_RANGE_HASH, textRangeHash)\n        .set(KNOWN_FINDINGS.LINE, line)\n        .set(KNOWN_FINDINGS.LINE_HASH, lineHash)\n        .whenNotMatchedThenInsert(KNOWN_FINDINGS.ID, KNOWN_FINDINGS.CONFIGURATION_SCOPE_ID, KNOWN_FINDINGS.IDE_RELATIVE_FILE_PATH, KNOWN_FINDINGS.SERVER_KEY,\n          KNOWN_FINDINGS.RULE_KEY, KNOWN_FINDINGS.MESSAGE, KNOWN_FINDINGS.INTRODUCTION_DATE, KNOWN_FINDINGS.FINDING_TYPE,\n          KNOWN_FINDINGS.START_LINE, KNOWN_FINDINGS.START_LINE_OFFSET, KNOWN_FINDINGS.END_LINE, KNOWN_FINDINGS.END_LINE_OFFSET, KNOWN_FINDINGS.TEXT_RANGE_HASH,\n          KNOWN_FINDINGS.LINE, KNOWN_FINDINGS.LINE_HASH)\n        .values(finding.getId(), configurationScopeId, clientRelativePath.toString(), finding.getServerKey(), finding.getRuleKey(),\n          finding.getMessage(), introDate, type.name(),\n          startLine, startLineOffset, endLine, endLineOffset, textRangeHash,\n          line, lineHash)\n        .execute();\n    }));\n  }\n\n  private List<KnownFinding> getKnownFindingsForFile(String configurationScopeId, Path filePath, KnownFindingType type) {\n    var issuesInFile = database.dsl()\n      .selectFrom(KNOWN_FINDINGS)\n      .where(KNOWN_FINDINGS.CONFIGURATION_SCOPE_ID.eq(configurationScopeId)\n        .and(KNOWN_FINDINGS.IDE_RELATIVE_FILE_PATH.eq(filePath.toString()))\n        .and(KNOWN_FINDINGS.FINDING_TYPE.eq(type.name())))\n      .fetch();\n    return issuesInFile.stream()\n      .map(KnownFindingsRepository::recordToKnownFinding)\n      .toList();\n  }\n\n  private static KnownFinding recordToKnownFinding(Record rec) {\n    var id = rec.get(KNOWN_FINDINGS.ID);\n    var introductionDate = rec.get(KNOWN_FINDINGS.INTRODUCTION_DATE).toInstant(ZoneOffset.UTC);\n    var textRangeWithHash = getTextRangeWithHash(rec);\n    var lineWithHash = getLineWithHash(rec);\n    return new KnownFinding(\n      id,\n      rec.get(KNOWN_FINDINGS.SERVER_KEY),\n      textRangeWithHash, lineWithHash,\n      rec.get(KNOWN_FINDINGS.RULE_KEY),\n      rec.get(KNOWN_FINDINGS.MESSAGE),\n      introductionDate);\n  }\n\n  private static LineWithHash getLineWithHash(Record rec) {\n    var line = rec.get(KNOWN_FINDINGS.LINE);\n    if (line == null) {\n      return null;\n    }\n    var hash = rec.get(KNOWN_FINDINGS.LINE_HASH);\n    return new LineWithHash(line, hash);\n  }\n\n  private static TextRangeWithHash getTextRangeWithHash(Record rec) {\n    var startLine = rec.get(KNOWN_FINDINGS.START_LINE);\n    if (startLine == null) {\n      return null;\n    }\n    var endLine = rec.get(KNOWN_FINDINGS.END_LINE);\n    var startLineOffset = rec.get(KNOWN_FINDINGS.START_LINE_OFFSET);\n    var endLineOffset = rec.get(KNOWN_FINDINGS.END_LINE_OFFSET);\n    var hash = rec.get(KNOWN_FINDINGS.TEXT_RANGE_HASH);\n    return new TextRangeWithHash(startLine, startLineOffset, endLine, endLineOffset, hash);\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/issues/LineLevelServerIssue.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.issues;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.Map;\nimport java.util.UUID;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\n\n/**\n * Issues with line level precision (from old /batch/issues WS, in SQ < 9.6 and SC)\n */\npublic class LineLevelServerIssue extends ServerIssue<LineLevelServerIssue> {\n  private int line;\n  private String lineHash;\n\n  public LineLevelServerIssue(@Nullable UUID id, String key, boolean resolved, @Nullable IssueStatus resolutionStatus, String ruleKey,\n    String message, String lineHash, Path filePath, Instant creationDate,\n    @Nullable IssueSeverity userSeverity, RuleType type, int line, Map<SoftwareQuality, ImpactSeverity> impacts) {\n    super(id, key, resolved, resolutionStatus, ruleKey, message, filePath, creationDate, userSeverity, type, impacts);\n    this.lineHash = lineHash;\n    this.line = line;\n  }\n\n  /**\n   * constructor for backward compatibility, after finalization of migration from Xodus to H2 should not be used\n   * when using with H2 UUID should always be set\n   */\n  public LineLevelServerIssue(String key, boolean resolved, @Nullable IssueStatus resolutionStatus, String ruleKey,\n    String message, String lineHash, Path filePath, Instant creationDate,\n    @Nullable IssueSeverity userSeverity, RuleType type, int line, Map<SoftwareQuality, ImpactSeverity> impacts) {\n    this(null, key, resolved, resolutionStatus, ruleKey, message, lineHash, filePath, creationDate, userSeverity, type, line, impacts);\n  }\n\n  public String getLineHash() {\n    return lineHash;\n  }\n\n  public Integer getLine() {\n    return line;\n  }\n\n  public LineLevelServerIssue setLineHash(String lineHash) {\n    this.lineHash = lineHash;\n    return this;\n  }\n\n  public LineLevelServerIssue setLine(int line) {\n    this.line = line;\n    return this;\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/issues/LocalOnlyIssuesRepository.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.issues;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.time.LocalDateTime;\nimport java.time.ZoneOffset;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.UUID;\nimport org.jooq.Configuration;\nimport org.jooq.DSLContext;\nimport org.jooq.Record;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.LineWithHash;\nimport org.sonarsource.sonarlint.core.commons.LocalOnlyIssue;\nimport org.sonarsource.sonarlint.core.commons.LocalOnlyIssueResolution;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.commons.storage.model.tables.records.LocalOnlyIssuesRecord;\n\nimport static org.sonarsource.sonarlint.core.commons.storage.model.Tables.LOCAL_ONLY_ISSUES;\n\npublic class LocalOnlyIssuesRepository {\n\n  private final DSLContext database;\n\n  public LocalOnlyIssuesRepository(DSLContext database) {\n    this.database = database;\n  }\n\n  public List<LocalOnlyIssue> loadForFile(String configurationScopeId, Path filePath) {\n    var issuesInFile = database\n      .selectFrom(LOCAL_ONLY_ISSUES)\n      .where(LOCAL_ONLY_ISSUES.CONFIGURATION_SCOPE_ID.eq(configurationScopeId)\n        .and(LOCAL_ONLY_ISSUES.SERVER_RELATIVE_PATH.eq(filePath.toString())))\n      .fetch();\n    return issuesInFile.stream()\n      .map(LocalOnlyIssuesRepository::recordToLocalOnlyIssue)\n      .toList();\n  }\n\n  public List<LocalOnlyIssue> loadAll(String configurationScopeId) {\n    var allIssues = database\n      .selectFrom(LOCAL_ONLY_ISSUES)\n      .where(LOCAL_ONLY_ISSUES.CONFIGURATION_SCOPE_ID.eq(configurationScopeId))\n      .fetch();\n    return allIssues.stream()\n      .map(LocalOnlyIssuesRepository::recordToLocalOnlyIssue)\n      .toList();\n  }\n\n  public void storeIssues(Map<String, List<LocalOnlyIssue>> issuesPerConfigScopeId) {\n    database.deleteFrom(LOCAL_ONLY_ISSUES).execute();\n    database.batchInsert(issuesPerConfigScopeId.entrySet().stream()\n      .flatMap(entry -> {\n        var configScopeId = entry.getKey();\n        return entry.getValue().stream().map(\n          issue -> {\n            var resolution = issue.getResolution();\n            var textRangeWithHash = issue.getTextRangeWithHash();\n            var lineWithHash = issue.getLineWithHash();\n            return new LocalOnlyIssuesRecord(\n              issue.getId(),\n              configScopeId,\n              issue.getServerRelativePath().toString(),\n              issue.getRuleKey(),\n              issue.getMessage(),\n              resolution == null ? null : resolution.getStatus().name(),\n              resolution == null ? null : LocalDateTime.ofInstant(resolution.getResolutionDate(), ZoneOffset.UTC),\n              resolution == null ? null : resolution.getComment(),\n              textRangeWithHash == null ? null : textRangeWithHash.getStartLine(),\n              textRangeWithHash == null ? null : textRangeWithHash.getStartLineOffset(),\n              textRangeWithHash == null ? null : textRangeWithHash.getEndLine(),\n              textRangeWithHash == null ? null : textRangeWithHash.getEndLineOffset(),\n              textRangeWithHash == null ? null : textRangeWithHash.getHash(),\n              lineWithHash == null ? null : lineWithHash.getNumber(),\n              lineWithHash == null ? null : lineWithHash.getHash());\n\n          });\n      })\n      .toList())\n      .execute();\n  }\n\n  public void storeLocalOnlyIssue(String configurationScopeId, LocalOnlyIssue issue) {\n    database.transaction((Configuration trx) -> {\n      var textRangeWithHash = issue.getTextRangeWithHash();\n      var startLine = textRangeWithHash == null ? null : textRangeWithHash.getStartLine();\n      var startLineOffset = textRangeWithHash == null ? null : textRangeWithHash.getStartLineOffset();\n      var endLine = textRangeWithHash == null ? null : textRangeWithHash.getEndLine();\n      var endLineOffset = textRangeWithHash == null ? null : textRangeWithHash.getEndLineOffset();\n      var textRangeHash = textRangeWithHash == null ? null : textRangeWithHash.getHash();\n\n      var lineWithHash = issue.getLineWithHash();\n      var line = lineWithHash == null ? null : lineWithHash.getNumber();\n      var lineHash = lineWithHash == null ? null : lineWithHash.getHash();\n\n      var resolution = issue.getResolution();\n      var resolutionStatus = resolution == null ? null : resolution.getStatus().name();\n      var resolutionDate = resolution == null ? null : LocalDateTime.ofInstant(resolution.getResolutionDate(), ZoneOffset.UTC);\n      var comment = resolution == null ? null : resolution.getComment();\n\n      trx.dsl().mergeInto(LOCAL_ONLY_ISSUES)\n        .using(trx.dsl().selectOne())\n        .on(LOCAL_ONLY_ISSUES.ID.eq(issue.getId()))\n        .whenMatchedThenUpdate()\n        .set(LOCAL_ONLY_ISSUES.CONFIGURATION_SCOPE_ID, configurationScopeId)\n        .set(LOCAL_ONLY_ISSUES.SERVER_RELATIVE_PATH, issue.getServerRelativePath().toString())\n        .set(LOCAL_ONLY_ISSUES.RULE_KEY, issue.getRuleKey())\n        .set(LOCAL_ONLY_ISSUES.MESSAGE, issue.getMessage())\n        .set(LOCAL_ONLY_ISSUES.RESOLUTION_STATUS, resolutionStatus)\n        .set(LOCAL_ONLY_ISSUES.RESOLUTION_DATE, resolutionDate)\n        .set(LOCAL_ONLY_ISSUES.COMMENT, comment)\n        .set(LOCAL_ONLY_ISSUES.START_LINE, startLine)\n        .set(LOCAL_ONLY_ISSUES.START_LINE_OFFSET, startLineOffset)\n        .set(LOCAL_ONLY_ISSUES.END_LINE, endLine)\n        .set(LOCAL_ONLY_ISSUES.END_LINE_OFFSET, endLineOffset)\n        .set(LOCAL_ONLY_ISSUES.TEXT_RANGE_HASH, textRangeHash)\n        .set(LOCAL_ONLY_ISSUES.LINE, line)\n        .set(LOCAL_ONLY_ISSUES.LINE_HASH, lineHash)\n        .whenNotMatchedThenInsert(\n          LOCAL_ONLY_ISSUES.ID,\n          LOCAL_ONLY_ISSUES.CONFIGURATION_SCOPE_ID,\n          LOCAL_ONLY_ISSUES.SERVER_RELATIVE_PATH,\n          LOCAL_ONLY_ISSUES.RULE_KEY,\n          LOCAL_ONLY_ISSUES.MESSAGE,\n          LOCAL_ONLY_ISSUES.RESOLUTION_STATUS,\n          LOCAL_ONLY_ISSUES.RESOLUTION_DATE,\n          LOCAL_ONLY_ISSUES.COMMENT,\n          LOCAL_ONLY_ISSUES.START_LINE,\n          LOCAL_ONLY_ISSUES.START_LINE_OFFSET,\n          LOCAL_ONLY_ISSUES.END_LINE,\n          LOCAL_ONLY_ISSUES.END_LINE_OFFSET,\n          LOCAL_ONLY_ISSUES.TEXT_RANGE_HASH,\n          LOCAL_ONLY_ISSUES.LINE,\n          LOCAL_ONLY_ISSUES.LINE_HASH)\n        .values(\n          issue.getId(),\n          configurationScopeId,\n          issue.getServerRelativePath().toString(),\n          issue.getRuleKey(),\n          issue.getMessage(),\n          resolutionStatus,\n          resolutionDate,\n          comment,\n          startLine,\n          startLineOffset,\n          endLine,\n          endLineOffset,\n          textRangeHash,\n          line,\n          lineHash)\n        .execute();\n    });\n  }\n\n  public boolean removeIssue(UUID issueId) {\n    var deleted = database\n      .deleteFrom(LOCAL_ONLY_ISSUES)\n      .where(LOCAL_ONLY_ISSUES.ID.eq(issueId))\n      .execute();\n    return deleted > 0;\n  }\n\n  public boolean removeAllIssuesForFile(String configurationScopeId, Path filePath) {\n    var deleted = database\n      .deleteFrom(LOCAL_ONLY_ISSUES)\n      .where(LOCAL_ONLY_ISSUES.CONFIGURATION_SCOPE_ID.eq(configurationScopeId)\n        .and(LOCAL_ONLY_ISSUES.SERVER_RELATIVE_PATH.eq(filePath.toString())))\n      .execute();\n    return deleted > 0;\n  }\n\n  public Optional<LocalOnlyIssue> find(UUID issueId) {\n    var issue = database\n      .selectFrom(LOCAL_ONLY_ISSUES)\n      .where(LOCAL_ONLY_ISSUES.ID.eq(issueId))\n      .fetchOne();\n    return issue == null ? Optional.empty() : Optional.of(recordToLocalOnlyIssue(issue));\n  }\n\n  public void purgeIssuesOlderThan(Instant limit) {\n    var limitDateTime = LocalDateTime.ofInstant(limit, ZoneOffset.UTC);\n    database\n      .deleteFrom(LOCAL_ONLY_ISSUES)\n      .where(LOCAL_ONLY_ISSUES.RESOLUTION_DATE.isNotNull()\n        .and(LOCAL_ONLY_ISSUES.RESOLUTION_DATE.le(limitDateTime)))\n      .execute();\n  }\n\n  private static LocalOnlyIssue recordToLocalOnlyIssue(Record rec) {\n    var id = rec.get(LOCAL_ONLY_ISSUES.ID);\n    var serverRelativePath = Path.of(rec.get(LOCAL_ONLY_ISSUES.SERVER_RELATIVE_PATH));\n    var ruleKey = rec.get(LOCAL_ONLY_ISSUES.RULE_KEY);\n    var message = rec.get(LOCAL_ONLY_ISSUES.MESSAGE);\n\n    var textRangeWithHash = getTextRangeWithHash(rec);\n    var lineWithHash = getLineWithHash(rec);\n\n    LocalOnlyIssueResolution resolution = null;\n    var resolutionStatus = rec.get(LOCAL_ONLY_ISSUES.RESOLUTION_STATUS);\n    var resolutionDate = rec.get(LOCAL_ONLY_ISSUES.RESOLUTION_DATE);\n    if (resolutionStatus != null && resolutionDate != null) {\n      var status = IssueStatus.valueOf(resolutionStatus);\n      var instant = resolutionDate.toInstant(ZoneOffset.UTC);\n      var comment = rec.get(LOCAL_ONLY_ISSUES.COMMENT);\n      resolution = new LocalOnlyIssueResolution(status, instant, comment);\n    }\n\n    return new LocalOnlyIssue(id, serverRelativePath, textRangeWithHash, lineWithHash, ruleKey, message, resolution);\n  }\n\n  private static LineWithHash getLineWithHash(Record rec) {\n    var line = rec.get(LOCAL_ONLY_ISSUES.LINE);\n    if (line == null) {\n      return null;\n    }\n    var hash = rec.get(LOCAL_ONLY_ISSUES.LINE_HASH);\n    return new LineWithHash(line, hash);\n  }\n\n  private static TextRangeWithHash getTextRangeWithHash(Record rec) {\n    var startLine = rec.get(LOCAL_ONLY_ISSUES.START_LINE);\n    if (startLine == null) {\n      return null;\n    }\n    var endLine = rec.get(LOCAL_ONLY_ISSUES.END_LINE);\n    var startLineOffset = rec.get(LOCAL_ONLY_ISSUES.START_LINE_OFFSET);\n    var endLineOffset = rec.get(LOCAL_ONLY_ISSUES.END_LINE_OFFSET);\n    var hash = rec.get(LOCAL_ONLY_ISSUES.TEXT_RANGE_HASH);\n    if (hash == null) {\n      return null;\n    }\n    return new TextRangeWithHash(startLine, startLineOffset, endLine, endLineOffset, hash);\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/issues/RangeLevelServerIssue.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.issues;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.Map;\nimport java.util.UUID;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\n\n/**\n * Issues with precise location (from api/issues/pull, SQ >= 9.6)\n */\npublic class RangeLevelServerIssue extends ServerIssue<RangeLevelServerIssue> {\n  private TextRangeWithHash textRange;\n\n  public RangeLevelServerIssue(@Nullable UUID id, String key, boolean resolved, @Nullable IssueStatus resolutionStatus, String ruleKey,\n    String message, Path filePath, Instant creationDate,\n    @Nullable IssueSeverity userSeverity, RuleType type, TextRangeWithHash textRange, Map<SoftwareQuality, ImpactSeverity> impacts) {\n    super(id, key, resolved, resolutionStatus, ruleKey, message, filePath, creationDate, userSeverity, type, impacts);\n    this.textRange = textRange;\n  }\n\n  /**\n   * constructor for backward compatibility, after finalization of migration from Xodus to H2 should not be used\n   * when using with H2 UUID should always be set\n   */\n  public RangeLevelServerIssue(String key, boolean resolved, @Nullable IssueStatus resolutionStatus, String ruleKey,\n    String message, Path filePath, Instant creationDate,\n    @Nullable IssueSeverity userSeverity, RuleType type, TextRangeWithHash textRange, Map<SoftwareQuality, ImpactSeverity> impacts) {\n    this(null, key, resolved, resolutionStatus, ruleKey, message, filePath, creationDate, userSeverity, type, textRange, impacts);\n  }\n\n  public TextRangeWithHash getTextRange() {\n    return textRange;\n  }\n\n  public RangeLevelServerIssue setTextRange(TextRangeWithHash textRange) {\n    this.textRange = textRange;\n    return this;\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/issues/ServerDependencyRisk.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.issues;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.UUID;\nimport javax.annotation.Nullable;\n\npublic record ServerDependencyRisk(UUID key, Type type, Severity severity, SoftwareQuality quality,\n                                   Status status, String packageName, String packageVersion, @Nullable String vulnerabilityId,\n                                   @Nullable String cvssScore, List<Transition> transitions) {\n\n  public ServerDependencyRisk withStatus(Status newStatus) {\n    var newTransitions = new ArrayList<>(Arrays.asList(Transition.values()));\n    newTransitions.remove(Transition.FIXED);\n    newTransitions.remove(newStatus.equals(Status.OPEN) ? Transition.REOPEN : Transition.valueOf(newStatus.name()));\n    return new ServerDependencyRisk(key, type, severity, quality, newStatus, packageName, packageVersion,\n      vulnerabilityId, cvssScore, newTransitions);\n  }\n\n  public enum Severity {\n    INFO, LOW, MEDIUM, HIGH, BLOCKER\n  }\n\n  public enum SoftwareQuality {\n    MAINTAINABILITY,\n    RELIABILITY,\n    SECURITY\n  }\n\n  public enum Type {\n    VULNERABILITY, PROHIBITED_LICENSE\n  }\n\n  public enum Status {\n    OPEN, CONFIRM, ACCEPT, SAFE, FIXED\n  }\n\n  public enum Transition {\n    CONFIRM, REOPEN, SAFE, FIXED, ACCEPT\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/issues/ServerFinding.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.issues;\n\npublic interface ServerFinding {\n  String getRuleKey();\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/issues/ServerIssue.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.issues;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.Map;\nimport java.util.UUID;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\n\npublic abstract class ServerIssue<G extends ServerIssue<G>> implements ServerFinding {\n\n  private UUID id;\n  private String key;\n  private boolean resolved;\n  private IssueStatus resolutionStatus;\n  private String ruleKey;\n  private String message;\n  private Path filePath;\n  private Instant creationDate;\n  private IssueSeverity userSeverity;\n  private RuleType type;\n  private Map<SoftwareQuality, ImpactSeverity> impacts;\n\n  protected ServerIssue(@Nullable UUID id, String key, boolean resolved, @Nullable IssueStatus resolutionStatus, String ruleKey,\n    String message, Path filePath, Instant creationDate, @Nullable IssueSeverity userSeverity, RuleType type,\n    Map<SoftwareQuality, ImpactSeverity> impacts) {\n    this.id = id;\n    this.key = key;\n    this.resolved = resolved;\n    this.resolutionStatus = resolutionStatus;\n    this.ruleKey = ruleKey;\n    this.message = message;\n    this.filePath = filePath;\n    this.creationDate = creationDate;\n    this.userSeverity = userSeverity;\n    this.type = type;\n    this.impacts = impacts;\n  }\n\n  @CheckForNull\n  public UUID getId() {\n    return id;\n  }\n\n  public String getKey() {\n    return key;\n  }\n\n  public boolean isResolved() {\n    return resolved;\n  }\n\n  @CheckForNull\n  public IssueStatus getResolutionStatus() {\n    return resolutionStatus;\n  }\n\n  @Override\n  public String getRuleKey() {\n    return ruleKey;\n  }\n\n  public String getMessage() {\n    return message;\n  }\n\n  public Path getFilePath() {\n    return filePath;\n  }\n\n  public Instant getCreationDate() {\n    return creationDate;\n  }\n\n  @CheckForNull\n  public IssueSeverity getUserSeverity() {\n    return userSeverity;\n  }\n\n  public RuleType getType() {\n    return type;\n  }\n\n  public Map<SoftwareQuality, ImpactSeverity> getImpacts() {\n    return impacts;\n  }\n\n  public G setId(@Nullable UUID id) {\n    this.id = id;\n    return (G) this;\n  }\n\n  public G setKey(String key) {\n    this.key = key;\n    return (G) this;\n  }\n\n  public G setResolutionStatus(@Nullable IssueStatus resolutionStatus) {\n    this.resolutionStatus = resolutionStatus;\n    return (G) this;\n  }\n\n  public G setRuleKey(String ruleKey) {\n    this.ruleKey = ruleKey;\n    return (G) this;\n  }\n\n  public G setMessage(String message) {\n    this.message = message;\n    return (G) this;\n  }\n\n  public G setFilePath(Path filePath) {\n    this.filePath = filePath;\n    return (G) this;\n  }\n\n  public G setCreationDate(Instant creationDate) {\n    this.creationDate = creationDate;\n    return (G) this;\n  }\n\n  public G setUserSeverity(@Nullable IssueSeverity userSeverity) {\n    this.userSeverity = userSeverity;\n    return (G) this;\n  }\n\n  public G setType(RuleType type) {\n    this.type = type;\n    return (G) this;\n  }\n\n  public G setResolved(boolean resolved) {\n    this.resolved = resolved;\n    return (G) this;\n  }\n\n  public G setImpacts(Map<SoftwareQuality, ImpactSeverity> impacts) {\n    this.impacts = impacts;\n    return (G) this;\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/issues/ServerTaintIssue.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.issues;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.UUID;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\n\npublic class ServerTaintIssue implements ServerFinding {\n  private final UUID id;\n  private final String key;\n  private boolean resolved;\n  @Nullable\n  private final IssueStatus resolutionStatus;\n  private final String ruleKey;\n  private final String message;\n  private final Path filePath;\n  private final Instant creationDate;\n  private IssueSeverity severity;\n  private RuleType type;\n  private final List<Flow> flows;\n  private final TextRangeWithHash textRange;\n  private Map<SoftwareQuality, ImpactSeverity> impacts;\n  @Nullable\n  private final String ruleDescriptionContextKey;\n  @Nullable\n  private final CleanCodeAttribute cleanCodeAttribute;\n\n  public ServerTaintIssue(UUID id, String key, boolean resolved, @Nullable IssueStatus resolutionStatus, String ruleKey,\n    String message, Path filePath, Instant creationDate, IssueSeverity severity, RuleType type,\n    @Nullable TextRangeWithHash textRange, @Nullable String ruleDescriptionContextKey, @Nullable CleanCodeAttribute cleanCodeAttribute,\n    Map<SoftwareQuality, ImpactSeverity> impacts, List<Flow> flows) {\n    this.id = id;\n    this.key = key;\n    this.resolved = resolved;\n    this.resolutionStatus = resolutionStatus;\n    this.ruleKey = ruleKey;\n    this.message = message;\n    this.filePath = filePath;\n    this.creationDate = creationDate;\n    this.severity = severity;\n    this.type = type;\n    this.textRange = textRange;\n    this.ruleDescriptionContextKey = ruleDescriptionContextKey;\n    this.cleanCodeAttribute = cleanCodeAttribute;\n    this.impacts = impacts;\n    this.flows = flows;\n  }\n\n  public UUID getId() {\n    return id;\n  }\n\n  public String getSonarServerKey() {\n    return key;\n  }\n\n  public boolean isResolved() {\n    return resolved;\n  }\n\n  @CheckForNull\n  public IssueStatus getResolutionStatus() {\n    return resolutionStatus;\n  }\n\n  @Override\n  public String getRuleKey() {\n    return ruleKey;\n  }\n\n  public String getMessage() {\n    return message;\n  }\n\n  public Path getFilePath() {\n    return filePath;\n  }\n\n  public Instant getCreationDate() {\n    return creationDate;\n  }\n\n  public IssueSeverity getSeverity() {\n    return severity;\n  }\n\n  public RuleType getType() {\n    return type;\n  }\n\n  @CheckForNull\n  public TextRangeWithHash getTextRange() {\n    return textRange;\n  }\n\n  @CheckForNull\n  public String getRuleDescriptionContextKey() {\n    return ruleDescriptionContextKey;\n  }\n\n  public List<Flow> getFlows() {\n    return flows;\n  }\n\n  public Optional<CleanCodeAttribute> getCleanCodeAttribute() {\n    return Optional.ofNullable(cleanCodeAttribute);\n  }\n\n  public Map<SoftwareQuality, ImpactSeverity> getImpacts() {\n    return impacts;\n  }\n\n  public ServerTaintIssue setResolved(boolean resolved) {\n    this.resolved = resolved;\n    return this;\n  }\n\n  public ServerTaintIssue setImpacts(Map<SoftwareQuality, ImpactSeverity> impacts) {\n    this.impacts = impacts;\n    return this;\n  }\n\n  public ServerTaintIssue setSeverity(IssueSeverity severity) {\n    this.severity = severity;\n    return this;\n  }\n\n  public ServerTaintIssue setType(RuleType type) {\n    this.type = type;\n    return this;\n  }\n\n  public record Flow(List<ServerIssueLocation> locations) {\n  }\n\n  public record ServerIssueLocation(@Nullable Path filePath, @Nullable TextRangeWithHash textRange, @Nullable String message) {\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/issues/package-info.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverconnection.issues;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/package-info.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/prefix/FileTreeMatcher.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.prefix;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Comparator;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\n\nimport static java.util.Collections.reverseOrder;\n\npublic class FileTreeMatcher {\n\n  public Result match(List<Path> serverRelativePaths, List<Path> ideRelativePaths) {\n    var reversePathTree = new ReversePathTree();\n\n    Map<Result, Double> resultScores = new LinkedHashMap<>();\n\n    // No need to index server files if no ide path ends with the same filename\n    Set<Path> ideFilenames = ideRelativePaths.stream().map(Path::getFileName).collect(Collectors.toSet());\n    serverRelativePaths.stream().filter(sqPath -> ideFilenames.contains(sqPath.getFileName())).forEach(reversePathTree::index);\n\n    for (Path ide : ideRelativePaths) {\n      var match = reversePathTree.findLongestSuffixMatches(ide);\n      if (match.matchLen() > 0) {\n        var idePrefix = getIdePrefix(ide, match);\n\n        for (Path sqPrefix : match.matchPrefixes()) {\n          var r = new Result(idePrefix, sqPrefix);\n          resultScores.compute(r, (p, i) -> computeScore(i, match));\n        }\n      }\n    }\n\n    return higherScoreResult(resultScores);\n  }\n\n  private static double computeScore(@Nullable Double currentScore, ReversePathTree.Match match) {\n    var matchScore = (double) match.matchLen() / match.matchPrefixes().size();\n    return currentScore != null ? (currentScore.doubleValue() + matchScore) : matchScore;\n  }\n\n  private static Path getIdePrefix(Path idePath, ReversePathTree.Match match) {\n    var prefixLen = depth(idePath) - match.matchLen();\n    if (prefixLen > 0) {\n      return idePath.subpath(0, depth(idePath) - match.matchLen());\n    }\n    return Paths.get(\"\");\n  }\n\n  private static Result higherScoreResult(Map<Result, Double> prefixes) {\n    // Prefere higher score\n    Comparator<Map.Entry<Result, Double>> c = Comparator.comparing(Map.Entry::getValue);\n    c = c\n      // fallback on prefix depth\n      .thenComparing(x -> depth(x.getKey().serverPrefix), reverseOrder())\n      // fallback on prefix lexicographic order\n      .thenComparing(x -> x.getKey().serverPrefix.toString(), reverseOrder());\n\n    return prefixes.entrySet().stream()\n      .max(c)\n      .map(Map.Entry::getKey)\n      .orElse(new Result(Paths.get(\"\"), Paths.get(\"\")));\n  }\n\n  private static int depth(Path path) {\n    return path.toString().isEmpty() ? 0 : path.getNameCount();\n  }\n\n  public static class Result {\n    private final Path idePrefix;\n    private final Path serverPrefix;\n\n    Result(Path idePrefix, Path serverPrefix) {\n      this.idePrefix = idePrefix;\n      this.serverPrefix = serverPrefix;\n    }\n\n    public Path idePrefix() {\n      return idePrefix;\n    }\n\n    public Path sqPrefix() {\n      return serverPrefix;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n      if (this == o) {\n        return true;\n      }\n      if (o == null || getClass() != o.getClass()) {\n        return false;\n      }\n      var result = (Result) o;\n      return Objects.equals(idePrefix, result.idePrefix) && Objects.equals(serverPrefix, result.serverPrefix);\n    }\n\n    @Override\n    public int hashCode() {\n      return Objects.hash(idePrefix, serverPrefix);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/prefix/ReversePathTree.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.prefix;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.AbstractMap;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\nclass ReversePathTree {\n  private final Node root = new MultipleChildrenNode();\n\n  public void index(Path path) {\n    Node parent = null;\n    var currentNode = root;\n    Path currentNodePath = null;\n\n    for (var i = path.getNameCount() - 1; i >= 0; i--) {\n      var childNodePath = path.getName(i);\n      var result = currentNode.computeChildrenIfAbsent(parent, currentNodePath, childNodePath);\n      parent = result[0];\n      currentNode = result[1];\n      currentNodePath = childNodePath;\n    }\n\n    currentNode.setTerminal(true);\n  }\n\n  public Match findLongestSuffixMatches(Path path) {\n    var currentNode = root;\n    var matchLen = 0;\n\n    while (matchLen < path.getNameCount()) {\n      var nextEl = path.getName(path.getNameCount() - matchLen - 1);\n      var nextNode = currentNode.getChild(nextEl);\n      if (nextNode == null) {\n        break;\n      }\n      matchLen++;\n      currentNode = nextNode;\n    }\n\n    return collectAllPrefixes(currentNode, matchLen);\n  }\n\n  private static Match collectAllPrefixes(Node node, int matchLen) {\n    List<Path> paths = new ArrayList<>();\n    if (matchLen > 0) {\n      collectPrefixes(node, Paths.get(\"\"), paths);\n    }\n    return new Match(paths, matchLen);\n  }\n\n  private static void collectPrefixes(Node node, Path currentPath, List<Path> paths) {\n    if (node.isTerminal()) {\n      paths.add(currentPath);\n    }\n\n    for (Map.Entry<Path, Node> child : node.childrenEntrySet()) {\n      var childPath = child.getKey().resolve(currentPath);\n      collectPrefixes(child.getValue(), childPath, paths);\n    }\n  }\n\n  /**\n   * Since it is very common that a node will have only one child, we save memory by lazily creating a children HashMap only when a second item is added.\n   */\n  private interface Node {\n    Node[] computeChildrenIfAbsent(Node parent, Path currentNodePath, Path childNodePath);\n\n    Set<Map.Entry<Path, Node>> childrenEntrySet();\n\n    Node getChild(Path name);\n\n    void setTerminal(boolean b);\n\n    boolean isTerminal();\n\n    void put(Path path, Node node);\n  }\n\n  private abstract static class AbstractNode implements Node {\n    private boolean terminal;\n\n    @Override\n    public final boolean isTerminal() {\n      return terminal;\n    }\n\n    @Override\n    public final void setTerminal(boolean b) {\n      this.terminal = b;\n    }\n  }\n\n  private static class SingleChildNode extends AbstractNode {\n    @Nullable\n    private Path singleChildKey;\n    @Nullable\n    private Node singleChildValue;\n\n    @Override\n    public Node[] computeChildrenIfAbsent(Node parent, Path currentNodePath, Path childNodePath) {\n      if (singleChildKey == null) {\n        put(childNodePath, new SingleChildNode());\n        return new Node[] {this, singleChildValue};\n      }\n      if (childNodePath.equals(singleChildKey)) {\n        return new Node[] {this, singleChildValue};\n      }\n      var child = new SingleChildNode();\n      var replacement = new MultipleChildrenNode();\n      replacement.put(singleChildKey, singleChildValue);\n      replacement.put(childNodePath, child);\n      parent.put(currentNodePath, replacement);\n      return new Node[] {replacement, child};\n    }\n\n    @Override\n    public Set<Map.Entry<Path, Node>> childrenEntrySet() {\n      if (singleChildKey == null) {\n        return Collections.emptySet();\n      } else {\n        return Collections.singleton(new AbstractMap.SimpleEntry<>(singleChildKey, singleChildValue));\n      }\n    }\n\n    @Override\n    public void put(Path path, Node node) {\n      this.singleChildKey = path;\n      this.singleChildValue = node;\n    }\n\n    @Override\n    @CheckForNull\n    public Node getChild(Path name) {\n      return name.equals(singleChildKey) ? singleChildValue : null;\n    }\n  }\n\n  private static class MultipleChildrenNode extends AbstractNode {\n\n    private final Map<Path, Node> children = new HashMap<>();\n\n    @Override\n    public Node[] computeChildrenIfAbsent(Node parent, Path currentNodePath, Path childNodePath) {\n      return new Node[] {this, children.computeIfAbsent(childNodePath, e -> new SingleChildNode())};\n    }\n\n    @Override\n    public Set<Map.Entry<Path, Node>> childrenEntrySet() {\n      return children.entrySet();\n    }\n\n    @CheckForNull\n    @Override\n    public Node getChild(Path name) {\n      return children.get(name);\n    }\n\n    @Override\n    public void put(Path path, Node node) {\n      children.put(path, node);\n    }\n\n  }\n\n  public static class Match {\n    private final List<Path> paths;\n    private final int matchLen;\n\n    private Match(List<Path> paths, int matchLen) {\n      this.paths = paths;\n      this.matchLen = matchLen;\n    }\n\n    public List<Path> matchPrefixes() {\n      return paths;\n    }\n\n    public int matchLen() {\n      return matchLen;\n    }\n\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/prefix/package-info.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverconnection.prefix;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/EntityMapper.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.time.LocalDateTime;\nimport java.time.ZoneOffset;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.jooq.JSON;\nimport org.sonarsource.sonarlint.core.commons.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.commons.storage.model.tables.records.ServerDependencyRisksRecord;\nimport org.sonarsource.sonarlint.core.commons.storage.model.tables.records.ServerFindingsRecord;\nimport org.sonarsource.sonarlint.core.serverapi.hotspot.ServerHotspot;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.FileLevelServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.LineLevelServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.RangeLevelServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerDependencyRisk;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerTaintIssue;\n\npublic class EntityMapper {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  private final ObjectMapper objectMapper = new ObjectMapper();\n\n  public JSON serializeImpacts(Map<SoftwareQuality, ImpactSeverity> impacts) {\n    try {\n      return JSON.valueOf(objectMapper.writeValueAsString(impacts));\n    } catch (Exception e) {\n      return JSON.valueOf(\"{}\");\n    }\n  }\n\n  public JSON serializeFlows(List<ServerTaintIssue.Flow> flows) {\n    try {\n      var flowsToSerialize = flows.stream().map(f -> new TaintFlow(f.locations().stream().map(l -> {\n        var filePath = l.filePath();\n        var textRangeWithHash = l.textRange();\n        return new TaintLocation(filePath == null ? null : filePath.toString(),\n          textRangeWithHash == null ? null\n            : new TextRangeWithHash(textRangeWithHash.getStartLine(), textRangeWithHash.getStartLineOffset(), textRangeWithHash.getEndLine(), textRangeWithHash.getEndLineOffset(),\n              textRangeWithHash.getHash()),\n          l.message());\n      }).toList())).toList();\n      return JSON.valueOf(objectMapper.writeValueAsString(flowsToSerialize));\n    } catch (Exception e) {\n      return JSON.valueOf(\"[]\");\n    }\n  }\n\n  List<ServerTaintIssue.Flow> deserializeTaintFlows(JSON flows) {\n    try {\n      return objectMapper.readValue(flows.data(), new TypeReference<List<TaintFlow>>() {\n      }).stream()\n        .map(flow -> new ServerTaintIssue.Flow(flow.locations.stream()\n          .map(l -> {\n            var textRange = l.textRange;\n            var filePath = l.filePath;\n            return new ServerTaintIssue.ServerIssueLocation(filePath == null ? null : Path.of(filePath),\n              textRange == null ? null\n                : new org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash(textRange.startLine, textRange.startLineOffset, textRange.endLine, textRange.endLineOffset,\n                  textRange.hash),\n              l.message);\n          }).toList()))\n        .toList();\n    } catch (Exception e) {\n      return List.of();\n    }\n  }\n\n  // only needed to allow deserializing with Jackson\n  record TaintFlow(List<TaintLocation> locations) {\n\n  }\n\n  record TaintLocation(@Nullable String filePath, @Nullable TextRangeWithHash textRange, @Nullable String message) {\n  }\n\n  record TextRangeWithHash(int startLine, int startLineOffset, int endLine, int endLineOffset, String hash) {\n  }\n\n  public JSON serializeTransitions(@Nullable List<ServerDependencyRisk.Transition> transitions) {\n    if (transitions == null) {\n      return null;\n    }\n    try {\n      var stringList = transitions.stream().map(Enum::name).toList();\n      return JSON.valueOf(objectMapper.writeValueAsString(stringList));\n    } catch (Exception e) {\n      LOG.error(\"Failed to serialize transitions {}\", transitions, e);\n      return JSON.valueOf(\"{}\");\n    }\n  }\n\n  public Map<SoftwareQuality, ImpactSeverity> deserializeImpacts(@Nullable JSON impactsJson) {\n    if (impactsJson == null) {\n      return Map.of();\n    }\n    try {\n      var map = objectMapper.readValue(impactsJson.data(), new TypeReference<Map<String, String>>() {\n      });\n      return map.entrySet().stream()\n        .collect(Collectors.toMap(entry -> SoftwareQuality.valueOf(entry.getKey()), entry -> ImpactSeverity.valueOf(entry.getValue())));\n    } catch (Exception e) {\n      LOG.error(\"Failed to deserialize impacts {}\", impactsJson.data(), e);\n      return Map.of();\n    }\n  }\n\n  public List<ServerDependencyRisk.Transition> deserializeTransitions(@Nullable JSON json) {\n    if (json == null) {\n      return List.of();\n    }\n    try {\n      var transitions = objectMapper.readValue(json.data(), new TypeReference<List<String>>() {\n      });\n      return transitions.stream()\n        .map(transition -> {\n          try {\n            return ServerDependencyRisk.Transition.valueOf(transition);\n          } catch (Exception e) {\n            return null;\n          }\n        })\n        .filter(Objects::nonNull)\n        .toList();\n    } catch (Exception e) {\n      LOG.error(\"Failed to deserialize transitions {}\", json.data(), e);\n      return List.of();\n    }\n  }\n\n  public Set<SonarLanguage> deserializeLanguages(@Nullable String[] languages) {\n    if (languages == null) {\n      return Set.of();\n    }\n    return Arrays.stream(languages).map(SonarLanguage::valueOf).collect(Collectors.toSet());\n  }\n\n  public String[] serializeLanguages(Set<SonarLanguage> enabledLanguages) {\n    return enabledLanguages.stream().map(Enum::name).toList().toArray(new String[0]);\n  }\n\n  public ServerFindingsRecord serverIssueToRecord(ServerIssue<?> issue, String branchName, String connectionId, String sonarProjectKey) {\n    var rec = new ServerFindingsRecord();\n    // Server Issue fields\n    rec.setId(issue.getId());\n    rec.setServerKey(issue.getKey());\n    rec.setResolved(issue.isResolved());\n    var resolutionStatus = issue.getResolutionStatus();\n    if (resolutionStatus != null) {\n      rec.setIssueResolutionStatus(resolutionStatus.name());\n    }\n    rec.setRuleKey(issue.getRuleKey());\n    rec.setMessage(issue.getMessage());\n    rec.setFilePath(issue.getFilePath().toString());\n    rec.setCreationDate(toLocalDateTime(issue.getCreationDate()));\n    var userSeverity = issue.getUserSeverity();\n    if (userSeverity != null) {\n      rec.setUserSeverity(userSeverity.name());\n    }\n    rec.setRuleType(issue.getType().name());\n    rec.setImpacts(serializeImpacts(issue.getImpacts()));\n    // Range-Level Issue fields\n    if (issue instanceof RangeLevelServerIssue rangeIssue) {\n      rec.setStartLine(rangeIssue.getTextRange().getStartLine());\n      rec.setStartLineOffset(rangeIssue.getTextRange().getStartLineOffset());\n      rec.setEndLine(rangeIssue.getTextRange().getEndLine());\n      rec.setEndLineOffset(rangeIssue.getTextRange().getEndLineOffset());\n      rec.setTextRangeHash(rangeIssue.getTextRange().getHash());\n    }\n    // Line Level Issue fields\n    if (issue instanceof LineLevelServerIssue lineIssue) {\n      rec.setLine(lineIssue.getLine());\n      rec.setLineHash(lineIssue.getLineHash());\n    }\n    // Record fields\n    rec.setFindingType(ServerFindingType.ISSUE.name());\n    rec.setBranchName(branchName);\n    rec.setConnectionId(connectionId);\n    rec.setSonarProjectKey(sonarProjectKey);\n    return rec;\n  }\n\n  public ServerFindingsRecord serverHotspotToRecord(ServerHotspot hotspot, String branchName, String connectionId, String sonarProjectKey) {\n    var rec = new ServerFindingsRecord();\n    // Server Hotspot fields\n    rec.setId(hotspot.getId());\n    rec.setServerKey(hotspot.getKey());\n    rec.setRuleKey(hotspot.getRuleKey());\n    rec.setMessage(hotspot.getMessage());\n    rec.setFilePath(hotspot.getFilePath().toString());\n    // Text Range fields\n    rec.setStartLine(hotspot.getTextRange().getStartLine());\n    rec.setStartLineOffset(hotspot.getTextRange().getStartLineOffset());\n    rec.setEndLine(hotspot.getTextRange().getEndLine());\n    rec.setEndLineOffset(hotspot.getTextRange().getEndLineOffset());\n\n    rec.setCreationDate(toLocalDateTime(hotspot.getCreationDate()));\n    rec.setHotspotReviewStatus(hotspot.getStatus().name());\n    rec.setVulnerabilityProbability(hotspot.getVulnerabilityProbability().name());\n    rec.setAssignee(hotspot.getAssignee());\n    // Record fields\n    rec.setFindingType(ServerFindingType.HOTSPOT.name());\n    rec.setBranchName(branchName);\n    rec.setConnectionId(connectionId);\n    rec.setSonarProjectKey(sonarProjectKey);\n    return rec;\n  }\n\n  public ServerFindingsRecord serverTaintToRecord(ServerTaintIssue taint, String branchName, String connectionId, String sonarProjectKey) {\n    var rec = new ServerFindingsRecord();\n    // Server Taint fields\n    rec.setId(taint.getId());\n    rec.setServerKey(taint.getSonarServerKey());\n    rec.setResolved(taint.isResolved());\n    var resolutionStatus = taint.getResolutionStatus();\n    if (resolutionStatus != null) {\n      rec.setIssueResolutionStatus(resolutionStatus.name());\n    }\n    rec.setRuleKey(taint.getRuleKey());\n    rec.setMessage(taint.getMessage());\n    rec.setFilePath(taint.getFilePath().toString());\n    rec.setCreationDate(toLocalDateTime(taint.getCreationDate()));\n    rec.setUserSeverity(taint.getSeverity().name());\n    rec.setRuleType(taint.getType().name());\n    rec.setFlows(serializeFlows(taint.getFlows()));\n    var textRange = taint.getTextRange();\n    if (textRange != null) {\n      rec.setStartLine(textRange.getStartLine());\n      rec.setStartLineOffset(textRange.getStartLineOffset());\n      rec.setEndLine(textRange.getEndLine());\n      rec.setEndLineOffset(textRange.getEndLineOffset());\n      rec.setTextRangeHash(textRange.getHash());\n    }\n    rec.setImpacts(serializeImpacts(taint.getImpacts()));\n    rec.setRuleDescriptionContextKey(taint.getRuleDescriptionContextKey());\n    taint.getCleanCodeAttribute()\n      .ifPresent(codeAttribute -> rec.setCleanCodeAttribute(codeAttribute.name()));\n\n    // Record fields\n    rec.setFindingType(ServerFindingType.TAINT.name());\n    rec.setBranchName(branchName);\n    rec.setConnectionId(connectionId);\n    rec.setSonarProjectKey(sonarProjectKey);\n    return rec;\n  }\n\n  public ServerDependencyRisksRecord serverDependencyRiskToRecord(ServerDependencyRisk risk, String branchName, String connectionId, String sonarProjectKey) {\n    var rec = new ServerDependencyRisksRecord();\n    // Server Dependency Risk fields\n    rec.setId(risk.key());\n    rec.setType(risk.type().name());\n    rec.setSeverity(risk.severity().name());\n    rec.setSoftwareQuality(risk.quality().name());\n    rec.setStatus(risk.status().name());\n    rec.setPackageName(risk.packageName());\n    rec.setPackageVersion(risk.packageVersion());\n    rec.setVulnerabilityId(risk.vulnerabilityId());\n    rec.setCvssScore(risk.cvssScore());\n    rec.setTransitions(serializeTransitions(risk.transitions()));\n    // Record fields\n    rec.setBranchName(branchName);\n    rec.setConnectionId(connectionId);\n    rec.setSonarProjectKey(sonarProjectKey);\n    return rec;\n  }\n\n  public ServerIssue<?> adaptIssue(ServerFindingsRecord rec) {\n    var id = rec.getId();\n    var serverKey = rec.getServerKey();\n    var ruleKey = rec.getRuleKey();\n    var message = rec.getMessage();\n    var filePath = Path.of((rec.getFilePath()));\n    var creationDate = toInstant(rec.getCreationDate());\n    var userSeverity = rec.getUserSeverity() != null ? IssueSeverity.valueOf(rec.getUserSeverity()) : null;\n    var type = rec.getRuleType() != null ? RuleType.valueOf(rec.getRuleType()) : RuleType.CODE_SMELL;\n    var resolved = Boolean.TRUE.equals(rec.getResolved());\n    var resolutionStatus = rec.getIssueResolutionStatus() != null ? IssueStatus.valueOf(rec.getIssueResolutionStatus()) : null;\n    var impactsJson = rec.getImpacts();\n    var impacts = deserializeImpacts(impactsJson);\n    if (rec.getLine() != null) {\n      return new LineLevelServerIssue(id, serverKey, resolved, resolutionStatus, ruleKey, message, rec.getLineHash(), filePath, creationDate, userSeverity, type,\n        rec.getLine(), impacts);\n    }\n    if (rec.getStartLine() != null) {\n      var textRangeWithHash = new org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash(rec.getStartLine(), rec.getStartLineOffset(), rec.getEndLine(),\n        rec.getEndLineOffset(), rec.getTextRangeHash());\n      return new RangeLevelServerIssue(id, serverKey, resolved, resolutionStatus, ruleKey, message, filePath, creationDate, userSeverity, type, textRangeWithHash, impacts);\n    }\n    return new FileLevelServerIssue(id, serverKey, resolved, resolutionStatus, ruleKey, message, filePath, creationDate, userSeverity, type, impacts);\n  }\n\n  public ServerHotspot adaptHotspot(ServerFindingsRecord rec) {\n    var id = rec.getId();\n    var key = rec.getServerKey();\n    var ruleKey = rec.getRuleKey();\n    var message = rec.getMessage();\n    var filePath = Path.of((rec.getFilePath()));\n    var textRange = new TextRange(rec.getStartLine(), rec.getStartLineOffset(), rec.getEndLine(), rec.getEndLineOffset());\n    var creationDate = toInstant(rec.getCreationDate());\n    var status = HotspotReviewStatus.valueOf(rec.getHotspotReviewStatus());\n    var prob = rec.getVulnerabilityProbability() != null ? VulnerabilityProbability.valueOf(rec.getVulnerabilityProbability()) : VulnerabilityProbability.MEDIUM;\n    var assignee = rec.getAssignee();\n    return new ServerHotspot(id, key, ruleKey, message, filePath, textRange, creationDate, status, prob, assignee);\n  }\n\n  public ServerTaintIssue adaptTaint(ServerFindingsRecord rec) {\n    var id = rec.getId();\n    var key = rec.getServerKey();\n    var resolved = Boolean.TRUE.equals(rec.getResolved());\n    var resolutionStatus = rec.getIssueResolutionStatus() != null ? IssueStatus.valueOf(rec.getIssueResolutionStatus()) : null;\n    var ruleKey = rec.getRuleKey();\n    var message = rec.getMessage();\n    var filePath = Path.of(rec.getFilePath());\n    var creationDate = toInstant(rec.getCreationDate());\n    var severity = rec.getUserSeverity() != null ? IssueSeverity.valueOf(rec.getUserSeverity()) : IssueSeverity.MAJOR;\n    var type = rec.getRuleType() != null ? RuleType.valueOf(rec.getRuleType()) : RuleType.CODE_SMELL;\n    org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash textRangeWithHash = null;\n    if (rec.getStartLine() != null) {\n      textRangeWithHash = new org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash(rec.getStartLine(), rec.getStartLineOffset(), rec.getEndLine(), rec.getEndLineOffset(),\n        rec.getTextRangeHash());\n    }\n    var ruleDescCtx = rec.getRuleDescriptionContextKey();\n    var cleanCodeAttr = rec.getCleanCodeAttribute() != null ? CleanCodeAttribute.valueOf(rec.getCleanCodeAttribute()) : null;\n    var impactsJson = rec.getImpacts();\n    var impacts = deserializeImpacts(impactsJson);\n    var flows = deserializeTaintFlows(rec.getFlows());\n    return new ServerTaintIssue(id, key, resolved, resolutionStatus, ruleKey, message, filePath, creationDate,\n      severity, type, textRangeWithHash, ruleDescCtx, cleanCodeAttr, impacts, flows);\n  }\n\n  private static Instant toInstant(LocalDateTime ldt) {\n    return ldt.toInstant(ZoneOffset.UTC);\n  }\n\n  static LocalDateTime toLocalDateTime(Instant instant) {\n    return LocalDateTime.ofInstant(instant, ZoneOffset.UTC);\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/HotspotReviewStatusBinding.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.io.ByteArrayInputStream;\nimport jetbrains.exodus.bindings.BindingUtils;\nimport jetbrains.exodus.bindings.ComparableBinding;\nimport jetbrains.exodus.util.LightOutputStream;\nimport org.jetbrains.annotations.NotNull;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\n\npublic class HotspotReviewStatusBinding extends ComparableBinding {\n\n  @Override\n  public Comparable readObject(@NotNull ByteArrayInputStream stream) {\n    return HotspotReviewStatus.values()[BindingUtils.readInt(stream)];\n  }\n\n  @Override\n  public void writeObject(@NotNull LightOutputStream output, @NotNull Comparable object) {\n    final HotspotReviewStatus cPair = (HotspotReviewStatus) object;\n    output.writeUnsignedInt(cPair.ordinal() ^ 0x80_000_000);\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/InstantBinding.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.io.ByteArrayInputStream;\nimport java.time.Instant;\nimport jetbrains.exodus.bindings.BindingUtils;\nimport jetbrains.exodus.bindings.ComparableBinding;\nimport jetbrains.exodus.util.LightOutputStream;\nimport org.jetbrains.annotations.NotNull;\n\npublic class InstantBinding extends ComparableBinding {\n\n  @Override\n  public Comparable readObject(@NotNull ByteArrayInputStream stream) {\n    return Instant.ofEpochMilli(BindingUtils.readLong(stream));\n  }\n\n  @Override\n  public void writeObject(@NotNull LightOutputStream output, @NotNull Comparable object) {\n    final var instant = (Instant) object;\n    output.writeUnsignedLong(instant.toEpochMilli() ^ 0x8_000_000_000_000_000L);\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/IssueSeverityBinding.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.io.ByteArrayInputStream;\nimport jetbrains.exodus.bindings.BindingUtils;\nimport jetbrains.exodus.bindings.ComparableBinding;\nimport jetbrains.exodus.util.LightOutputStream;\nimport org.jetbrains.annotations.NotNull;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\n\npublic class IssueSeverityBinding extends ComparableBinding {\n\n  @Override\n  public Comparable readObject(@NotNull ByteArrayInputStream stream) {\n    return IssueSeverity.values()[BindingUtils.readInt(stream)];\n  }\n\n  @Override\n  public void writeObject(@NotNull LightOutputStream output, @NotNull Comparable object) {\n    final IssueSeverity cPair = (IssueSeverity) object;\n    output.writeUnsignedInt(cPair.ordinal() ^ 0x80000000);\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/IssueStatusBinding.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.io.ByteArrayInputStream;\nimport jetbrains.exodus.bindings.BindingUtils;\nimport jetbrains.exodus.bindings.ComparableBinding;\nimport jetbrains.exodus.util.LightOutputStream;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\n\npublic class IssueStatusBinding extends ComparableBinding {\n\n  @Override\n  public IssueStatus readObject(ByteArrayInputStream stream) {\n    return IssueStatus.values()[BindingUtils.readInt(stream)];\n  }\n\n  @Override\n  public void writeObject(LightOutputStream output, Comparable object) {\n    final IssueStatus cPair = (IssueStatus) object;\n    output.writeUnsignedInt(cPair.ordinal() ^ 0x80_000_000);\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/IssueTypeBinding.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.io.ByteArrayInputStream;\nimport jetbrains.exodus.bindings.BindingUtils;\nimport jetbrains.exodus.bindings.ComparableBinding;\nimport jetbrains.exodus.util.LightOutputStream;\nimport org.jetbrains.annotations.NotNull;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\n\npublic class IssueTypeBinding extends ComparableBinding {\n\n  @Override\n  public Comparable readObject(@NotNull ByteArrayInputStream stream) {\n    return RuleType.values()[BindingUtils.readInt(stream)];\n  }\n\n  @Override\n  public void writeObject(@NotNull LightOutputStream output, @NotNull Comparable object) {\n    final RuleType cPair = (RuleType) object;\n    output.writeUnsignedInt(cPair.ordinal() ^ 0x80_000_000);\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/NewCodeDefinitionStorage.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Optional;\nimport org.sonarsource.sonarlint.core.commons.NewCodeDefinition;\nimport org.sonarsource.sonarlint.core.commons.NewCodeMode;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.serverconnection.FileUtils;\nimport org.sonarsource.sonarlint.core.serverconnection.proto.Sonarlint;\n\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ProtobufFileUtil.writeToFile;\n\npublic class NewCodeDefinitionStorage {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  public static final String NEW_CODE_DEFINITION_PB = \"new_code_definition.pb\";\n\n  private final Path storageFilePath;\n  private final RWLock rwLock = new RWLock();\n\n  public NewCodeDefinitionStorage(Path rootPath) {\n    this.storageFilePath = rootPath.resolve(NEW_CODE_DEFINITION_PB);\n  }\n\n  public void store(NewCodeDefinition newCodeDefinition) {\n    FileUtils.mkdirs(storageFilePath.getParent());\n    var newCodeDefinitionToStore = adapt(newCodeDefinition);\n    LOG.debug(\"Storing new code definition in {}\", storageFilePath);\n    rwLock.write(() -> writeToFile(newCodeDefinitionToStore, storageFilePath));\n  }\n\n  public Optional<NewCodeDefinition> read() {\n    return rwLock.read(() -> Files.exists(storageFilePath) ?\n      Optional.of(adapt(ProtobufFileUtil.readFile(storageFilePath, Sonarlint.NewCodeDefinition.parser())))\n      : Optional.empty());\n  }\n\n  static Sonarlint.NewCodeDefinition adapt(NewCodeDefinition newCodeDefinition) {\n    var builder = Sonarlint.NewCodeDefinition.newBuilder()\n      .setMode(Sonarlint.NewCodeDefinitionMode.valueOf(newCodeDefinition.getMode().name()));\n    if (newCodeDefinition.getMode() == NewCodeMode.NUMBER_OF_DAYS) {\n      var newCodeNumberOfDays = (NewCodeDefinition.NewCodeNumberOfDaysWithDate) newCodeDefinition;\n      builder.setDays(newCodeNumberOfDays.getDays());\n    }\n    if (newCodeDefinition.getMode() != NewCodeMode.REFERENCE_BRANCH) {\n      var newCodeDefinitionWithDate = (NewCodeDefinition.NewCodeDefinitionWithDate) newCodeDefinition;\n      builder.setThresholdDate(newCodeDefinitionWithDate.getThresholdDate().toEpochMilli());\n    } else {\n      var newCodeReferenceBranch = (NewCodeDefinition.NewCodeReferenceBranch) newCodeDefinition;\n      builder.setReferenceBranch(newCodeReferenceBranch.getBranchName());\n    }\n    if (newCodeDefinition.getMode() == NewCodeMode.PREVIOUS_VERSION) {\n      var newCodePreviousVersion = (NewCodeDefinition.NewCodePreviousVersion) newCodeDefinition;\n      var version = newCodePreviousVersion.getVersion();\n      if (version != null) {\n        builder.setVersion(version);\n      }\n    }\n    return builder.build();\n  }\n\n  static NewCodeDefinition adapt(Sonarlint.NewCodeDefinition newCodeDefinition) {\n    var thresholdDate = newCodeDefinition.getThresholdDate();\n    var mode = newCodeDefinition.getMode();\n    switch (mode) {\n      case NUMBER_OF_DAYS:\n        return NewCodeDefinition.withNumberOfDaysWithDate(newCodeDefinition.getDays(), thresholdDate);\n      case PREVIOUS_VERSION:\n        var version = newCodeDefinition.hasVersion() ? newCodeDefinition.getVersion() : null;\n        return NewCodeDefinition.withPreviousVersion(thresholdDate, version);\n      case REFERENCE_BRANCH:\n        return NewCodeDefinition.withReferenceBranch(newCodeDefinition.getReferenceBranch());\n      case SPECIFIC_ANALYSIS:\n        return NewCodeDefinition.withSpecificAnalysis(thresholdDate);\n      default:\n        throw new IllegalArgumentException(\"Unsupported mode: \" + mode);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/OrganizationStorage.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Optional;\nimport java.util.UUID;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.serverconnection.FileUtils;\nimport org.sonarsource.sonarlint.core.serverconnection.Organization;\nimport org.sonarsource.sonarlint.core.serverconnection.proto.Sonarlint;\n\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ProtobufFileUtil.writeToFile;\n\npublic class OrganizationStorage {\n  public static final String ORGANIZATION_PB = \"organization.pb\";\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final Path storageFilePath;\n  private final RWLock rwLock = new RWLock();\n\n  public OrganizationStorage(Path rootPath) {\n    this.storageFilePath = rootPath.resolve(ORGANIZATION_PB);\n  }\n\n  public void store(Organization organization) {\n    FileUtils.mkdirs(storageFilePath.getParent());\n    var settingsToStore = adapt(organization);\n    LOG.debug(\"Storing organization settings in {}\", storageFilePath);\n    rwLock.write(() -> writeToFile(settingsToStore, storageFilePath));\n    LOG.debug(\"Stored organization settings\");\n  }\n\n  public Optional<Organization> read() {\n    return rwLock.read(() -> Files.exists(storageFilePath) ? Optional.of(adapt(ProtobufFileUtil.readFile(storageFilePath, Sonarlint.Organization.parser())))\n      : Optional.empty());\n  }\n\n  private static Sonarlint.Organization adapt(Organization organization) {\n    return Sonarlint.Organization.newBuilder().setId(organization.id()).setUuidV4(organization.uuidV4().toString()).build();\n  }\n\n  private static Organization adapt(Sonarlint.Organization organization) {\n    return new Organization(organization.getId(), UUID.fromString(organization.getUuidV4()));\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/PluginsStorage.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport org.apache.commons.io.FileUtils;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.serverapi.plugins.ServerPlugin;\nimport org.sonarsource.sonarlint.core.serverconnection.StoredPlugin;\nimport org.sonarsource.sonarlint.core.serverconnection.proto.Sonarlint;\n\npublic class PluginsStorage {\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  public static final String PLUGIN_REFERENCES_PB = \"plugin_references.pb\";\n\n  private final Path rootPath;\n  private final Path pluginReferencesFilePath;\n  private final RWLock rwLock = new RWLock();\n\n  public PluginsStorage(Path connectionStorageRoot) {\n    this.rootPath = connectionStorageRoot.resolve(\"plugins\");\n    this.pluginReferencesFilePath = rootPath.resolve(PLUGIN_REFERENCES_PB);\n  }\n\n  public boolean isValid() {\n    if (!Files.exists(pluginReferencesFilePath)) {\n      return false;\n    }\n    try {\n      rwLock.read(() -> ProtobufFileUtil.readFile(pluginReferencesFilePath, Sonarlint.PluginReferences.parser()));\n      return true;\n    } catch (Exception e) {\n      LOG.debug(\"Could not load plugins storage\", e);\n      return false;\n    }\n  }\n\n  public void store(ServerPlugin plugin, InputStream pluginBinary) {\n    rwLock.write(() -> {\n      try {\n        var pluginPath = rootPath.resolve(plugin.getFilename());\n        FileUtils.copyInputStreamToFile(pluginBinary, pluginPath.toFile());\n        LOG.debug(\"Storing plugin to {} with file size {} bytes\", pluginPath.toAbsolutePath(), Files.size(pluginPath));\n        var pluginFile = pluginPath.toFile();\n        LOG.debug(\"Plugin file created: {}\", pluginFile.exists());\n        LOG.debug(\"Written plugin file size {} bytes\", Files.size(pluginPath));\n        var reference = adapt(plugin);\n        var references = Files.exists(pluginReferencesFilePath)\n          ? ProtobufFileUtil.readFile(pluginReferencesFilePath, Sonarlint.PluginReferences.parser())\n          : Sonarlint.PluginReferences.newBuilder().build();\n        var currentReferences = Sonarlint.PluginReferences.newBuilder(references);\n        currentReferences.putPluginsByKey(plugin.getKey(), reference);\n        ProtobufFileUtil.writeToFile(currentReferences.build(), pluginReferencesFilePath);\n        LOG.debug(\"Plugin file {} created: {}\", pluginReferencesFilePath, pluginReferencesFilePath.toFile().exists());\n      } catch (IOException e) {\n        // XXX should we stop the whole sync ? just continue and log ?\n        throw new StorageException(\"Cannot save plugin \" + plugin.getFilename() + \" in \" + rootPath, e);\n      }\n    });\n  }\n\n  public List<StoredPlugin> getStoredPlugins() {\n    return rwLock.read(() -> Files.exists(pluginReferencesFilePath) ? ProtobufFileUtil.readFile(pluginReferencesFilePath, Sonarlint.PluginReferences.parser())\n      : Sonarlint.PluginReferences.newBuilder().build()).getPluginsByKeyMap().values().stream().map(this::adapt).toList();\n  }\n\n  public Map<String, StoredPlugin> getStoredPluginsByKey() {\n    return byKey(getStoredPlugins());\n  }\n\n  public Map<String, Path> getStoredPluginPathsByKey() {\n    return getStoredPluginsByKey().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, v -> v.getValue().getJarPath()));\n  }\n\n  private static Map<String, StoredPlugin> byKey(List<StoredPlugin> plugins) {\n    return plugins.stream().collect(Collectors.toMap(StoredPlugin::getKey, Function.identity()));\n  }\n\n  private static Sonarlint.PluginReferences.PluginReference adapt(ServerPlugin plugin) {\n    return Sonarlint.PluginReferences.PluginReference.newBuilder()\n      .setKey(plugin.getKey())\n      .setHash(plugin.getHash())\n      .setFilename(plugin.getFilename())\n      .build();\n  }\n\n  private StoredPlugin adapt(Sonarlint.PluginReferences.PluginReference plugin) {\n    return new StoredPlugin(\n      plugin.getKey(),\n      plugin.getHash(),\n      rootPath.resolve(plugin.getFilename()));\n  }\n\n  public void cleanUpUnknownPlugins(List<ServerPlugin> serverPluginsExpectedInStorage) {\n    var expectedPluginPaths = serverPluginsExpectedInStorage.stream().map(plugin -> rootPath.resolve(plugin.getFilename())).collect(Collectors.toSet());\n    var pluginsByKey = serverPluginsExpectedInStorage.stream().collect(Collectors.toMap(ServerPlugin::getKey, PluginsStorage::adapt));\n    var currentReferences = Sonarlint.PluginReferences.newBuilder();\n    currentReferences.putAllPluginsByKey(pluginsByKey);\n    rwLock.write(() -> {\n      var unknownFiles = getUnknownFiles(expectedPluginPaths);\n      deleteFiles(unknownFiles);\n      createPluginDirectory();\n      ProtobufFileUtil.writeToFile(currentReferences.build(), pluginReferencesFilePath);\n    });\n  }\n\n  private void deleteFiles(List<File> unknownFiles) {\n    if (!unknownFiles.isEmpty()) {\n      LOG.debug(\"Cleaning up the plugins storage {}, removing {} unknown files:\", rootPath, unknownFiles.size());\n      unknownFiles.forEach(f -> LOG.debug(f.getAbsolutePath()));\n      unknownFiles.forEach(FileUtils::deleteQuietly);\n    }\n  }\n\n  private List<File> getUnknownFiles(Set<Path> knownPluginsPaths) {\n    if (!Files.exists(rootPath)) {\n      return Collections.emptyList();\n    }\n    LOG.debug(\"Known plugin paths: {}\", knownPluginsPaths);\n    try (Stream<Path> pathsInDir = Files.list(rootPath)) {\n      var paths = pathsInDir.toList();\n      LOG.debug(\"Paths in dir: {}\", paths);\n      var unknownFiles = paths.stream()\n        .filter(p -> !p.equals(pluginReferencesFilePath))\n        .filter(p -> !knownPluginsPaths.contains(p))\n        .map(Path::toFile)\n        .toList();\n      LOG.debug(\"Unknown files: {}\", unknownFiles);\n      return unknownFiles;\n    } catch (Exception e) {\n      LOG.error(\"Cannot list files in '{}'\", rootPath, e);\n      return Collections.emptyList();\n    }\n  }\n\n  public void storeNoPlugins() {\n    if (!Files.exists(pluginReferencesFilePath)) {\n      createPluginDirectory();\n      rwLock.write(() -> {\n        var references = Sonarlint.PluginReferences.newBuilder().build();\n        ProtobufFileUtil.writeToFile(references, pluginReferencesFilePath);\n      });\n    }\n  }\n\n  private void createPluginDirectory() {\n    try {\n      Files.createDirectories(pluginReferencesFilePath.getParent());\n    } catch (IOException e) {\n      throw new StorageException(String.format(\"Cannot create plugin file directory: %s\", rootPath), e);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/ProjectServerIssueStore.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.function.Consumer;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.serverapi.hotspot.ServerHotspot;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerDependencyRisk;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerFinding;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerTaintIssue;\n\npublic interface ProjectServerIssueStore {\n  boolean wasEverUpdated();\n\n  /**\n   * Store issues for a branch by replacing existing ones.\n   */\n  void replaceAllIssuesOfBranch(String branchName, List<ServerIssue<?>> issues, Set<SonarLanguage> enabledLanguages);\n\n  void replaceAllHotspotsOfBranch(String branchName, Collection<ServerHotspot> serverHotspots, Set<SonarLanguage> enabledLanguages);\n\n  void replaceAllHotspotsOfFile(String branchName, Path serverFilePath, Collection<ServerHotspot> serverHotspots);\n\n  /**\n   * Update the resolution status of a hotspot by its key.\n   * @return true if the hotspot with the given key was found, else false\n   */\n  boolean changeHotspotStatus(String hotspotKey, HotspotReviewStatus newStatus);\n\n  /**\n   * Store issues for a single file by replacing existing ones and moving issues if necessary.\n   */\n  void replaceAllIssuesOfFile(String branchName, Path serverFilePath, List<ServerIssue<?>> issues);\n\n  /**\n   * Merge provided issues to stored ones for the given project:\n   *  - new issues are added\n   *  - existing issues are updated\n   *  - closed issues are removed from the store\n   */\n  void mergeIssues(String branchName, List<ServerIssue<?>> issuesToMerge, Set<String> closedIssueKeysToDelete, Instant syncTimestamp, Set<SonarLanguage> enabledLanguages);\n\n  /**\n   * Merge provided taint issues to stored ones for the given project:\n   *  - new issues are added\n   *  - existing issues are updated\n   *  - closed issues are removed from the store\n   */\n  void mergeTaintIssues(String branchName, List<ServerTaintIssue> issuesToMerge, Set<String> closedIssueKeysToDelete, Instant syncTimestamp, Set<SonarLanguage> enabledLanguages);\n\n  /**\n   * Merge provided hotspots to stored ones for the given project:\n   *  - new hotspots are added\n   *  - existing hotspots are updated\n   *  - closed hotspots are removed from the store\n   */\n  void mergeHotspots(String branchName, List<ServerHotspot> hotspotsToMerge, Set<String> closedHotspotKeysToDelete, Instant syncTimestamp, Set<SonarLanguage> enabledLanguages);\n\n  /**\n   * Return the timestamp of the last issue sync for a given branch.\n   * @return empty if the issues of the branch have never been pulled\n   */\n  Optional<Instant> getLastIssueSyncTimestamp(String branchName);\n\n  /**\n   * Return the last enabled languages of the last issue sync for a given branch.\n   *\n   * @return empty if the issues of the branch have never been pulled\n   */\n  Set<SonarLanguage> getLastIssueEnabledLanguages(String branchName);\n\n  /**\n   * Return the last enabled languages of the last taint sync for a given branch.\n   * @return empty if the taints of the branch have never been pulled\n   */\n  Set<SonarLanguage> getLastTaintEnabledLanguages(String branchName);\n\n  /**\n   * Return the last enabled languages of the last hotspot sync for a given branch.\n   * @return empty if the hotspots of the branch have never been pulled\n   */\n  Set<SonarLanguage> getLastHotspotEnabledLanguages(String branchName);\n\n  /**\n   * Return the timestamp of the last taint issue sync for a given branch.\n   * @return empty if the taint issues of the branch have never been pulled\n   */\n  Optional<Instant> getLastTaintSyncTimestamp(String branchName);\n\n  /**\n   * Return the timestamp of the last hotspot sync for a given branch.\n   * @return empty if the hotspots of the branch have never been pulled\n   */\n  Optional<Instant> getLastHotspotSyncTimestamp(String branchName);\n\n  /**\n   * Load issues stored for specified file.\n   *\n   * @param branchName\n   * @param sqFilePath the relative path to the base of project, in SonarQube\n   * @return issues, possibly empty\n   */\n  List<ServerIssue<?>> load(String branchName, Path sqFilePath);\n\n  /**\n   * Store taint issues for a branch.\n   */\n  void replaceAllTaintsOfBranch(String branchName, List<ServerTaintIssue> taintIssues, Set<SonarLanguage> enabledLanguages);\n\n  /**\n   * Load hotspots stored for specified file.\n   *\n   * @param serverFilePath the relative path to the base of project, from the server point of view\n   * @return issues, possibly empty\n   */\n  Collection<ServerHotspot> loadHotspots(String branchName, Path serverFilePath);\n\n  /**\n   * Load all taint issues stored for a branch.\n   *\n   *\n   * @param branchName\n   * @return issues, possibly empty\n   */\n  List<ServerTaintIssue> loadTaint(String branchName);\n\n  /**\n   * @param issueKey\n   * @param issueUpdater\n   * @return true if the issue with the corresponding key exists in the store and has been updated\n   */\n  boolean updateIssue(String issueKey, Consumer<ServerIssue<?>> issueUpdater);\n\n  /**\n   * Retrieve an issue from the store\n   * @param issueKey\n   * @return the server issue if found, null otherwise\n   */\n  ServerIssue<?> getIssue(String issueKey);\n\n  /**\n   * Retrieve a hotspot from the store\n   * @param hotspotKey\n   * @return the hotspot if found, null otherwise\n   */\n  ServerHotspot getHotspot(String hotspotKey);\n\n  /**\n   * Set the resolution status of an Issue (by its key).\n   */\n  Optional<ServerFinding> updateIssueResolutionStatus(String issueKey, boolean isTaintIssue, boolean isResolved);\n\n  /**\n   * @return the updated issue if found, else empty\n   */\n  Optional<ServerTaintIssue> updateTaintIssueBySonarServerKey(String sonarServerKey, Consumer<ServerTaintIssue> taintIssueUpdater);\n\n  void insert(String branchName, ServerTaintIssue taintIssue);\n\n  void insert(String branchName, ServerHotspot hotspot);\n\n  /**\n   * @return the id of the deleted taint issue if it was found\n   */\n  Optional<UUID> deleteTaintIssueBySonarServerKey(String sonarServerKeyToDelete);\n\n  void deleteHotspot(String hotspotKey);\n\n  void updateHotspot(String hotspotKey, Consumer<ServerHotspot> hotspotUpdater);\n\n  boolean containsIssue(String issueKey);\n\n  /**\n   * Store dependency risks for a branch by replacing existing ones.\n   */\n  void replaceAllDependencyRisksOfBranch(String branchName, List<ServerDependencyRisk> serverDependencyRisks);\n\n  /**\n   * Load all dependency risks stored for a branch.\n   */\n  List<ServerDependencyRisk> loadDependencyRisks(String branchName);\n\n  void updateDependencyRiskStatus(UUID key, ServerDependencyRisk.Status newStatus, List<ServerDependencyRisk.Transition> transitions);\n\n  void removeFindingsForConnection(String connectionId);\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/ProjectStoragePaths.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.nio.charset.StandardCharsets;\nimport org.apache.commons.codec.binary.Hex;\n\npublic class ProjectStoragePaths {\n\n  private static final int MAX_FOLDER_NAME_SIZE = 255;\n\n  /**\n   * Encodes a string to be used as a valid file or folder name.\n   * It should work in all OS and different names should never collide.\n   * See SLCORE-148 and SLCORE-228.\n   */\n  public static String encodeForFs(String name) {\n    var encoded = Hex.encodeHexString(name.getBytes(StandardCharsets.UTF_8));\n    if (encoded.length() > MAX_FOLDER_NAME_SIZE) {\n      // Most FS will not support a folder name greater than 255\n      var md5 = org.apache.commons.codec.digest.DigestUtils.md5Hex(name);\n      return encoded.substring(0, MAX_FOLDER_NAME_SIZE - md5.length()) + md5;\n    }\n    return encoded;\n  }\n\n  private ProjectStoragePaths() {\n    // utility class\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/ProtobufFileUtil.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport com.google.protobuf.Message;\nimport com.google.protobuf.Parser;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\npublic class ProtobufFileUtil {\n  private ProtobufFileUtil() {\n    // only static stuff\n  }\n\n  public static <T extends Message> T readFile(Path file, Parser<T> parser) {\n    try (var input = Files.newInputStream(file)) {\n      return parser.parseFrom(input);\n    } catch (IOException e) {\n      throw new StorageException(\"Failed to read file: \" + file, e);\n    }\n  }\n\n  public static void writeToFile(Message message, Path toFile) {\n    try (var out = Files.newOutputStream(toFile)) {\n      message.writeTo(out);\n      out.flush();\n    } catch (IOException e) {\n      throw new StorageException(\"Unable to write protocol buffer data to file \" + toFile, e);\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/RWLock.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.util.concurrent.locks.ReadWriteLock;\nimport java.util.concurrent.locks.ReentrantReadWriteLock;\nimport java.util.function.Supplier;\n\npublic class RWLock {\n  private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();\n\n  public <T> T read(Supplier<T> supplier) {\n    readWriteLock.readLock().lock();\n    try {\n      return supplier.get();\n    } finally {\n      readWriteLock.readLock().unlock();\n    }\n  }\n\n  public void write(Runnable runnable) {\n    readWriteLock.writeLock().lock();\n    try {\n      runnable.run();\n    } finally {\n      readWriteLock.writeLock().unlock();\n    }\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/ServerFindingRepository.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.time.LocalDateTime;\nimport java.time.ZoneOffset;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\nimport org.jooq.Configuration;\nimport org.jooq.DSLContext;\nimport org.jooq.Record1;\nimport org.jooq.TableField;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.storage.model.Tables;\nimport org.sonarsource.sonarlint.core.commons.storage.model.tables.records.ServerBranchesRecord;\nimport org.sonarsource.sonarlint.core.serverapi.hotspot.ServerHotspot;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerDependencyRisk;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerFinding;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerTaintIssue;\n\nimport static org.sonarsource.sonarlint.core.commons.storage.model.Tables.SERVER_BRANCHES;\nimport static org.sonarsource.sonarlint.core.commons.storage.model.Tables.SERVER_DEPENDENCY_RISKS;\nimport static org.sonarsource.sonarlint.core.commons.storage.model.Tables.SERVER_FINDINGS;\n\npublic class ServerFindingRepository implements ProjectServerIssueStore {\n\n  private final EntityMapper mapper = new EntityMapper();\n  private final DSLContext database;\n  private final String connectionId;\n  private final String sonarProjectKey;\n\n  public ServerFindingRepository(DSLContext database, String connectionId, String sonarProjectKey) {\n    this.database = database;\n    this.connectionId = connectionId;\n    this.sonarProjectKey = sonarProjectKey;\n  }\n\n  @Override\n  public boolean wasEverUpdated() {\n    return database.fetchExists(\n      database.selectFrom(SERVER_BRANCHES)\n        .where(SERVER_BRANCHES.CONNECTION_ID.eq(connectionId)\n          .and(SERVER_BRANCHES.SONAR_PROJECT_KEY.eq(sonarProjectKey))\n          .and(SERVER_BRANCHES.LAST_ISSUE_SYNC_TS.isNotNull().or(SERVER_BRANCHES.LAST_HOTSPOT_SYNC_TS.isNotNull()).or(SERVER_BRANCHES.LAST_TAINT_SYNC_TS.isNotNull()))));\n  }\n\n  @Override\n  public void replaceAllIssuesOfBranch(String branchName, List<ServerIssue<?>> issues, Set<SonarLanguage> enabledLanguages) {\n    database.transaction(trx -> {\n      trx.dsl().deleteFrom(SERVER_FINDINGS)\n        .where(SERVER_FINDINGS.BRANCH_NAME.eq(branchName)\n          .and(SERVER_FINDINGS.FINDING_TYPE.eq(ServerFindingType.ISSUE.name()))\n          .and(SERVER_FINDINGS.CONNECTION_ID.eq(connectionId))\n          .and(SERVER_FINDINGS.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n        .execute();\n      batchMergeIssues(branchName, connectionId, sonarProjectKey, trx, issues);\n    });\n    upsertBranchMetadata(branchName,\n      SERVER_BRANCHES.LAST_ISSUE_SYNC_TS,\n      SERVER_BRANCHES.LAST_ISSUE_ENABLED_LANGS,\n      Instant.now(), enabledLanguages);\n  }\n\n  @Override\n  public void replaceAllHotspotsOfBranch(String branchName, Collection<ServerHotspot> serverHotspots, Set<SonarLanguage> enabledLanguages) {\n    database.transaction(trx -> {\n      trx.dsl().deleteFrom(SERVER_FINDINGS)\n        .where(SERVER_FINDINGS.BRANCH_NAME.eq(branchName)\n          .and(SERVER_FINDINGS.FINDING_TYPE.eq(ServerFindingType.HOTSPOT.name()))\n          .and(SERVER_FINDINGS.CONNECTION_ID.eq(connectionId))\n          .and(SERVER_FINDINGS.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n        .execute();\n      batchMergeHotspots(branchName, connectionId, sonarProjectKey, trx, serverHotspots);\n      upsertBranchMetadata(branchName,\n        SERVER_BRANCHES.LAST_HOTSPOT_SYNC_TS,\n        SERVER_BRANCHES.LAST_HOTSPOT_ENABLED_LANGS,\n        Instant.now(), enabledLanguages);\n    });\n  }\n\n  @Override\n  public void replaceAllHotspotsOfFile(String branchName, Path serverFilePath, Collection<ServerHotspot> serverHotspots) {\n    database.transaction(trx -> {\n      trx.dsl().deleteFrom(SERVER_FINDINGS)\n        .where(SERVER_FINDINGS.BRANCH_NAME.eq(branchName)\n          .and(SERVER_FINDINGS.FILE_PATH.eq(serverFilePath.toString()))\n          .and(SERVER_FINDINGS.FINDING_TYPE.eq(ServerFindingType.HOTSPOT.name()))\n          .and(SERVER_FINDINGS.CONNECTION_ID.eq(connectionId))\n          .and(SERVER_FINDINGS.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n        .execute();\n      batchMergeHotspots(branchName, connectionId, sonarProjectKey, trx, serverHotspots);\n    });\n  }\n\n  // we don't consume return value for now, probably should be void\n  @Override\n  public boolean changeHotspotStatus(String hotspotKey, HotspotReviewStatus newStatus) {\n    database.update(SERVER_FINDINGS)\n      .set(SERVER_FINDINGS.HOTSPOT_REVIEW_STATUS, newStatus.name())\n      .where(SERVER_FINDINGS.SERVER_KEY.eq(hotspotKey)\n        .and(SERVER_FINDINGS.FINDING_TYPE.eq(ServerFindingType.HOTSPOT.name()))\n        .and(SERVER_FINDINGS.CONNECTION_ID.eq(connectionId))\n        .and(SERVER_FINDINGS.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n      .execute();\n    return true;\n  }\n\n  @Override\n  public void replaceAllIssuesOfFile(String branchName, Path serverFilePath, List<ServerIssue<?>> issues) {\n    database.transaction(trx -> {\n      trx.dsl().deleteFrom(SERVER_FINDINGS)\n        .where(SERVER_FINDINGS.BRANCH_NAME.eq(branchName)\n          .and(SERVER_FINDINGS.FILE_PATH.eq(serverFilePath.toString()))\n          .and(SERVER_FINDINGS.FINDING_TYPE.eq(ServerFindingType.ISSUE.name()))\n          .and(SERVER_FINDINGS.CONNECTION_ID.eq(connectionId))\n          .and(SERVER_FINDINGS.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n        .execute();\n      batchMergeIssues(branchName, connectionId, sonarProjectKey, trx, issues);\n    });\n  }\n\n  @Override\n  public void mergeIssues(String branchName, List<ServerIssue<?>> issuesToMerge, Set<String> closedIssueKeysToDelete, Instant syncTimestamp, Set<SonarLanguage> enabledLanguages) {\n    database.transaction(trx -> {\n      if (!closedIssueKeysToDelete.isEmpty()) {\n        trx.dsl().deleteFrom(SERVER_FINDINGS)\n          .where(SERVER_FINDINGS.BRANCH_NAME.eq(branchName)\n            .and(SERVER_FINDINGS.FINDING_TYPE.eq(ServerFindingType.ISSUE.name()))\n            .and(SERVER_FINDINGS.SERVER_KEY.in(closedIssueKeysToDelete))\n            .and(SERVER_FINDINGS.CONNECTION_ID.eq(connectionId))\n            .and(SERVER_FINDINGS.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n          .execute();\n      }\n      var issueIds = issuesToMerge.stream().map(ServerIssue::getId).collect(Collectors.toSet());\n      trx.dsl().deleteFrom(SERVER_FINDINGS)\n        .where(SERVER_FINDINGS.ID.in(issueIds))\n        .and(SERVER_FINDINGS.CONNECTION_ID.eq(connectionId))\n        .and(SERVER_FINDINGS.SONAR_PROJECT_KEY.eq(sonarProjectKey))\n        .execute();\n      batchMergeIssues(branchName, connectionId, sonarProjectKey, trx, issuesToMerge);\n      upsertBranchMetadata(branchName,\n        SERVER_BRANCHES.LAST_ISSUE_SYNC_TS,\n        SERVER_BRANCHES.LAST_ISSUE_ENABLED_LANGS,\n        syncTimestamp, enabledLanguages);\n    });\n  }\n\n  @Override\n  public void mergeTaintIssues(String branchName, List<ServerTaintIssue> taintsToMerge, Set<String> closedIssueKeysToDelete, Instant syncTimestamp,\n    Set<SonarLanguage> enabledLanguages) {\n    database.transaction(trx -> {\n      if (!closedIssueKeysToDelete.isEmpty()) {\n        trx.dsl().deleteFrom(SERVER_FINDINGS)\n          .where(SERVER_FINDINGS.BRANCH_NAME.eq(branchName)\n            .and(SERVER_FINDINGS.FINDING_TYPE.eq(ServerFindingType.TAINT.name()))\n            .and(SERVER_FINDINGS.SERVER_KEY.in(closedIssueKeysToDelete))\n            .and(SERVER_FINDINGS.CONNECTION_ID.eq(connectionId))\n            .and(SERVER_FINDINGS.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n          .execute();\n      }\n      batchMergeTaints(branchName, connectionId, sonarProjectKey, trx, taintsToMerge);\n      upsertBranchMetadata(branchName,\n        SERVER_BRANCHES.LAST_TAINT_SYNC_TS,\n        SERVER_BRANCHES.LAST_TAINT_ENABLED_LANGS,\n        syncTimestamp, enabledLanguages);\n    });\n  }\n\n  @Override\n  public void mergeHotspots(String branchName, List<ServerHotspot> hotspotsToMerge, Set<String> closedHotspotKeysToDelete, Instant syncTimestamp,\n    Set<SonarLanguage> enabledLanguages) {\n    database.transaction(trx -> {\n      if (!closedHotspotKeysToDelete.isEmpty()) {\n        trx.dsl().deleteFrom(SERVER_FINDINGS)\n          .where(SERVER_FINDINGS.BRANCH_NAME.eq(branchName)\n            .and(SERVER_FINDINGS.FINDING_TYPE.eq(ServerFindingType.HOTSPOT.name()))\n            .and(SERVER_FINDINGS.SERVER_KEY.in(closedHotspotKeysToDelete))\n            .and(SERVER_FINDINGS.CONNECTION_ID.eq(connectionId))\n            .and(SERVER_FINDINGS.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n          .execute();\n      }\n      batchMergeHotspots(branchName, connectionId, sonarProjectKey, trx, hotspotsToMerge);\n      upsertBranchMetadata(branchName,\n        SERVER_BRANCHES.LAST_HOTSPOT_SYNC_TS,\n        SERVER_BRANCHES.LAST_HOTSPOT_ENABLED_LANGS,\n        syncTimestamp, enabledLanguages);\n    });\n  }\n\n  @Override\n  public Optional<Instant> getLastIssueSyncTimestamp(String branchName) {\n    return database.select(SERVER_BRANCHES.LAST_ISSUE_SYNC_TS)\n      .from(SERVER_BRANCHES)\n      .where(SERVER_BRANCHES.BRANCH_NAME.eq(branchName)\n        .and(SERVER_BRANCHES.CONNECTION_ID.eq(connectionId))\n        .and(SERVER_BRANCHES.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n      .fetchOptional()\n      .map(Record1::value1)\n      .map(ServerFindingRepository::toInstant);\n  }\n\n  @Override\n  public Set<SonarLanguage> getLastIssueEnabledLanguages(String branchName) {\n    return readLanguages(branchName, SERVER_BRANCHES.LAST_ISSUE_ENABLED_LANGS);\n  }\n\n  @Override\n  public Set<SonarLanguage> getLastTaintEnabledLanguages(String branchName) {\n    return readLanguages(branchName, SERVER_BRANCHES.LAST_TAINT_ENABLED_LANGS);\n  }\n\n  @Override\n  public Set<SonarLanguage> getLastHotspotEnabledLanguages(String branchName) {\n    return readLanguages(branchName, SERVER_BRANCHES.LAST_HOTSPOT_ENABLED_LANGS);\n  }\n\n  @Override\n  public Optional<Instant> getLastTaintSyncTimestamp(String branchName) {\n    return database.select(SERVER_BRANCHES.LAST_TAINT_SYNC_TS)\n      .from(SERVER_BRANCHES)\n      .where(SERVER_BRANCHES.BRANCH_NAME.eq(branchName)\n        .and(SERVER_BRANCHES.CONNECTION_ID.eq(connectionId))\n        .and(SERVER_BRANCHES.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n      .fetchOptional()\n      .map(Record1::value1)\n      .map(ServerFindingRepository::toInstant);\n  }\n\n  @Override\n  public Optional<Instant> getLastHotspotSyncTimestamp(String branchName) {\n    return database.select(SERVER_BRANCHES.LAST_HOTSPOT_SYNC_TS)\n      .from(SERVER_BRANCHES)\n      .where(SERVER_BRANCHES.BRANCH_NAME.eq(branchName)\n        .and(SERVER_BRANCHES.CONNECTION_ID.eq(connectionId))\n        .and(SERVER_BRANCHES.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n      .fetchOptional()\n      .map(Record1::value1)\n      .map(ServerFindingRepository::toInstant);\n  }\n\n  private static Instant toInstant(LocalDateTime ldt) {\n    return ldt.toInstant(ZoneOffset.UTC);\n  }\n\n  private void upsertBranchMetadata(String branchName, TableField<ServerBranchesRecord, LocalDateTime> lastSyncField,\n    TableField<ServerBranchesRecord, String[]> enabledLanguagesField, Instant syncTimestamp, Set<SonarLanguage> enabledLanguages) {\n    var lastSyncTime = LocalDateTime.ofInstant(syncTimestamp, ZoneOffset.UTC);\n    var enabledLanguagesAsJson = mapper.serializeLanguages(enabledLanguages);\n\n    database.insertInto(SERVER_BRANCHES, SERVER_BRANCHES.BRANCH_NAME, SERVER_BRANCHES.CONNECTION_ID, SERVER_BRANCHES.SONAR_PROJECT_KEY, lastSyncField, enabledLanguagesField)\n      .values(branchName, connectionId, sonarProjectKey, lastSyncTime, enabledLanguagesAsJson)\n      .onDuplicateKeyUpdate()\n      .set(lastSyncField, lastSyncTime)\n      .set(enabledLanguagesField, enabledLanguagesAsJson)\n      .execute();\n  }\n\n  private Set<SonarLanguage> readLanguages(String branchName, TableField<ServerBranchesRecord, String[]> langsField) {\n    var table = SERVER_BRANCHES;\n    var rec = database.select(langsField)\n      .from(table)\n      .where(table.BRANCH_NAME.eq(branchName)\n        .and(table.CONNECTION_ID.eq(connectionId))\n        .and(table.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n      .fetchOne();\n    if (rec == null) {\n      return Set.of();\n    }\n    return mapper.deserializeLanguages(rec.value1());\n  }\n\n  @Override\n  public List<ServerIssue<?>> load(String branchName, Path sqFilePath) {\n    return database.selectFrom(SERVER_FINDINGS)\n      .where(SERVER_FINDINGS.BRANCH_NAME.eq(branchName)\n        .and(SERVER_FINDINGS.FILE_PATH.eq(sqFilePath.toString()))\n        .and(SERVER_FINDINGS.FINDING_TYPE.eq(ServerFindingType.ISSUE.name()))\n        .and(SERVER_FINDINGS.CONNECTION_ID.eq(connectionId))\n        .and(SERVER_FINDINGS.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n      .fetch().stream()\n      .<ServerIssue<?>>map(mapper::adaptIssue)\n      .toList();\n  }\n\n  @Override\n  public void replaceAllTaintsOfBranch(String branchName, List<ServerTaintIssue> taintIssues, Set<SonarLanguage> enabledLanguages) {\n    database.transaction(trx -> {\n      trx.dsl().deleteFrom(SERVER_FINDINGS)\n        .where(SERVER_FINDINGS.BRANCH_NAME.eq(branchName)\n          .and(SERVER_FINDINGS.FINDING_TYPE.eq(ServerFindingType.TAINT.name()))\n          .and(SERVER_FINDINGS.CONNECTION_ID.eq(connectionId))\n          .and(SERVER_FINDINGS.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n        .execute();\n      batchMergeTaints(branchName, connectionId, sonarProjectKey, trx, taintIssues);\n      upsertBranchMetadata(branchName,\n        SERVER_BRANCHES.LAST_TAINT_SYNC_TS,\n        SERVER_BRANCHES.LAST_TAINT_ENABLED_LANGS,\n        Instant.now(), enabledLanguages);\n    });\n  }\n\n  @Override\n  public Collection<ServerHotspot> loadHotspots(String branchName, Path serverFilePath) {\n    return database.selectFrom(SERVER_FINDINGS)\n      .where(SERVER_FINDINGS.BRANCH_NAME.eq(branchName)\n        .and(SERVER_FINDINGS.FILE_PATH.eq(serverFilePath.toString()))\n        .and(SERVER_FINDINGS.FINDING_TYPE.eq(ServerFindingType.HOTSPOT.name()))\n        .and(SERVER_FINDINGS.CONNECTION_ID.eq(connectionId))\n        .and(SERVER_FINDINGS.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n      .fetch().stream()\n      .map(mapper::adaptHotspot)\n      .toList();\n  }\n\n  @Override\n  public List<ServerTaintIssue> loadTaint(String branchName) {\n    return database.selectFrom(SERVER_FINDINGS)\n      .where(SERVER_FINDINGS.BRANCH_NAME.eq(branchName)\n        .and(SERVER_FINDINGS.FINDING_TYPE.eq(ServerFindingType.TAINT.name()))\n        .and(SERVER_FINDINGS.CONNECTION_ID.eq(connectionId))\n        .and(SERVER_FINDINGS.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n      .fetch().stream()\n      .map(mapper::adaptTaint)\n      .toList();\n  }\n\n  @Override\n  public boolean updateIssue(String issueKey, Consumer<ServerIssue<?>> issueUpdater) {\n    var rec = database.selectFrom(SERVER_FINDINGS)\n      .where(SERVER_FINDINGS.SERVER_KEY.eq(issueKey)\n        .and(SERVER_FINDINGS.FINDING_TYPE.eq(ServerFindingType.ISSUE.name()))\n        .and(SERVER_FINDINGS.CONNECTION_ID.eq(connectionId))\n        .and(SERVER_FINDINGS.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n      .fetchOne();\n    if (rec == null) {\n      return false;\n    }\n    var current = mapper.adaptIssue(rec);\n    issueUpdater.accept(current);\n    database.update(SERVER_FINDINGS)\n      .set(SERVER_FINDINGS.RESOLVED, current.isResolved())\n      .set(SERVER_FINDINGS.USER_SEVERITY, current.getUserSeverity() != null ? current.getUserSeverity().name() : null)\n      .set(SERVER_FINDINGS.RULE_TYPE, current.getType() != null ? current.getType().name() : null)\n      .set(SERVER_FINDINGS.IMPACTS, mapper.serializeImpacts(current.getImpacts()))\n      .where(SERVER_FINDINGS.SERVER_KEY.eq(issueKey)\n        .and(SERVER_FINDINGS.FINDING_TYPE.eq(ServerFindingType.ISSUE.name()))\n        .and(SERVER_FINDINGS.CONNECTION_ID.eq(connectionId))\n        .and(SERVER_FINDINGS.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n      .execute();\n    return true;\n  }\n\n  @Override\n  public ServerIssue<?> getIssue(String issueKey) {\n    var rec = database.selectFrom(SERVER_FINDINGS)\n      .where(SERVER_FINDINGS.SERVER_KEY.eq(issueKey)\n        .and(SERVER_FINDINGS.FINDING_TYPE.eq(ServerFindingType.ISSUE.name()))\n        .and(SERVER_FINDINGS.CONNECTION_ID.eq(connectionId))\n        .and(SERVER_FINDINGS.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n      .fetchOne();\n    return rec != null ? mapper.adaptIssue(rec) : null;\n  }\n\n  @Override\n  public ServerHotspot getHotspot(String hotspotKey) {\n    var rec = database.selectFrom(SERVER_FINDINGS)\n      .where(SERVER_FINDINGS.SERVER_KEY.eq(hotspotKey)\n        .and(SERVER_FINDINGS.FINDING_TYPE.eq(ServerFindingType.HOTSPOT.name()))\n        .and(SERVER_FINDINGS.CONNECTION_ID.eq(connectionId))\n        .and(SERVER_FINDINGS.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n      .fetchOne();\n    return rec != null ? mapper.adaptHotspot(rec) : null;\n  }\n\n  @Override\n  public Optional<ServerFinding> updateIssueResolutionStatus(String issueKey, boolean isTaintIssue, boolean isResolved) {\n    var type = isTaintIssue ? ServerFindingType.TAINT.name() : ServerFindingType.ISSUE.name();\n    database.update(SERVER_FINDINGS)\n      .set(SERVER_FINDINGS.RESOLVED, isResolved)\n      .where(SERVER_FINDINGS.SERVER_KEY.eq(issueKey)\n        .and(SERVER_FINDINGS.FINDING_TYPE.eq(type)))\n      .execute();\n    var rec = database.selectFrom(SERVER_FINDINGS)\n      .where(SERVER_FINDINGS.SERVER_KEY.eq(issueKey)\n        .and(SERVER_FINDINGS.FINDING_TYPE.eq(type)))\n      .fetchOne();\n    if (rec == null) {\n      return Optional.empty();\n    }\n    return Optional.of(isTaintIssue ? mapper.adaptTaint(rec) : mapper.adaptIssue(rec));\n  }\n\n  @Override\n  public Optional<ServerTaintIssue> updateTaintIssueBySonarServerKey(String sonarServerKey, Consumer<ServerTaintIssue> taintIssueUpdater) {\n    var rec = database.selectFrom(SERVER_FINDINGS)\n      .where(SERVER_FINDINGS.SERVER_KEY.eq(sonarServerKey)\n        .and(SERVER_FINDINGS.FINDING_TYPE.eq(ServerFindingType.TAINT.name())))\n      .fetchOne();\n    if (rec == null) {\n      return Optional.empty();\n    }\n    var current = mapper.adaptTaint(rec);\n    taintIssueUpdater.accept(current);\n    database.update(SERVER_FINDINGS)\n      .set(SERVER_FINDINGS.USER_SEVERITY, current.getSeverity().name())\n      .set(SERVER_FINDINGS.RULE_TYPE, current.getType().name())\n      .set(SERVER_FINDINGS.RESOLVED, current.isResolved())\n      .set(SERVER_FINDINGS.IMPACTS, mapper.serializeImpacts(current.getImpacts()))\n      .where(SERVER_FINDINGS.SERVER_KEY.eq(sonarServerKey))\n      .execute();\n    return Optional.of(current);\n  }\n\n  @Override\n  public void insert(String branchName, ServerTaintIssue taintIssue) {\n    database.transaction(trx -> batchMergeTaints(branchName, connectionId, sonarProjectKey, trx, List.of(taintIssue)));\n  }\n\n  @Override\n  public void insert(String branchName, ServerHotspot hotspot) {\n    database.transaction(trx -> batchMergeHotspots(branchName, connectionId, sonarProjectKey, trx, List.of(hotspot)));\n  }\n\n  @Override\n  public Optional<UUID> deleteTaintIssueBySonarServerKey(String sonarServerKeyToDelete) {\n    var rec = database.select(SERVER_FINDINGS.ID)\n      .from(SERVER_FINDINGS)\n      .where(SERVER_FINDINGS.SERVER_KEY.eq(sonarServerKeyToDelete)\n        .and(SERVER_FINDINGS.FINDING_TYPE.eq(ServerFindingType.TAINT.name())))\n      .fetchOne();\n    if (rec == null) {\n      return Optional.empty();\n    }\n    var idStr = rec.get(0, UUID.class);\n    database.deleteFrom(SERVER_FINDINGS)\n      .where(SERVER_FINDINGS.ID.eq(idStr))\n      .execute();\n    return Optional.of(idStr);\n  }\n\n  @Override\n  public void deleteHotspot(String hotspotKey) {\n    database.deleteFrom(SERVER_FINDINGS)\n      .where(SERVER_FINDINGS.SERVER_KEY.eq(hotspotKey)\n        .and(SERVER_FINDINGS.FINDING_TYPE.eq(ServerFindingType.HOTSPOT.name())))\n      .execute();\n  }\n\n  @Override\n  public void updateHotspot(String hotspotKey, Consumer<ServerHotspot> hotspotUpdater) {\n    var rec = database.selectFrom(SERVER_FINDINGS)\n      .where(SERVER_FINDINGS.SERVER_KEY.eq(hotspotKey)\n        .and(SERVER_FINDINGS.FINDING_TYPE.eq(ServerFindingType.HOTSPOT.name())))\n      .fetchOne();\n    if (rec == null) {\n      return;\n    }\n    var current = mapper.adaptHotspot(rec);\n    hotspotUpdater.accept(current);\n    database.update(SERVER_FINDINGS)\n      .set(SERVER_FINDINGS.HOTSPOT_REVIEW_STATUS, current.getStatus().name())\n      .set(SERVER_FINDINGS.ASSIGNEE, current.getAssignee())\n      .where(SERVER_FINDINGS.SERVER_KEY.eq(hotspotKey)\n        .and(SERVER_FINDINGS.FINDING_TYPE.eq(ServerFindingType.HOTSPOT.name())))\n      .execute();\n  }\n\n  @Override\n  public boolean containsIssue(String issueKey) {\n    var cnt = database.selectCount().from(SERVER_FINDINGS)\n      .where(SERVER_FINDINGS.SERVER_KEY.eq(issueKey)\n        .and(SERVER_FINDINGS.FINDING_TYPE.in(ServerFindingType.ISSUE.name(), ServerFindingType.TAINT.name())))\n      .fetchOne();\n    return cnt != null && cnt.value1() > 0;\n  }\n\n  @Override\n  public void replaceAllDependencyRisksOfBranch(String branchName, List<ServerDependencyRisk> serverDependencyRisks) {\n    var table = SERVER_DEPENDENCY_RISKS;\n\n    database.transaction(trx -> {\n      trx.dsl().deleteFrom(table)\n        .where(table.BRANCH_NAME.eq(branchName)\n          .and(table.CONNECTION_ID.eq(connectionId))\n          .and(table.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n        .execute();\n      batchMergeDependencyRisks(branchName, connectionId, sonarProjectKey, trx, serverDependencyRisks);\n    });\n  }\n\n  @Override\n  public List<ServerDependencyRisk> loadDependencyRisks(String branchName) {\n    var table = SERVER_DEPENDENCY_RISKS;\n\n    return database.select(table.ID, table.TYPE, table.SEVERITY, table.SOFTWARE_QUALITY, table.STATUS,\n      table.PACKAGE_NAME, table.PACKAGE_VERSION, table.VULNERABILITY_ID, table.CVSS_SCORE, table.TRANSITIONS)\n      .from(table)\n      .where(table.BRANCH_NAME.eq(branchName)\n        .and(table.CONNECTION_ID.eq(connectionId))\n        .and(table.SONAR_PROJECT_KEY.eq(sonarProjectKey)))\n      .fetch()\n      .stream()\n      .map(rec -> {\n        var id = rec.get(table.ID);\n        var type = ServerDependencyRisk.Type.valueOf(rec.get(table.TYPE));\n        var severity = ServerDependencyRisk.Severity.valueOf(rec.get(table.SEVERITY));\n        var quality = ServerDependencyRisk.SoftwareQuality.valueOf(rec.get(table.SOFTWARE_QUALITY));\n        var status = ServerDependencyRisk.Status.valueOf(rec.get(table.STATUS));\n        var pkg = rec.get(table.PACKAGE_NAME);\n        var ver = rec.get(table.PACKAGE_VERSION);\n        var vuln = rec.get(table.VULNERABILITY_ID);\n        var cvss = rec.get(table.CVSS_SCORE);\n        var transitions = mapper.deserializeTransitions(rec.get(table.TRANSITIONS));\n        return new ServerDependencyRisk(id, type, severity, quality, status, pkg, ver, vuln, cvss, transitions);\n      })\n      .toList();\n  }\n\n  @Override\n  public void updateDependencyRiskStatus(UUID key, ServerDependencyRisk.Status newStatus, List<ServerDependencyRisk.Transition> transitions) {\n    var table = Tables.SERVER_DEPENDENCY_RISKS;\n    database.update(table)\n      .set(table.STATUS, newStatus.name())\n      .set(table.TRANSITIONS, mapper.serializeTransitions(transitions))\n      .where(table.ID.eq(key))\n      .execute();\n  }\n\n  @Override\n  public void removeFindingsForConnection(String connectionId) {\n    database.deleteFrom(SERVER_FINDINGS)\n      .where(SERVER_FINDINGS.CONNECTION_ID.eq(connectionId))\n      .execute();\n    database.deleteFrom(SERVER_DEPENDENCY_RISKS)\n      .where(SERVER_DEPENDENCY_RISKS.CONNECTION_ID.eq(connectionId))\n      .execute();\n    database.deleteFrom(SERVER_BRANCHES)\n      .where(SERVER_BRANCHES.CONNECTION_ID.eq(connectionId))\n      .execute();\n  }\n\n  private void batchMergeHotspots(String branchName, String connectionId, String sonarProjectKey, Configuration trx, Collection<ServerHotspot> hotspots) {\n    trx.dsl().batchMerge(hotspots.stream()\n      .map(hotspot -> mapper.serverHotspotToRecord(hotspot, branchName, connectionId, sonarProjectKey)).toList())\n      .execute();\n  }\n\n  private void batchMergeIssues(String branchName, String connectionId, String sonarProjectKey, Configuration trx, Collection<ServerIssue<?>> issues) {\n    trx.dsl().batchMerge(issues.stream()\n      .map(issue -> mapper.serverIssueToRecord(issue, branchName, connectionId, sonarProjectKey)).toList())\n      .execute();\n  }\n\n  private void batchMergeTaints(String branchName, String connectionId, String sonarProjectKey, Configuration trx, Collection<ServerTaintIssue> taints) {\n    trx.dsl().batchMerge(taints.stream()\n      .map(taint -> mapper.serverTaintToRecord(taint, branchName, connectionId, sonarProjectKey)).toList())\n      .execute();\n  }\n\n  private void batchMergeDependencyRisks(String branchName, String connectionId, String sonarProjectKey, Configuration trx, Collection<ServerDependencyRisk> risks) {\n    trx.dsl().batchMerge(risks.stream()\n      .map(risk -> mapper.serverDependencyRiskToRecord(risk, branchName, connectionId, sonarProjectKey)).toList())\n      .execute();\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/ServerFindingType.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\npublic enum ServerFindingType {\n  ISSUE,\n  HOTSPOT,\n  TAINT\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/ServerInfoStorage.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.serverapi.features.Feature;\nimport org.sonarsource.sonarlint.core.serverapi.system.ServerStatusInfo;\nimport org.sonarsource.sonarlint.core.serverconnection.FileUtils;\nimport org.sonarsource.sonarlint.core.serverconnection.ServerSettings;\nimport org.sonarsource.sonarlint.core.serverconnection.StoredServerInfo;\nimport org.sonarsource.sonarlint.core.serverconnection.proto.Sonarlint;\n\nimport static org.sonarsource.sonarlint.core.serverconnection.ServerSettings.MQR_MODE_SETTING;\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ProtobufFileUtil.writeToFile;\n\npublic class ServerInfoStorage {\n  public static final String SERVER_INFO_PB = \"server_info.pb\";\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final Path storageFilePath;\n  private final RWLock rwLock = new RWLock();\n\n  public ServerInfoStorage(Path rootPath) {\n    this.storageFilePath = rootPath.resolve(SERVER_INFO_PB);\n  }\n\n  public void store(ServerStatusInfo serverStatus, Set<Feature> features, Map<String, String> globalSettings) {\n    FileUtils.mkdirs(storageFilePath.getParent());\n    var serverInfoToStore = adapt(serverStatus, features, globalSettings);\n    LOG.debug(\"Storing server info in {}\", storageFilePath);\n    rwLock.write(() -> writeToFile(serverInfoToStore, storageFilePath));\n    LOG.debug(\"Stored server info\");\n  }\n\n  public Optional<StoredServerInfo> read() {\n    return rwLock.read(() -> Files.exists(storageFilePath) ? Optional.of(adapt(ProtobufFileUtil.readFile(storageFilePath, Sonarlint.ServerInfo.parser())))\n      : Optional.empty());\n  }\n\n  private static Sonarlint.ServerInfo adapt(ServerStatusInfo serverStatus, Set<Feature> features, Map<String, String> globalSettings) {\n    return Sonarlint.ServerInfo.newBuilder()\n      .setVersion(serverStatus.version())\n      .setServerId(serverStatus.id())\n      .putAllGlobalSettings(globalSettings)\n      .addAllSupportedFeatures(features.stream().map(Feature::getKey).toList())\n      .build();\n  }\n\n  private static StoredServerInfo adapt(Sonarlint.ServerInfo serverInfo) {\n    var globalSettings = serverInfo.getGlobalSettingsMap();\n    if (globalSettings.isEmpty()) {\n      // migration for not yet synchronized storage\n      globalSettings = new HashMap<>();\n      if (serverInfo.hasIsMqrMode()) {\n        globalSettings.put(MQR_MODE_SETTING, Boolean.toString(serverInfo.getIsMqrMode()));\n      }\n      globalSettings.put(ServerSettings.EARLY_ACCESS_MISRA_ENABLED, Boolean.toString(serverInfo.getMisraEarlyAccessRulesEnabled()));\n    }\n    // Making sure that CFamily analyzer gets updated flag even with old Server/Cloud.\n    if (globalSettings.containsKey(ServerSettings.EARLY_ACCESS_MISRA_ENABLED)) {\n      globalSettings = new HashMap<>(globalSettings);\n      globalSettings.put(ServerSettings.MISRA_COMPLIANCE_ENABLED, globalSettings.get(ServerSettings.EARLY_ACCESS_MISRA_ENABLED));\n    }\n    return new StoredServerInfo(Version.create(serverInfo.getVersion()),\n      serverInfo.getSupportedFeaturesList().stream().map(Feature::fromKey).flatMap(Optional::stream).collect(Collectors.toSet()), new ServerSettings(globalSettings),\n      serverInfo.getServerId());\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/ServerIssueStoresManager.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabase;\n\npublic class ServerIssueStoresManager {\n\n  private final Map<String, ProjectServerIssueStore> serverIssueStoreByKey = new ConcurrentHashMap<>();\n  private final SonarLintDatabase database;\n  private final String connectionId;\n\n  public ServerIssueStoresManager(String connectionId, SonarLintDatabase database) {\n    this.database = database;\n    this.connectionId = connectionId;\n  }\n\n  public ProjectServerIssueStore get(String projectKey) {\n    return serverIssueStoreByKey.computeIfAbsent(projectKey, p -> new ServerFindingRepository(database.dsl(), connectionId, projectKey));\n  }\n\n  public void delete() {\n    // removing connections via any repository is fine, it's the same tables behind\n    serverIssueStoreByKey.values().stream().findFirst()\n      .ifPresent(repository -> repository.removeFindingsForConnection(connectionId));\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/SmartNotificationsStorage.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Optional;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.serverconnection.FileUtils;\nimport org.sonarsource.sonarlint.core.serverconnection.proto.Sonarlint;\n\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ProtobufFileUtil.writeToFile;\n\npublic class SmartNotificationsStorage {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n  public static final String LAST_EVENT_POLLING_PB = \"last_event_polling.pb\";\n  private final Path storageFilePath;\n  private final RWLock rwLock = new RWLock();\n\n  public SmartNotificationsStorage(Path projectStorageRoot) {\n    this.storageFilePath = projectStorageRoot.resolve(LAST_EVENT_POLLING_PB);\n  }\n\n  public void store(Long lastEventPolling) {\n    FileUtils.mkdirs(storageFilePath.getParent());\n    var serverInfoToStore = adapt(lastEventPolling);\n    LOG.debug(\"Storing last event polling in {}\", storageFilePath);\n    rwLock.write(() -> writeToFile(serverInfoToStore, storageFilePath));\n  }\n\n  public Optional<Long> readLastEventPolling() {\n    try {\n      return rwLock.read(() -> Files.exists(storageFilePath) ?\n        Optional.of(adapt(ProtobufFileUtil.readFile(storageFilePath, Sonarlint.LastEventPolling.parser()))) : Optional.empty());\n    } catch (StorageException e) {\n      LOG.debug(\"Couldn't access storage to read and update last event polling: \" + storageFilePath);\n      return Optional.empty();\n    }\n  }\n\n  private static Sonarlint.LastEventPolling adapt(Long lastEventPolling) {\n    return Sonarlint.LastEventPolling.newBuilder().setLastEventPolling(lastEventPolling).build();\n  }\n\n  private static Long adapt(Sonarlint.LastEventPolling lastEventPolling) {\n    return lastEventPolling.getLastEventPolling();\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/StorageException.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport org.sonarsource.sonarlint.core.commons.SonarLintException;\n\npublic class StorageException extends SonarLintException {\n\n  public StorageException(String msg) {\n    super(msg);\n  }\n\n  public StorageException(String msg, Throwable cause) {\n    super(msg, cause);\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/StorageUtils.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\n\npublic class StorageUtils {\n\n  public static Set<SonarLanguage> deserializeLanguages(Optional<String> lastEnabledLanguages) {\n    Set<String> lastIssueEnabledLanguagesStringSet = Collections.emptySet();\n    Set<SonarLanguage> lastIssueEnabledLanguagesSet = new HashSet<>();\n\n    if (lastEnabledLanguages.isPresent()) {\n      lastIssueEnabledLanguagesStringSet = Stream.of(lastEnabledLanguages.get().split(\",\", -1))\n        .collect(Collectors.toSet());\n    }\n\n    for(String languageString : lastIssueEnabledLanguagesStringSet){\n      var language = SonarLanguage.getLanguageByLanguageKey(languageString);\n      if(language.isPresent()){\n        lastIssueEnabledLanguagesSet.add(language.get());\n      }\n    }\n\n    return lastIssueEnabledLanguagesSet;\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/TarGzUtils.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.io.BufferedInputStream;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport org.apache.commons.compress.archivers.ArchiveEntry;\nimport org.apache.commons.compress.archivers.tar.TarArchiveInputStream;\nimport org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;\nimport org.apache.commons.io.IOUtils;\n\npublic class TarGzUtils {\n\n  private TarGzUtils() {\n\n  }\n\n  public static void extractTarGz(Path tarGzFile, Path destinationDir) throws IOException {\n    try (var fi = Files.newInputStream(tarGzFile);\n      var bi = new BufferedInputStream(fi);\n      var gzi = new GzipCompressorInputStream(bi);\n      var o = new TarArchiveInputStream(gzi)) {\n      ArchiveEntry entry = null;\n      while ((entry = o.getNextEntry()) != null) {\n        if (!o.canReadEntryData(entry)) {\n          throw new IllegalStateException(\"Unable to read entry data from \" + tarGzFile);\n        }\n        Path f = fileName(destinationDir, entry);\n        if (entry.isDirectory()) {\n          Files.createDirectories(f);\n        } else {\n          Path parent = f.getParent();\n          Files.createDirectories(parent);\n\n          try (var os = Files.newOutputStream(f)) {\n            IOUtils.copy(o, os);\n          }\n        }\n      }\n    }\n  }\n\n  private static Path fileName(Path destinationDir, ArchiveEntry zipEntry) throws IOException {\n    var destFile = destinationDir.resolve(zipEntry.getName());\n\n    if (!destFile.normalize().startsWith(destinationDir)) {\n      throw new IOException(\"Entry is outside of the target dir: \" + zipEntry.getName());\n    }\n\n    return destFile;\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/UpdateSummary.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.util.List;\nimport java.util.Set;\nimport java.util.UUID;\n\npublic record UpdateSummary<T>(Set<UUID> deletedItemIds, List<T> addedItems, List<T> updatedItems) {\n  public boolean hasAnythingChanged() {\n    return !deletedItemIds.isEmpty() || !addedItems.isEmpty() || !updatedItems.isEmpty();\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/UserStorage.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Optional;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.serverconnection.FileUtils;\nimport org.sonarsource.sonarlint.core.serverconnection.proto.Sonarlint;\n\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ProtobufFileUtil.writeToFile;\n\npublic class UserStorage {\n  public static final String USER_PB = \"user.pb\";\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final Path storageFilePath;\n  private final RWLock rwLock = new RWLock();\n\n  public UserStorage(Path rootPath) {\n    this.storageFilePath = rootPath.resolve(USER_PB);\n  }\n\n  public void store(String userId) {\n    FileUtils.mkdirs(storageFilePath.getParent());\n    var user = Sonarlint.User.newBuilder().setId(userId).build();\n    LOG.debug(\"Storing user in {}\", storageFilePath);\n    rwLock.write(() -> writeToFile(user, storageFilePath));\n    LOG.debug(\"Stored user\");\n  }\n\n  public Optional<String> read() {\n    return rwLock.read(() -> Files.exists(storageFilePath) ? Optional.of(ProtobufFileUtil.readFile(storageFilePath, Sonarlint.User.parser()).getId())\n      : Optional.empty());\n  }\n}\n\n\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/UuidBinding.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.io.ByteArrayInputStream;\nimport java.util.UUID;\nimport jetbrains.exodus.bindings.BindingUtils;\nimport jetbrains.exodus.bindings.ComparableBinding;\nimport jetbrains.exodus.util.LightOutputStream;\nimport org.jetbrains.annotations.NotNull;\n\npublic class UuidBinding extends ComparableBinding {\n\n  @Override\n  public Comparable readObject(@NotNull ByteArrayInputStream stream) {\n    return UUID.fromString(BindingUtils.readString(stream));\n  }\n\n  @Override\n  public void writeObject(@NotNull LightOutputStream output, @NotNull Comparable object) {\n    final var uuid = (UUID) object;\n    output.writeString(uuid.toString());\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/storage/package-info.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/server-connection/src/main/proto/sonarlint.proto",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\nsyntax = \"proto3\";\n\npackage sonarlint;\n\n// The java package can be changed without breaking compatibility.\n// it impacts only the generated Java code.\noption java_package = \"org.sonarsource.sonarlint.core.serverconnection.proto\";\noption optimize_for = SPEED;\n\nmessage ServerInfo {\n  string version = 1;\n  // deprecated, store in the settings map below\n  optional bool isMqrMode = 2 [deprecated=true];\n  // deprecated, store in the settings map below\n  optional bool misraEarlyAccessRulesEnabled = 3 [deprecated=true];\n  map<string, string> globalSettings = 4;\n  repeated string supportedFeatures = 5;\n  string serverId = 6;\n}\n\nmessage PluginReferences {\n  map<string, PluginReference> plugins_by_key = 1;\n\n  message PluginReference {\n    string key = 1;\n    string hash = 2;\n    string filename = 3;\n  }\n}\n\nmessage AnalyzerConfiguration {\n  map<string, string> settings = 1;\n  map<string, RuleSet> rule_sets_by_language_key = 2;\n  uint32 schema_version = 3;\n}\n\nmessage RuleSet {\n  repeated ActiveRule rule = 1;\n  string last_modified = 3;\n\n  message ActiveRule {\n    string rule_key = 1;\n    string severity = 2;\n    string template_key = 3;\n    map<string, string> params = 4;\n    repeated Impact overriddenImpacts = 5;\n  }\n}\n\nmessage ProjectBranches {\n  repeated string branch_name = 1;\n  string main_branch_name = 2;\n}\n\nmessage Flows {\n  repeated Flow flow = 1;\n}\n\nmessage Flow {\n  repeated Location location = 1;\n}\n\nmessage Impact {\n  string softwareQuality = 1;\n  string severity = 2;\n}\n\nmessage TextRange {\n  int32 start_line = 1;\n  int32 start_line_offset = 2;\n  int32 end_line = 3;\n  int32 end_line_offset = 4;\n  string hash = 5;\n}\n\nmessage Location {\n  optional string file_path = 1;\n  string message = 2;\n  optional TextRange text_range = 3;\n}\n\nmessage LastEventPolling {\n  int64 last_event_polling = 1;\n}\n\nenum NewCodeDefinitionMode {\n  UNKNOWN = 0;\n  NUMBER_OF_DAYS = 1;\n  PREVIOUS_VERSION = 2;\n  REFERENCE_BRANCH = 3;\n  SPECIFIC_ANALYSIS = 4;\n}\n\nmessage NewCodeDefinition {\n  NewCodeDefinitionMode mode = 1;\n  optional int32 days = 2;\n  optional int64 thresholdDate = 3;\n  optional string version = 4;\n  optional string referenceBranch = 5;\n}\n\nmessage Organization {\n  string id = 1;\n  string uuidV4 = 2;\n}\n\nmessage User {\n  optional string id = 1;\n}\n\nmessage AiCodeFixSettings {\n  repeated string supportedRules = 1;\n  bool organizationEligible = 2;\n  AiCodeFixEnablement enablement = 3;\n  repeated string enabledProjectKeys = 4;\n}\n\nenum AiCodeFixEnablement {\n  UNKNOWN_ENABLEMENT = 0;\n  DISABLED = 1;\n  ENABLED_FOR_SOME_PROJECTS = 2;\n  ENABLED_FOR_ALL_PROJECTS = 3;\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/AnalyzerConfigurationStorageTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.nio.file.Path;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\n\nclass AnalyzerConfigurationStorageTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @Test\n  void should_consider_config_storage_invalid_if_not_readable_and_do_not_log_exception(@TempDir Path tempDir) {\n    var analyzerConfigurationStorage = new AnalyzerConfigurationStorage(tempDir);\n\n    var valid = analyzerConfigurationStorage.isValid();\n\n    assertFalse(valid);\n    assertThat(logTester.logs()).contains(\"Analyzer configuration storage doesn't exist: \" + tempDir.toAbsolutePath().resolve(\"analyzer_config.pb\"));\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/DownloadExceptionTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.io.IOException;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass DownloadExceptionTests {\n  @Test\n  void testNoArgs() {\n    var exception = new DownloadException();\n    assertThat(exception.getMessage()).isNull();\n    assertThat(exception.getCause()).isNull();\n  }\n\n  @Test\n  void testCauseAndMsg() {\n    var cause = new IOException(\"cause msg\");\n    var exception = new DownloadException(\"msg\", cause);\n    assertThat(exception.getMessage()).isEqualTo(\"msg\");\n    assertThat(exception.getCause()).isEqualTo(cause);\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/FileUtilsTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.AccessDeniedException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\n\nclass FileUtilsTests {\n\n  @Test\n  void deleteRecursively(@TempDir Path dir) {\n    var fileInDir = createNewFile(dir, \"dummy\");\n    assertThat(fileInDir).isFile();\n\n    FileUtils.deleteRecursively(dir);\n    assertThat(fileInDir).doesNotExist();\n    assertThat(dir).doesNotExist();\n  }\n\n  @Test\n  void deleteRecursively_should_ignore_nonexistent_dir(@TempDir Path temp) {\n    var dir = new File(temp.toFile(), \"nonexistent\");\n    assertThat(dir).doesNotExist();\n\n    FileUtils.deleteRecursively(dir.toPath());\n  }\n\n  @Test\n  void deleteRecursively_should_delete_file(@TempDir Path temp) {\n    var file = createNewFile(temp, \"foo.txt\");\n    assertThat(file).isFile();\n\n    FileUtils.deleteRecursively(file.toPath());\n    assertThat(file).doesNotExist();\n  }\n\n  @Test\n  void deleteRecursively_should_delete_deeply_nested_dirs(@TempDir Path basedir) {\n    var deeplyNestedDir = basedir.resolve(\"a\").resolve(\"b\").resolve(\"c\");\n    assertThat(deeplyNestedDir.toFile().isDirectory()).isFalse();\n    FileUtils.mkdirs(deeplyNestedDir);\n\n    FileUtils.deleteRecursively(basedir);\n    assertThat(basedir.toFile()).doesNotExist();\n  }\n\n  @Test\n  void mkdirs(@TempDir Path temp) {\n    var deeplyNestedDir = temp.resolve(\"a\").resolve(\"b\").resolve(\"c\");\n    assertThat(deeplyNestedDir).doesNotExist();\n    if (deeplyNestedDir.toFile().mkdir()) {\n      throw new IllegalStateException(\"creating nested dir should have failed\");\n    }\n\n    FileUtils.mkdirs(deeplyNestedDir);\n    assertThat(deeplyNestedDir).isDirectory();\n  }\n\n  @Test\n  void mkdirs_should_fail_if_destination_is_a_file(@TempDir Path temp) {\n    var file = createNewFile(temp, \"foo\").toPath();\n    assertThrows(IllegalStateException.class, () -> FileUtils.mkdirs(file));\n  }\n\n  @Test\n  void always_retry_at_least_once() throws IOException {\n    var runnable = mock(FileUtils.IORunnable.class);\n    FileUtils.retry(runnable, 0);\n    verify(runnable, times(1)).run();\n  }\n\n  @Test\n  void retry_on_failure() throws IOException {\n    int[] count = {0};\n    FileUtils.IORunnable throwOnce = () -> {\n      count[0]++;\n      if (count[0] == 1) {\n        throw new AccessDeniedException(\"foo\");\n      }\n    };\n    FileUtils.retry(throwOnce, 10);\n    assertThat(count[0]).isEqualTo(2);\n  }\n\n  private File createNewFile(Path basedir, String filename) {\n    var path = basedir.resolve(filename);\n    try {\n      return Files.createFile(path).toFile();\n    } catch (IOException e) {\n      fail(\"could not create file: \" + path);\n    }\n    throw new IllegalStateException(\"should be unreachable\");\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/HotspotDownloaderTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.Optional;\nimport java.util.Set;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Hotspots;\nimport testutils.MockWebServerExtensionWithProtobuf;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass HotspotDownloaderTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private static final String DUMMY_KEY = \"dummyKey\";\n\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n  private ServerApi serverApi;\n\n  private HotspotDownloader underTest;\n\n  @BeforeEach\n  void prepare() {\n    underTest = new HotspotDownloader(Set.of(SonarLanguage.JAVA));\n    serverApi = new ServerApi(mockServer.serverApiHelper());\n  }\n\n  @Test\n  void test_download_one_hotspot_pull_ws() {\n    var timestamp = Hotspots.HotspotPullQueryTimestamp.newBuilder().setQueryTimestamp(123L).build();\n    var hotspot1 = Hotspots.HotspotLite.newBuilder()\n      .setKey(\"someHotspotKey\")\n      .setFilePath(\"foo/bar/Hello.java\")\n      .setVulnerabilityProbability(VulnerabilityProbability.LOW.toString())\n      .setStatus(\"TO_REVIEW\")\n      .setMessage(\"This is security sensitive\")\n      .setCreationDate(123456789L)\n      .setTextRange(Hotspots.TextRange.newBuilder()\n        .setStartLine(1)\n        .setStartLineOffset(2)\n        .setEndLine(3)\n        .setEndLineOffset(4)\n        .setHash(\"clearly not a hash\")\n        .build())\n      .setRuleKey(\"java:S123\")\n      .setClosed(false)\n      .build();\n    var hotspot2 = Hotspots.HotspotLite.newBuilder()\n      .setKey(\"otherHotspotKey\")\n      .setFilePath(\"foo/bar/Hello.java\")\n      .setVulnerabilityProbability(VulnerabilityProbability.LOW.toString())\n      .setStatus(\"REVIEWED\")\n      .setResolution(\"SAFE\")\n      .setMessage(\"This is security sensitive\")\n      .setCreationDate(123456789L)\n      .setTextRange(Hotspots.TextRange.newBuilder()\n        .setStartLine(5)\n        .setStartLineOffset(6)\n        .setEndLine(7)\n        .setEndLineOffset(8)\n        .setHash(\"not a hash either\")\n        .build())\n      .setRuleKey(\"java:S123\")\n      .setClosed(false)\n      .build();\n\n    mockServer.addProtobufResponseDelimited(\"/api/hotspots/pull?projectKey=\" + DUMMY_KEY + \"&branchName=myBranch&languages=java\", timestamp, hotspot1, hotspot2);\n\n    var result = underTest.downloadFromPull(serverApi.hotspot(), DUMMY_KEY, \"myBranch\", Optional.empty(), new SonarLintCancelMonitor());\n    assertThat(result.getChangedHotspots()).hasSize(2);\n    assertThat(result.getClosedHotspotKeys()).isEmpty();\n\n    var serverHotspot1 = result.getChangedHotspots().get(0);\n    assertThat(serverHotspot1.getKey()).isEqualTo(\"someHotspotKey\");\n    assertThat(serverHotspot1.getFilePath()).isEqualTo(Path.of(\"foo/bar/Hello.java\"));\n    assertThat(serverHotspot1.getVulnerabilityProbability()).isEqualTo(VulnerabilityProbability.LOW);\n    assertThat(serverHotspot1.getStatus()).isEqualTo(HotspotReviewStatus.TO_REVIEW);\n    assertThat(serverHotspot1.getMessage()).isEqualTo(\"This is security sensitive\");\n    assertThat(serverHotspot1.getCreationDate()).isAfter(Instant.EPOCH);\n    assertThat(serverHotspot1.getTextRange().getStartLine()).isEqualTo(1);\n    assertThat(serverHotspot1.getTextRange().getStartLineOffset()).isEqualTo(2);\n    assertThat(serverHotspot1.getTextRange().getEndLine()).isEqualTo(3);\n    assertThat(serverHotspot1.getTextRange().getEndLineOffset()).isEqualTo(4);\n    assertThat(((TextRangeWithHash) serverHotspot1.getTextRange()).getHash()).isEqualTo(\"clearly not a hash\");\n    assertThat(serverHotspot1.getRuleKey()).isEqualTo(\"java:S123\");\n\n    var serverHotspot2 = result.getChangedHotspots().get(1);\n    assertThat(serverHotspot2.getKey()).isEqualTo(\"otherHotspotKey\");\n    assertThat(serverHotspot2.getFilePath()).isEqualTo(Path.of(\"foo/bar/Hello.java\"));\n    assertThat(serverHotspot2.getVulnerabilityProbability()).isEqualTo(VulnerabilityProbability.LOW);\n    assertThat(serverHotspot2.getStatus()).isEqualTo(HotspotReviewStatus.SAFE);\n    assertThat(serverHotspot2.getMessage()).isEqualTo(\"This is security sensitive\");\n    assertThat(serverHotspot2.getCreationDate()).isAfter(Instant.EPOCH);\n    assertThat(serverHotspot2.getTextRange().getStartLine()).isEqualTo(5);\n    assertThat(serverHotspot2.getTextRange().getStartLineOffset()).isEqualTo(6);\n    assertThat(serverHotspot2.getTextRange().getEndLine()).isEqualTo(7);\n    assertThat(serverHotspot2.getTextRange().getEndLineOffset()).isEqualTo(8);\n    assertThat(((TextRangeWithHash) serverHotspot2.getTextRange()).getHash()).isEqualTo(\"not a hash either\");\n    assertThat(serverHotspot2.getRuleKey()).isEqualTo(\"java:S123\");\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/IssueDownloaderTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport mockwebserver3.MockResponse;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonar.scanner.protocol.Constants.Severity;\nimport org.sonar.scanner.protocol.input.ScannerInput;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.exception.ServerErrorException;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues.IssueLite;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues.Location;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.FileLevelServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.LineLevelServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.RangeLevelServerIssue;\nimport testutils.MockWebServerExtensionWithProtobuf;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass IssueDownloaderTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private static final String DUMMY_KEY = \"dummyKey\";\n\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n  private ServerApi serverApi;\n\n  private IssueDownloader underTest;\n\n  @BeforeEach\n  void prepare() {\n    underTest = new IssueDownloader(Set.of(SonarLanguage.JAVA));\n    serverApi = new ServerApi(mockServer.serverApiHelper());\n  }\n\n  @Test\n  void test_download_one_issue_old_batch_ws() {\n    var response = ScannerInput.ServerIssue.newBuilder()\n      .setKey(\"uuid\")\n      .setRuleRepository(\"sonarjava\")\n      .setRuleKey(\"S123\")\n      .setChecksum(\"hash\")\n      .setMsg(\"Primary message\")\n      .setLine(1)\n      .setCreationDate(123456789L)\n      .setPath(\"foo/bar/Hello.java\")\n      .setType(\"BUG\")\n      .setManualSeverity(false)\n      .setSeverity(Severity.BLOCKER)\n      .build();\n\n    mockServer.addProtobufResponseDelimited(\"/batch/issues?key=\" + DUMMY_KEY, response);\n\n    var issues = underTest.downloadFromBatch(serverApi, DUMMY_KEY, null, new SonarLintCancelMonitor());\n    assertThat(issues).hasSize(1);\n\n    var serverIssue = issues.get(0);\n    assertThat(serverIssue).isInstanceOf(LineLevelServerIssue.class);\n    assertThat(serverIssue.getKey()).isEqualTo(\"uuid\");\n    assertThat(serverIssue.getType()).isEqualTo(RuleType.BUG);\n    assertThat(serverIssue.getUserSeverity()).isNull();\n    assertThat(((LineLevelServerIssue) serverIssue).getLineHash()).isEqualTo(\"hash\");\n    assertThat(serverIssue.getMessage()).isEqualTo(\"Primary message\");\n    assertThat(serverIssue.getFilePath()).isEqualTo(Path.of(\"foo/bar/Hello.java\"));\n    assertThat(((LineLevelServerIssue) serverIssue).getLine()).isEqualTo(1);\n  }\n\n  @Test\n  void test_download_one_issue_old_batch_ws_with_user_severity() {\n    var response = ScannerInput.ServerIssue.newBuilder()\n      .setKey(\"uuid\")\n      .setRuleRepository(\"sonarjava\")\n      .setRuleKey(\"S123\")\n      .setChecksum(\"hash\")\n      .setMsg(\"Primary message\")\n      .setLine(1)\n      .setCreationDate(123456789L)\n      .setPath(\"foo/bar/Hello.java\")\n      .setType(\"BUG\")\n      .setManualSeverity(true)\n      .setSeverity(Severity.BLOCKER)\n      .build();\n\n    mockServer.addProtobufResponseDelimited(\"/batch/issues?key=\" + DUMMY_KEY, response);\n\n    var issues = underTest.downloadFromBatch(serverApi, DUMMY_KEY, null, new SonarLintCancelMonitor());\n    assertThat(issues).hasSize(1);\n\n    var serverIssue = issues.get(0);\n    assertThat(serverIssue.getUserSeverity()).isEqualTo(IssueSeverity.BLOCKER);\n  }\n\n  @Test\n  void test_download_one_file_level_issue_old_batch_ws() {\n    var response = ScannerInput.ServerIssue.newBuilder()\n      .setKey(\"uuid\")\n      .setRuleRepository(\"sonarjava\")\n      .setRuleKey(\"S123\")\n      .setMsg(\"Primary message\")\n      .setCreationDate(123456789L)\n      .setPath(\"foo/bar/Hello.java\")\n      .setType(\"BUG\")\n      .build();\n\n    mockServer.addProtobufResponseDelimited(\"/batch/issues?key=\" + DUMMY_KEY, response);\n\n    var issues = underTest.downloadFromBatch(serverApi, DUMMY_KEY, null, new SonarLintCancelMonitor());\n    assertThat(issues).hasSize(1);\n\n    var serverIssue = issues.get(0);\n    assertThat(serverIssue).isInstanceOf(FileLevelServerIssue.class);\n    assertThat(serverIssue.getKey()).isEqualTo(\"uuid\");\n    assertThat(serverIssue.getMessage()).isEqualTo(\"Primary message\");\n    assertThat(serverIssue.getFilePath()).isEqualTo(Path.of(\"foo/bar/Hello.java\"));\n  }\n\n  @Test\n  void test_download_one_issue_pull_ws() {\n    var timestamp = Issues.IssuesPullQueryTimestamp.newBuilder().setQueryTimestamp(123L).build();\n    var issue = IssueLite.newBuilder()\n      .setKey(\"uuid\")\n      .setRuleKey(\"sonarjava:S123\")\n      .setType(Common.RuleType.BUG)\n      .setMainLocation(Location.newBuilder().setFilePath(\"foo/bar/Hello.java\").setMessage(\"Primary message\")\n        .setTextRange(org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues.TextRange.newBuilder().setStartLine(1).setStartLineOffset(2).setEndLine(3)\n          .setEndLineOffset(4).setHash(\"hash\")))\n      .setCreationDate(123456789L)\n      .build();\n\n    mockServer.addProtobufResponseDelimited(\"/api/issues/pull?projectKey=\" + DUMMY_KEY + \"&branchName=myBranch&languages=java\", timestamp, issue);\n\n    var result = underTest.downloadFromPull(serverApi, DUMMY_KEY, \"myBranch\", Optional.empty(), new SonarLintCancelMonitor());\n    assertThat(result.getChangedIssues()).hasSize(1);\n    assertThat(result.getClosedIssueKeys()).isEmpty();\n\n    var serverIssue = result.getChangedIssues().get(0);\n    assertThat(serverIssue).isInstanceOf(RangeLevelServerIssue.class);\n    assertThat(serverIssue.getKey()).isEqualTo(\"uuid\");\n    assertThat(serverIssue.getMessage()).isEqualTo(\"Primary message\");\n    assertThat(serverIssue.getFilePath()).isEqualTo(Path.of(\"foo/bar/Hello.java\"));\n    assertThat(serverIssue.getUserSeverity()).isNull();\n    assertThat(serverIssue.getType()).isEqualTo(RuleType.BUG);\n    assertThat(((RangeLevelServerIssue) serverIssue).getTextRange().getStartLine()).isEqualTo(1);\n    assertThat(((RangeLevelServerIssue) serverIssue).getTextRange().getStartLineOffset()).isEqualTo(2);\n    assertThat(((RangeLevelServerIssue) serverIssue).getTextRange().getEndLine()).isEqualTo(3);\n    assertThat(((RangeLevelServerIssue) serverIssue).getTextRange().getEndLineOffset()).isEqualTo(4);\n    assertThat(((RangeLevelServerIssue) serverIssue).getTextRange().getHash()).isEqualTo(\"hash\");\n  }\n\n  @Test\n  void test_download_one_issue_pull_ws_with_user_severity() {\n    var timestamp = Issues.IssuesPullQueryTimestamp.newBuilder().setQueryTimestamp(123L).build();\n    var issue = IssueLite.newBuilder()\n      .setKey(\"uuid\")\n      .setRuleKey(\"sonarjava:S123\")\n      .setType(Common.RuleType.BUG)\n      .setUserSeverity(Common.Severity.MAJOR)\n      .setMainLocation(Location.newBuilder().setFilePath(\"foo/bar/Hello.java\").setMessage(\"Primary message\")\n        .setTextRange(org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues.TextRange.newBuilder().setStartLine(1).setStartLineOffset(2).setEndLine(3)\n          .setEndLineOffset(4).setHash(\"hash\")))\n      .setCreationDate(123456789L)\n      .build();\n\n    mockServer.addProtobufResponseDelimited(\"/api/issues/pull?projectKey=\" + DUMMY_KEY + \"&branchName=myBranch&languages=java\", timestamp, issue);\n\n    var result = underTest.downloadFromPull(serverApi, DUMMY_KEY, \"myBranch\", Optional.empty(), new SonarLintCancelMonitor());\n    assertThat(result.getChangedIssues()).hasSize(1);\n    assertThat(result.getClosedIssueKeys()).isEmpty();\n\n    var serverIssue = result.getChangedIssues().get(0);\n    assertThat(serverIssue.getUserSeverity()).isEqualTo(IssueSeverity.MAJOR);\n  }\n\n  @Test\n  void test_download_one_issue_pull_ws_with_user_impacts() {\n    var timestamp = Issues.IssuesPullQueryTimestamp.newBuilder().setQueryTimestamp(123L).build();\n    var issue = IssueLite.newBuilder()\n      .setKey(\"uuid\")\n      .setRuleKey(\"sonarjava:S123\")\n      .setType(Common.RuleType.BUG)\n      .setMainLocation(Location.newBuilder().setFilePath(\"foo/bar/Hello.java\").setMessage(\"Primary message\")\n        .setTextRange(Issues.TextRange.newBuilder().setStartLine(1).setStartLineOffset(2).setEndLine(3)\n          .setEndLineOffset(4).setHash(\"hash\")))\n      .setCreationDate(123456789L)\n      .addImpacts(Common.Impact.newBuilder()\n        .setSoftwareQuality(Common.SoftwareQuality.SECURITY)\n        .setSeverity(Common.ImpactSeverity.HIGH)\n        .build())\n      .build();\n\n    mockServer.addProtobufResponseDelimited(\"/api/issues/pull?projectKey=\" + DUMMY_KEY + \"&branchName=myBranch&languages=java\", timestamp, issue);\n\n    var result = underTest.downloadFromPull(serverApi, DUMMY_KEY, \"myBranch\", Optional.empty(), new SonarLintCancelMonitor());\n    assertThat(result.getChangedIssues()).hasSize(1);\n    assertThat(result.getClosedIssueKeys()).isEmpty();\n\n    var serverIssue = result.getChangedIssues().get(0);\n    assertThat(serverIssue.getImpacts()).isEqualTo(Map.of(SoftwareQuality.SECURITY, ImpactSeverity.HIGH));\n  }\n\n  @Test\n  void test_download_one_file_level_issue_pull_ws() {\n    var timestamp = Issues.IssuesPullQueryTimestamp.newBuilder().setQueryTimestamp(123L).build();\n    var issue = IssueLite.newBuilder()\n      .setKey(\"uuid\")\n      .setRuleKey(\"sonarjava:S123\")\n      .setMainLocation(Location.newBuilder().setFilePath(\"foo/bar/Hello.java\").setMessage(\"Primary message\"))\n      .setCreationDate(123456789L)\n      .setType(Common.RuleType.BUG)\n      .build();\n\n    mockServer.addProtobufResponseDelimited(\"/api/issues/pull?projectKey=\" + DUMMY_KEY + \"&branchName=myBranch&languages=java\", timestamp, issue);\n\n    var result = underTest.downloadFromPull(serverApi, DUMMY_KEY, \"myBranch\", Optional.empty(), new SonarLintCancelMonitor());\n    assertThat(result.getChangedIssues()).hasSize(1);\n    assertThat(result.getClosedIssueKeys()).isEmpty();\n\n    var serverIssue = result.getChangedIssues().get(0);\n    assertThat(serverIssue).isInstanceOf(FileLevelServerIssue.class);\n    assertThat(serverIssue.getKey()).isEqualTo(\"uuid\");\n    assertThat(serverIssue.getMessage()).isEqualTo(\"Primary message\");\n    assertThat(serverIssue.getFilePath()).isEqualTo(Path.of(\"foo/bar/Hello.java\"));\n    assertThat(serverIssue.getType()).isEqualTo(RuleType.BUG);\n  }\n\n  @Test\n  void test_download_closed_file_level_issues_from_pull_ws() {\n    var timestamp = Issues.IssuesPullQueryTimestamp.newBuilder().setQueryTimestamp(123L).build();\n    var issue = IssueLite.newBuilder()\n      .setKey(\"key\")\n      .setClosed(true)\n      .setMainLocation(Location.newBuilder().setFilePath(\"foo/bar/Hello.java\").setMessage(\"Primary message\")\n        .setTextRange(org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues.TextRange.newBuilder().setStartLine(1).setStartLineOffset(2).setEndLine(3)\n          .setEndLineOffset(4).setHash(\"hash\")))\n      .build();\n    mockServer.addProtobufResponseDelimited(\"/api/issues/pull?projectKey=\" + DUMMY_KEY + \"&branchName=myBranch&languages=java&changedSince=123456789\", timestamp, issue);\n\n    var result = underTest.downloadFromPull(serverApi, DUMMY_KEY, \"myBranch\", Optional.of(Instant.ofEpochMilli(123456789)), new SonarLintCancelMonitor());\n\n    assertThat(result.getChangedIssues()).isEmpty();\n    assertThat(result.getClosedIssueKeys()).containsOnly(\"key\");\n  }\n\n  @Test\n  void test_download_issue_ignore_project_level() {\n    var response = ScannerInput.ServerIssue.newBuilder()\n      .setRuleRepository(\"sonarjava\")\n      .setRuleKey(\"S123\")\n      .setChecksum(\"hash\")\n      .setMsg(\"Primary message\")\n      .setLine(1)\n      .setCreationDate(123456789L)\n      // No path\n      .setModuleKey(\"project\")\n      .build();\n\n    mockServer.addProtobufResponseDelimited(\"/batch/issues?key=\" + DUMMY_KEY, response);\n\n    var issues = underTest.downloadFromBatch(serverApi, DUMMY_KEY, null, new SonarLintCancelMonitor());\n    assertThat(issues).isEmpty();\n  }\n\n  @Test\n  void test_pull_issue_ignore_project_level() {\n    var timestamp = Issues.IssuesPullQueryTimestamp.newBuilder().setQueryTimestamp(123L).build();\n    var issue = IssueLite.newBuilder()\n      .setKey(\"uuid\")\n      .setRuleKey(\"sonarjava:S123\")\n      .setMainLocation(Location.newBuilder().setMessage(\"Primary message\"))\n      .setCreationDate(123456789L)\n      .build();\n\n    mockServer.addProtobufResponseDelimited(\"/api/issues/pull?projectKey=\" + DUMMY_KEY + \"&branchName=myBranch&languages=java\", timestamp, issue);\n\n    var issues = underTest.downloadFromPull(serverApi, DUMMY_KEY, \"myBranch\", Optional.empty(), new SonarLintCancelMonitor());\n    assertThat(issues.getChangedIssues()).isEmpty();\n    assertThat(issues.getClosedIssueKeys()).isEmpty();\n  }\n\n  @Test\n  void test_ignore_taint_vulnerabilities() {\n    var issue1 = ScannerInput.ServerIssue.newBuilder()\n      .setRuleRepository(\"sonarjava\")\n      .setRuleKey(\"S123\")\n      .setChecksum(\"hash1\")\n      .setMsg(\"Primary message 1\")\n      .setLine(1)\n      .setCreationDate(123456789L)\n      .setPath(\"foo/bar/Hello.java\")\n      .setModuleKey(\"project\")\n      .setType(\"BUG\")\n      .build();\n\n    var taint1 = ScannerInput.ServerIssue.newBuilder()\n      .setRuleRepository(\"javasecurity\")\n      .setRuleKey(\"S789\")\n      .setChecksum(\"hash2\")\n      .setMsg(\"Primary message 2\")\n      .setLine(2)\n      .setCreationDate(123456789L)\n      .setPath(\"foo/bar/Hello2.java\")\n      .setModuleKey(\"project\")\n      .setType(\"VULNERABILITY\")\n      .build();\n\n    mockServer.addProtobufResponseDelimited(\"/batch/issues?key=\" + DUMMY_KEY, issue1, taint1);\n\n    var issues = underTest.downloadFromBatch(serverApi, DUMMY_KEY, null, new SonarLintCancelMonitor());\n\n    assertThat(issues).hasSize(1);\n  }\n\n  @Test\n  void test_download_no_issues() {\n    mockServer.addProtobufResponseDelimited(\"/batch/issues?key=\" + DUMMY_KEY);\n\n    var issues = underTest.downloadFromBatch(serverApi, DUMMY_KEY, null, new SonarLintCancelMonitor());\n    assertThat(issues).isEmpty();\n  }\n\n  @Test\n  void test_fail_other_codes() {\n    mockServer.addResponse(\"/batch/issues?key=\" + DUMMY_KEY, new MockResponse.Builder().code(503).build());\n\n    var cancelMonitor = new SonarLintCancelMonitor();\n    var thrown = assertThrows(ServerErrorException.class,\n      () -> underTest.downloadFromBatch(serverApi, DUMMY_KEY, null, cancelMonitor));\n    assertThat(thrown).hasMessageContaining(\"Error 503\");\n  }\n\n  @Test\n  void test_return_empty_if_404() {\n    mockServer.addResponse(\"/batch/issues?key=\" + DUMMY_KEY, new MockResponse.Builder().code(404).build());\n\n    var issues = underTest.downloadFromBatch(serverApi, DUMMY_KEY, null, new SonarLintCancelMonitor());\n    assertThat(issues).isEmpty();\n  }\n\n  @Test\n  void test_filter_batch_issues_by_branch_if_branch_parameter_provided() {\n    var response = ScannerInput.ServerIssue.newBuilder()\n      .setRuleRepository(\"sonarjava\")\n      .setRuleKey(\"S123\")\n      .setPath(\"src/Foo.java\")\n      .setType(\"BUG\")\n      .build();\n\n    mockServer.addProtobufResponseDelimited(\"/batch/issues?key=\" + DUMMY_KEY + \"&branch=branchName\", response);\n\n    var issues = underTest.downloadFromBatch(serverApi, DUMMY_KEY, \"branchName\", new SonarLintCancelMonitor());\n    assertThat(issues).hasSize(1);\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/IssueStorePathsTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass IssueStorePathsTests {\n\n  @Test\n  void local_path_to_sq_path_uses_both_prefixes() {\n    var projectBinding = new ProjectBinding(\"project\", \"sq\", \"ide\");\n    var sqPath = IssueStorePaths.idePathToServerPath(projectBinding, Path.of(\"ide/project1/path1\"));\n    assertThat(sqPath).isEqualTo(Path.of(\"sq/project1/path1\"));\n  }\n\n  @Test\n  void local_path_to_fileKey() {\n    var projectBinding = new ProjectBinding(\"projectKey\", \"project\", \"ide\");\n    var fileKey = IssueStorePaths.idePathToFileKey(projectBinding, Paths.get(\"ide/B/path1\"));\n    assertThat(fileKey).isEqualTo(\"projectKey:project/B/path1\");\n  }\n\n  @Test\n  void local_path_to_sq_path_returns_null_if_path_doesnt_match_prefix() {\n    var projectBinding = new ProjectBinding(\"project\", \"sq\", \"ide\");\n    var sqPath = IssueStorePaths.idePathToServerPath(projectBinding, Path.of(\"unknown/project1/path1\").normalize());\n    assertThat(sqPath).isNull();\n  }\n\n  @Test\n  void local_path_to_sq_path_returns_null_if_path_match_prefix_partially() {\n    var projectBinding = new ProjectBinding(\"project\", \"sq\", \"src\");\n    var sqPath = IssueStorePaths.idePathToServerPath(projectBinding, Path.of(\"src2/project1/path1\"));\n    assertThat(sqPath).isNull();\n  }\n\n  @Test\n  void local_path_to_sq_path_without_sq_prefix() {\n    var projectBinding = new ProjectBinding(\"project\", \"\", \"ide\");\n    var sqPath = IssueStorePaths.idePathToServerPath(projectBinding, Path.of(\"ide/project1/path1\"));\n    assertThat(sqPath).isEqualTo(Path.of(\"project1/path1\"));\n  }\n\n  @Test\n  void local_path_to_sq_path_without_ide_prefix() {\n    var projectBinding = new ProjectBinding(\"project\", \"sq\", \"\");\n    var sqPath = IssueStorePaths.idePathToServerPath(projectBinding, Path.of(\"ide/project1/path1\"));\n    assertThat(sqPath).isEqualTo(Path.of(\"sq/ide/project1/path1\"));\n  }\n\n  @Test\n  void local_path_to_fileKey_returns_null_if_path_doesnt_match_prefix() {\n    var projectBinding = new ProjectBinding(\"project\", \"project\", \"ide\");\n    var fileKey = IssueStorePaths.idePathToFileKey(projectBinding, Path.of(\"unknown/B/path1\"));\n    assertThat(fileKey).isNull();\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/ProjectBindingTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ProjectBindingTests {\n  @Test\n  void should_assign_all_parameters_in_constructor() {\n    var projectBinding = new ProjectBinding(\"key\", \"sqPrefix\", \"localPrefix\");\n    assertThat(projectBinding.projectKey()).isEqualTo(\"key\");\n    assertThat(projectBinding.serverPathPrefix()).isEqualTo(\"sqPrefix\");\n    assertThat(projectBinding.idePathPrefix()).isEqualTo(\"localPrefix\");\n  }\n\n  @Test\n  void equals_and_hashCode_should_use_all_fields() {\n    var projectBinding1 = new ProjectBinding(\"key\", \"sqPrefix\", \"localPrefix\");\n    var projectBinding2 = new ProjectBinding(\"key1\", \"sqPrefix\", \"localPrefix\");\n    var projectBinding3 = new ProjectBinding(\"key\", \"sqPrefix1\", \"localPrefix\");\n    var projectBinding4 = new ProjectBinding(\"key\", \"sqPrefix\", \"localPrefix1\");\n    var projectBinding5 = new ProjectBinding(\"key\", \"sqPrefix\", \"localPrefix\");\n\n    assertThat(projectBinding1.equals(projectBinding2)).isFalse();\n    assertThat(projectBinding1.equals(projectBinding3)).isFalse();\n    assertThat(projectBinding1.equals(projectBinding4)).isFalse();\n    assertThat(projectBinding1.equals(projectBinding5)).isTrue();\n\n    assertThat(projectBinding1.hashCode()).isNotEqualTo(projectBinding2.hashCode());\n    assertThat(projectBinding1.hashCode()).isNotEqualTo(projectBinding3.hashCode());\n    assertThat(projectBinding1.hashCode()).isNotEqualTo(projectBinding4.hashCode());\n    assertThat(projectBinding1.hashCode()).isEqualTo(projectBinding5.hashCode());\n  }\n\n  @Test\n  void serverPathToIdePath_no_match_from_server_path() {\n    var projectBinding = new ProjectBinding(\"key\", \"sqPrefix\", \"localPrefix\");\n    assertThat(projectBinding.serverPathToIdePath(\"notSqPrefix/some/path\")).isEmpty();\n  }\n\n  @Test\n  void serverPathToIdePath_general_case() {\n    var projectBinding = new ProjectBinding(\"key\", \"sq/path/prefix\", \"local/prefix\");\n    assertThat(projectBinding.serverPathToIdePath(\"sq/path/prefix/some/path\")).hasValue(\"local/prefix/some/path\");\n  }\n\n  @Test\n  void serverPathToIdePath_empty_local_path() {\n    var projectBinding = new ProjectBinding(\"key\", \"sq/path/prefix\", \"\");\n    assertThat(projectBinding.serverPathToIdePath(\"sq/path/prefix/some/path\")).hasValue(\"some/path\");\n  }\n\n  @Test\n  void serverPathToIdePath_empty_sq_path() {\n    var projectBinding = new ProjectBinding(\"key\", \"\", \"local/prefix\");\n    assertThat(projectBinding.serverPathToIdePath(\"some/path\")).hasValue(\"local/prefix/some/path\");\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/ProjectStoragePathsTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.apache.commons.lang3.StringUtils.repeat;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ProjectStoragePaths.encodeForFs;\n\nclass ProjectStoragePathsTests {\n\n  @Test\n  void encode_paths_for_fs() {\n    assertThat(encodeForFs(\"my/string%to encode**\")).isEqualTo(\"6d792f737472696e6725746f20656e636f64652a2a\");\n    assertThat(encodeForFs(\"AU-TpxcA-iU5OvuD2FLz\").toLowerCase()).isNotEqualTo(encodeForFs(\"AU-TpxcA-iU5OvuD2FLZ\"));\n    assertThat(encodeForFs(\"too_long_for_most_fs\" + repeat(\"a\", 1000))).hasSize(255);\n    assertThat(encodeForFs(\"too_long_for_most_fs\" + repeat(\"a\", 1000)))\n      .isNotEqualTo(encodeForFs(\"too_long_for_most_fs\" + repeat(\"a\", 1000) + \"2\"));\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/ServerHotspotUpdaterTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.time.Instant;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.Set;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.mockito.ArgumentCaptor;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.hotspot.HotspotApi;\nimport org.sonarsource.sonarlint.core.serverapi.hotspot.ServerHotspot;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ProjectServerIssueStore;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.anySet;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ServerHotspotFixtures.aServerHotspot;\n\nclass ServerHotspotUpdaterTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private static final String PROJECT_KEY = \"module\";\n  private final ProjectServerIssueStore issueStore = mock(ProjectServerIssueStore.class);\n  private final ProjectBinding projectBinding = new ProjectBinding(PROJECT_KEY, \"\", \"\");\n\n  private ServerHotspotUpdater updater;\n  private HotspotApi hotspotApi;\n  private HotspotDownloader hotspotDownloader;\n\n  @BeforeEach\n  void setUp() {\n    hotspotApi = mock(HotspotApi.class);\n    hotspotDownloader = mock(HotspotDownloader.class);\n    ConnectionStorage storage = mock(ConnectionStorage.class);\n    var projectStorage = mock(SonarProjectStorage.class);\n    when(storage.project(PROJECT_KEY)).thenReturn(projectStorage);\n    when(projectStorage.findings()).thenReturn(issueStore);\n    updater = new ServerHotspotUpdater(storage, hotspotDownloader);\n  }\n\n  @Test\n  void should_sync_hotspots() {\n    var timestamp = Instant.ofEpochMilli(123456789L);\n    var hotspotKey = \"hotspotKey\";\n    var hotspots = List.of(aServerHotspot(hotspotKey));\n    var cancelMonitor = new SonarLintCancelMonitor();\n    when(hotspotDownloader.downloadFromPull(hotspotApi, PROJECT_KEY, \"branch\", Optional.empty(), cancelMonitor))\n      .thenReturn(new HotspotDownloader.PullResult(timestamp, hotspots, Set.of()));\n\n    updater.sync(hotspotApi, PROJECT_KEY, \"branch\", Set.of(SonarLanguage.C), cancelMonitor);\n\n    var hotspotCaptor = ArgumentCaptor.forClass(List.class);\n    verify(issueStore).mergeHotspots(eq(\"branch\"), hotspotCaptor.capture(), eq(Set.of()), eq(timestamp), eq(Set.of(SonarLanguage.C)));\n    assertThat(hotspotCaptor.getValue()).hasSize(1);\n    var capturedHotspot = (ServerHotspot) (hotspotCaptor.getValue().get(0));\n    assertThat(capturedHotspot.getKey()).isEqualTo(hotspotKey);\n  }\n\n  @Test\n  void update_hotspots_with_pull_when_enabled_language_not_changed() {\n    var timestamp = Instant.ofEpochMilli(123456789L);\n    var lastHotspotEnabledLanguages = Set.of(SonarLanguage.C, SonarLanguage.GO);\n    var hotspotKey = \"hotspotKey\";\n    var hotspots = List.of(aServerHotspot(hotspotKey));\n    var cancelMonitor = new SonarLintCancelMonitor();\n    when(hotspotDownloader.downloadFromPull(hotspotApi, PROJECT_KEY, \"branch\", Optional.of(timestamp), cancelMonitor))\n      .thenReturn(new HotspotDownloader.PullResult(timestamp, hotspots, Set.of()));\n    when(issueStore.getLastHotspotEnabledLanguages(\"branch\")).thenReturn(lastHotspotEnabledLanguages);\n    when(issueStore.getLastHotspotSyncTimestamp(\"branch\")).thenReturn(Optional.of(timestamp));\n\n    updater.sync(hotspotApi, PROJECT_KEY, \"branch\", Set.of(SonarLanguage.C, SonarLanguage.GO), cancelMonitor);\n\n    var hotspotCaptor = ArgumentCaptor.forClass(List.class);\n    verify(issueStore).mergeHotspots(eq(\"branch\"), hotspotCaptor.capture(), eq(Set.of()), eq(timestamp), anySet());\n    assertThat(hotspotCaptor.getValue()).hasSize(1);\n    var capturedHotspot = (ServerHotspot) (hotspotCaptor.getValue().get(0));\n    assertThat(capturedHotspot.getKey()).isEqualTo(hotspotKey);\n    verify(hotspotDownloader).downloadFromPull(hotspotApi, projectBinding.projectKey(), \"branch\", Optional.of(timestamp), cancelMonitor);\n  }\n\n  @Test\n  void update_hotspots_with_pull_when_enabled_language_changed() {\n    var timestamp = Instant.ofEpochMilli(123456789L);\n    var lastHotspotEnabledLanguages = Set.of(SonarLanguage.C);\n    var hotspotKey = \"hotspotKey\";\n    var hotspots = List.of(aServerHotspot(hotspotKey));\n    var cancelMonitor = new SonarLintCancelMonitor();\n    when(hotspotDownloader.downloadFromPull(hotspotApi, PROJECT_KEY, \"branch\", Optional.empty(), cancelMonitor))\n      .thenReturn(new HotspotDownloader.PullResult(timestamp, hotspots, Set.of()));\n    when(issueStore.getLastHotspotEnabledLanguages(\"branch\")).thenReturn(lastHotspotEnabledLanguages);\n    when(issueStore.getLastHotspotSyncTimestamp(\"branch\")).thenReturn(Optional.of(timestamp));\n\n    updater.sync(hotspotApi, PROJECT_KEY, \"branch\", Set.of(SonarLanguage.C, SonarLanguage.GO), cancelMonitor);\n\n    var hotspotCaptor = ArgumentCaptor.forClass(List.class);\n    verify(issueStore).mergeHotspots(eq(\"branch\"), hotspotCaptor.capture(), eq(Set.of()), eq(timestamp), anySet());\n    assertThat(hotspotCaptor.getValue()).hasSize(1);\n    var capturedHotspot = (ServerHotspot) (hotspotCaptor.getValue().get(0));\n    assertThat(capturedHotspot.getKey()).isEqualTo(hotspotKey);\n    verify(hotspotDownloader).downloadFromPull(hotspotApi, projectBinding.projectKey(), \"branch\", Optional.empty(), cancelMonitor);\n  }\n\n  @Test\n  void update_hotspots_with_pull_when_last_enabled_language_were_not_there() {\n    var timestamp = Instant.ofEpochMilli(123456789L);\n    var lastHotspotEnabledLanguages = new HashSet<SonarLanguage>();\n    var hotspotKey = \"hotspotKey\";\n    var hotspots = List.of(aServerHotspot(hotspotKey));\n    var cancelMonitor = new SonarLintCancelMonitor();\n    when(hotspotDownloader.downloadFromPull(hotspotApi, PROJECT_KEY, \"branch\", Optional.empty(), cancelMonitor))\n      .thenReturn(new HotspotDownloader.PullResult(timestamp, hotspots, Set.of()));\n    when(issueStore.getLastHotspotEnabledLanguages(\"branch\")).thenReturn(lastHotspotEnabledLanguages);\n    when(issueStore.getLastHotspotSyncTimestamp(\"branch\")).thenReturn(Optional.of(timestamp));\n\n    updater.sync(hotspotApi, PROJECT_KEY, \"branch\", Set.of(SonarLanguage.C, SonarLanguage.GO), cancelMonitor);\n\n    var hotspotCaptor = ArgumentCaptor.forClass(List.class);\n    verify(issueStore).mergeHotspots(eq(\"branch\"), hotspotCaptor.capture(), eq(Set.of()), eq(timestamp), anySet());\n    assertThat(hotspotCaptor.getValue()).hasSize(1);\n    var capturedHotspot = (ServerHotspot) (hotspotCaptor.getValue().get(0));\n    assertThat(capturedHotspot.getKey()).isEqualTo(hotspotKey);\n    verify(hotspotDownloader).downloadFromPull(hotspotApi, projectBinding.projectKey(), \"branch\", Optional.empty(), cancelMonitor);\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/ServerInfoSynchronizerTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Map;\nimport java.util.Set;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabase;\nimport org.sonarsource.sonarlint.core.http.HttpClientProvider;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.features.Feature;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Settings;\nimport org.sonarsource.sonarlint.core.serverconnection.proto.Sonarlint;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ProtobufFileUtil;\nimport testutils.MockWebServerExtensionWithProtobuf;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\nimport static org.mockito.Mockito.mock;\n\nclass ServerInfoSynchronizerTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n\n  @TempDir\n  Path tmpDir;\n  private ServerInfoSynchronizer synchronizer;\n\n  @BeforeEach\n  void prepare() {\n    var databaseService = mock(SonarLintDatabase.class);\n    var storage = new ConnectionStorage(tmpDir, \"connectionId\", databaseService);\n    synchronizer = new ServerInfoSynchronizer(storage);\n  }\n\n  @Test\n  void it_should_read_version_from_storage_when_available() throws IOException {\n    var connectionPath = tmpDir.resolve(\"636f6e6e656374696f6e4964\");\n    Files.createDirectory(connectionPath);\n    ProtobufFileUtil.writeToFile(Sonarlint.ServerInfo.newBuilder().setVersion(\"1.0.0\").build(), connectionPath.resolve(\"server_info.pb\"));\n\n    var storedServerInfo = synchronizer.readOrSynchronizeServerInfo(new ServerApi(mockServer.endpointParams(), HttpClientProvider.forTesting().getHttpClientWithoutAuth()),\n      new SonarLintCancelMonitor());\n\n    assertThat(storedServerInfo)\n      .extracting(StoredServerInfo::version)\n      .hasToString(\"1.0.0\");\n  }\n\n  @Test\n  void it_should_synchronize_version_and_settings() {\n    mockServer.addStringResponse(\"/api/system/status\", \"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"9.9\\\",\\\"status\\\": \\\"UP\\\"}\");\n    mockServer.addStringResponse(\"/api/features/list\", \"[\\\"sca\\\"]\");\n    mockServer.addProtobufResponse(\"/api/settings/values.protobuf\", Settings.ValuesWsResponse.newBuilder()\n      .addSettings(Settings.Setting.newBuilder()\n        .setKey(\"sonar.multi-quality-mode.enabled\")\n        .setValue(\"true\"))\n      .addSettings(Settings.Setting.newBuilder()\n        .setKey(\"sonar.earlyAccess.misra.enabled\")\n        .setValue(\"true\"))\n      .build());\n\n    var storedServerInfo = synchronizer.readOrSynchronizeServerInfo(new ServerApi(mockServer.endpointParams(), HttpClientProvider.forTesting().getHttpClientWithoutAuth()),\n      new SonarLintCancelMonitor());\n\n    assertThat(storedServerInfo)\n      .extracting(StoredServerInfo::version, StoredServerInfo::features, StoredServerInfo::globalSettings)\n      .containsExactly(Version.create(\"9.9\"), Set.of(Feature.SCA),\n        new ServerSettings(Map.of(\n          \"sonar.multi-quality-mode.enabled\", \"true\",\n          \"sonar.earlyAccess.misra.enabled\", \"true\",\n          \"sonar.misracompliance.enabled\", \"true\")));\n  }\n\n  @Test\n  void it_should_fail_when_server_is_down() {\n    mockServer.addStringResponse(\"/api/system/status\", \"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"9.9\\\",\\\"status\\\": \\\"DOWN\\\"}\");\n\n    var throwable = catchThrowable(\n      () -> synchronizer.readOrSynchronizeServerInfo(new ServerApi(mockServer.endpointParams(), HttpClientProvider.forTesting().getHttpClientWithoutAuth()), new SonarLintCancelMonitor()));\n\n    assertThat(throwable)\n      .isInstanceOf(IllegalStateException.class)\n      .hasMessage(\"Server not ready (DOWN)\");\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/ServerIssueUpdaterTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.Set;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerTaintIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ProjectServerIssueStore;\n\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyList;\nimport static org.mockito.ArgumentMatchers.anySet;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.when;\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ServerIssueFixtures.aServerIssue;\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ServerIssueFixtures.aServerTaintIssue;\n\nclass ServerIssueUpdaterTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private static final String PROJECT_KEY = \"module\";\n  private final IssueDownloader downloader = mock(IssueDownloader.class);\n  private final TaintIssueDownloader taintDownloader = mock(TaintIssueDownloader.class);\n  private final ProjectServerIssueStore issueStore = mock(ProjectServerIssueStore.class);\n  private ProjectBinding projectBinding = new ProjectBinding(PROJECT_KEY, \"\", \"\");\n\n  private ServerIssueUpdater updater;\n  private ServerApi serverApi;\n\n  @BeforeEach\n  void setUp() {\n    serverApi = new ServerApi(mock(ServerApiHelper.class));\n    ConnectionStorage storage = mock(ConnectionStorage.class);\n    var projectStorage = mock(SonarProjectStorage.class);\n    when(storage.project(PROJECT_KEY)).thenReturn(projectStorage);\n    when(projectStorage.findings()).thenReturn(issueStore);\n    updater = new ServerIssueUpdater(storage, downloader, taintDownloader);\n  }\n\n  @Test\n  void update_project_issues_sonarcloud() {\n    var issue = aServerIssue();\n    List<ServerIssue<?>> issues = Collections.singletonList(issue);\n    var cancelMonitor = new SonarLintCancelMonitor();\n    when(downloader.downloadFromBatch(serverApi, \"module:file\", null, cancelMonitor)).thenReturn(issues);\n    when(serverApi.isSonarCloud()).thenReturn(true);\n\n    updater.update(serverApi, projectBinding.projectKey(), \"branch\", Set.of(), cancelMonitor);\n\n    verify(issueStore).replaceAllIssuesOfBranch(eq(\"branch\"), anyList(), eq(Set.of()));\n  }\n\n  @Test\n  void update_project_issues_with_pull_first_time() {\n    var issue = aServerIssue();\n    List<ServerIssue<?>> issues = Collections.singletonList(issue);\n    var queryTimestamp = Instant.now();\n    var lastSync = Optional.<Instant>empty();\n    when(issueStore.getLastIssueSyncTimestamp(\"master\")).thenReturn(lastSync);\n    var cancelMonitor = new SonarLintCancelMonitor();\n    when(downloader.downloadFromPull(serverApi, projectBinding.projectKey(), \"master\", lastSync, cancelMonitor)).thenReturn(new IssueDownloader.PullResult(queryTimestamp, issues, Set.of()));\n\n    updater.update(serverApi, projectBinding.projectKey(), \"master\", Set.of(), cancelMonitor);\n\n    verify(issueStore).mergeIssues(eq(\"master\"), anyList(), anySet(), eq(queryTimestamp), anySet());\n  }\n\n  @Test\n  void update_project_issues_with_pull_using_last_sync() {\n    var issue = aServerIssue();\n    List<ServerIssue<?>> issues = Collections.singletonList(issue);\n    var queryTimestamp = Instant.now();\n    var lastSync = Optional.of(Instant.ofEpochMilli(123456789));\n    var lastIssueEnabledLanguages = Set.of(SonarLanguage.C, SonarLanguage.GO);\n    when(issueStore.getLastIssueEnabledLanguages(\"master\")).thenReturn(lastIssueEnabledLanguages);\n    when(issueStore.getLastIssueSyncTimestamp(\"master\")).thenReturn(lastSync);\n    when(downloader.getEnabledLanguages()).thenReturn(Set.of(SonarLanguage.C, SonarLanguage.GO));\n    var cancelMonitor = new SonarLintCancelMonitor();\n    when(downloader.downloadFromPull(serverApi, projectBinding.projectKey(), \"master\", lastSync, cancelMonitor)).thenReturn(new IssueDownloader.PullResult(queryTimestamp, issues, Set.of()));\n\n    updater.update(serverApi, projectBinding.projectKey(), \"master\", Set.of(), cancelMonitor);\n\n    verify(issueStore).mergeIssues(eq(\"master\"), anyList(), anySet(), eq(queryTimestamp), anySet());\n  }\n\n  @Test\n  void update_project_issues_with_pull_when_there_were_no_enabled_languages() {\n    var issue = aServerIssue();\n    List<ServerIssue<?>> issues = Collections.singletonList(issue);\n    var queryTimestamp = Instant.now();\n    var lastSync = Optional.of(Instant.ofEpochMilli(123456789));\n    var lastIssueEnabledLanguages = new HashSet<SonarLanguage>();\n    when(issueStore.getLastIssueSyncTimestamp(\"master\")).thenReturn(lastSync);\n    when(issueStore.getLastIssueEnabledLanguages(\"master\")).thenReturn(lastIssueEnabledLanguages);\n    when(downloader.getEnabledLanguages()).thenReturn(Set.of(SonarLanguage.C));\n    var cancelMonitor = new SonarLintCancelMonitor();\n    when(downloader.downloadFromPull(serverApi, projectBinding.projectKey(), \"master\", Optional.empty(), cancelMonitor)).thenReturn(new IssueDownloader.PullResult(queryTimestamp, issues, Set.of()));\n    updater.update(serverApi, projectBinding.projectKey(), \"master\", Set.of(), cancelMonitor);\n    verify(downloader).downloadFromPull(serverApi, projectBinding.projectKey(), \"master\", Optional.empty(), cancelMonitor);\n  }\n\n  @Test\n  void update_project_issues_with_pull_when_enabled_language_changed() {\n    var issue = aServerIssue();\n    List<ServerIssue<?>> issues = Collections.singletonList(issue);\n    var queryTimestamp = Instant.now();\n    var lastSync = Optional.of(Instant.ofEpochMilli(123456789));\n    var lastIssueEnabledLanguages = Set.of(SonarLanguage.C, SonarLanguage.GO);\n    when(issueStore.getLastIssueSyncTimestamp(\"master\")).thenReturn(lastSync);\n    when(issueStore.getLastIssueEnabledLanguages(\"master\")).thenReturn(lastIssueEnabledLanguages);\n    when(downloader.getEnabledLanguages()).thenReturn(Set.of(SonarLanguage.C));\n    var cancelMonitor = new SonarLintCancelMonitor();\n    when(downloader.downloadFromPull(serverApi, projectBinding.projectKey(), \"master\", Optional.empty(), cancelMonitor)).thenReturn(new IssueDownloader.PullResult(queryTimestamp, issues, Set.of()));\n    updater.update(serverApi, projectBinding.projectKey(), \"master\", Set.of(), cancelMonitor);\n    verify(downloader).downloadFromPull(serverApi, projectBinding.projectKey(), \"master\", Optional.empty(), cancelMonitor);\n  }\n\n  @Test\n  void update_project_issues_with_pull_when_enabled_language_not_changed() {\n    var issue = aServerIssue();\n    List<ServerIssue<?>> issues = Collections.singletonList(issue);\n    var queryTimestamp = Instant.now();\n    var lastSync = Optional.of(Instant.ofEpochMilli(123456789));\n    var lastIssueEnabledLanguages = Set.of(SonarLanguage.C, SonarLanguage.GO);\n    when(issueStore.getLastIssueSyncTimestamp(\"master\")).thenReturn(lastSync);\n    when(issueStore.getLastIssueEnabledLanguages(\"master\")).thenReturn(lastIssueEnabledLanguages);\n    when(downloader.getEnabledLanguages()).thenReturn(Set.of(SonarLanguage.C, SonarLanguage.GO));\n    var cancelMonitor = new SonarLintCancelMonitor();\n    when(downloader.downloadFromPull(serverApi, projectBinding.projectKey(), \"master\", lastSync, cancelMonitor)).thenReturn(new IssueDownloader.PullResult(queryTimestamp, issues, Set.of()));\n    updater.update(serverApi, projectBinding.projectKey(), \"master\", Set.of(), cancelMonitor);\n    verify(downloader).downloadFromPull(serverApi, projectBinding.projectKey(), \"master\", lastSync, cancelMonitor);\n  }\n\n  @Test\n  void update_project_taints_with_pull_when_there_were_no_enabled_languages() {\n    var issue = aServerTaintIssue();\n    List<ServerTaintIssue> issues = Collections.singletonList(issue);\n    var queryTimestamp = Instant.now();\n    var lastSync = Optional.of(Instant.ofEpochMilli(123456789));\n    var lastIssueEnabledLanguages = new HashSet<SonarLanguage>();\n    when(issueStore.getLastTaintSyncTimestamp(\"master\")).thenReturn(lastSync);\n    when(issueStore.getLastTaintEnabledLanguages(\"master\")).thenReturn(lastIssueEnabledLanguages);\n    var cancelMonitor = new SonarLintCancelMonitor();\n    when(taintDownloader.downloadTaintFromPull(serverApi, projectBinding.projectKey(), \"master\", Optional.empty(), cancelMonitor)).thenReturn(new TaintIssueDownloader.PullTaintResult(queryTimestamp, issues, Set.of()));\n\n    updater.syncTaints(serverApi, projectBinding.projectKey(), \"master\", Set.of(SonarLanguage.C), cancelMonitor);\n    verify(taintDownloader).downloadTaintFromPull(serverApi, projectBinding.projectKey(), \"master\", Optional.empty(), cancelMonitor);\n  }\n\n  @Test\n  void update_project_taints_with_pull_when_enabled_language_changed() {\n    var issue = aServerTaintIssue();\n    List<ServerTaintIssue> issues = Collections.singletonList(issue);\n    var queryTimestamp = Instant.now();\n    var lastSync = Optional.of(Instant.ofEpochMilli(123456789));\n    var lastIssueEnabledLanguages = Set.of(SonarLanguage.C, SonarLanguage.GO);\n    when(issueStore.getLastTaintSyncTimestamp(\"master\")).thenReturn(lastSync);\n    when(issueStore.getLastTaintEnabledLanguages(\"master\")).thenReturn(lastIssueEnabledLanguages);\n    var cancelMonitor = new SonarLintCancelMonitor();\n    when(taintDownloader.downloadTaintFromPull(serverApi, projectBinding.projectKey(), \"master\", Optional.empty(), cancelMonitor)).thenReturn(new TaintIssueDownloader.PullTaintResult(queryTimestamp, issues, Set.of()));\n\n    updater.syncTaints(serverApi, projectBinding.projectKey(), \"master\", Set.of(SonarLanguage.C), cancelMonitor);\n    verify(taintDownloader).downloadTaintFromPull(serverApi, projectBinding.projectKey(), \"master\", Optional.empty(), cancelMonitor);\n  }\n\n  @Test\n  void update_project_taints_with_pull_when_enabled_language_not_changed() {\n    var issue = aServerTaintIssue();\n    List<ServerTaintIssue> issues = Collections.singletonList(issue);\n    var queryTimestamp = Instant.now();\n    var lastSync = Optional.of(Instant.ofEpochMilli(123456789));\n    var lastIssueEnabledLanguages = Set.of(SonarLanguage.C, SonarLanguage.GO);\n    when(issueStore.getLastTaintSyncTimestamp(\"master\")).thenReturn(lastSync);\n    when(issueStore.getLastTaintEnabledLanguages(\"master\")).thenReturn(lastIssueEnabledLanguages);\n    var cancelMonitor = new SonarLintCancelMonitor();\n    when(taintDownloader.downloadTaintFromPull(serverApi, projectBinding.projectKey(), \"master\", lastSync, cancelMonitor)).thenReturn(new TaintIssueDownloader.PullTaintResult(queryTimestamp, issues, Set.of()));\n\n    updater.syncTaints(serverApi, projectBinding.projectKey(), \"master\", Set.of(SonarLanguage.C, SonarLanguage.GO), cancelMonitor);\n    verify(taintDownloader).downloadTaintFromPull(serverApi, projectBinding.projectKey(), \"master\", lastSync, cancelMonitor);\n  }\n\n  @Test\n  void update_file_issues_for_unknown_file() {\n    projectBinding = new ProjectBinding(PROJECT_KEY, \"\", \"ide_prefix\");\n\n    updater.updateFileIssuesIfNeeded(serverApi, PROJECT_KEY, Path.of(\"not_ide_prefix\"), null, new SonarLintCancelMonitor());\n\n    verifyNoInteractions(downloader);\n    verifyNoInteractions(issueStore);\n  }\n\n  @Test\n  void error_downloading_file_issues() {\n    var cancelMonitor = new SonarLintCancelMonitor();\n    when(serverApi.isSonarCloud()).thenReturn(true);\n    when(downloader.downloadFromBatch(serverApi, \"module:file\", null, cancelMonitor)).thenThrow(IllegalArgumentException.class);\n    var filePath = Path.of(\"file\");\n\n    assertThrows(DownloadException.class, () -> updater.updateFileIssuesIfNeeded(serverApi, PROJECT_KEY, filePath, null, cancelMonitor));\n  }\n\n  @Test\n  void update_file_issues_sonarcloud() {\n    var issue = aServerIssue();\n    List<ServerIssue<?>> issues = Collections.singletonList(issue);\n    when(serverApi.isSonarCloud()).thenReturn(true);\n\n    var cancelMonitor = new SonarLintCancelMonitor();\n    when(downloader.downloadFromBatch(serverApi, projectBinding.projectKey() + \":src/main/Foo.java\", null, cancelMonitor)).thenReturn(issues);\n\n    updater.updateFileIssuesIfNeeded(serverApi, PROJECT_KEY, Path.of(\"src/main/Foo.java\"), \"branch\", cancelMonitor);\n\n    verify(issueStore).replaceAllIssuesOfFile(eq(\"branch\"), eq(Path.of(\"src/main/Foo.java\")), anyList());\n  }\n\n  @Test\n  void dont_update_file_issues_with_pull() {\n    updater.updateFileIssuesIfNeeded(serverApi, PROJECT_KEY, Path.of(\"src/main/Foo.java\"), \"branch\", new SonarLintCancelMonitor());\n\n    verify(issueStore, never()).replaceAllIssuesOfFile(eq(\"branch\"), any(), anyList());\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/ServerVersionAndStatusCheckerTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.exception.UnexpectedBodyException;\nimport testutils.MockWebServerExtensionWithProtobuf;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\n\nclass ServerVersionAndStatusCheckerTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n  private ServerVersionAndStatusChecker underTest;\n\n  @BeforeEach\n  void setUp() {\n    underTest = new ServerVersionAndStatusChecker(new ServerApi(mockServer.serverApiHelper()));\n  }\n\n  @Test\n  void failWhenServerNotReady() {\n    mockServer.addStringResponse(\"/api/system/status\", \"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"5.5-SNAPSHOT\\\",\\\"status\\\": \\\"DOWN\\\"}\");\n\n    var throwable = catchThrowable(() -> underTest.checkVersionAndStatus(new SonarLintCancelMonitor()));\n\n    assertThat(throwable).hasMessage(\"Server not ready (DOWN)\");\n  }\n\n  @Test\n  void failWhenIncompatibleVersion() {\n    mockServer.addStringResponse(\"/api/system/status\", \"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"6.7\\\",\\\"status\\\": \\\"UP\\\"}\");\n\n    var throwable = catchThrowable(() -> underTest.checkVersionAndStatus(new SonarLintCancelMonitor()));\n\n    assertThat(throwable).hasMessage(\"Your SonarQube Server instance has version 6.7. Version should be greater or equal to 9.9\");\n  }\n\n  @Test\n  void shouldNotFailWhenIncompatibleVersionSc() {\n    underTest = new ServerVersionAndStatusChecker(new ServerApi(mockServer.serverApiHelper(\"orgKey\")));\n    mockServer.addStringResponse(\"/api/system/status\", \"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"6.7\\\",\\\"status\\\": \\\"UP\\\"}\");\n\n    var throwable = catchThrowable(() -> underTest.checkVersionAndStatus(new SonarLintCancelMonitor()));\n\n    assertThat(throwable).isNull();\n  }\n\n  @Test\n  void responseParsingError() {\n    mockServer.addStringResponse(\"/api/system/status\", \"bla bla\");\n\n    var throwable = catchThrowable(() -> underTest.checkVersionAndStatus(new SonarLintCancelMonitor()));\n\n    assertThat(throwable).isInstanceOf(UnexpectedBodyException.class);\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/StorageExceptionTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.io.IOException;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.StorageException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass StorageExceptionTests {\n  @Test\n  void withCauseAndMessage() {\n    var cause = new IOException(\"cause\");\n    var ex = new StorageException(\"msg\", cause);\n    assertThat(ex.getCause()).isEqualTo(cause);\n    assertThat(ex.getMessage()).isEqualTo(\"msg\");\n    assertThat(ex.getStackTrace()).isNotEmpty();\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/TaintIssueDownloaderTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.net.URLEncoder;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.Optional;\nimport java.util.Set;\nimport javax.annotation.Nullable;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common.Flow;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common.Paging;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common.Severity;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common.TextRange;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues.Location;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues.TaintVulnerabilityLite;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Rules;\nimport testutils.MockWebServerExtensionWithProtobuf;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.assertj.core.api.Assertions.entry;\nimport static org.sonarsource.sonarlint.core.serverconnection.DownloaderUtils.parseProtoImpactSeverity;\nimport static org.sonarsource.sonarlint.core.serverconnection.DownloaderUtils.parseProtoSoftwareQuality;\nimport static org.sonarsource.sonarlint.core.serverconnection.TaintIssueDownloader.hash;\nimport static org.sonarsource.sonarlint.core.serverconnection.TaintIssueDownloader.parseProtoCleanCodeAttribute;\n\nclass TaintIssueDownloaderTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private static final String PROJECT_KEY = \"project\";\n  private static final String FILE_1_KEY = PROJECT_KEY + \":foo/bar/Hello.java\";\n  private static final String FILE_2_KEY = PROJECT_KEY + \":foo/bar/Hello2.java\";\n  private static final String FILE_3_KEY = PROJECT_KEY + \":foo/bar/Hello3.java\";\n\n  private static final String DUMMY_KEY = \"dummyKey\";\n\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n  private ServerApi serverApi;\n\n  private TaintIssueDownloader underTest;\n\n  @BeforeEach\n  void prepare() {\n    underTest = new TaintIssueDownloader(Set.of(SonarLanguage.JAVA));\n    serverApi = new ServerApi(mockServer.serverApiHelper());\n  }\n\n  @Test\n  void test_download_vulnerabilities_from_issue_search() {\n    var ruleSearchResponse = Rules.SearchResponse.newBuilder()\n      .setTotal(1)\n      .addRules(Rules.Rule.newBuilder()\n        .setKey(\"javasecurity:S789\"))\n      .build();\n\n    var issueSearchResponse = Issues.SearchWsResponse.newBuilder()\n      .addIssues(Issues.Issue.newBuilder()\n        .setKey(\"uuid1\")\n        .setRule(\"javasecurity:S789\")\n        .setHash(\"hash2\")\n        .setMessage(\"Primary message 2\")\n        .setTextRange(TextRange.newBuilder().setStartLine(2).setStartOffset(7).setEndLine(4).setEndOffset(9))\n        .setCreationDate(\"2021-01-11T18:17:31+0000\")\n        .setComponent(FILE_1_KEY)\n        .setType(Common.RuleType.VULNERABILITY)\n        .setSeverity(Severity.INFO)\n        .setCleanCodeAttribute(Common.CleanCodeAttribute.COMPLETE)\n        .addImpacts(Common.Impact.newBuilder()\n          .setSoftwareQuality(Common.SoftwareQuality.SECURITY)\n          .setSeverity(Common.ImpactSeverity.HIGH)\n          .build())\n        .addFlows(Flow.newBuilder()\n          .addLocations(Common.Location.newBuilder().setMsg(\"Flow 1 - Location 1\").setComponent(FILE_1_KEY)\n            .setTextRange(TextRange.newBuilder().setStartLine(5).setStartOffset(1).setEndLine(5).setEndOffset(6)))\n          .addLocations(Common.Location.newBuilder().setMsg(\"Flow 1 - Invalid text range\").setComponent(FILE_1_KEY)\n            .setTextRange(TextRange.newBuilder().setStartLine(5).setStartOffset(1).setEndLine(7).setEndOffset(6)))\n          .addLocations(Common.Location.newBuilder().setMsg(\"Flow 1 - Another file\").setComponent(FILE_2_KEY)\n            .setTextRange(TextRange.newBuilder().setStartLine(9).setStartOffset(10).setEndLine(11).setEndOffset(12)))\n          .addLocations(Common.Location.newBuilder().setMsg(\"Flow 1 - Location No Text Range\").setComponent(FILE_3_KEY)))\n        .addFlows(Flow.newBuilder()\n          .addLocations(Common.Location.newBuilder().setMsg(\"Flow 2 - Location 1\").setComponent(FILE_1_KEY)\n            .setTextRange(TextRange.newBuilder().setStartLine(5).setStartOffset(1).setEndLine(5).setEndOffset(6))))\n        .setRuleDescriptionContextKey(\"context1\"))\n      .addIssues(Issues.Issue.newBuilder()\n        .setKey(\"uuid2\")\n        .setRule(\"javasecurity:S789\")\n        .setMessage(\"Project level issue\")\n        .setCreationDate(\"2021-01-11T18:17:31+0000\")\n        .setComponent(PROJECT_KEY)\n        .setType(Common.RuleType.VULNERABILITY)\n        .setSeverity(Severity.CRITICAL)\n        .addFlows(Flow.newBuilder()\n          .addLocations(Common.Location.newBuilder().setMsg(\"Flow 1 - Location 1\").setComponent(FILE_1_KEY)\n            .setTextRange(TextRange.newBuilder().setStartLine(5).setStartOffset(1).setEndLine(5).setEndOffset(6)))\n          .addLocations(Common.Location.newBuilder().setMsg(\"Flow 1 - Invalid text range\").setComponent(FILE_1_KEY)\n            .setTextRange(TextRange.newBuilder().setStartLine(5).setStartOffset(1).setEndLine(7).setEndOffset(6)))\n          .addLocations(Common.Location.newBuilder().setMsg(\"Flow 1 - Another file\").setComponent(FILE_2_KEY)\n            .setTextRange(TextRange.newBuilder().setStartLine(9).setStartOffset(10).setEndLine(11).setEndOffset(12)))\n          .addLocations(Common.Location.newBuilder().setMsg(\"Flow 1 - Location No Text Range\").setComponent(FILE_3_KEY)))\n        .addFlows(Flow.newBuilder()\n          .addLocations(Common.Location.newBuilder().setMsg(\"Flow 2 - Location 1\").setComponent(FILE_1_KEY)\n            .setTextRange(TextRange.newBuilder().setStartLine(5).setStartOffset(1).setEndLine(5).setEndOffset(6))))\n        .setRuleDescriptionContextKey(\"context2\"))\n      .addComponents(Issues.Component.newBuilder()\n        .setKey(PROJECT_KEY))\n      .addComponents(Issues.Component.newBuilder()\n        .setKey(FILE_1_KEY)\n        .setPath(\"foo/bar/Hello.java\"))\n      .addComponents(Issues.Component.newBuilder()\n        .setKey(FILE_2_KEY)\n        .setPath(\"foo/bar/Hello2.java\"))\n      .addComponents(Issues.Component.newBuilder()\n        .setKey(FILE_3_KEY)\n        .setPath(\"foo/bar/Hello3.java\"))\n      .setPaging(Paging.newBuilder()\n        .setPageIndex(1)\n        .setPageSize(500)\n        .setTotal(1))\n      .build();\n\n    mockServer.addProtobufResponse(\n      \"/api/rules/search.protobuf?repositories=roslyn.sonaranalyzer.security.cs,javasecurity,jssecurity,kotlinsecurity,phpsecurity,pythonsecurity,tssecurity,vbnetsecurity,gosecurity&f=repo&s=key&ps=500&p=1\",\n      ruleSearchResponse);\n    mockServer.addProtobufResponse(\n      \"/api/issues/search.protobuf?statuses=OPEN,CONFIRMED,REOPENED,RESOLVED&types=VULNERABILITY&componentKeys=\" + DUMMY_KEY + \"&components=\" + DUMMY_KEY + \"&rules=javasecurity%3AS789&ps=500&p=1\",\n      issueSearchResponse);\n    mockServer.addStringResponse(\"/api/sources/raw?key=\" + URLEncoder.encode(FILE_1_KEY, StandardCharsets.UTF_8), \"Even\\nBefore My\\n\\tCode\\n  Snippet And\\n After\");\n\n    var issues = underTest.downloadTaintFromIssueSearch(serverApi, DUMMY_KEY, null, new SonarLintCancelMonitor());\n\n    assertThat(issues).hasSize(1);\n\n    var taintIssue = issues.get(0);\n\n    assertThat(taintIssue.getMessage()).isEqualTo(\"Primary message 2\");\n    assertThat(taintIssue.getFilePath()).isEqualTo(Path.of(\"foo/bar/Hello.java\"));\n    assertThat(taintIssue.getType()).isEqualTo(RuleType.VULNERABILITY);\n    assertThat(taintIssue.getSeverity()).isEqualTo(IssueSeverity.INFO);\n    assertThat(taintIssue.getCleanCodeAttribute()).hasValue(CleanCodeAttribute.COMPLETE);\n    assertThat(taintIssue.getImpacts()).containsExactly(entry(SoftwareQuality.SECURITY, ImpactSeverity.HIGH));\n\n    assertTextRange(taintIssue.getTextRange(), 2, 7, 4, 9, hash(\"My\\n\\tCode\\n  Snippet\"));\n\n    assertThat(taintIssue.getFlows()).hasSize(2);\n    assertThat(taintIssue.getFlows().get(0).locations()).hasSize(4);\n\n    var flowLocation11 = taintIssue.getFlows().get(0).locations().get(0);\n    assertThat(flowLocation11.filePath()).isEqualTo(Path.of(\"foo/bar/Hello.java\"));\n\n    assertTextRange(flowLocation11.textRange(), 5, 1, 5, 6, hash(\"After\"));\n\n    // Invalid text range\n    assertThat(taintIssue.getFlows().get(0).locations().get(1).textRange().getHash()).isEmpty();\n\n    // 404\n    assertThat(taintIssue.getFlows().get(0).locations().get(2).textRange().getHash()).isEmpty();\n\n    // No text range\n    assertThat(taintIssue.getFlows().get(0).locations().get(3).textRange()).isNull();\n\n    assertThat(taintIssue.getFlows().get(1).locations()).hasSize(1);\n    assertThat(taintIssue.getRuleDescriptionContextKey()).isEqualTo(\"context1\");\n  }\n\n  @Test\n  void test_filter_taint_issues_by_branch_if_branch_parameter_provided() {\n    var response = Issues.SearchWsResponse.newBuilder()\n      .addIssues(Issues.Issue.newBuilder()\n        .setRule(\"javasecurity:S789\")\n        .setCreationDate(\"2021-01-11T18:17:31+0000\")\n        .setComponent(FILE_1_KEY)\n        .setType(Common.RuleType.BUG)\n        .setSeverity(Severity.INFO))\n      .addComponents(Issues.Component.newBuilder()\n        .setKey(FILE_1_KEY)\n        .setPath(\"foo/bar/Hello2.java\"))\n      .setPaging(Paging.newBuilder()\n        .setPageIndex(1)\n        .setPageSize(500)\n        .setTotal(1))\n      .build();\n    var ruleSearchResponse = Rules.SearchResponse.newBuilder()\n      .setTotal(1)\n      .addRules(Rules.Rule.newBuilder()\n        .setKey(\"javasecurity:S789\"))\n      .build();\n    mockServer.addProtobufResponse(\n      \"/api/rules/search.protobuf?repositories=roslyn.sonaranalyzer.security.cs,javasecurity,jssecurity,kotlinsecurity,phpsecurity,pythonsecurity,tssecurity,vbnetsecurity,gosecurity&f=repo&s=key&ps=500&p=1\",\n      ruleSearchResponse);\n    mockServer.addProtobufResponse(\n      \"/api/issues/search.protobuf?statuses=OPEN,CONFIRMED,REOPENED,RESOLVED&types=VULNERABILITY&componentKeys=dummyKey&components=dummyKey&rules=javasecurity%3AS789&branch=branchName&ps=500&p=1\", response);\n\n    var issues = underTest.downloadTaintFromIssueSearch(serverApi, DUMMY_KEY, \"branchName\", new SonarLintCancelMonitor());\n\n    assertThat(issues).hasSize(1);\n  }\n\n  @Test\n  void test_download_taint_issues_from_pull_ws() {\n    var timestamp = Issues.IssuesPullQueryTimestamp.newBuilder().setQueryTimestamp(123L).build();\n    var taint1 = TaintVulnerabilityLite.newBuilder()\n      .setKey(\"uuid1\")\n      .setRuleKey(\"sonarjava:S123\")\n      .setType(Common.RuleType.VULNERABILITY)\n      .setSeverity(Severity.MAJOR)\n      .setCleanCodeAttribute(Common.CleanCodeAttribute.COMPLETE)\n      .addImpacts(Common.Impact.newBuilder()\n        .setSoftwareQuality(Common.SoftwareQuality.SECURITY)\n        .setSeverity(Common.ImpactSeverity.HIGH)\n        .build())\n      .setMainLocation(Location.newBuilder().setFilePath(\"foo/bar/Hello.java\").setMessage(\"Primary message\")\n        .setTextRange(Issues.TextRange.newBuilder().setStartLine(1).setStartLineOffset(2).setEndLine(3)\n          .setEndLineOffset(4).setHash(\"hash\")))\n      .setCreationDate(123456789L)\n      .addFlows(Issues.Flow.newBuilder()\n        .addLocations(Location.newBuilder().setMessage(\"Flow 1 - Location 1\").setFilePath(\"foo/bar/Hello.java\")\n          .setTextRange(Issues.TextRange.newBuilder().setStartLine(5).setStartLineOffset(1).setEndLine(5).setEndLineOffset(6).setHash(\"hashLocation11\")))\n        .addLocations(Location.newBuilder().setMessage(\"Flow 1 - Another file\").setFilePath(\"foo/bar/Hello2.java\")\n          .setTextRange(Issues.TextRange.newBuilder().setStartLine(9).setStartLineOffset(10).setEndLine(11).setEndLineOffset(12).setHash(\"hashLocation12\")))\n        .addLocations(Location.newBuilder().setMessage(\"Flow 1 - Location No Text Range\").setFilePath(\"foo/bar/Hello.java\")))\n      .addFlows(Issues.Flow.newBuilder()\n        .addLocations(Location.newBuilder().setMessage(\"Flow 2 - Location 1\").setFilePath(\"foo/bar/Hello.java\")\n          .setTextRange(Issues.TextRange.newBuilder().setStartLine(5).setStartLineOffset(1).setEndLine(5).setEndLineOffset(6).setHash(\"hashLocation21\"))))\n      .setRuleDescriptionContextKey(\"context\")\n      .build();\n\n    var taintNoRange = TaintVulnerabilityLite.newBuilder()\n      .setKey(\"uuid2\")\n      .setRuleKey(\"sonarjava:S123\")\n      .setType(Common.RuleType.VULNERABILITY)\n      .setSeverity(Common.Severity.MINOR)\n      .setMainLocation(Location.newBuilder().setFilePath(\"foo/bar/Hello.java\").setMessage(\"Primary message\"))\n      .setCreationDate(123456789L)\n      .build();\n\n    var taintResolved = TaintVulnerabilityLite.newBuilder(taint1)\n      .setResolved(true)\n      .build();\n\n    mockServer.addProtobufResponseDelimited(\"/api/issues/pull_taint?projectKey=\" + DUMMY_KEY + \"&branchName=myBranch&languages=java\", timestamp, taint1, taintNoRange, taintResolved);\n\n    var result = underTest.downloadTaintFromPull(serverApi, DUMMY_KEY, \"myBranch\", Optional.empty(), new SonarLintCancelMonitor());\n    assertThat(result.getQueryTimestamp()).isEqualTo(Instant.ofEpochMilli(123L));\n\n    assertThat(result.getChangedTaintIssues()).hasSize(3);\n    assertThat(result.getClosedIssueKeys()).isEmpty();\n\n    var serverTaintIssue = result.getChangedTaintIssues().get(0);\n    assertThat(serverTaintIssue.getSonarServerKey()).isEqualTo(\"uuid1\");\n    assertThat(serverTaintIssue.getMessage()).isEqualTo(\"Primary message\");\n    assertThat(serverTaintIssue.getFilePath()).isEqualTo(Path.of(\"foo/bar/Hello.java\"));\n    assertThat(serverTaintIssue.getSeverity()).isEqualTo(IssueSeverity.MAJOR);\n    assertThat(serverTaintIssue.getType()).isEqualTo(RuleType.VULNERABILITY);\n    assertThat(serverTaintIssue.getCleanCodeAttribute()).hasValue(CleanCodeAttribute.COMPLETE);\n    assertThat(serverTaintIssue.getImpacts()).containsExactly(entry(SoftwareQuality.SECURITY, ImpactSeverity.HIGH));\n\n    assertTextRange(serverTaintIssue.getTextRange(), 1, 2, 3, 4, \"hash\");\n\n    assertThat(serverTaintIssue.getFlows()).hasSize(2);\n    assertThat(serverTaintIssue.getFlows().get(0).locations()).hasSize(3);\n\n    var flowLocation11 = serverTaintIssue.getFlows().get(0).locations().get(0);\n    assertThat(flowLocation11.filePath()).isEqualTo(Path.of(\"foo/bar/Hello.java\"));\n    assertTextRange(flowLocation11.textRange(), 5, 1, 5, 6, \"hashLocation11\");\n\n    // No text range\n    assertThat(serverTaintIssue.getFlows().get(0).locations().get(2).textRange()).isNull();\n\n    assertThat(serverTaintIssue.getFlows().get(1).locations()).hasSize(1);\n    assertThat(serverTaintIssue.getRuleDescriptionContextKey()).isEqualTo(\"context\");\n\n    var taintIssueNoRange = result.getChangedTaintIssues().get(1);\n    assertThat(taintIssueNoRange.getSonarServerKey()).isEqualTo(\"uuid2\");\n    assertThat(taintIssueNoRange.getFilePath()).isEqualTo(Path.of(\"foo/bar/Hello.java\"));\n    assertThat(taintIssueNoRange.getTextRange()).isNull();\n\n    var resolvedTaint = result.getChangedTaintIssues().get(2);\n    assertThat(resolvedTaint.isResolved()).isTrue();\n  }\n\n  @Test\n  void parse_clean_code_attribute_from_stream() {\n    var partialIssue = Issues.Issue.newBuilder().setCleanCodeAttribute(Common.CleanCodeAttribute.CLEAR).build();\n    var cleanCodeAttribute = parseProtoCleanCodeAttribute(partialIssue);\n    assertThat(cleanCodeAttribute).isEqualTo(CleanCodeAttribute.CLEAR);\n  }\n\n  @Test\n  void parse_clean_code_attribute_from_stream_missing() {\n    var partialIssue = Issues.Issue.newBuilder().build();\n    var cleanCodeAttribute = parseProtoCleanCodeAttribute(partialIssue);\n    assertThat(cleanCodeAttribute).isNull();\n  }\n\n  @Test\n  void parse_clean_code_attribute_from_stream_unknown() {\n    var partialIssue = Issues.Issue.newBuilder().setCleanCodeAttribute(Common.CleanCodeAttribute.UNKNOWN_ATTRIBUTE).build();\n    var cleanCodeAttribute = parseProtoCleanCodeAttribute(partialIssue);\n    assertThat(cleanCodeAttribute).isNull();\n  }\n\n  @Test\n  void parse_clean_code_attribute_from_lite_stream() {\n    var partialIssue = Issues.TaintVulnerabilityLite.newBuilder().setKey(\"key\").setCleanCodeAttribute(Common.CleanCodeAttribute.CLEAR).build();\n    var cleanCodeAttribute = parseProtoCleanCodeAttribute(partialIssue);\n    assertThat(cleanCodeAttribute).isEqualTo(CleanCodeAttribute.CLEAR);\n  }\n\n  @Test\n  void parse_clean_code_attribute_from_lite_stream_missing() {\n    var partialIssue = Issues.TaintVulnerabilityLite.newBuilder().setKey(\"key\").build();\n    var cleanCodeAttribute = parseProtoCleanCodeAttribute(partialIssue);\n    assertThat(cleanCodeAttribute).isNull();\n  }\n\n  @Test\n  void parse_clean_code_attribute_from_lite_stream_unknown() {\n    var partialIssue = Issues.TaintVulnerabilityLite.newBuilder().setKey(\"key\").setCleanCodeAttribute(Common.CleanCodeAttribute.UNKNOWN_ATTRIBUTE).build();\n    var cleanCodeAttribute = parseProtoCleanCodeAttribute(partialIssue);\n    assertThat(cleanCodeAttribute).isNull();\n  }\n\n  @Test\n  void parse_software_quality_and_impact_severity() {\n    var impact = Common.Impact.newBuilder().setSoftwareQuality(Common.SoftwareQuality.SECURITY).setSeverity(Common.ImpactSeverity.MEDIUM).build();\n    assertThat(parseProtoSoftwareQuality(impact)).isEqualTo(SoftwareQuality.SECURITY);\n    assertThat(parseProtoImpactSeverity(impact)).isEqualTo(ImpactSeverity.MEDIUM);\n  }\n\n  @Test\n  void parse_software_quality_and_impact_severity_info() {\n    var impact = Common.Impact.newBuilder().setSoftwareQuality(Common.SoftwareQuality.SECURITY).setSeverity(Common.ImpactSeverity.ImpactSeverity_INFO).build();\n    assertThat(parseProtoSoftwareQuality(impact)).isEqualTo(SoftwareQuality.SECURITY);\n    assertThat(parseProtoImpactSeverity(impact)).isEqualTo(ImpactSeverity.INFO);\n  }\n\n  @Test\n  void parse_software_quality_and_impact_severity_blocker() {\n    var impact = Common.Impact.newBuilder().setSoftwareQuality(Common.SoftwareQuality.SECURITY).setSeverity(Common.ImpactSeverity.ImpactSeverity_BLOCKER).build();\n    assertThat(parseProtoSoftwareQuality(impact)).isEqualTo(SoftwareQuality.SECURITY);\n    assertThat(parseProtoImpactSeverity(impact)).isEqualTo(ImpactSeverity.BLOCKER);\n  }\n\n  @Test\n  void parse_software_quality_unknown() {\n    var impact = Common.Impact.newBuilder().setSoftwareQuality(Common.SoftwareQuality.UNKNOWN_IMPACT_QUALITY).setSeverity(Common.ImpactSeverity.HIGH).build();\n    assertThatThrownBy(() -> parseProtoSoftwareQuality(impact))\n      .isInstanceOf(IllegalArgumentException.class)\n      .hasMessage(\"Unknown or missing software quality\");\n  }\n\n  @Test\n  void parse_impact_severity_unknown() {\n    var impact = Common.Impact.newBuilder().setSeverity(Common.ImpactSeverity.UNKNOWN_IMPACT_SEVERITY).setSoftwareQuality(Common.SoftwareQuality.MAINTAINABILITY).build();\n    assertThatThrownBy(() -> parseProtoImpactSeverity(impact))\n      .isInstanceOf(IllegalArgumentException.class)\n      .hasMessage(\"Unknown or missing impact severity\");\n  }\n\n  private static void assertTextRange(@Nullable TextRangeWithHash textRangeWithHash, int startLine, int startLineOffset,\n    int endLine, int endLineOffset, String hash) {\n    assertThat(textRangeWithHash).isNotNull();\n    assertThat(textRangeWithHash.getStartLine()).isEqualTo(startLine);\n    assertThat(textRangeWithHash.getStartLineOffset()).isEqualTo(startLineOffset);\n    assertThat(textRangeWithHash.getEndLine()).isEqualTo(endLine);\n    assertThat(textRangeWithHash.getEndLineOffset()).isEqualTo(endLineOffset);\n    assertThat(textRangeWithHash.getHash()).isEqualTo(hash);\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/UserSynchronizerTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;\nimport org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabase;\nimport org.sonarsource.sonarlint.core.http.HttpClientProvider;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApi;\nimport testutils.MockWebServerExtensionWithProtobuf;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\n\nclass UserSynchronizerTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @RegisterExtension\n  static MockWebServerExtensionWithProtobuf mockServer = new MockWebServerExtensionWithProtobuf();\n\n  @TempDir\n  Path tmpDir;\n  private UserSynchronizer synchronizer;\n  private ConnectionStorage storage;\n\n  @BeforeEach\n  void prepare() {\n    var databaseService = mock(SonarLintDatabase.class);\n    storage = new ConnectionStorage(tmpDir, \"connectionId\", databaseService);\n    synchronizer = new UserSynchronizer(storage);\n  }\n\n  @Test\n  void it_should_synchronize_user_id_on_sonarcloud() {\n    mockServer.addStringResponse(\"/api/users/current\", \"\"\"\n      {\n        \"isLoggedIn\": true,\n        \"id\": \"16c9b3b3-3f7e-4d61-91fe-31d731456c08\",\n        \"login\": \"obiwan.kenobi\"\n      }\"\"\");\n\n    var serverApi = new ServerApi(mockServer.endpointParams(\"orgKey\"), HttpClientProvider.forTesting().getHttpClientWithoutAuth());\n    synchronizer.synchronize(serverApi, new SonarLintCancelMonitor());\n\n    var storedUserId = storage.user().read();\n    assertThat(storedUserId)\n      .isPresent()\n      .contains(\"16c9b3b3-3f7e-4d61-91fe-31d731456c08\");\n  }\n\n  @Test\n  void it_should_synchronize_user_id_on_sonarqube_server() {\n    mockServer.addStringResponse(\"/api/users/current\", \"\"\"\n      {\n        \"isLoggedIn\": true,\n        \"id\": \"00000000-0000-0000-0000-000000000001\",\n        \"login\": \"obiwan.kenobi\"\n      }\"\"\");\n\n    var serverApi = new ServerApi(mockServer.endpointParams(), HttpClientProvider.forTesting().getHttpClientWithoutAuth());\n    synchronizer.synchronize(serverApi, new SonarLintCancelMonitor());\n\n    var storedUserId = storage.user().read();\n    assertThat(storedUserId)\n      .isPresent()\n      .contains(\"00000000-0000-0000-0000-000000000001\");\n  }\n\n  @Test\n  void it_should_not_store_null_user_id() {\n    mockServer.addStringResponse(\"/api/users/current\", \"{}\");\n\n    var serverApi = new ServerApi(mockServer.endpointParams(\"orgKey\"), HttpClientProvider.forTesting().getHttpClientWithoutAuth());\n    synchronizer.synchronize(serverApi, new SonarLintCancelMonitor());\n\n    var storedUserId = storage.user().read();\n    assertThat(storedUserId).isEmpty();\n  }\n\n  @Test\n  void it_should_store_user_id_in_correct_file() throws IOException {\n    mockServer.addStringResponse(\"/api/users/current\", \"\"\"\n      {\n        \"isLoggedIn\": true,\n        \"id\": \"test-user-id\",\n        \"login\": \"test.user\"\n      }\"\"\");\n\n    var serverApi = new ServerApi(mockServer.endpointParams(\"orgKey\"), HttpClientProvider.forTesting().getHttpClientWithoutAuth());\n    synchronizer.synchronize(serverApi, new SonarLintCancelMonitor());\n\n    var connectionPath = tmpDir.resolve(\"636f6e6e656374696f6e4964\");\n    var userFile = connectionPath.resolve(\"user.pb\");\n    assertThat(userFile).exists();\n    assertThat(Files.size(userFile)).isGreaterThan(0);\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/VersionUtilsTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection;\n\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.commons.Version;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.sonarsource.sonarlint.core.serverconnection.VersionUtils.getCurrentLts;\nimport static org.sonarsource.sonarlint.core.serverconnection.VersionUtils.getMinimalSupportedVersion;\n\nclass VersionUtilsTests {\n\n  @Test\n  void grace_period_should_be_false_if_connected_current_lts() {\n    assertThat(VersionUtils.isVersionSupportedDuringGracePeriod(getCurrentLts())).isFalse();\n    assertThat(VersionUtils.isVersionSupportedDuringGracePeriod(Version.create(getCurrentLts().getName() + \".1\"))).isFalse();\n  }\n\n  @Test\n  void grace_period_should_be_false_if_connected_outdated_version() {\n    assertThat(VersionUtils.isVersionSupportedDuringGracePeriod(Version.create(\"5.9\"))).isFalse();\n  }\n\n  @Test\n  void grace_period_should_be_true_if_connected_during_grace_period() {\n    // read isVersionSupportedDuringGracePeriod javadoc\n    assertThat(VersionUtils.isVersionSupportedDuringGracePeriod(getMinimalSupportedVersion())).isFalse();\n    assertThat(VersionUtils.isVersionSupportedDuringGracePeriod(Version.create(getMinimalSupportedVersion().getName() + \".1\"))).isFalse();\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/aicodefix/AiCodeFixRepositoryTest.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.aicodefix;\n\nimport java.nio.file.Path;\nimport java.util.Set;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabase;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass AiCodeFixRepositoryTest {\n\n  @RegisterExtension\n  static SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @TempDir\n  Path temp;\n\n  @Test\n  void upsert_and_get_should_persist_to_h2_file_database() {\n    // Given a file-based H2 database under a temporary storage root\n    var storageRoot = temp.resolve(\"storage\");\n\n    var db = new SonarLintDatabase(storageRoot);\n    var aiCodeFixRepo = new AiCodeFixRepository(db.dsl());\n\n    var entityToStore = new AiCodeFix(\n      \"test-connection\",\n      Set.of(\"java:S100\", \"js:S200\"),\n      true,\n      AiCodeFix.Enablement.ENABLED_FOR_SOME_PROJECTS,\n      Set.of(\"project-a\", \"project-b\")\n    );\n\n    // When we upsert the entity\n    aiCodeFixRepo.upsert(entityToStore);\n\n    // And shutdown the first DB to force closing connections\n    db.shutdown();\n\n    // Create a new repository with a fresh DB instance pointing to the same storage root\n    var db2 = new SonarLintDatabase(storageRoot);\n    var repo2 = new AiCodeFixRepository(db2.dsl());\n    // With a different connection id, no settings should be visible\n    var loadedOptDifferent = repo2.get(\"test-connection-2\");\n    assertThat(loadedOptDifferent).isEmpty();\n\n    // With the same connection id, we should read back exactly what we stored\n    var repoSame = new AiCodeFixRepository(db2.dsl());\n    var loadedOpt = repoSame.get(\"test-connection\");\n    assertThat(loadedOpt).isPresent();\n    var loaded = loadedOpt.get();\n\n    assertThat(loaded.supportedRules()).containsExactlyInAnyOrder(\"java:S100\", \"js:S200\");\n    assertThat(loaded.organizationEligible()).isTrue();\n    assertThat(loaded.enablement()).isEqualTo(AiCodeFix.Enablement.ENABLED_FOR_SOME_PROJECTS);\n    assertThat(loaded.enabledProjectKeys()).containsExactlyInAnyOrder(\"project-a\", \"project-b\");\n\n    db2.shutdown();\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/issues/KnownFindingsRepositoryTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.issues;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.UUID;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.KnownFinding;\nimport org.sonarsource.sonarlint.core.commons.LineWithHash;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabase;\n\nimport static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;\n\nclass KnownFindingsRepositoryTests {\n\n  @RegisterExtension\n  static SonarLintLogTester logTester = new SonarLintLogTester();\n  private SonarLintDatabase db;\n\n  @AfterEach\n  void shutdown() {\n    db.shutdown();\n  }\n\n  @Test\n  void testKnownFindingsRepository(@TempDir Path temp) {\n    var storageRoot = temp.resolve(\"storage\");\n\n    db = new SonarLintDatabase(storageRoot);\n    var repo = new KnownFindingsRepository(db);\n\n    var filePath = Path.of(\"/file/path\");\n    var configScopeId = \"configScopeId\";\n    var issues = new ArrayList<KnownFinding>();\n    var issueUuid1 = UUID.randomUUID();\n    var issueIntroDate1 = Instant.now();\n    var issue1 = new KnownFinding(issueUuid1, \"test-message\", new TextRangeWithHash(1, 2, 3, 4, \"hash1\"),\n      new LineWithHash(1, \"hash\"), \"test-issue-rule-1\", \"Test issue message 1\", issueIntroDate1);\n    issues.add(issue1);\n    var issueUuid2 = UUID.randomUUID();\n    var issueIntroDate2 = Instant.now();\n    var issue2 = new KnownFinding(issueUuid2, \"test-message\", new TextRangeWithHash(5, 6, 7, 8, \"hash2\"),\n      new LineWithHash(1, \"hash\"), \"test-issue-rule-2\", \"Test issue message 2\", issueIntroDate2);\n    issues.add(issue2);\n    var hotspots = new ArrayList<KnownFinding>();\n    var hotspotUuid1 = UUID.randomUUID();\n    var hotspotIntroDate1 = Instant.now();\n    var hotspot1 = new KnownFinding(hotspotUuid1, \"test-message\", new TextRangeWithHash(1, 2, 3, 4, \"hash1\"),\n      new LineWithHash(1, \"hash\"), \"test-hotspot-rule-1\", \"Test hotspot message 1\", hotspotIntroDate1);\n    hotspots.add(hotspot1);\n    var hotspotUuid2 = UUID.randomUUID();\n    var hotspotIntroDate2 = Instant.now();\n    var hotspot2 = new KnownFinding(hotspotUuid2, \"test-message\", new TextRangeWithHash(5, 6, 7, 8, \"hash2\"),\n      new LineWithHash(1, \"hash\"), \"test-hotspot-rule-2\", \"Test hotspot message 2\", hotspotIntroDate2);\n    hotspots.add(hotspot2);\n\n    repo.storeKnownIssues(configScopeId, filePath, issues);\n    repo.storeKnownSecurityHotspots(configScopeId, filePath, hotspots);\n\n    var knownIssues = repo.loadIssuesForFile(configScopeId, filePath);\n    var knownHotspots = repo.loadSecurityHotspotsForFile(configScopeId, filePath);\n\n    assertThat(knownIssues).hasSize(2);\n    assertThat(knownHotspots).hasSize(2);\n    var knownIssue = knownIssues.get(0);\n    assertThat(knownIssue.getRuleKey()).isEqualTo(issue1.getRuleKey());\n    assertThat(knownIssue.getServerKey()).isEqualTo(issue1.getServerKey());\n    assertThat(knownIssue.getMessage()).isEqualTo(issue1.getMessage());\n    assertThat(knownIssue.getTextRangeWithHash()).isEqualTo(issue1.getTextRangeWithHash());\n    assertThat(knownIssue.getLineWithHash().getNumber()).isEqualTo(issue1.getLineWithHash().getNumber());\n    assertThat(knownIssue.getLineWithHash().getHash()).isEqualTo(issue1.getLineWithHash().getHash());\n    var knownHotspot = knownHotspots.get(0);\n    assertThat(knownHotspot.getRuleKey()).isEqualTo(hotspot1.getRuleKey());\n    assertThat(knownHotspot.getServerKey()).isEqualTo(hotspot1.getServerKey());\n    assertThat(knownHotspot.getMessage()).isEqualTo(hotspot1.getMessage());\n    assertThat(knownHotspot.getTextRangeWithHash()).isEqualTo(hotspot1.getTextRangeWithHash());\n    assertThat(knownHotspot.getLineWithHash().getNumber()).isEqualTo(hotspot1.getLineWithHash().getNumber());\n    assertThat(knownHotspot.getLineWithHash().getHash()).isEqualTo(hotspot1.getLineWithHash().getHash());\n  }\n\n  @Test\n  void should_allow_for_a_long_message(@TempDir Path temp) {\n    var storageRoot = temp.resolve(\"storage\");\n\n    db = new SonarLintDatabase(storageRoot);\n    var repo = new KnownFindingsRepository(db);\n    var longMessage = \"m\".repeat(10000);\n    var path = Path.of(\"path\");\n    repo.storeKnownIssues(\"configScope\", path, List.of(new KnownFinding(UUID.randomUUID(), \"serverKey\", null, null, \"rule:key\", longMessage, Instant.now())));\n\n    var issues = repo.loadIssuesForFile(\"configScope\", path);\n\n    assertThat(issues).hasSize(1);\n    assertThat(issues.get(0).getMessage()).isEqualTo(longMessage);\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/issues/LocalOnlyIssuesRepositoryTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.issues;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.UUID;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.LineWithHash;\nimport org.sonarsource.sonarlint.core.commons.LocalOnlyIssue;\nimport org.sonarsource.sonarlint.core.commons.LocalOnlyIssueResolution;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabase;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass LocalOnlyIssuesRepositoryTests {\n\n  @RegisterExtension\n  static SonarLintLogTester logTester = new SonarLintLogTester();\n  private LocalOnlyIssuesRepository repository;\n  private SonarLintDatabase db;\n\n  @BeforeEach\n  void prepare(@TempDir Path temp) {\n    var storageRoot = temp.resolve(\"storage\");\n    db = new SonarLintDatabase(storageRoot);\n    repository = new LocalOnlyIssuesRepository(db.dsl());\n  }\n\n  @AfterEach\n  void shutdown() {\n    db.shutdown();\n  }\n\n  @Test\n  void should_store_and_load_issues_for_file() {\n    var filePath = Path.of(\"/file/path\");\n    var configScopeId = \"configScopeId\";\n    var issueUuid1 = UUID.randomUUID();\n    var issue1 = new LocalOnlyIssue(issueUuid1, filePath, new TextRangeWithHash(1, 2, 3, 4, \"hash1\"),\n      new LineWithHash(1, \"linehash1\"), \"test-rule-1\", \"Test issue message 1\", null);\n    var issueUuid2 = UUID.randomUUID();\n    var issue2 = new LocalOnlyIssue(issueUuid2, filePath, new TextRangeWithHash(5, 6, 7, 8, \"hash2\"),\n      new LineWithHash(2, \"linehash2\"), \"test-rule-2\", \"Test issue message 2\", null);\n\n    repository.storeLocalOnlyIssue(configScopeId, issue1);\n    repository.storeLocalOnlyIssue(configScopeId, issue2);\n\n    var loadedIssues = repository.loadForFile(configScopeId, filePath);\n\n    assertThat(loadedIssues).hasSize(2);\n    assertThat(loadedIssues).extracting(LocalOnlyIssue::getId).containsExactlyInAnyOrder(issueUuid1, issueUuid2);\n    var loadedIssue1 = loadedIssues.stream().filter(i -> i.getId().equals(issueUuid1)).findFirst().orElseThrow();\n    assertThat(loadedIssue1.getRuleKey()).isEqualTo(\"test-rule-1\");\n    assertThat(loadedIssue1.getMessage()).isEqualTo(\"Test issue message 1\");\n    assertThat(loadedIssue1.getTextRangeWithHash()).isEqualTo(new TextRangeWithHash(1, 2, 3, 4, \"hash1\"));\n    assertThat(loadedIssue1.getLineWithHash().getNumber()).isEqualTo(1);\n    assertThat(loadedIssue1.getLineWithHash().getHash()).isEqualTo(\"linehash1\");\n    assertThat(loadedIssue1.getResolution()).isNull();\n  }\n\n  @Test\n  void should_store_and_load_resolved_issue() {\n    var filePath = Path.of(\"/file/path\");\n    var configScopeId = \"configScopeId\";\n    var issueUuid = UUID.randomUUID();\n    var resolutionDate = Instant.now().truncatedTo(ChronoUnit.MILLIS);\n    var resolution = new LocalOnlyIssueResolution(IssueStatus.WONT_FIX, resolutionDate, \"Test comment\");\n    var issue = new LocalOnlyIssue(issueUuid, filePath, new TextRangeWithHash(1, 2, 3, 4, \"hash1\"),\n      new LineWithHash(1, \"linehash1\"), \"test-rule-1\", \"Test issue message\", resolution);\n\n    repository.storeLocalOnlyIssue(configScopeId, issue);\n\n    var loadedIssues = repository.loadForFile(configScopeId, filePath);\n\n    assertThat(loadedIssues).hasSize(1);\n    var loadedIssue = loadedIssues.get(0);\n    assertThat(loadedIssue.getId()).isEqualTo(issueUuid);\n    assertThat(loadedIssue.getResolution()).isNotNull();\n    assertThat(loadedIssue.getResolution().getStatus()).isEqualTo(IssueStatus.WONT_FIX);\n    assertThat(loadedIssue.getResolution().getResolutionDate()).isEqualTo(resolutionDate);\n    assertThat(loadedIssue.getResolution().getComment()).isEqualTo(\"Test comment\");\n  }\n\n  @Test\n  void should_store_issue_without_text_range_and_line_hash() {\n    var filePath = Path.of(\"/file/path\");\n    var configScopeId = \"configScopeId\";\n    var issueUuid = UUID.randomUUID();\n    var issue = new LocalOnlyIssue(issueUuid, filePath, null, null, \"test-rule-1\", \"Test issue message\", null);\n\n    repository.storeLocalOnlyIssue(configScopeId, issue);\n\n    var loadedIssues = repository.loadForFile(configScopeId, filePath);\n\n    assertThat(loadedIssues).hasSize(1);\n    var loadedIssue = loadedIssues.get(0);\n    assertThat(loadedIssue.getId()).isEqualTo(issueUuid);\n    assertThat(loadedIssue.getTextRangeWithHash()).isNull();\n    assertThat(loadedIssue.getLineWithHash()).isNull();\n  }\n\n  @Test\n  void should_load_all_issues_for_configuration_scope() {\n    var configScopeId = \"configScopeId\";\n    var filePath1 = Path.of(\"/file/path1\");\n    var filePath2 = Path.of(\"/file/path2\");\n    var issue1 = new LocalOnlyIssue(UUID.randomUUID(), filePath1, null, null, \"test-rule-1\", \"Message 1\", null);\n    var issue2 = new LocalOnlyIssue(UUID.randomUUID(), filePath2, null, null, \"test-rule-2\", \"Message 2\", null);\n    var issue3 = new LocalOnlyIssue(UUID.randomUUID(), filePath1, null, null, \"test-rule-3\", \"Message 3\", null);\n\n    repository.storeLocalOnlyIssue(configScopeId, issue1);\n    repository.storeLocalOnlyIssue(configScopeId, issue2);\n    repository.storeLocalOnlyIssue(configScopeId, issue3);\n\n    var allIssues = repository.loadAll(configScopeId);\n\n    assertThat(allIssues).hasSize(3);\n    assertThat(allIssues).extracting(LocalOnlyIssue::getId)\n      .containsExactlyInAnyOrder(issue1.getId(), issue2.getId(), issue3.getId());\n  }\n\n  @Test\n  void should_find_issue_by_id() {\n    var configScopeId = \"configScopeId\";\n    var issueUuid = UUID.randomUUID();\n    var issue = new LocalOnlyIssue(issueUuid, Path.of(\"/file/path\"), null, null, \"test-rule-1\", \"Test message\", null);\n\n    repository.storeLocalOnlyIssue(configScopeId, issue);\n\n    var foundIssue = repository.find(issueUuid);\n\n    assertThat(foundIssue).isPresent();\n    assertThat(foundIssue.get().getId()).isEqualTo(issueUuid);\n    assertThat(foundIssue.get().getRuleKey()).isEqualTo(\"test-rule-1\");\n    assertThat(foundIssue.get().getMessage()).isEqualTo(\"Test message\");\n  }\n\n  @Test\n  void should_return_empty_when_issue_not_found() {\n    var foundIssue = repository.find(UUID.randomUUID());\n\n    assertThat(foundIssue).isEmpty();\n  }\n\n  @Test\n  void should_remove_issue() {\n    var configScopeId = \"configScopeId\";\n    var filePath = Path.of(\"/file/path\");\n    var issueUuid1 = UUID.randomUUID();\n    var issueUuid2 = UUID.randomUUID();\n    var issue1 = new LocalOnlyIssue(issueUuid1, filePath, null, null, \"test-rule-1\", \"Message 1\", null);\n    var issue2 = new LocalOnlyIssue(issueUuid2, filePath, null, null, \"test-rule-2\", \"Message 2\", null);\n\n    repository.storeLocalOnlyIssue(configScopeId, issue1);\n    repository.storeLocalOnlyIssue(configScopeId, issue2);\n\n    var removed = repository.removeIssue(issueUuid1);\n\n    assertThat(removed).isTrue();\n    var remainingIssues = repository.loadForFile(configScopeId, filePath);\n    assertThat(remainingIssues).hasSize(1);\n    assertThat(remainingIssues.get(0).getId()).isEqualTo(issueUuid2);\n  }\n\n  @Test\n  void should_return_false_when_removing_nonexistent_issue() {\n    var removed = repository.removeIssue(UUID.randomUUID());\n\n    assertThat(removed).isFalse();\n  }\n\n  @Test\n  void should_remove_all_issues_for_file() {\n    var configScopeId = \"configScopeId\";\n    var filePath1 = Path.of(\"/file/path1\");\n    var filePath2 = Path.of(\"/file/path2\");\n    var issue1 = new LocalOnlyIssue(UUID.randomUUID(), filePath1, null, null, \"test-rule-1\", \"Message 1\", null);\n    var issue2 = new LocalOnlyIssue(UUID.randomUUID(), filePath1, null, null, \"test-rule-2\", \"Message 2\", null);\n    var issue3 = new LocalOnlyIssue(UUID.randomUUID(), filePath2, null, null, \"test-rule-3\", \"Message 3\", null);\n\n    repository.storeLocalOnlyIssue(configScopeId, issue1);\n    repository.storeLocalOnlyIssue(configScopeId, issue2);\n    repository.storeLocalOnlyIssue(configScopeId, issue3);\n\n    var removed = repository.removeAllIssuesForFile(configScopeId, filePath1);\n\n    assertThat(removed).isTrue();\n    assertThat(repository.loadForFile(configScopeId, filePath1)).isEmpty();\n    assertThat(repository.loadForFile(configScopeId, filePath2)).hasSize(1);\n  }\n\n  @Test\n  void should_update_existing_issue() {\n    var configScopeId = \"configScopeId\";\n    var filePath = Path.of(\"/file/path\");\n    var issueUuid = UUID.randomUUID();\n    var issue1 = new LocalOnlyIssue(issueUuid, filePath, null, null, \"test-rule-1\", \"Original message\", null);\n\n    repository.storeLocalOnlyIssue(configScopeId, issue1);\n\n    var resolution = new LocalOnlyIssueResolution(IssueStatus.WONT_FIX, Instant.now().truncatedTo(ChronoUnit.MILLIS), \"Updated comment\");\n    var issue2 = new LocalOnlyIssue(issueUuid, filePath, new TextRangeWithHash(1, 2, 3, 4, \"hash\"),\n      new LineWithHash(1, \"linehash\"), \"test-rule-1\", \"Updated message\", resolution);\n\n    repository.storeLocalOnlyIssue(configScopeId, issue2);\n\n    var loadedIssues = repository.loadForFile(configScopeId, filePath);\n\n    assertThat(loadedIssues).hasSize(1);\n    var loadedIssue = loadedIssues.get(0);\n    assertThat(loadedIssue.getId()).isEqualTo(issueUuid);\n    assertThat(loadedIssue.getMessage()).isEqualTo(\"Updated message\");\n    assertThat(loadedIssue.getTextRangeWithHash()).isEqualTo(new TextRangeWithHash(1, 2, 3, 4, \"hash\"));\n    assertThat(loadedIssue.getLineWithHash().getNumber()).isEqualTo(1);\n    assertThat(loadedIssue.getLineWithHash().getHash()).isEqualTo(\"linehash\");\n    assertThat(loadedIssue.getResolution()).isNotNull();\n    assertThat(loadedIssue.getResolution().getStatus()).isEqualTo(IssueStatus.WONT_FIX);\n    assertThat(loadedIssue.getResolution().getComment()).isEqualTo(\"Updated comment\");\n  }\n\n  @Test\n  void should_purge_old_resolved_issues() {\n    var configScopeId = \"configScopeId\";\n    var filePath = Path.of(\"/file/path\");\n    var oldDate = Instant.now().minus(10, ChronoUnit.DAYS);\n    var recentDate = Instant.now().minus(1, ChronoUnit.DAYS);\n    var limit = Instant.now().minus(5, ChronoUnit.DAYS);\n\n    var oldIssue = new LocalOnlyIssue(UUID.randomUUID(), filePath, null, null, \"test-rule-1\", \"Old issue\",\n      new LocalOnlyIssueResolution(IssueStatus.WONT_FIX, oldDate.truncatedTo(ChronoUnit.MILLIS), \"comment\"));\n    var recentIssue = new LocalOnlyIssue(UUID.randomUUID(), filePath, null, null, \"test-rule-2\", \"Recent issue\",\n      new LocalOnlyIssueResolution(IssueStatus.WONT_FIX, recentDate.truncatedTo(ChronoUnit.MILLIS), \"comment\"));\n    var unresolvedIssue = new LocalOnlyIssue(UUID.randomUUID(), filePath, null, null, \"test-rule-3\", \"Unresolved issue\", null);\n\n    repository.storeLocalOnlyIssue(configScopeId, oldIssue);\n    repository.storeLocalOnlyIssue(configScopeId, recentIssue);\n    repository.storeLocalOnlyIssue(configScopeId, unresolvedIssue);\n\n    repository.purgeIssuesOlderThan(limit);\n\n    var remainingIssues = repository.loadAll(configScopeId);\n    assertThat(remainingIssues).hasSize(2);\n    assertThat(remainingIssues).extracting(LocalOnlyIssue::getId)\n      .containsExactlyInAnyOrder(recentIssue.getId(), unresolvedIssue.getId());\n  }\n\n  @Test\n  void should_isolate_issues_by_configuration_scope() {\n    var configScopeId1 = \"configScopeId1\";\n    var configScopeId2 = \"configScopeId2\";\n    var filePath = Path.of(\"/file/path\");\n    var issue1 = new LocalOnlyIssue(UUID.randomUUID(), filePath, null, null, \"test-rule-1\", \"Message 1\", null);\n    var issue2 = new LocalOnlyIssue(UUID.randomUUID(), filePath, null, null, \"test-rule-2\", \"Message 2\", null);\n\n    repository.storeLocalOnlyIssue(configScopeId1, issue1);\n    repository.storeLocalOnlyIssue(configScopeId2, issue2);\n\n    assertThat(repository.loadAll(configScopeId1)).hasSize(1);\n    assertThat(repository.loadAll(configScopeId2)).hasSize(1);\n    assertThat(repository.loadForFile(configScopeId1, filePath)).hasSize(1);\n    assertThat(repository.loadForFile(configScopeId2, filePath)).hasSize(1);\n  }\n\n  @Test\n  void should_handle_issue_with_only_text_range() {\n    var configScopeId = \"configScopeId\";\n    var filePath = Path.of(\"/file/path\");\n    var issueUuid = UUID.randomUUID();\n    var issue = new LocalOnlyIssue(issueUuid, filePath, new TextRangeWithHash(1, 2, 3, 4, \"hash1\"),\n      null, \"test-rule-1\", \"Test message\", null);\n\n    repository.storeLocalOnlyIssue(configScopeId, issue);\n\n    var loadedIssues = repository.loadForFile(configScopeId, filePath);\n\n    assertThat(loadedIssues).hasSize(1);\n    var loadedIssue = loadedIssues.get(0);\n    assertThat(loadedIssue.getTextRangeWithHash()).isEqualTo(new TextRangeWithHash(1, 2, 3, 4, \"hash1\"));\n    assertThat(loadedIssue.getLineWithHash()).isNull();\n  }\n\n  @Test\n  void should_handle_issue_with_only_line_hash() {\n    var configScopeId = \"configScopeId\";\n    var filePath = Path.of(\"/file/path\");\n    var issueUuid = UUID.randomUUID();\n    var issue = new LocalOnlyIssue(issueUuid, filePath, null,\n      new LineWithHash(5, \"linehash\"), \"test-rule-1\", \"Test message\", null);\n\n    repository.storeLocalOnlyIssue(configScopeId, issue);\n\n    var loadedIssues = repository.loadForFile(configScopeId, filePath);\n\n    assertThat(loadedIssues).hasSize(1);\n    var loadedIssue = loadedIssues.get(0);\n    assertThat(loadedIssue.getTextRangeWithHash()).isNull();\n    assertThat(loadedIssue.getLineWithHash().getNumber()).isEqualTo(5);\n    assertThat(loadedIssue.getLineWithHash().getHash()).isEqualTo(\"linehash\");\n  }\n\n}\n\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/issues/ServerIssueTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.issues;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ServerIssueFixtures.aServerIssue;\n\nclass ServerIssueTests {\n  @Test\n  void testRoundTrips() {\n    var issue = aServerIssue();\n    var i1 = Instant.ofEpochMilli(100_000_000);\n    assertThat(issue.setCreationDate(i1).getCreationDate()).isEqualTo(i1);\n    assertThat(issue.setFilePath(Path.of(\"path1\")).getFilePath()).isEqualTo(Path.of(\"path1\"));\n    assertThat(issue.setKey(\"key1\").getKey()).isEqualTo(\"key1\");\n    assertThat(issue.setUserSeverity(IssueSeverity.MAJOR).getUserSeverity()).isEqualTo(IssueSeverity.MAJOR);\n    assertThat(issue.setRuleKey(\"rule1\").getRuleKey()).isEqualTo(\"rule1\");\n    assertThat(issue.isResolved()).isTrue();\n    assertThat(issue.setMessage(\"msg1\").getMessage()).isEqualTo(\"msg1\");\n    assertThat(issue.setType(RuleType.BUG).getType()).isEqualTo(RuleType.BUG);\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/prefix/FileTreeMatcherTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.prefix;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass FileTreeMatcherTests {\n  private final FileTreeMatcher fileMatcher = new FileTreeMatcher();\n\n  @Test\n  void simple_case_without_prefixes() {\n    List<Path> paths = Collections.singletonList(Paths.get(\"project1/src/main/java/File.java\"));\n    var match = fileMatcher.match(paths, paths);\n    assertThat(match.idePrefix()).isEqualTo(Paths.get(\"\"));\n    assertThat(match.sqPrefix()).isEqualTo(Paths.get(\"\"));\n  }\n\n  @Test\n  void simple_case_with_prefixes() {\n    List<Path> idePaths = Collections.singletonList(Paths.get(\"local/src/main/java/File.java\"));\n    List<Path> sqPaths = Collections.singletonList(Paths.get(\"sq/src/main/java/File.java\"));\n    var match = fileMatcher.match(sqPaths, idePaths);\n    assertThat(match.idePrefix()).isEqualTo(Paths.get(\"local\"));\n    assertThat(match.sqPrefix()).isEqualTo(Paths.get(\"sq\"));\n  }\n\n  @Test\n  void no_match() {\n    List<Path> idePaths = Collections.singletonList(Paths.get(\"local/src/main/java/File1.java\"));\n    List<Path> sqPaths = Collections.singletonList(Paths.get(\"sq/src/main/java/File2.java\"));\n    var match = fileMatcher.match(sqPaths, idePaths);\n    assertThat(match.idePrefix()).isEqualTo(Paths.get(\"\"));\n    assertThat(match.sqPrefix()).isEqualTo(Paths.get(\"\"));\n  }\n\n  @Test\n  void empty_project_in_ide() {\n    List<Path> idePaths = Collections.emptyList();\n    List<Path> sqPaths = Collections.singletonList(Paths.get(\"sq/src/main/java/File2.java\"));\n    var match = fileMatcher.match(sqPaths, idePaths);\n    assertThat(match.idePrefix()).isEqualTo(Paths.get(\"\"));\n    assertThat(match.sqPrefix()).isEqualTo(Paths.get(\"\"));\n  }\n\n  @Test\n  void should_return_shortest_sq_prefix_if_there_are_ties() {\n    List<Path> idePaths = List.of(\n      Paths.get(\"pom.xml\"));\n\n    List<Path> sqPaths = Arrays.asList(\n      Paths.get(\"aq1/module2/pom.xml\"),\n      Paths.get(\"aq2/pom.xml\"),\n      Paths.get(\"pom.xml\"),\n      Paths.get(\"aq1/module1/pom.xml\"));\n    var match = fileMatcher.match(sqPaths, idePaths);\n    assertThat(match.idePrefix()).isEqualTo(Paths.get(\"\"));\n    assertThat(match.sqPrefix()).isEqualTo(Paths.get(\"\"));\n\n    sqPaths = Arrays.asList(\n      Paths.get(\"aq1/module2/pom.xml\"),\n      Paths.get(\"aq2/pom.xml\"),\n      Paths.get(\"aq1/module1/pom.xml\"));\n    match = fileMatcher.match(sqPaths, idePaths);\n    assertThat(match.idePrefix()).isEqualTo(Paths.get(\"\"));\n    assertThat(match.sqPrefix()).isEqualTo(Paths.get(\"aq2\"));\n\n    // In case there is also a tie on the prefix segment count, fallback on lexicographic order\n    sqPaths = Arrays.asList(\n      Paths.get(\"aq1/module2/pom.xml\"),\n      Paths.get(\"aq1/module1/pom.xml\"));\n    match = fileMatcher.match(sqPaths, idePaths);\n    assertThat(match.idePrefix()).isEqualTo(Paths.get(\"\"));\n    assertThat(match.sqPrefix()).isEqualTo(Paths.get(\"aq1/module1\"));\n  }\n\n  @Test\n  void more_complex_test_with_multiple_files() throws Exception {\n    List<Path> idePaths = Arrays.asList(\n      Paths.get(\"local/sub/index.html\"),\n      Paths.get(\"local/sub/product1/index.html\"),\n      Paths.get(\"local/sub/product2/index.html\"),\n      Paths.get(\"local/sub/product3/index.html\"));\n    List<Path> sqPaths = Arrays.asList(\n      Paths.get(\"sq/index.html\"),\n      Paths.get(\"sq/news/index.html\"),\n      Paths.get(\"sq/news/product1/index.html\"),\n      Paths.get(\"sq/news/product2/index.html\"),\n      Paths.get(\"sq/news/product3/index.html\"),\n      Paths.get(\"sq/products/index.html\"),\n      Paths.get(\"sq/products/product1/index.html\"),\n      Paths.get(\"sq/products/product2/index.html\"),\n      Paths.get(\"sq/products/product3/index.html\"),\n      Paths.get(\"sq/company/index.html\"),\n      Paths.get(\"sq/company/jobs/index.html\"),\n      Paths.get(\"sq/company/news/index.html\"),\n      Paths.get(\"sq/company/contact/index.html\"));\n    var match = fileMatcher.match(sqPaths, idePaths);\n    assertThat(match.idePrefix()).isEqualTo(Paths.get(\"local/sub\"));\n    // sq/news is preferred to sq/products because of lexicographic order\n    assertThat(match.sqPrefix()).isEqualTo(Paths.get(\"sq/news\"));\n  }\n\n  @Disabled(\"Only used to investigate performance issues like SLCORE-266\")\n  @Test\n  void performance_test_worst_case() throws Exception {\n    var depthFactor = 10;\n    var sqNbPerFolder = 10;\n    var sqDepth = 5;\n    var ideNbPerFolder = 10;\n    var ideDepth = 3;\n    var idePaths = generateChildren(Paths.get(\"local/sub/src/main/java/com/mycompany/myapp/foo/bar\"), ideNbPerFolder, depthFactor, ideDepth * depthFactor);\n    System.out.println(\"IDE file count: \" + idePaths.size());\n    assertThat(idePaths).hasSize((int) Math.pow(ideNbPerFolder, ideDepth + 1));\n    var sqPaths = generateChildren(Paths.get(\"sq/src/main/java/com/mycompany/myapp/foo/bar\"), sqNbPerFolder, depthFactor, sqDepth * depthFactor);\n    System.out.println(\"SQ file count: \" + sqPaths.size());\n    assertThat(sqPaths).hasSize((int) Math.pow(sqNbPerFolder, sqDepth + 1));\n    var start = Instant.now();\n    var match = fileMatcher.match(sqPaths, idePaths);\n    System.out.println(Duration.between(start, Instant.now()).toMillis() + \"ms ellapsed\");\n    assertThat(match.idePrefix()).isEqualTo(Paths.get(\"local/sub/src/main/java/com/mycompany/myapp/foo/bar\"));\n    // sq/folder0/[...]/folder0 is preferred to other sq/folderx because of lexicographic order\n    assertThat(match.sqPrefix()).isEqualTo(Paths.get(\n      \"sq/src/main/java/com/mycompany/myapp/foo/bar/folder0/extra49/extra48/extra47/extra46/extra45/extra44/extra43/extra42/extra41/folder0/extra39/extra38/extra37/extra36/extra35/extra34/extra33/extra32/extra31\"));\n  }\n\n  @Disabled(\"Only used to investigate performance issues like SLCORE-266\")\n  @Test\n  void performance_test_only_index_files_with_same_filename() throws Exception {\n    var depthFactor = 10;\n    var sqNbPerFolder = 10;\n    var sqDepth = 5;\n    // IDE contains only paths with filename 'file1.txt'\n    var ideNbPerFolder = 1;\n    var ideDepth = 3;\n    performance_test(depthFactor, sqNbPerFolder, sqDepth, ideNbPerFolder, ideDepth);\n  }\n\n  private void performance_test(int depthFactor, int sqNbPerFolder, int sqDepth, int ideNbPerFolder, int ideDepth) {\n    var idePaths = generateChildren(Paths.get(\"local/sub/src/main/java/com/mycompany/myapp/foo/bar\"), ideNbPerFolder, depthFactor, ideDepth * depthFactor);\n    System.out.println(\"IDE file count: \" + idePaths.size());\n    assertThat(idePaths).hasSize((int) Math.pow(ideNbPerFolder, ideDepth + 1));\n    var sqPaths = generateChildren(Paths.get(\"sq/src/main/java/com/mycompany/myapp/foo/bar\"), sqNbPerFolder, depthFactor, sqDepth * depthFactor);\n    System.out.println(\"SQ file count: \" + sqPaths.size());\n    assertThat(sqPaths).hasSize((int) Math.pow(sqNbPerFolder, sqDepth + 1));\n    var start = Instant.now();\n    var match = fileMatcher.match(sqPaths, idePaths);\n    System.out.println(Duration.between(start, Instant.now()).toMillis() + \"ms ellapsed\");\n    assertThat(match.idePrefix()).isEqualTo(Paths.get(\"local/sub/src/main/java/com/mycompany/myapp/foo/bar\"));\n    // sq/folder0/[...]/folder0 is preferred to other sq/folderx because of lexicographic order\n    assertThat(match.sqPrefix()).isEqualTo(Paths.get(\n      \"sq/src/main/java/com/mycompany/myapp/foo/bar/folder0/extra49/extra48/extra47/extra46/extra45/extra44/extra43/extra42/extra41/folder0/extra39/extra38/extra37/extra36/extra35/extra34/extra33/extra32/extra31\"));\n  }\n\n  private List<Path> generateChildren(Path parent, int count, int everyDepth, int depth) {\n    List<Path> result = new ArrayList<>();\n    if (depth == 0) {\n      for (var i = 0; i < count; i++) {\n        result.add(parent.resolve(\"file\" + i + \".txt\"));\n      }\n    } else if (depth % everyDepth == 0) {\n      for (var i = 0; i < count; i++) {\n        var current = parent.resolve(\"folder\" + i);\n        result.addAll(generateChildren(current, count, everyDepth, depth - 1));\n      }\n    } else {\n      var current = parent.resolve(\"extra\" + depth);\n      result.addAll(generateChildren(current, count, everyDepth, depth - 1));\n    }\n    return result;\n  }\n\n  @Test\n  void should_return_most_common_prefixes() {\n    List<Path> idePaths = Arrays.asList(\n      Paths.get(\"local1/src/main/java/A.java\"),\n      Paths.get(\"local1/src/main/java/B.java\"),\n      Paths.get(\"local2/src/main/java/B.java\"));\n\n    List<Path> sqPaths = Arrays.asList(\n      Paths.get(\"sq1/src/main/java/A.java\"),\n      Paths.get(\"sq2/src/main/java/A.java\"),\n      Paths.get(\"sq1/src/main/java/B.java\")\n\n    );\n    var match = fileMatcher.match(sqPaths, idePaths);\n    assertThat(match.idePrefix()).isEqualTo(Paths.get(\"local1\"));\n    assertThat(match.sqPrefix()).isEqualTo(Paths.get(\"sq1\"));\n  }\n\n  @Test\n  void should_favor_deepest_common_path() {\n    List<Path> idePaths = Arrays.asList(\n      Paths.get(\"local1/pom.xml\"),\n      Paths.get(\"local1/build.properties\"),\n      Paths.get(\"local1/src/main/java/com/foo/A.java\"));\n\n    List<Path> sqPaths = Arrays.asList(\n      Paths.get(\"sq1/pom.xml\"),\n      Paths.get(\"sq1/build.properties\"),\n      Paths.get(\"sq2/src/main/java/com/foo/A.java\")\n\n    );\n    var match = fileMatcher.match(sqPaths, idePaths);\n    assertThat(match.idePrefix()).isEqualTo(Paths.get(\"local1\"));\n    assertThat(match.sqPrefix()).isEqualTo(Paths.get(\"sq2\"));\n  }\n\n  @Test\n  void should_disfavor_path_having_multiple_matches() {\n    List<Path> idePaths = Arrays.asList(\n      Paths.get(\"local1/pom.xml\"),\n      Paths.get(\"local1/build.properties\"),\n      Paths.get(\"local1/src/A.java\"));\n\n    List<Path> sqPaths = Arrays.asList(\n      Paths.get(\"sq1/pom.xml\"),\n      Paths.get(\"sq1/build.properties\"),\n      Paths.get(\"sq2/pom.xml\"),\n      Paths.get(\"sq2/build.properties\"),\n      Paths.get(\"sq3/pom.xml\"),\n      Paths.get(\"sq3/build.properties\"),\n      Paths.get(\"sq4/src/A.java\")\n\n    );\n    var match = fileMatcher.match(sqPaths, idePaths);\n    assertThat(match.idePrefix()).isEqualTo(Paths.get(\"local1\"));\n    assertThat(match.sqPrefix()).isEqualTo(Paths.get(\"sq4\"));\n  }\n\n  @Test\n  void verify_equals_and_hashcode_of_result() {\n    var r1 = new FileTreeMatcher.Result(Paths.get(\"ide1\"), Paths.get(\"sq1\"));\n    var r2 = new FileTreeMatcher.Result(Paths.get(\"ide2\"), Paths.get(\"sq1\"));\n    var r3 = new FileTreeMatcher.Result(Paths.get(\"ide1\"), Paths.get(\"sq2\"));\n    var r4 = new FileTreeMatcher.Result(Paths.get(\"ide1\"), Paths.get(\"sq1\"));\n\n    assertThat(r1.equals(r1)).isTrue();\n    assertThat(r1.equals(r4)).isTrue();\n    assertThat(r1).hasSameHashCodeAs(r4);\n\n    assertThat(r1.equals(r3)).isFalse();\n    assertThat(r3.equals(r2)).isFalse();\n    assertThat(r1.equals(new Object())).isFalse();\n    assertThat(r1.equals(null)).isFalse();\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/prefix/ReversePathTreeTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.prefix;\n\nimport java.nio.file.Paths;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ReversePathTreeTests {\n  private final ReversePathTree tree = new ReversePathTree();\n\n  @Test\n  void should_return_matching_prefix() {\n    tree.index(Paths.get(\"A/src/main/java/File.java\"));\n\n    var match = tree.findLongestSuffixMatches(Paths.get(\"B/src/main/java/File.java\"));\n\n    assertThat(match.matchLen()).isEqualTo(4);\n    assertThat(match.matchPrefixes()).containsExactly(Paths.get(\"A\"));\n  }\n\n  @Test\n  void should_return_matching_prefixes() {\n    tree.index(Paths.get(\"project1/src/main/java/File.java\"));\n    tree.index(Paths.get(\"project2/src/main/java/File.java\"));\n    tree.index(Paths.get(\"project2/src/test/java/File.java\"));\n\n    var match = tree.findLongestSuffixMatches(Paths.get(\"src/main/java/File.java\"));\n\n    assertThat(match.matchLen()).isEqualTo(4);\n    assertThat(match.matchPrefixes()).containsExactlyInAnyOrder(Paths.get(\"project1\"), Paths.get(\"project2\"));\n  }\n\n  @Test\n  void should_return_empty_prefix_if_full_match() {\n    tree.index(Paths.get(\"project1/src/main/java/File.java\"));\n    tree.index(Paths.get(\"project2/src/main/java/File.java\"));\n    tree.index(Paths.get(\"project2/src/test/java/File.java\"));\n\n    var match = tree.findLongestSuffixMatches(Paths.get(\"project2/src/main/java/File.java\"));\n\n    assertThat(match.matchLen()).isEqualTo(5);\n    assertThat(match.matchPrefixes()).containsExactly(Paths.get(\"\"));\n  }\n\n  @Test\n  void should_return_empty_if_no_match() {\n    tree.index(Paths.get(\"project1/src/main/java/File.java\"));\n    tree.index(Paths.get(\"project2/src/main/java/File.java\"));\n    tree.index(Paths.get(\"project2/src/test/java/File.java\"));\n\n    var match = tree.findLongestSuffixMatches(Paths.get(\"File2.java\"));\n\n    assertThat(match.matchLen()).isEqualTo(0);\n    assertThat(match.matchPrefixes()).isEmpty();\n  }\n\n  @Test\n  void should_return_matches_that_are_part_of_other_matches() {\n    tree.index(Paths.get(\"project1/A/pom.xml\"));\n    tree.index(Paths.get(\"project1/pom.xml\"));\n    tree.index(Paths.get(\"pom.xml\"));\n    var match = tree.findLongestSuffixMatches(Paths.get(\"pom.xml\"));\n    assertThat(match.matchLen()).isEqualTo(1);\n    assertThat(match.matchPrefixes()).containsOnly(Paths.get(\"\"), Paths.get(\"project1\"), Paths.get(\"project1/A\"));\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/storage/EntityMapperTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.EnumMap;\nimport java.util.List;\nimport org.jooq.JSON;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerTaintIssue;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass EntityMapperTests {\n\n  private final EntityMapper underTest = new EntityMapper();\n\n  @Test\n  void should_serialize_issue_impacts() {\n    var impacts = new EnumMap<SoftwareQuality, ImpactSeverity>(SoftwareQuality.class);\n    impacts.put(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.HIGH);\n    impacts.put(SoftwareQuality.SECURITY, ImpactSeverity.LOW);\n\n    var json = underTest.serializeImpacts(impacts);\n\n    assertThat(json.data()).isEqualTo(\"{\\\"MAINTAINABILITY\\\":\\\"HIGH\\\",\\\"SECURITY\\\":\\\"LOW\\\"}\");\n    var impactsDeserialized = underTest.deserializeImpacts(json);\n\n    assertThat(impactsDeserialized).isEqualTo(impacts);\n  }\n\n  @Test\n  void should_serialize_issue_flows() {\n    var flows = new ArrayList<ServerTaintIssue.Flow>();\n    var path = Path.of(\"file/path\");\n    var stringPath = path.toString().replace(\"\\\\\", \"\\\\\\\\\");\n    flows.add(new ServerTaintIssue.Flow(List.of(\n      new ServerTaintIssue.ServerIssueLocation(path,\n        new TextRangeWithHash(1, 2, 3, 4, \"hash1\"), \"Message 1\"),\n      new ServerTaintIssue.ServerIssueLocation(path,\n        new TextRangeWithHash(5, 6, 7, 8, \"hash2\"), \"Message 2\"))));\n    flows.add(new ServerTaintIssue.Flow(List.of(\n      new ServerTaintIssue.ServerIssueLocation(path,\n        new TextRangeWithHash(1, 2, 3, 4, \"hash1\"), \"Message 1\"))));\n    var taint = new ServerTaintIssue(null, null, true, null, null, null, null,\n      null, null, null, null, null, null, null, flows);\n\n    var json = underTest.serializeFlows(taint.getFlows());\n\n    assertThat(json.data())\n      .isEqualTo(\"[{\\\"locations\\\":[{\\\"filePath\\\":\\\"\" + stringPath + \"\\\",\" +\n        \"\\\"textRange\\\":{\\\"startLine\\\":1,\\\"startLineOffset\\\":2,\\\"endLine\\\":3,\\\"endLineOffset\\\":4,\\\"hash\\\":\\\"hash1\\\"},\\\"message\\\":\\\"Message 1\\\"},\" +\n        \"{\\\"filePath\\\":\\\"\" + stringPath + \"\\\",\\\"textRange\\\":{\\\"startLine\\\":5,\\\"startLineOffset\\\":6,\\\"endLine\\\":7,\\\"endLineOffset\\\":8,\\\"hash\\\":\\\"hash2\\\"},\" +\n        \"\\\"message\\\":\\\"Message 2\\\"}]},{\\\"locations\\\":[{\\\"filePath\\\":\\\"\" + stringPath + \"\\\",\" +\n        \"\\\"textRange\\\":{\\\"startLine\\\":1,\\\"startLineOffset\\\":2,\\\"endLine\\\":3,\\\"endLineOffset\\\":4,\\\"hash\\\":\\\"hash1\\\"},\\\"message\\\":\\\"Message 1\\\"}]}]\");\n  }\n\n  @Test\n  void should_deserialize_taint_flows() {\n    var path = Path.of(\"file/path\");\n    var stringPath = path.toString().replace(\"\\\\\", \"\\\\\\\\\");\n\n    var flows = underTest.deserializeTaintFlows(JSON.valueOf(\"[{\\\"locations\\\":[{\\\"filePath\\\":\\\"\" + stringPath + \"\\\",\" +\n      \"\\\"textRange\\\":{\\\"startLine\\\":1,\\\"startLineOffset\\\":2,\\\"endLine\\\":3,\\\"endLineOffset\\\":4,\\\"hash\\\":\\\"hash1\\\"},\\\"message\\\":\\\"Message 1\\\"},\" +\n      \"{\\\"filePath\\\":\\\"\" + stringPath + \"\\\",\\\"textRange\\\":{\\\"startLine\\\":5,\\\"startLineOffset\\\":6,\\\"endLine\\\":7,\\\"endLineOffset\\\":8,\\\"hash\\\":\\\"hash2\\\"},\" +\n      \"\\\"message\\\":\\\"Message 2\\\"}]},{\\\"locations\\\":[{\\\"filePath\\\":\\\"\" + stringPath + \"\\\",\" +\n      \"\\\"textRange\\\":{\\\"startLine\\\":1,\\\"startLineOffset\\\":2,\\\"endLine\\\":3,\\\"endLineOffset\\\":4,\\\"hash\\\":\\\"hash1\\\"},\\\"message\\\":\\\"Message 1\\\"}]}]\"));\n\n    assertThat(flows).isEqualTo(List.of(\n      new ServerTaintIssue.Flow(List.of(\n        new ServerTaintIssue.ServerIssueLocation(path,\n          new TextRangeWithHash(1, 2, 3, 4, \"hash1\"), \"Message 1\"),\n        new ServerTaintIssue.ServerIssueLocation(path,\n          new TextRangeWithHash(5, 6, 7, 8, \"hash2\"), \"Message 2\"))),\n      new ServerTaintIssue.Flow(List.of(\n        new ServerTaintIssue.ServerIssueLocation(path,\n          new TextRangeWithHash(1, 2, 3, 4, \"hash1\"), \"Message 1\")))));\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/storage/NewCodeDefinitionStorageTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.commons.NewCodeDefinition;\nimport org.sonarsource.sonarlint.core.serverconnection.proto.Sonarlint;\n\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThat;\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.NewCodeDefinitionStorage.adapt;\n\nclass NewCodeDefinitionStorageTests {\n\n  @Test\n  void shouldAdaptToProtobuf() {\n    var days = adapt(NewCodeDefinition.withNumberOfDaysWithDate(30, 1000));\n    assertThat(days.getDays()).isEqualTo(30);\n    assertThat(days.getThresholdDate()).isEqualTo(1000);\n\n    var previousWithVersion = adapt(NewCodeDefinition.withPreviousVersion(1000, \"1.0-SNAPSHOT\"));\n    assertThat(previousWithVersion.getVersion()).isEqualTo(\"1.0-SNAPSHOT\");\n    assertThat(previousWithVersion.getThresholdDate()).isEqualTo(1000);\n\n    var previousWithoutVersion = adapt(NewCodeDefinition.withPreviousVersion(1000, null));\n    assertThat(previousWithoutVersion.getVersion()).isEmpty();\n    assertThat(previousWithoutVersion.getThresholdDate()).isEqualTo(1000);\n\n    var branch = adapt(NewCodeDefinition.withReferenceBranch(\"master\"));\n    assertThat(branch.getReferenceBranch()).isEqualTo(\"master\");\n  }\n\n  @Test\n  void shouldAdaptFromProtobuf() {\n    var daysProto = Sonarlint.NewCodeDefinition.newBuilder()\n      .setMode(Sonarlint.NewCodeDefinitionMode.NUMBER_OF_DAYS)\n      .setDays(30)\n      .setThresholdDate(1000)\n      .build();\n    var days = adapt(daysProto);\n    assertThat(days.isSupported()).isTrue();\n    assertThat(days).isInstanceOf(NewCodeDefinition.NewCodeNumberOfDaysWithDate.class);\n    assertThat(days.getThresholdDate().toEpochMilli()).isEqualTo(1000);\n    assertThat(((NewCodeDefinition.NewCodeNumberOfDaysWithDate) days).getDays()).isEqualTo(30);\n\n    var previousWithVersionProto = Sonarlint.NewCodeDefinition.newBuilder()\n      .setMode(Sonarlint.NewCodeDefinitionMode.PREVIOUS_VERSION)\n      .setVersion(\"1.0-SNAPSHOT\")\n      .setThresholdDate(1000)\n      .build();\n    var previousWithVersion = adapt(previousWithVersionProto);\n    assertThat(previousWithVersion.isSupported()).isTrue();\n    assertThat(previousWithVersion).isInstanceOf(NewCodeDefinition.NewCodePreviousVersion.class);\n    assertThat(previousWithVersion.getThresholdDate().toEpochMilli()).isEqualTo(1000);\n    assertThat(((NewCodeDefinition.NewCodePreviousVersion) previousWithVersion).getVersion()).isEqualTo(\"1.0-SNAPSHOT\");\n\n    var previousWithoutVersionProto = Sonarlint.NewCodeDefinition.newBuilder()\n      .setMode(Sonarlint.NewCodeDefinitionMode.PREVIOUS_VERSION)\n      .setThresholdDate(1000)\n      .build();\n    var previousWithoutVersion = adapt(previousWithoutVersionProto);\n    assertThat(previousWithoutVersion.isSupported()).isTrue();\n    assertThat(previousWithoutVersion).isInstanceOf(NewCodeDefinition.NewCodePreviousVersion.class);\n    assertThat(previousWithoutVersion.getThresholdDate().toEpochMilli()).isEqualTo(1000);\n    assertThat(((NewCodeDefinition.NewCodePreviousVersion) previousWithoutVersion).getVersion()).isNull();\n\n    var branchProto = Sonarlint.NewCodeDefinition.newBuilder()\n      .setMode(Sonarlint.NewCodeDefinitionMode.REFERENCE_BRANCH)\n      .setReferenceBranch(\"master\")\n      .build();\n    var branch = adapt(branchProto);\n    assertThat(branch.isSupported()).isFalse();\n    assertThat(branch).isInstanceOf(NewCodeDefinition.NewCodeReferenceBranch.class);\n    assertThat(((NewCodeDefinition.NewCodeReferenceBranch) branch).getBranchName()).isEqualTo(\"master\");\n\n    var analysisProto = Sonarlint.NewCodeDefinition.newBuilder()\n      .setMode(Sonarlint.NewCodeDefinitionMode.SPECIFIC_ANALYSIS)\n      .setThresholdDate(1000)\n      .build();\n    var analysis = adapt(analysisProto);\n    assertThat(analysis.isSupported()).isTrue();\n    assertThat(analysis).isInstanceOf(NewCodeDefinition.NewCodeSpecificAnalysis.class);\n    assertThat(analysis.getThresholdDate().toEpochMilli()).isEqualTo(1000);\n\n    var unknownProto = Sonarlint.NewCodeDefinition.newBuilder()\n      .setMode(Sonarlint.NewCodeDefinitionMode.UNKNOWN)\n      .build();\n    assertThatThrownBy(() -> adapt(unknownProto)).hasMessage(\"Unsupported mode: UNKNOWN\");\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/storage/PluginsStorageTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.nio.file.Path;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport static org.junit.jupiter.api.Assertions.assertFalse;\n\nclass PluginsStorageTests {\n\n  @TempDir\n  Path storageRoot;\n  PluginsStorage underTest;\n\n  @BeforeEach\n  void setUp() {\n    underTest = new PluginsStorage(storageRoot);\n  }\n\n  @Test\n  void should_consider_storage_invalid_if_file_doesnt_exist() {\n    assertFalse(underTest.isValid());\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/storage/ProtobufFileUtilTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport com.google.protobuf.Parser;\nimport java.nio.file.Paths;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.serverconnection.proto.Sonarlint;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass ProtobufFileUtilTests {\n\n  private static final Sonarlint.PluginReferences SOME_MESSAGE = Sonarlint.PluginReferences.newBuilder().build();\n  private static final Parser<Sonarlint.PluginReferences> SOME_PARSER = Sonarlint.PluginReferences.parser();\n\n  @Test\n  void test_readFile_error() {\n    var p = Paths.get(\"invalid_non_existing_file\");\n    var thrown = assertThrows(StorageException.class, () -> ProtobufFileUtil.readFile(p, SOME_PARSER));\n    assertThat(thrown).hasMessageStartingWith(\"Failed to read file\");\n  }\n\n  @Test\n  void test_writeFile_error() {\n    var p = Paths.get(\"invalid\", \"non_existing\", \"file\");\n    var thrown = assertThrows(StorageException.class, () -> ProtobufFileUtil.writeToFile(SOME_MESSAGE, p));\n    assertThat(thrown).hasMessageStartingWith(\"Unable to write protocol buffer data to file\");\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/storage/ServerFindingRepositoryTests.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.UUID;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabase;\nimport org.sonarsource.sonarlint.core.serverapi.hotspot.ServerHotspot;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.LineLevelServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.RangeLevelServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerDependencyRisk;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerTaintIssue;\n\nimport static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;\n\nclass ServerFindingRepositoryTests {\n\n  private static final long INSTANT_TOLERANCE_MS = 1500;\n\n  @RegisterExtension\n  static SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @TempDir\n  Path tempDir;\n\n  private ServerFindingRepository repo;\n  private String branch;\n  private Path filePath;\n  private SonarLintDatabase db;\n\n  @BeforeEach\n  void setUp() {\n    var storageRoot = tempDir.resolve(\"storage\");\n    db = new SonarLintDatabase(storageRoot);\n    repo = new ServerFindingRepository(db.dsl(), \"conn-1\", \"project-1\");\n    branch = \"main\";\n    filePath = Path.of(\"/file/path\");\n  }\n\n  @AfterEach\n  void tearDown() {\n    if (repo != null) {\n      db.shutdown();\n    }\n  }\n\n  @Test\n  void hotspots_replace_load_get_change_update_delete() {\n    var h1 = hotspot(\"HOTSPOT_KEY_1\", filePath, 1, HotspotReviewStatus.TO_REVIEW, VulnerabilityProbability.MEDIUM, null);\n    var h2 = hotspot(\"HOTSPOT_KEY_2\", filePath, 2, HotspotReviewStatus.TO_REVIEW, VulnerabilityProbability.HIGH, \"john.doe\");\n\n    repo.replaceAllHotspotsOfFile(branch, filePath, List.of(h1, h2));\n\n    var loaded = repo.loadHotspots(branch, filePath);\n    assertThat(loaded).hasSize(2);\n    var loadedH1 = loaded.stream().filter(h -> h.getKey().equals(h1.getKey())).findFirst().orElseThrow();\n    var loadedH2 = loaded.stream().filter(h -> h.getKey().equals(h2.getKey())).findFirst().orElseThrow();\n    assertHotspotEquals(h1, loadedH1);\n    assertHotspotEquals(h2, loadedH2);\n\n    var fetched = repo.getHotspot(\"HOTSPOT_KEY_2\");\n    assertThat(fetched).isNotNull();\n    assertHotspotEquals(h2, fetched);\n\n    assertThat(repo.changeHotspotStatus(\"HOTSPOT_KEY_1\", HotspotReviewStatus.SAFE)).isTrue();\n    var afterStatus = repo.getHotspot(\"HOTSPOT_KEY_1\");\n    var expectedAfterStatus = new ServerHotspot(h1.getId(), h1.getKey(), h1.getRuleKey(), h1.getMessage(), h1.getFilePath(), h1.getTextRange(), h1.getCreationDate(),\n      HotspotReviewStatus.SAFE, h1.getVulnerabilityProbability(), h1.getAssignee());\n    assertHotspotEquals(expectedAfterStatus, afterStatus);\n\n    repo.updateHotspot(\"HOTSPOT_KEY_2\", hs -> { /* no-op */ });\n    var afterUpdate = repo.getHotspot(\"HOTSPOT_KEY_2\");\n    assertHotspotEquals(h2, afterUpdate);\n\n    repo.deleteHotspot(\"HOTSPOT_KEY_1\");\n    assertThat(repo.getHotspot(\"HOTSPOT_KEY_1\")).isNull();\n  }\n\n  @Test\n  void hotspots_replace_for_branch_load_get_change_update_delete() {\n    var h1 = hotspot(\"HOTSPOT_KEY_1\", filePath, 1, HotspotReviewStatus.TO_REVIEW, VulnerabilityProbability.MEDIUM, null);\n    var h2 = hotspot(\"HOTSPOT_KEY_2\", filePath, 2, HotspotReviewStatus.TO_REVIEW, VulnerabilityProbability.HIGH, \"john.doe\");\n\n    repo.replaceAllHotspotsOfBranch(branch, List.of(h1, h2), Set.of());\n\n    var loaded = repo.loadHotspots(branch, filePath);\n    assertThat(loaded).hasSize(2);\n    var loadedH1 = loaded.stream().filter(h -> h.getKey().equals(h1.getKey())).findFirst().orElseThrow();\n    var loadedH2 = loaded.stream().filter(h -> h.getKey().equals(h2.getKey())).findFirst().orElseThrow();\n    assertHotspotEquals(h1, loadedH1);\n    assertHotspotEquals(h2, loadedH2);\n\n    var fetched = repo.getHotspot(\"HOTSPOT_KEY_2\");\n    assertThat(fetched).isNotNull();\n    assertHotspotEquals(h2, fetched);\n\n    assertThat(repo.changeHotspotStatus(\"HOTSPOT_KEY_1\", HotspotReviewStatus.SAFE)).isTrue();\n    var afterStatus = repo.getHotspot(\"HOTSPOT_KEY_1\");\n    var expectedAfterStatus = new ServerHotspot(h1.getId(), h1.getKey(), h1.getRuleKey(), h1.getMessage(), h1.getFilePath(), h1.getTextRange(), h1.getCreationDate(),\n      HotspotReviewStatus.SAFE, h1.getVulnerabilityProbability(), h1.getAssignee());\n    assertHotspotEquals(expectedAfterStatus, afterStatus);\n\n    repo.updateHotspot(\"HOTSPOT_KEY_2\", hs -> { /* no-op */ });\n    var afterUpdate = repo.getHotspot(\"HOTSPOT_KEY_2\");\n    assertHotspotEquals(h2, afterUpdate);\n\n    repo.deleteHotspot(\"HOTSPOT_KEY_1\");\n    assertThat(repo.getHotspot(\"HOTSPOT_KEY_1\")).isNull();\n  }\n\n  @Test\n  void server_dependency_risk() {\n    var serverDependencyRisk1 = dependencyRisk();\n\n    repo.replaceAllDependencyRisksOfBranch(branch, List.of(serverDependencyRisk1));\n\n    var serverDependencyRisks = repo.loadDependencyRisks(branch);\n\n    assertThat(serverDependencyRisks).hasSize(1);\n    assertThat(serverDependencyRisks.get(0)).isEqualTo(serverDependencyRisk1);\n  }\n\n  @Test\n  void issues_update() {\n    var issueKey = \"ISSUE_KEY\";\n    var file = Path.of(\"/file/path\");\n    var issue = rangeIssue(issueKey, file, new TextRangeWithHash(1, 10, 1, 20, \"hash\"));\n\n    repo.replaceAllIssuesOfFile(branch, file, List.of(issue));\n    var loadedIssue = repo.getIssue(issueKey);\n    assertRangeIssueEquals(issue, (RangeLevelServerIssue) loadedIssue);\n\n    repo.updateIssue(issueKey, issueToUpdate -> issueToUpdate.setUserSeverity(IssueSeverity.MAJOR));\n    loadedIssue = repo.getIssue(issueKey);\n    assertThat(loadedIssue.getUserSeverity()).isEqualTo(IssueSeverity.MAJOR);\n  }\n\n  @Test\n  void replace_all_issues_of_branch() {\n    var issueKey = \"ISSUE_KEY\";\n    var file = Path.of(\"/file/path\");\n    var issue = rangeIssue(issueKey, file, new TextRangeWithHash(1, 10, 1, 20, \"hash\"));\n\n    repo.replaceAllIssuesOfBranch(branch, List.of(issue), Set.of());\n\n    var loadedIssue = repo.getIssue(issueKey);\n    assertRangeIssueEquals(issue, (RangeLevelServerIssue) loadedIssue);\n  }\n\n  @Test\n  void was_ever_updated() {\n    var issueKey = \"ISSUE_KEY\";\n    var file = Path.of(\"/file/path\");\n    var issue = rangeIssue(issueKey, file, new TextRangeWithHash(1, 10, 1, 20, \"hash\"));\n\n    repo.replaceAllIssuesOfBranch(branch, List.of(issue), Set.of());\n    assertThat(repo.wasEverUpdated()).isTrue();\n  }\n\n  @Test\n  void was_ever_updated_for_no_issues() {\n    repo.replaceAllIssuesOfBranch(branch, List.of(), Set.of());\n\n    assertThat(repo.wasEverUpdated()).isTrue();\n  }\n\n  @Test\n  void was_never_updated() {\n    assertThat(repo.wasEverUpdated()).isFalse();\n  }\n\n  @Test\n  void was_ever_updated_when_only_hotspots_synced() {\n    var h1 = hotspot(\"HOTSPOT_KEY_1\", filePath, 1, HotspotReviewStatus.TO_REVIEW, VulnerabilityProbability.MEDIUM, null);\n    repo.mergeHotspots(branch, List.of(h1), Set.of(), Instant.now(), Set.of());\n\n    assertThat(repo.wasEverUpdated()).isTrue();\n  }\n\n  @Test\n  void was_ever_updated_when_only_taints_synced() {\n    var t1 = taint(\"TAINT_KEY_1\", filePath);\n    repo.mergeTaintIssues(branch, List.of(t1), Set.of(), Instant.now(), Set.of());\n\n    assertThat(repo.wasEverUpdated()).isTrue();\n  }\n\n  @Test\n  void was_ever_updated_when_several_branches() {\n    var t1 = taint(\"TAINT_KEY_1\", filePath);\n    repo.mergeTaintIssues(branch, List.of(t1), Set.of(), Instant.now(), Set.of());\n    repo.mergeTaintIssues(\"otherbranch\", List.of(t1), Set.of(), Instant.now(), Set.of());\n\n    assertThat(repo.wasEverUpdated()).isTrue();\n  }\n\n  @Test\n  void update_issue_resolution_status() {\n    var issueKey = \"ISSUE_KEY\";\n    var file = Path.of(\"/file/path\");\n    var issue = rangeIssue(issueKey, file, new TextRangeWithHash(1, 10, 1, 20, \"hash\"));\n    repo.replaceAllIssuesOfFile(branch, file, List.of(issue));\n    assertThat(issue.isResolved()).isFalse();\n    var loadedIssue = repo.getIssue(issueKey);\n    assertRangeIssueEquals(issue, (RangeLevelServerIssue) loadedIssue);\n\n    var serverFinding = repo.updateIssueResolutionStatus(issueKey, false, true);\n    assertThat(serverFinding).isPresent();\n\n    var repoIssue = repo.getIssue(issueKey);\n    assertThat(repoIssue.isResolved()).isTrue();\n  }\n\n  @Test\n  void taints_replace_load_insert_delete_update() {\n    var t1 = taint(\"TAINT_KEY_1\", filePath);\n    repo.replaceAllTaintsOfBranch(branch, List.of(t1), Set.of());\n\n    var loaded = repo.loadTaint(branch);\n    assertThat(loaded).hasSize(1);\n    assertTaintEquals(t1, loaded.get(0));\n\n    var t2 = taint(\"TAINT_KEY_2\", filePath);\n    repo.insert(branch, t2);\n    loaded = repo.loadTaint(branch);\n    assertThat(loaded).hasSize(2);\n    var loadedT1 = loaded.stream().filter(t -> t.getSonarServerKey().equals(\"TAINT_KEY_1\")).findFirst().orElseThrow();\n    var loadedT2 = loaded.stream().filter(t -> t.getSonarServerKey().equals(\"TAINT_KEY_2\")).findFirst().orElseThrow();\n    assertTaintEquals(t1, loadedT1);\n    assertTaintEquals(t2, loadedT2);\n\n    var deletedId = repo.deleteTaintIssueBySonarServerKey(\"TAINT_KEY_2\");\n    assertThat(deletedId).isPresent();\n    loaded = repo.loadTaint(branch);\n    assertThat(loaded).hasSize(1);\n    assertTaintEquals(t1, loaded.get(0));\n\n    assertThat(repo.updateTaintIssueBySonarServerKey(\"TAINT_KEY_1\", t -> { /* no-op */ })).isPresent();\n    var afterUpdate = repo.loadTaint(branch).get(0);\n    assertTaintEquals(t1, afterUpdate);\n  }\n\n  @Test\n  void merge_issues_removes_closed_and_upserts() {\n    var newIssue = lineIssue(\"ISSUE_KEY_4\", filePath, 2);\n    repo.mergeIssues(branch, List.of(newIssue), Set.of(\"ISSUE_KEY_1\"), Instant.now(), Set.of());\n\n    var afterMerge = repo.load(branch, filePath);\n    assertThat(afterMerge.stream().anyMatch(i -> i.getKey().equals(\"ISSUE_KEY_1\"))).isFalse();\n    assertThat(afterMerge.stream().anyMatch(i -> i.getKey().equals(\"ISSUE_KEY_4\"))).isTrue();\n  }\n\n  @Test\n  void merge_taints_removes_closed_and_upserts() {\n    var t3 = new ServerTaintIssue(UUID.randomUUID(), \"TAINT_KEY_3\", false, null, \"rule\", \"msg\", filePath,\n      Instant.now(), IssueSeverity.MINOR, RuleType.CODE_SMELL, null, null, null, Map.of(), List.of());\n    repo.mergeTaintIssues(branch, List.of(t3), Set.of(\"TAINT_KEY_1\"), Instant.now(), Set.of());\n\n    var afterMerge = repo.loadTaint(branch);\n    assertThat(afterMerge.stream().anyMatch(t -> t.getSonarServerKey().equals(\"TAINT_KEY_1\"))).isFalse();\n    assertThat(afterMerge.stream().anyMatch(t -> t.getSonarServerKey().equals(\"TAINT_KEY_3\"))).isTrue();\n  }\n\n  @Test\n  void merge_hotspots_removes_closed_and_upserts() {\n    var h3 = new ServerHotspot(UUID.randomUUID(), \"HOTSPOT_KEY_3\", \"rule\", \"msg\", filePath,\n      new TextRange(4, 0, 4, 1), Instant.now(), HotspotReviewStatus.TO_REVIEW, VulnerabilityProbability.LOW, null);\n    repo.mergeHotspots(branch, List.of(h3), Set.of(\"HOTSPOT_KEY_2\"), Instant.now(), Set.of());\n\n    var afterMerge = repo.loadHotspots(branch, filePath);\n    assertThat(afterMerge.stream().anyMatch(h -> h.getKey().equals(\"HOTSPOT_KEY_2\"))).isFalse();\n    assertThat(afterMerge.stream().anyMatch(h -> h.getKey().equals(\"HOTSPOT_KEY_3\"))).isTrue();\n  }\n\n  @Test\n  void branch_metadata_is_stored_during_merges() {\n    // perform one merge for each type to set metadata\n    repo.mergeIssues(branch, List.of(lineIssue(\"ISSUE_KEY_X\", filePath, 1)), Set.of(), Instant.now(), Set.of());\n    repo.mergeTaintIssues(branch, List.of(taint(\"TAINT_KEY_X\", filePath)), Set.of(), Instant.now(), Set.of());\n    repo.mergeHotspots(branch, List.of(hotspot(\"HOTSPOT_KEY_X\", filePath, 1, HotspotReviewStatus.TO_REVIEW, VulnerabilityProbability.LOW, null)), Set.of(), Instant.now(),\n      Set.of());\n\n    assertThat(repo.getLastIssueSyncTimestamp(branch)).isPresent();\n    assertThat(repo.getLastTaintSyncTimestamp(branch)).isPresent();\n    assertThat(repo.getLastHotspotSyncTimestamp(branch)).isPresent();\n\n    assertThat(repo.getLastIssueEnabledLanguages(branch)).isEmpty();\n    assertThat(repo.getLastHotspotEnabledLanguages(branch)).isEmpty();\n    assertThat(repo.getLastTaintEnabledLanguages(branch)).isEmpty();\n  }\n\n  // Helpers\n  private static void assertInstantsClose(Instant expected, Instant actual) {\n    long diff = Math.abs(expected.toEpochMilli() - actual.toEpochMilli());\n    assertThat(diff).isLessThan(INSTANT_TOLERANCE_MS);\n  }\n\n  private static void assertTextRangeEquals(TextRange expected, TextRange actual) {\n    assertThat(actual.getStartLine()).isEqualTo(expected.getStartLine());\n    assertThat(actual.getStartLineOffset()).isEqualTo(expected.getStartLineOffset());\n    assertThat(actual.getEndLine()).isEqualTo(expected.getEndLine());\n    assertThat(actual.getEndLineOffset()).isEqualTo(expected.getEndLineOffset());\n  }\n\n  private static void assertTextRangeWithHashEquals(TextRangeWithHash expected, TextRangeWithHash actual) {\n    assertThat(actual.getHash()).isEqualTo(expected.getHash());\n    assertTextRangeEquals(expected, actual);\n  }\n\n  private static void assertHotspotEquals(ServerHotspot expected, ServerHotspot actual) {\n    assertThat(actual.getId()).isEqualTo(expected.getId());\n    assertThat(actual.getKey()).isEqualTo(expected.getKey());\n    assertThat(actual.getRuleKey()).isEqualTo(expected.getRuleKey());\n    assertThat(actual.getMessage()).isEqualTo(expected.getMessage());\n    assertThat(actual.getFilePath()).isEqualTo(expected.getFilePath());\n    assertTextRangeEquals(expected.getTextRange(), actual.getTextRange());\n    assertInstantsClose(expected.getCreationDate(), actual.getCreationDate());\n    assertThat(actual.getStatus()).isEqualTo(expected.getStatus());\n    assertThat(actual.getVulnerabilityProbability()).isEqualTo(expected.getVulnerabilityProbability());\n    assertThat(actual.getAssignee()).isEqualTo(expected.getAssignee());\n  }\n\n  private static void assertTaintEquals(ServerTaintIssue expected, ServerTaintIssue actual) {\n    assertThat(actual.getId()).isEqualTo(expected.getId());\n    assertThat(actual.getSonarServerKey()).isEqualTo(expected.getSonarServerKey());\n    assertThat(actual.getRuleKey()).isEqualTo(expected.getRuleKey());\n    assertThat(actual.getMessage()).isEqualTo(expected.getMessage());\n    assertThat(actual.getFilePath()).isEqualTo(expected.getFilePath());\n    assertInstantsClose(expected.getCreationDate(), actual.getCreationDate());\n    assertThat(actual.getSeverity()).isEqualTo(expected.getSeverity());\n    assertThat(actual.getType()).isEqualTo(expected.getType());\n    if (expected.getTextRange() != null && actual.getTextRange() != null) {\n      assertTextRangeWithHashEquals(expected.getTextRange(), actual.getTextRange());\n    } else {\n      assertThat(actual.getTextRange()).isNull();\n      assertThat(expected.getTextRange()).isNull();\n    }\n  }\n\n  private static void assertRangeIssueEquals(RangeLevelServerIssue expected, RangeLevelServerIssue actual) {\n    assertThat(actual.getId()).isEqualTo(expected.getId());\n    assertThat(actual.getKey()).isEqualTo(expected.getKey());\n    assertThat(actual.getRuleKey()).isEqualTo(expected.getRuleKey());\n    assertThat(actual.getMessage()).isEqualTo(expected.getMessage());\n    assertThat(actual.getFilePath()).isEqualTo(expected.getFilePath());\n    assertInstantsClose(expected.getCreationDate(), actual.getCreationDate());\n    assertThat(actual.getUserSeverity()).isEqualTo(expected.getUserSeverity());\n    assertThat(actual.getType()).isEqualTo(expected.getType());\n    assertTextRangeWithHashEquals(expected.getTextRange(), actual.getTextRange());\n  }\n\n  private static ServerHotspot hotspot(String key, Path file, int startLine, HotspotReviewStatus status, VulnerabilityProbability prob, String assignee) {\n    return new ServerHotspot(UUID.randomUUID(), key, \"hotspot:rule-\" + key, \"Hotspot Message \" + key, file,\n      new TextRange(startLine, 0, startLine, 10), Instant.now(), status, prob, assignee);\n  }\n\n  private static ServerTaintIssue taint(String key, Path file) {\n    return new ServerTaintIssue(UUID.randomUUID(), key, false, null, \"java:S\" + Math.abs(key.hashCode() % 1000),\n      \"Taint message \" + key, file, Instant.now(), IssueSeverity.MAJOR, RuleType.SECURITY_HOTSPOT,\n      new TextRangeWithHash(3, 1, 3, 5, \"hash-\" + key), null, null, Map.of(), List.of());\n  }\n\n  private static LineLevelServerIssue lineIssue(String key, Path file, int line) {\n    return new LineLevelServerIssue(UUID.randomUUID(), key, false, null, \"rule\", \"msg\", \"h\", file, Instant.now(),\n      IssueSeverity.MINOR, RuleType.CODE_SMELL, line, Map.of());\n  }\n\n  private static RangeLevelServerIssue rangeIssue(String key, Path file, TextRangeWithHash range) {\n    return new RangeLevelServerIssue(UUID.randomUUID(), key, false, IssueStatus.ACCEPT, \"ruleKey\",\n      \"message\", file, Instant.now(), IssueSeverity.MINOR, RuleType.CODE_SMELL, range, Map.of());\n  }\n\n  private static ServerDependencyRisk dependencyRisk() {\n    return new ServerDependencyRisk(UUID.randomUUID(), ServerDependencyRisk.Type.VULNERABILITY, ServerDependencyRisk.Severity.HIGH, ServerDependencyRisk.SoftwareQuality.SECURITY,\n      ServerDependencyRisk.Status.ACCEPT, \"package\", \"version\", \"vulnerabilityId\", \"cvssScore\",\n      List.of(ServerDependencyRisk.Transition.REOPEN, ServerDependencyRisk.Transition.CONFIRM));\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/storage/ServerHotspotFixtures.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.serverapi.hotspot.ServerHotspot;\n\npublic class ServerHotspotFixtures {\n\n  public static ServerHotspot aServerHotspot() {\n    return aServerHotspot(\"key\", Path.of(\"file/path\"));\n  }\n\n  public static ServerHotspot aServerHotspot(String key) {\n    return aServerHotspot(key, Path.of(\"file/path\"));\n  }\n\n  public static ServerHotspot aServerHotspot(String key, Path filePath) {\n    return new ServerHotspot(\n      key,\n      \"repo:key\",\n      \"message\",\n      filePath,\n      new TextRangeWithHash(1, 2, 3, 4, \"\"),\n      Instant.now(),\n      HotspotReviewStatus.TO_REVIEW, VulnerabilityProbability.HIGH,\n      \"test@user.com\");\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/org/sonarsource/sonarlint/core/serverconnection/storage/ServerIssueFixtures.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.serverconnection.storage;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport org.sonarsource.sonarlint.core.commons.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.FileLevelServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.LineLevelServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.RangeLevelServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerDependencyRisk;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerTaintIssue;\n\npublic class ServerIssueFixtures {\n  public static LineLevelServerIssue aBatchServerIssue() {\n    return new LineLevelServerIssue(\n      \"key\",\n      true,\n      IssueStatus.WONT_FIX,\n      \"repo:key\",\n      \"message\",\n      \"hash\",\n      Path.of(\"file/path\"),\n      Instant.now(),\n      IssueSeverity.MINOR,\n      RuleType.BUG,\n      1,\n      Map.of(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.HIGH));\n  }\n\n  public static FileLevelServerIssue aFileLevelServerIssue() {\n    return new FileLevelServerIssue(\n      \"key\",\n      true,\n      IssueStatus.WONT_FIX,\n      \"repo:key\",\n      \"message\",\n      Path.of(\"file/path\"),\n      Instant.now(),\n      IssueSeverity.MINOR,\n      RuleType.BUG,\n      Map.of(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.HIGH));\n  }\n\n  public static RangeLevelServerIssue aServerIssue() {\n    return new RangeLevelServerIssue(\n      \"key\",\n      true,\n      IssueStatus.WONT_FIX,\n      \"repo:key\",\n      \"message\",\n      Path.of(\"file/path\"),\n      Instant.now(),\n      IssueSeverity.MINOR,\n      RuleType.BUG,\n      new TextRangeWithHash(1, 2, 3, 4, \"ab12\"),\n      Map.of(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.HIGH));\n  }\n\n  public static ServerTaintIssue aServerTaintIssue() {\n    return new ServerTaintIssue(\n      UUID.randomUUID(),\n      \"key\",\n      false,\n      null,\n      \"repo:key\",\n      \"message\",\n      Path.of(\"file/path\"),\n      Instant.now(),\n      IssueSeverity.MINOR,\n      RuleType.VULNERABILITY,\n      new TextRangeWithHash(1, 2, 3, 4, \"ab12\"), \"context\",\n      CleanCodeAttribute.TRUSTWORTHY, Map.of(SoftwareQuality.SECURITY, ImpactSeverity.HIGH),\n      List.of(aServerTaintIssueFlow()));\n  }\n\n  public static ServerDependencyRisk aServerDependencyRisk() {\n    return new ServerDependencyRisk(\n      UUID.randomUUID(),\n      ServerDependencyRisk.Type.VULNERABILITY,\n      ServerDependencyRisk.Severity.HIGH,\n      ServerDependencyRisk.SoftwareQuality.SECURITY,\n      ServerDependencyRisk.Status.OPEN,\n      \"com.example.vulnerable\",\n      \"1.0.0\",\n      \"CVE-1234\",\n      \"7.5\",\n      List.of(\n        ServerDependencyRisk.Transition.CONFIRM,\n        ServerDependencyRisk.Transition.REOPEN));\n  }\n\n  private static ServerTaintIssue.Flow aServerTaintIssueFlow() {\n    return new ServerTaintIssue.Flow(List.of(aServerTaintIssueFlowLocation()));\n  }\n\n  private static ServerTaintIssue.ServerIssueLocation aServerTaintIssueFlowLocation() {\n    return new ServerTaintIssue.ServerIssueLocation(\n      Path.of(\"file/path\"),\n      new TextRangeWithHash(5, 6, 7, 8, \"rangeHash\"),\n      \"message\");\n  }\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/java/testutils/MockWebServerExtensionWithProtobuf.java",
    "content": "/*\n * SonarLint Core - Server Connection\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage testutils;\n\nimport com.google.protobuf.Message;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.Arrays;\nimport java.util.Iterator;\nimport javax.annotation.Nullable;\nimport mockwebserver3.MockResponse;\nimport okio.Buffer;\nimport org.sonarsource.sonarlint.core.commons.testutils.MockWebServerExtension;\nimport org.sonarsource.sonarlint.core.http.HttpClientProvider;\nimport org.sonarsource.sonarlint.core.serverapi.EndpointParams;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\n\nimport static org.junit.jupiter.api.Assertions.fail;\n\npublic class MockWebServerExtensionWithProtobuf extends MockWebServerExtension {\n\n  public void addProtobufResponse(String path, Message m) {\n    try (var b = new Buffer()) {\n      m.writeTo(b.outputStream());\n      responsesByPath.put(path, new MockResponse.Builder().body(b).build());\n    } catch (IOException e) {\n      fail(e);\n    }\n  }\n\n  public void addProtobufResponseDelimited(String path, Message... m) {\n    try (var b = new Buffer()) {\n      writeMessages(b.outputStream(), Arrays.asList(m).iterator());\n      responsesByPath.put(path, new MockResponse.Builder().body(b).build());\n    }\n  }\n\n  public static <T extends Message> void writeMessages(OutputStream output, Iterator<T> messages) {\n    while (messages.hasNext()) {\n      writeMessage(output, messages.next());\n    }\n  }\n\n  public static <T extends Message> void writeMessage(OutputStream output, T message) {\n    try {\n      message.writeDelimitedTo(output);\n    } catch (IOException e) {\n      throw new IllegalStateException(\"failed to write message: \" + message, e);\n    }\n  }\n\n  public ServerApiHelper serverApiHelper() {\n    return serverApiHelper(null);\n  }\n\n  public ServerApiHelper serverApiHelper(@Nullable String organizationKey) {\n    return new ServerApiHelper(endpointParams(organizationKey), HttpClientProvider.forTesting().getHttpClientWithoutAuth());\n  }\n\n  public EndpointParams endpointParams() {\n    return endpointParams(null);\n  }\n\n  public EndpointParams endpointParams(@Nullable String organizationKey) {\n    return new EndpointParams(url(\"/\"), url(\"/\"), organizationKey != null, organizationKey);\n  }\n\n}\n"
  },
  {
    "path": "backend/server-connection/src/test/resources/logback-test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE configuration>\n\n<configuration>\n  <include resource=\"logback-shared.xml\"/>\n</configuration>"
  },
  {
    "path": "backend/telemetry/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-backend-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../pom.xml</relativePath>\n  </parent>\n  <artifactId>sonarlint-telemetry</artifactId>\n  <name>SonarLint Core - Telemetry</name>\n  <description>Manage telemetry</description>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.code.findbugs</groupId>\n      <artifactId>jsr305</artifactId>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-commons</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-http</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.sonarlint.core</groupId>\n      <artifactId>sonarlint-rpc-protocol</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>com.google.code.gson</groupId>\n      <artifactId>gson</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-lang3</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.springframework</groupId>\n      <artifactId>spring-context</artifactId>\n      <scope>provided</scope>\n    </dependency>\n\n    <!-- unit tests -->\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-engine</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-params</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.assertj</groupId>\n      <artifactId>assertj-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.wiremock</groupId>\n      <artifactId>wiremock-jetty12</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <!-- For Apache HTTPClient -->\n    <dependency>\n      <groupId>ch.qos.logback</groupId>\n      <artifactId>logback-classic</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.awaitility</groupId>\n      <artifactId>awaitility</artifactId>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <profiles>\n    <!-- Workaround for https://issues.apache.org/jira/projects/MJAR/issues/MJAR-138 -->\n    <profile>\n      <id>conditionally-add-commons-tests-if-tests-not-skipped</id>\n      <activation>\n        <property>\n          <name>maven.test.skip</name>\n          <value>!true</value>\n        </property>\n      </activation>\n      <dependencies>\n        <dependency>\n          <groupId>${project.groupId}</groupId>\n          <artifactId>sonarlint-commons</artifactId>\n          <version>${project.version}</version>\n          <classifier>tests</classifier>\n          <type>test-jar</type>\n          <scope>test</scope>\n        </dependency>\n      </dependencies>\n    </profile>\n  </profiles>\n\n</project>\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/InternalDebug.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\n/**\n * Telemetry issues are silently ignored to not annoy users. In order to ease detection of issues, people (usually SonarSourcers) can define\n * this env variable to see telemetry errors in their logs.\n *\n */\npublic class InternalDebug {\n\n  static final String INTERNAL_DEBUG_ENV = \"SONARLINT_INTERNAL_DEBUG\";\n\n  private static boolean isEnabled = \"true\".equals(System.getenv(INTERNAL_DEBUG_ENV));\n\n  private InternalDebug() {\n    // utility class, forbidden constructor\n  }\n\n  public static boolean isEnabled() {\n    return isEnabled;\n  }\n\n  // For testing\n  public static void setEnabled(boolean isEnabled) {\n    InternalDebug.isEnabled = isEnabled;\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/TelemetryAnalysisReportingCounter.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\npublic class TelemetryAnalysisReportingCounter {\n  private int analysisReportingCount;\n\n  public TelemetryAnalysisReportingCounter() {\n  }\n\n  public TelemetryAnalysisReportingCounter(int analysisReportingTriggered) {\n    this.analysisReportingCount = analysisReportingTriggered;\n  }\n\n  public int getAnalysisReportingCount() {\n    return analysisReportingCount;\n  }\n\n  public void incrementAnalysisReportingCount() {\n    this.analysisReportingCount++;\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/TelemetryAnalyzerPerformance.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.TreeMap;\n\npublic class TelemetryAnalyzerPerformance {\n  private static final TreeMap<Integer, String> INTERVALS;\n  private int analysisCount;\n\n  static {\n    INTERVALS = new TreeMap<>();\n    INTERVALS.put(300, \"0-300\");\n    INTERVALS.put(500, \"300-500\");\n    INTERVALS.put(1000, \"500-1000\");\n    INTERVALS.put(2000, \"1000-2000\");\n    INTERVALS.put(4000, \"2000-4000\");\n    INTERVALS.put(Integer.MAX_VALUE, \"4000+\");\n  }\n\n  private final Map<String, Integer> frequencies;\n\n  public TelemetryAnalyzerPerformance() {\n    frequencies = new LinkedHashMap<>();\n    INTERVALS.forEach((k, v) -> frequencies.put(v, 0));\n  }\n\n  public void registerAnalysis(int analysisTimeMs) {\n    var entry = INTERVALS.higherEntry(analysisTimeMs);\n    if (entry != null) {\n      frequencies.compute(entry.getValue(), (k, v) -> v != null ? (v + 1) : 1);\n      analysisCount++;\n    }\n  }\n\n  public Map<String, Integer> frequencies() {\n    return frequencies;\n  }\n\n  public int analysisCount() {\n    return analysisCount;\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/TelemetryConnectionAttributes.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\nimport javax.annotation.Nullable;\n\npublic record TelemetryConnectionAttributes(\n  @Nullable String userId,\n  @Nullable String serverId,\n  @Nullable String organizationId\n) {\n\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/TelemetryFindingsFilteredCounter.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\npublic class TelemetryFindingsFilteredCounter {\n  private int findingsFilteredCount;\n\n  public TelemetryFindingsFilteredCounter() {\n  }\n\n  public TelemetryFindingsFilteredCounter(int findingsFilteredCount) {\n    this.findingsFilteredCount = findingsFilteredCount;\n  }\n\n  public int getFindingsFilteredCount() {\n    return findingsFilteredCount;\n  }\n\n  public void incrementFindingsFilteredCount() {\n    this.findingsFilteredCount++;\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/TelemetryFixSuggestionReceivedCounter.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AiSuggestionSource;\n\npublic record TelemetryFixSuggestionReceivedCounter(AiSuggestionSource aiSuggestionsSource,\n                                                    int snippetsCount,\n                                                    boolean wasGeneratedFromIde) {\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/TelemetryFixSuggestionResolvedStatus.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.FixSuggestionStatus;\n\npublic class TelemetryFixSuggestionResolvedStatus {\n  @Nullable\n  private FixSuggestionStatus fixSuggestionResolvedStatus;\n  @Nullable\n  private final Integer fixSuggestionResolvedSnippetIndex;\n\n  public TelemetryFixSuggestionResolvedStatus(@Nullable FixSuggestionStatus fixSuggestionResolvedStatus, @Nullable Integer fixSuggestionResolvedSnippetIndex) {\n    this.fixSuggestionResolvedStatus = fixSuggestionResolvedStatus;\n    this.fixSuggestionResolvedSnippetIndex = fixSuggestionResolvedSnippetIndex;\n  }\n\n  public FixSuggestionStatus getFixSuggestionResolvedStatus() {\n    return fixSuggestionResolvedStatus;\n  }\n\n  @Nullable\n  public Integer getFixSuggestionResolvedSnippetIndex() {\n    return fixSuggestionResolvedSnippetIndex;\n  }\n\n  public void setFixSuggestionResolvedStatus(FixSuggestionStatus fixSuggestionResolvedStatus) {\n    this.fixSuggestionResolvedStatus = fixSuggestionResolvedStatus;\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/TelemetryHelpAndFeedbackCounter.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\npublic class TelemetryHelpAndFeedbackCounter {\n  private int helpAndFeedbackLinkClickedCount;\n\n  public TelemetryHelpAndFeedbackCounter() {\n  }\n\n  public TelemetryHelpAndFeedbackCounter(int helpAndFeedbackLinkClicked) {\n    this.helpAndFeedbackLinkClickedCount = helpAndFeedbackLinkClicked;\n  }\n\n  public int getHelpAndFeedbackLinkClickedCount() {\n    return helpAndFeedbackLinkClickedCount;\n  }\n\n  public void incrementHelpAndFeedbackLinkClickedCount() {\n    this.helpAndFeedbackLinkClickedCount++;\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/TelemetryHttpClient.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport java.time.OffsetDateTime;\nimport java.time.temporal.ChronoUnit;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.CompletableFuture;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.http.HttpClient;\nimport org.sonarsource.sonarlint.core.http.HttpClientProvider;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.TelemetryClientConstantAttributesDto;\nimport org.sonarsource.sonarlint.core.telemetry.measures.payload.TelemetryMeasuresBuilder;\nimport org.sonarsource.sonarlint.core.telemetry.measures.payload.TelemetryMeasuresPayload;\nimport org.sonarsource.sonarlint.core.telemetry.payload.HotspotPayload;\nimport org.sonarsource.sonarlint.core.telemetry.payload.IssuePayload;\nimport org.sonarsource.sonarlint.core.telemetry.payload.ShareConnectedModePayload;\nimport org.sonarsource.sonarlint.core.telemetry.payload.ShowHotspotPayload;\nimport org.sonarsource.sonarlint.core.telemetry.payload.ShowIssuePayload;\nimport org.sonarsource.sonarlint.core.telemetry.payload.TaintVulnerabilitiesPayload;\nimport org.sonarsource.sonarlint.core.telemetry.payload.TelemetryHelpAndFeedbackPayload;\nimport org.sonarsource.sonarlint.core.telemetry.payload.TelemetryPayload;\nimport org.sonarsource.sonarlint.core.telemetry.payload.TelemetryRulesPayload;\nimport org.sonarsource.sonarlint.core.telemetry.payload.cayc.CleanAsYouCodePayload;\nimport org.sonarsource.sonarlint.core.telemetry.payload.cayc.NewCodeFocusPayload;\nimport org.springframework.beans.factory.annotation.Qualifier;\n\npublic class TelemetryHttpClient {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final String product;\n  private final String version;\n  private final String ideVersion;\n  private final String platform;\n  private final String architecture;\n  private final HttpClient client;\n  private final String endpoint;\n  private final Map<String, Object> additionalAttributes;\n\n  public TelemetryHttpClient(InitializeParams initializeParams, HttpClientProvider httpClientProvider, @Qualifier(\"telemetryEndpoint\") String telemetryEndpoint) {\n    TelemetryClientConstantAttributesDto attributes = initializeParams.getTelemetryConstantAttributes();\n    this.product = attributes.getProductName();\n    this.version = attributes.getProductVersion();\n    this.ideVersion = attributes.getIdeVersion();\n    this.platform = SystemUtils.OS_NAME;\n    this.architecture = SystemUtils.OS_ARCH;\n    this.client = httpClientProvider.getHttpClientWithoutAuth();\n    this.endpoint = telemetryEndpoint;\n    this.additionalAttributes = attributes.getAdditionalAttributes();\n  }\n\n  void upload(TelemetryLocalStorage data, TelemetryLiveAttributes telemetryLiveAttributes) {\n    try {\n      sendPost(createPayload(data, telemetryLiveAttributes));\n    } catch (Throwable catchEmAll) {\n      if (InternalDebug.isEnabled()) {\n        LOG.error(\"Failed to upload telemetry data\", catchEmAll);\n      }\n    }\n    try {\n      sendMetricsPostIfNeeded(new TelemetryMeasuresBuilder(platform, product, data, telemetryLiveAttributes).build());\n    } catch (Throwable catchEmAll) {\n      if (InternalDebug.isEnabled()) {\n        LOG.error(\"Failed to upload telemetry metrics data\", catchEmAll);\n      }\n    }\n  }\n\n  void optOut(TelemetryLocalStorage data, TelemetryLiveAttributes telemetryLiveAttributes) {\n    try {\n      sendDelete(createPayload(data, telemetryLiveAttributes));\n    } catch (Throwable catchEmAll) {\n      if (InternalDebug.isEnabled()) {\n        LOG.error(\"Failed to upload telemetry opt-out\", catchEmAll);\n      }\n    }\n  }\n\n  private TelemetryPayload createPayload(TelemetryLocalStorage data, TelemetryLiveAttributes telemetryLiveAttrs) {\n    var systemTime = OffsetDateTime.now();\n    var daysSinceInstallation = data.installTime().until(systemTime, ChronoUnit.DAYS);\n    var analyzers = TelemetryUtils.toPayload(data.analyzers());\n    var notifications = TelemetryUtils.toPayload(telemetryLiveAttrs.isDevNotificationsDisabled(), data.notifications());\n    var showHotspotPayload = new ShowHotspotPayload(data.showHotspotRequestsCount());\n    var showIssuePayload = new ShowIssuePayload(data.getShowIssueRequestsCount());\n    var hotspotPayload = new HotspotPayload(data.openHotspotInBrowserCount(), data.hotspotStatusChangedCount());\n    var taintVulnerabilitiesPayload = new TaintVulnerabilitiesPayload(data.taintVulnerabilitiesInvestigatedLocallyCount(),\n      data.taintVulnerabilitiesInvestigatedRemotelyCount());\n    var issuePayload = new IssuePayload(data.issueStatusChangedRuleKeys(), data.issueStatusChangedCount());\n    var jre = System.getProperty(\"java.version\");\n    var telemetryRulesPayload = new TelemetryRulesPayload(telemetryLiveAttrs.getNonDefaultEnabledRules(),\n      telemetryLiveAttrs.getDefaultDisabledRules(), data.getRaisedIssuesRules(), data.getQuickFixesApplied());\n    var helpAndFeedbackPayload = new TelemetryHelpAndFeedbackPayload(data.getHelpAndFeedbackLinkClickedCounter());\n    var fixSuggestionPayload = TelemetryUtils.toFixSuggestionResolvedPayload(\n      data.getFixSuggestionReceivedCounter(),\n      data.getFixSuggestionResolved()\n    );\n    var countIssuesWithPossibleAiFixFromIde = data.getCountIssuesWithPossibleAiFixFromIde();\n    var cleanAsYouCodePayload = new CleanAsYouCodePayload(new NewCodeFocusPayload(data.isFocusOnNewCode(), data.getCodeFocusChangedCount()));\n\n    ShareConnectedModePayload shareConnectedModePayload;\n    if (telemetryLiveAttrs.usesConnectedMode()) {\n      shareConnectedModePayload = new ShareConnectedModePayload(data.getManualAddedBindingsCount(), data.getImportedAddedBindingsCount(),\n        data.getAutoAddedBindingsCount(), data.getExportedConnectedModeCount());\n    } else {\n      shareConnectedModePayload = new ShareConnectedModePayload(null, null, null, null);\n    }\n\n    var mergedAdditionalAttributes = new HashMap<>(telemetryLiveAttrs.getAdditionalAttributes());\n    mergedAdditionalAttributes.putAll(additionalAttributes);\n\n    return new TelemetryPayload(daysSinceInstallation, data.numUseDays(), product, version, ideVersion, platform, architecture,\n      telemetryLiveAttrs.usesConnectedMode(), telemetryLiveAttrs.usesSonarCloud(), systemTime, data.installTime(), platform, jre,\n      telemetryLiveAttrs.getNodeVersion(), analyzers, notifications, showHotspotPayload, showIssuePayload,\n      taintVulnerabilitiesPayload, telemetryRulesPayload, hotspotPayload, issuePayload, helpAndFeedbackPayload,\n      fixSuggestionPayload, countIssuesWithPossibleAiFixFromIde, cleanAsYouCodePayload, shareConnectedModePayload, mergedAdditionalAttributes);\n  }\n\n  private void sendPost(TelemetryPayload payload) {\n    logTelemetryPayload(payload);\n    var responseCompletableFuture = client.postAsync(endpoint, HttpClient.JSON_CONTENT_TYPE, payload.toJson());\n    handleTelemetryResponse(responseCompletableFuture, \"data\");\n  }\n\n  private void sendMetricsPostIfNeeded(TelemetryMeasuresPayload payload) {\n    if (!payload.hasMetrics()) {\n      // No metrics to send\n      if (isTelemetryLogEnabled()) {\n        LOG.info(\"Not sending empty telemetry metrics payload.\");\n      }\n      return;\n    }\n\n    logTelemetryMetricsPayload(payload);\n    var responseCompletableFuture = client.postAsync(endpoint + \"/metrics\", HttpClient.JSON_CONTENT_TYPE, payload.toJson());\n    handleTelemetryResponse(responseCompletableFuture, \"data\");\n  }\n\n  private void logTelemetryPayload(TelemetryPayload payload) {\n    if (isTelemetryLogEnabled()) {\n      LOG.info(\"Sending telemetry payload.\");\n      LOG.info(payload.toJson());\n    }\n  }\n\n  private void logTelemetryMetricsPayload(TelemetryMeasuresPayload payload) {\n    if (isTelemetryLogEnabled()) {\n      LOG.info(\"Sending telemetry metrics payload.\");\n      LOG.info(payload.toJson());\n    }\n  }\n\n  private void sendDelete(TelemetryPayload payload) {\n    var responseCompletableFuture = client.deleteAsync(endpoint, HttpClient.JSON_CONTENT_TYPE, payload.toJson());\n    handleTelemetryResponse(responseCompletableFuture, \"opt-out\");\n  }\n\n  private static void handleTelemetryResponse(CompletableFuture<HttpClient.Response> responseCompletableFuture, String uploadType) {\n    responseCompletableFuture.thenAccept(response -> {\n      if (!response.isSuccessful() && InternalDebug.isEnabled()) {\n        LOG.error(\"Failed to upload telemetry {}: {}\", uploadType, response.toString());\n      }\n    }).exceptionally(exception -> {\n      if (InternalDebug.isEnabled()) {\n        LOG.error(String.format(\"Failed to upload telemetry %s\", uploadType), exception);\n      }\n      return null;\n    });\n  }\n\n  @VisibleForTesting\n  boolean isTelemetryLogEnabled(){\n    return Boolean.parseBoolean(System.getenv(\"SONARLINT_TELEMETRY_LOG\"));\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/TelemetryLiveAttributes.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\nimport java.util.List;\nimport java.util.Map;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.TelemetryClientLiveAttributesResponse;\n\npublic class TelemetryLiveAttributes {\n\n  private final TelemetryServerAttributes serverAttributes;\n  private final TelemetryClientLiveAttributesResponse clientAttributes;\n\n  public TelemetryLiveAttributes(TelemetryServerAttributes serverAttributes,\n    TelemetryClientLiveAttributesResponse clientAttributes) {\n    this.serverAttributes = serverAttributes;\n    this.clientAttributes = clientAttributes;\n  }\n\n  public boolean usesConnectedMode() {\n    return serverAttributes.usesConnectedMode();\n  }\n\n  public boolean usesSonarCloud() {\n    return serverAttributes.usesSonarCloud();\n  }\n\n  public int countChildBindings() {\n    return serverAttributes.childBindingCount();\n  }\n\n  public int countSonarQubeServerBindings() {\n    return serverAttributes.sonarQubeServerBindingCount();\n  }\n\n  public int countSonarQubeCloudEUBindings() {\n    return serverAttributes.sonarQubeCloudEUBindingCount();\n  }\n\n  public int countSonarQubeCloudUSBindings() {\n    return serverAttributes.sonarQubeCloudUSBindingCount();\n  }\n\n  public boolean isDevNotificationsDisabled() {\n    return serverAttributes.devNotificationsDisabled();\n  }\n\n  public List<String> getNonDefaultEnabledRules() {\n    return serverAttributes.nonDefaultEnabledRules();\n  }\n\n  public List<String> getDefaultDisabledRules() {\n    return serverAttributes.defaultDisabledRules();\n  }\n\n  @Nullable\n  public String getNodeVersion() {\n    return serverAttributes.nodeVersion();\n  }\n\n  public List<TelemetryConnectionAttributes> getConnectionsAttributes() {\n    return serverAttributes.connectionsAttributes();\n  }\n\n  public Map<String, Object> getAdditionalAttributes() {\n    return clientAttributes.getAdditionalAttributes();\n  }\n\n  public boolean hasJoinedIdeLabs() {\n    return clientAttributes.hasJoinedIdeLabs();\n  }\n\n  public boolean hasEnabledIdeLabs() {\n    return clientAttributes.hasEnabledIdeLabs();\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/TelemetryLocalStorage.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.OffsetDateTime;\nimport java.time.OffsetTime;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.EnumMap;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.UUID;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.storage.local.LocalStorage;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.AiAgent;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AiSuggestionSource;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AnalysisReportingType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.FixSuggestionStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.McpTransportMode;\n\nimport static java.time.temporal.ChronoUnit.DAYS;\n\npublic class TelemetryLocalStorage implements LocalStorage {\n  @Deprecated\n  private LocalDate installDate;\n  private LocalDate lastUseDate;\n  private LocalDateTime lastUploadDateTime;\n  private OffsetDateTime installTime;\n  private long numUseDays;\n  private boolean enabled;\n  private final Map<String, TelemetryAnalyzerPerformance> analyzers;\n  private final Map<String, TelemetryNotificationsCounter> notificationsCountersByEventType;\n  private int showHotspotRequestsCount;\n  private int showIssueRequestsCount;\n  private int openHotspotInBrowserCount;\n  private int taintVulnerabilitiesInvestigatedLocallyCount;\n  private int taintVulnerabilitiesInvestigatedRemotelyCount;\n  private int hotspotStatusChangedCount;\n  private final Set<String> issueStatusChangedRuleKeys;\n  private int issueStatusChangedCount;\n  private final Set<String> raisedIssuesRules;\n  private final Set<String> quickFixesApplied;\n  private final Map<String, Integer> quickFixCountByRuleKey;\n  private final Map<String, TelemetryHelpAndFeedbackCounter> helpAndFeedbackLinkClickedCount;\n  private final Map<AnalysisReportingType, TelemetryAnalysisReportingCounter> analysisReportingCountersByType;\n  private final Map<String, TelemetryFindingsFilteredCounter> findingsFilteredCountersByType;\n  private final Map<String, TelemetryFixSuggestionReceivedCounter> fixSuggestionReceivedCounter;\n  private final Map<String, List<TelemetryFixSuggestionResolvedStatus>> fixSuggestionResolved;\n  private final Map<String, ToolCallCounter> calledToolsByName;\n  private final Set<UUID> issuesUuidAiFixableSeen;\n  private boolean isFocusOnNewCode;\n  private int codeFocusChangedCount;\n  private int manualAddedBindingsCount;\n  private int importedAddedBindingsCount;\n  private int autoAddedBindingsCount;\n  private int exportedConnectedModeCount;\n  private int newBindingsPropertiesFileCount;\n  private int newBindingsRemoteUrlCount;\n  private int newBindingsProjectNameCount;\n  private int newBindingsSharedConfigurationCount;\n  private int suggestedRemoteBindingsCount;\n  private long newIssuesFoundCount;\n  private long issuesFixedCount;\n  private int biggestNumberOfFilesInConfigScope;\n  private long listingTimeForBiggestNumberConfigScopeFiles;\n  private long longestListingTimeForConfigScopeFiles;\n  private int numberOfFilesForLongestFilesListingTimeConfigScope;\n  private int taintInvestigatedLocallyCount;\n  private int taintInvestigatedRemotelyCount;\n  private int hotspotInvestigatedLocallyCount;\n  private int hotspotInvestigatedRemotelyCount;\n  private int issueInvestigatedLocallyCount;\n  private int dependencyRiskInvestigatedRemotelyCount;\n  private int dependencyRiskInvestigatedLocallyCount;\n  private boolean isAutomaticAnalysisEnabled;\n  private int automaticAnalysisToggledCount;\n  private int mcpServerConfigurationRequestedCount;\n  private int mcpRuleFileRequestedCount;\n  private boolean isMcpIntegrationEnabled;\n  @Nullable\n  private McpTransportMode mcpTransportModeUsed;\n  private final Map<String, Integer> labsLinkClickedCount;\n  private final Map<String, Integer> labsFeedbackLinkClickedCount;\n  private final Map<AiAgent, Integer> aiHooksInstalledCount;\n  private final Map<String, Integer> campaignsShown;\n  private final Map<String, String> campaignsResolutions;\n  private int supportedLanguagesPanelOpenedCount;\n  private int supportedLanguagesPanelCtaClickedCount;\n\n  TelemetryLocalStorage() {\n    enabled = true;\n    installTime = OffsetDateTime.now();\n    analyzers = new LinkedHashMap<>();\n    notificationsCountersByEventType = new LinkedHashMap<>();\n    issueStatusChangedRuleKeys = new HashSet<>();\n    raisedIssuesRules = new HashSet<>();\n    quickFixesApplied = new HashSet<>();\n    quickFixCountByRuleKey = new LinkedHashMap<>();\n    helpAndFeedbackLinkClickedCount = new LinkedHashMap<>();\n    analysisReportingCountersByType = new LinkedHashMap<>();\n    findingsFilteredCountersByType = new LinkedHashMap<>();\n    fixSuggestionReceivedCounter = new LinkedHashMap<>();\n    fixSuggestionResolved = new LinkedHashMap<>();\n    issuesUuidAiFixableSeen = new HashSet<>();\n    calledToolsByName = new HashMap<>();\n    labsLinkClickedCount = new HashMap<>();\n    labsFeedbackLinkClickedCount = new HashMap<>();\n    aiHooksInstalledCount = new EnumMap<>(AiAgent.class);\n    campaignsShown = new HashMap<>();\n    campaignsResolutions = new HashMap<>();\n  }\n\n  public Collection<String> getRaisedIssuesRules() {\n    return raisedIssuesRules;\n  }\n\n  public void addReportedRules(Set<String> reportedRuleKeys) {\n    this.raisedIssuesRules.addAll(reportedRuleKeys);\n  }\n\n  public Collection<String> getQuickFixesApplied() {\n    return quickFixesApplied;\n  }\n\n  public void addQuickFixAppliedForRule(String ruleKey) {\n    markSonarLintAsUsedToday();\n    this.quickFixesApplied.add(ruleKey);\n    var currentCountForKey = this.quickFixCountByRuleKey.getOrDefault(ruleKey, 0);\n    this.quickFixCountByRuleKey.put(ruleKey, currentCountForKey + 1);\n  }\n\n  public Map<String, Integer> getQuickFixCountByRuleKey() {\n    return quickFixCountByRuleKey;\n  }\n\n  @Deprecated\n  void setInstallDate(LocalDate date) {\n    this.installDate = date;\n  }\n\n  @Deprecated\n  public LocalDate installDate() {\n    return installDate;\n  }\n\n  public OffsetDateTime installTime() {\n    return installTime;\n  }\n\n  public void setInstallTime(OffsetDateTime installTime) {\n    this.installTime = installTime;\n  }\n\n  void setLastUseDate(@Nullable LocalDate date) {\n    this.lastUseDate = date;\n  }\n\n  @CheckForNull\n  public LocalDate lastUseDate() {\n    return lastUseDate;\n  }\n\n  public Map<String, TelemetryAnalyzerPerformance> analyzers() {\n    return analyzers;\n  }\n\n  public Map<String, TelemetryNotificationsCounter> notifications() {\n    return notificationsCountersByEventType;\n  }\n\n  public Map<String, TelemetryHelpAndFeedbackCounter> getHelpAndFeedbackLinkClickedCounter() {\n    return helpAndFeedbackLinkClickedCount;\n  }\n\n  public Map<AnalysisReportingType, TelemetryAnalysisReportingCounter> getAnalysisReportingCountersByType() {\n    return analysisReportingCountersByType;\n  }\n\n  public Map<String, TelemetryFindingsFilteredCounter> getFindingsFilteredCountersByType() {\n    return findingsFilteredCountersByType;\n  }\n\n  public Map<String, TelemetryFixSuggestionReceivedCounter> getFixSuggestionReceivedCounter() {\n    return fixSuggestionReceivedCounter;\n  }\n\n  public Map<String, List<TelemetryFixSuggestionResolvedStatus>> getFixSuggestionResolved() {\n    return fixSuggestionResolved;\n  }\n\n  public int getCountIssuesWithPossibleAiFixFromIde() {\n    return issuesUuidAiFixableSeen.size();\n  }\n\n  public boolean isFocusOnNewCode() {\n    return isFocusOnNewCode;\n  }\n\n  public int getCodeFocusChangedCount() {\n    return codeFocusChangedCount;\n  }\n\n  void setLastUploadTime() {\n    setLastUploadTime(LocalDateTime.now());\n  }\n\n  void setLastUploadTime(@Nullable LocalDateTime dateTime) {\n    this.lastUploadDateTime = dateTime;\n  }\n\n  @CheckForNull\n  public LocalDateTime lastUploadTime() {\n    return lastUploadDateTime;\n  }\n\n  void setNumUseDays(long numUseDays) {\n    this.numUseDays = numUseDays;\n  }\n\n  void clearAfterPing() {\n    analyzers.clear();\n    notificationsCountersByEventType.clear();\n    showHotspotRequestsCount = 0;\n    showIssueRequestsCount = 0;\n    openHotspotInBrowserCount = 0;\n    taintVulnerabilitiesInvestigatedLocallyCount = 0;\n    taintVulnerabilitiesInvestigatedRemotelyCount = 0;\n    hotspotStatusChangedCount = 0;\n    issueStatusChangedRuleKeys.clear();\n    issueStatusChangedCount = 0;\n    raisedIssuesRules.clear();\n    quickFixesApplied.clear();\n    quickFixCountByRuleKey.clear();\n    helpAndFeedbackLinkClickedCount.clear();\n    analysisReportingCountersByType.clear();\n    findingsFilteredCountersByType.clear();\n    fixSuggestionReceivedCounter.clear();\n    fixSuggestionResolved.clear();\n    issuesUuidAiFixableSeen.clear();\n    codeFocusChangedCount = 0;\n    manualAddedBindingsCount = 0;\n    importedAddedBindingsCount = 0;\n    autoAddedBindingsCount = 0;\n    exportedConnectedModeCount = 0;\n    newBindingsPropertiesFileCount = 0;\n    newBindingsRemoteUrlCount = 0;\n    newBindingsProjectNameCount = 0;\n    newBindingsSharedConfigurationCount = 0;\n    suggestedRemoteBindingsCount = 0;\n    newIssuesFoundCount = 0;\n    issuesFixedCount = 0;\n    biggestNumberOfFilesInConfigScope = 0;\n    calledToolsByName.clear();\n    dependencyRiskInvestigatedLocallyCount = 0;\n    dependencyRiskInvestigatedRemotelyCount = 0;\n    automaticAnalysisToggledCount = 0;\n    mcpServerConfigurationRequestedCount = 0;\n    mcpRuleFileRequestedCount = 0;\n    isMcpIntegrationEnabled = false;\n    mcpTransportModeUsed = null;\n    labsLinkClickedCount.clear();\n    labsFeedbackLinkClickedCount.clear();\n    aiHooksInstalledCount.clear();\n    campaignsShown.clear();\n    campaignsResolutions.clear();\n    supportedLanguagesPanelOpenedCount = 0;\n    supportedLanguagesPanelCtaClickedCount = 0;\n  }\n\n  public long numUseDays() {\n    return numUseDays;\n  }\n\n  public void setEnabled(boolean enabled) {\n    this.enabled = enabled;\n  }\n\n  public boolean enabled() {\n    return enabled;\n  }\n\n  /**\n   * Register that an analysis was performed.\n   * This should be used when multiple files are analyzed.\n   *\n   * @see #setUsedAnalysis(String, int)\n   */\n  void setUsedAnalysis() {\n    markSonarLintAsUsedToday();\n  }\n\n  private void markSonarLintAsUsedToday() {\n    var now = LocalDate.now();\n    if (lastUseDate == null || !lastUseDate.equals(now)) {\n      numUseDays++;\n    }\n    lastUseDate = now;\n  }\n\n  /**\n   * Register the analysis of a single file, with information regarding language and duration of the analysis.\n   */\n  void setUsedAnalysis(String language, int analysisTimeMs) {\n    markSonarLintAsUsedToday();\n\n    var analyzer = analyzers.computeIfAbsent(language, x -> new TelemetryAnalyzerPerformance());\n    analyzer.registerAnalysis(analysisTimeMs);\n  }\n\n  static boolean isOlder(@Nullable LocalDate first, @Nullable LocalDate second) {\n    return first == null || (second != null && first.isBefore(second));\n  }\n\n  static boolean isOlder(@Nullable LocalDateTime first, @Nullable LocalDateTime second) {\n    return first == null || (second != null && first.isBefore(second));\n  }\n\n  @Override\n  public void validateAndMigrate() {\n    var today = LocalDate.now();\n\n    // migrate deprecated installDate\n    if (installDate != null && (installTime == null || installTime.toLocalDate().isAfter(installDate))) {\n      setInstallTime(installDate.atTime(OffsetTime.now()));\n    }\n\n    // fix install time if necessary\n    if (installTime == null || installTime.isAfter(OffsetDateTime.now())) {\n      setInstallTime(OffsetDateTime.now());\n    }\n\n    // calculate use days\n    if (lastUseDate == null) {\n      numUseDays = 0;\n      analyzers.clear();\n      return;\n    }\n\n    if (lastUseDate.isBefore(installTime.toLocalDate())) {\n      lastUseDate = installTime.toLocalDate();\n    } else if (lastUseDate.isAfter(today)) {\n      lastUseDate = today;\n    }\n\n    var maxUseDays = installTime.toLocalDate().until(lastUseDate, DAYS) + 1;\n    if (numUseDays() > maxUseDays) {\n      numUseDays = maxUseDays;\n    }\n  }\n\n  public void incrementDevNotificationsCount(String eventType) {\n    this.notificationsCountersByEventType.computeIfAbsent(eventType, k -> new TelemetryNotificationsCounter()).incrementDevNotificationsCount();\n  }\n\n  public void incrementDevNotificationsClicked(String eventType) {\n    markSonarLintAsUsedToday();\n    this.notificationsCountersByEventType.computeIfAbsent(eventType, k -> new TelemetryNotificationsCounter()).incrementDevNotificationsClicked();\n  }\n\n  public void incrementShowHotspotRequestCount() {\n    markSonarLintAsUsedToday();\n    showHotspotRequestsCount++;\n  }\n\n  public int showHotspotRequestsCount() {\n    return showHotspotRequestsCount;\n  }\n\n  public void incrementShowIssueRequestCount() {\n    markSonarLintAsUsedToday();\n    showIssueRequestsCount++;\n  }\n\n  public void fixSuggestionReceived(String suggestionId, AiSuggestionSource aiSuggestionSource, int snippetsCount, boolean wasGeneratedFromIde) {\n    markSonarLintAsUsedToday();\n    this.fixSuggestionReceivedCounter.computeIfAbsent(suggestionId, k -> new TelemetryFixSuggestionReceivedCounter(aiSuggestionSource, snippetsCount, wasGeneratedFromIde));\n  }\n\n  public void fixSuggestionResolved(String suggestionId, FixSuggestionStatus status, @Nullable Integer snippetIndex) {\n    markSonarLintAsUsedToday();\n    var fixSuggestionSnippets = this.fixSuggestionResolved.computeIfAbsent(suggestionId, k -> new ArrayList<>());\n    var existingSnippetStatus = fixSuggestionSnippets.stream()\n      .filter(s -> {\n        var previousIndex = s.getFixSuggestionResolvedSnippetIndex();\n        return (snippetIndex == null && previousIndex == null) ||\n          (previousIndex != null && previousIndex.equals(snippetIndex));\n      })\n      .findFirst();\n    // if we already had a status for this snippet, we should replace it\n    existingSnippetStatus.ifPresentOrElse(telemetryFixSuggestionResolvedStatus -> telemetryFixSuggestionResolvedStatus.setFixSuggestionResolvedStatus(status),\n      () -> fixSuggestionSnippets.add(new TelemetryFixSuggestionResolvedStatus(status, snippetIndex)));\n  }\n\n  public void addIssuesWithPossibleAiFixFromIde(Set<UUID> issues) {\n    markSonarLintAsUsedToday();\n    issuesUuidAiFixableSeen.addAll(issues);\n  }\n\n  public int getShowIssueRequestsCount() {\n    return showIssueRequestsCount;\n  }\n\n  public void incrementOpenHotspotInBrowserCount() {\n    markSonarLintAsUsedToday();\n    openHotspotInBrowserCount++;\n  }\n\n  public int openHotspotInBrowserCount() {\n    return openHotspotInBrowserCount;\n  }\n\n  public void incrementTaintVulnerabilitiesInvestigatedLocallyCount() {\n    markSonarLintAsUsedToday();\n    taintVulnerabilitiesInvestigatedLocallyCount++;\n  }\n\n  public int taintVulnerabilitiesInvestigatedLocallyCount() {\n    return taintVulnerabilitiesInvestigatedLocallyCount;\n  }\n\n  public void incrementTaintVulnerabilitiesInvestigatedRemotelyCount() {\n    markSonarLintAsUsedToday();\n    taintVulnerabilitiesInvestigatedRemotelyCount++;\n  }\n\n  public int taintVulnerabilitiesInvestigatedRemotelyCount() {\n    return taintVulnerabilitiesInvestigatedRemotelyCount;\n  }\n\n  public void helpAndFeedbackLinkClicked(String itemId) {\n    this.helpAndFeedbackLinkClickedCount.computeIfAbsent(itemId, k -> new TelemetryHelpAndFeedbackCounter()).incrementHelpAndFeedbackLinkClickedCount();\n  }\n\n  public void analysisReportingTriggered(AnalysisReportingType analysisType) {\n    this.analysisReportingCountersByType.computeIfAbsent(analysisType, k -> new TelemetryAnalysisReportingCounter()).incrementAnalysisReportingCount();\n  }\n\n  public void findingsFiltered(String filterType) {\n    markSonarLintAsUsedToday();\n    this.findingsFilteredCountersByType.computeIfAbsent(filterType, k -> new TelemetryFindingsFilteredCounter()).incrementFindingsFilteredCount();\n  }\n\n  public void incrementHotspotStatusChangedCount() {\n    markSonarLintAsUsedToday();\n    hotspotStatusChangedCount++;\n  }\n\n  public int hotspotStatusChangedCount() {\n    return hotspotStatusChangedCount;\n  }\n\n  public void addIssueStatusChanged(String ruleKey) {\n    markSonarLintAsUsedToday();\n    issueStatusChangedRuleKeys.add(ruleKey);\n    issueStatusChangedCount++;\n  }\n\n  public Set<String> issueStatusChangedRuleKeys() {\n    return issueStatusChangedRuleKeys;\n  }\n\n  public int issueStatusChangedCount() {\n    return issueStatusChangedCount;\n  }\n\n  public void setInitialNewCodeFocus(boolean focusOnNewCode) {\n    markSonarLintAsUsedToday();\n    this.isFocusOnNewCode = focusOnNewCode;\n  }\n\n  public void incrementNewCodeFocusChange() {\n    markSonarLintAsUsedToday();\n    this.isFocusOnNewCode = !this.isFocusOnNewCode;\n    codeFocusChangedCount++;\n  }\n\n  public void incrementManualAddedBindingsCount() {\n    markSonarLintAsUsedToday();\n    manualAddedBindingsCount++;\n  }\n\n  public int getManualAddedBindingsCount() {\n    return manualAddedBindingsCount;\n  }\n\n  public void incrementImportedAddedBindingsCount() {\n    markSonarLintAsUsedToday();\n    importedAddedBindingsCount++;\n  }\n\n  public int getImportedAddedBindingsCount() {\n    return importedAddedBindingsCount;\n  }\n\n  public void incrementAutoAddedBindingsCount() {\n    markSonarLintAsUsedToday();\n    autoAddedBindingsCount++;\n  }\n\n  public int getAutoAddedBindingsCount() {\n    return autoAddedBindingsCount;\n  }\n\n  public void incrementExportedConnectedModeCount() {\n    markSonarLintAsUsedToday();\n    exportedConnectedModeCount++;\n  }\n\n  public void incrementNewBindingsPropertiesFileCount() {\n    markSonarLintAsUsedToday();\n    newBindingsPropertiesFileCount++;\n  }\n\n  public void incrementNewBindingsRemoteUrlCount() {\n    markSonarLintAsUsedToday();\n    newBindingsRemoteUrlCount++;\n  }\n\n  public void incrementNewBindingsProjectNameCount() {\n    markSonarLintAsUsedToday();\n    newBindingsProjectNameCount++;\n  }\n\n  public void incrementNewBindingsSharedConfigurationCount() {\n    markSonarLintAsUsedToday();\n    newBindingsSharedConfigurationCount++;\n  }\n\n  public void incrementSuggestedRemoteBindingsCount() {\n    suggestedRemoteBindingsCount++;\n  }\n\n  public int getExportedConnectedModeCount() {\n    return exportedConnectedModeCount;\n  }\n\n  public int getNewBindingsPropertiesFileCount() {\n    return newBindingsPropertiesFileCount;\n  }\n\n  public int getNewBindingsRemoteUrlCount() {\n    return newBindingsRemoteUrlCount;\n  }\n\n  public int getNewBindingsProjectNameCount() {\n    return newBindingsProjectNameCount;\n  }\n\n  public int getNewBindingsSharedConfigurationCount() {\n    return newBindingsSharedConfigurationCount;\n  }\n\n  public int getSuggestedRemoteBindingsCount() {\n    return suggestedRemoteBindingsCount;\n  }\n\n  public void addNewlyFoundIssues(long newIssues) {\n    markSonarLintAsUsedToday();\n    newIssuesFoundCount += newIssues;\n  }\n\n  public long getNewIssuesFoundCount() {\n    return newIssuesFoundCount;\n  }\n\n  public void addFixedIssues(long fixedIssues) {\n    markSonarLintAsUsedToday();\n    issuesFixedCount += fixedIssues;\n  }\n\n  public long getIssuesFixedCount() {\n    return issuesFixedCount;\n  }\n\n  public void setMcpIntegrationEnabled(boolean isMcpIntegrationEnabled) {\n    this.isMcpIntegrationEnabled = isMcpIntegrationEnabled;\n  }\n\n  public boolean isMcpIntegrationEnabled() {\n    return isMcpIntegrationEnabled;\n  }\n\n  public void setMcpTransportModeUsed(McpTransportMode mcpTransportMode) {\n    this.mcpTransportModeUsed = mcpTransportMode;\n  }\n\n  @CheckForNull\n  public McpTransportMode getMcpTransportModeUsed() {\n    return mcpTransportModeUsed;\n  }\n\n  public void incrementToolCalledCount(String toolName, boolean succeeded) {\n    markSonarLintAsUsedToday();\n    calledToolsByName.computeIfAbsent(toolName, k -> new ToolCallCounter()).incrementCount(succeeded);\n  }\n\n  public Map<String, ToolCallCounter> getCalledToolsByName() {\n    return calledToolsByName;\n  }\n\n  public void updateListFilesPerformance(int size, long timeMs) {\n    if (size > biggestNumberOfFilesInConfigScope) {\n      biggestNumberOfFilesInConfigScope = size;\n      listingTimeForBiggestNumberConfigScopeFiles = timeMs;\n    }\n    if (timeMs > longestListingTimeForConfigScopeFiles) {\n      longestListingTimeForConfigScopeFiles = timeMs;\n      numberOfFilesForLongestFilesListingTimeConfigScope = size;\n    }\n  }\n\n  public int getBiggestNumberOfFilesInConfigScope() {\n    return biggestNumberOfFilesInConfigScope;\n  }\n\n  public long getListingTimeForBiggestNumberConfigScopeFiles() {\n    return listingTimeForBiggestNumberConfigScopeFiles;\n  }\n\n  public int getNumberOfFilesForLongestFilesListingTimeConfigScope() {\n    return numberOfFilesForLongestFilesListingTimeConfigScope;\n  }\n\n  public long getLongestListingTimeForConfigScopeFiles() {\n    return longestListingTimeForConfigScopeFiles;\n  }\n\n  public void incrementHotspotInvestigatedLocallyCount() {\n    markSonarLintAsUsedToday();\n    hotspotInvestigatedLocallyCount++;\n  }\n\n  public void incrementHotspotInvestigatedRemotelyCount() {\n    markSonarLintAsUsedToday();\n    hotspotInvestigatedRemotelyCount++;\n  }\n\n  public void incrementTaintInvestigatedLocallyCount() {\n    markSonarLintAsUsedToday();\n    taintInvestigatedLocallyCount++;\n  }\n\n  public void incrementTaintInvestigatedRemotelyCount() {\n    markSonarLintAsUsedToday();\n    taintInvestigatedRemotelyCount++;\n  }\n\n  public void incrementIssueInvestigatedLocallyCount() {\n    markSonarLintAsUsedToday();\n    issueInvestigatedLocallyCount++;\n  }\n\n  public void incrementDependencyRiskInvestigatedRemotelyCount() {\n    markSonarLintAsUsedToday();\n    dependencyRiskInvestigatedRemotelyCount++;\n  }\n\n  public void incrementDependencyRiskInvestigatedLocallyCount() {\n    markSonarLintAsUsedToday();\n    dependencyRiskInvestigatedLocallyCount++;\n  }\n\n  public int getHotspotInvestigatedRemotelyCount() {\n    return hotspotInvestigatedRemotelyCount;\n  }\n\n  public int getHotspotInvestigatedLocallyCount() {\n    return hotspotInvestigatedLocallyCount;\n  }\n\n  public int getTaintInvestigatedRemotelyCount() {\n    return taintInvestigatedRemotelyCount;\n  }\n\n  public int getTaintInvestigatedLocallyCount() {\n    return taintInvestigatedLocallyCount;\n  }\n\n  public int getIssueInvestigatedLocallyCount() {\n    return issueInvestigatedLocallyCount;\n  }\n\n  public int getDependencyRiskInvestigatedRemotelyCount() {\n    return dependencyRiskInvestigatedRemotelyCount;\n  }\n\n  public int getDependencyRiskInvestigatedLocallyCount() {\n    return dependencyRiskInvestigatedLocallyCount;\n  }\n\n  public boolean isAutomaticAnalysisEnabled() {\n    return isAutomaticAnalysisEnabled;\n  }\n\n  public int getAutomaticAnalysisToggledCount() {\n    return automaticAnalysisToggledCount;\n  }\n\n  public void setInitialAutomaticAnalysisEnablement(boolean automaticAnalysisEnabled) {\n    markSonarLintAsUsedToday();\n    this.isAutomaticAnalysisEnabled = automaticAnalysisEnabled;\n  }\n\n  public void incrementAutomaticAnalysisToggledCount() {\n    markSonarLintAsUsedToday();\n    this.isAutomaticAnalysisEnabled = !this.isAutomaticAnalysisEnabled;\n    automaticAnalysisToggledCount++;\n  }\n\n  public void incrementMcpServerConfigurationRequestedCount() {\n    markSonarLintAsUsedToday();\n    mcpServerConfigurationRequestedCount++;\n  }\n\n  public int getMcpServerConfigurationRequestedCount() {\n    return mcpServerConfigurationRequestedCount;\n  }\n\n  public void incrementMcpRuleFileRequestedCount() {\n    markSonarLintAsUsedToday();\n    mcpRuleFileRequestedCount++;\n  }\n\n  public int getMcpRuleFileRequestedCount() {\n    return mcpRuleFileRequestedCount;\n  }\n\n  public Map<String, Integer> getLabsFeedbackLinkClickedCount() {\n    return labsFeedbackLinkClickedCount;\n  }\n\n  public Map<String, Integer> getLabsLinkClickedCount() {\n    return labsLinkClickedCount;\n  }\n\n  public void ideLabsLinkClicked(String linkId) {\n    this.labsLinkClickedCount.merge(linkId, 1, Integer::sum);\n  }\n\n  public void ideLabsFeedbackLinkClicked(String featureId) {\n    this.labsFeedbackLinkClickedCount.merge(featureId, 1, Integer::sum);\n  }\n\n  public void aiHookInstalled(AiAgent aiAgent) {\n    markSonarLintAsUsedToday();\n    this.aiHooksInstalledCount.merge(aiAgent, 1, Integer::sum);\n  }\n\n  public Map<AiAgent, Integer> getAiHooksInstalledCount() {\n    return aiHooksInstalledCount;\n  }\n\n  public void campaignShown(String campaignName) {\n    campaignsShown.merge(campaignName, 1, Integer::sum);\n  }\n\n  public void campaignResolved(String campaignName, String campaignResolution) {\n    campaignsResolutions.put(campaignName, campaignResolution);\n  }\n\n  public Map<String, Integer> getCampaignsShown() {\n    return campaignsShown;\n  }\n\n  public Map<String, String> getCampaignsResolutions() {\n    return campaignsResolutions;\n  }\n\n  public void incrementSupportedLanguagesPanelOpenedCount() {\n    markSonarLintAsUsedToday();\n    supportedLanguagesPanelOpenedCount++;\n  }\n\n  public int getSupportedLanguagesPanelOpenedCount() {\n    return supportedLanguagesPanelOpenedCount;\n  }\n\n  public void incrementSupportedLanguagesPanelCtaClickedCount() {\n    markSonarLintAsUsedToday();\n    supportedLanguagesPanelCtaClickedCount++;\n  }\n\n  public int getSupportedLanguagesPanelCtaClickedCount() {\n    return supportedLanguagesPanelCtaClickedCount;\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/TelemetryLocalStorageManager.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.time.LocalDateTime;\nimport java.time.OffsetDateTime;\nimport java.util.function.Consumer;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.storage.local.FileStorageManager;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.TelemetryMigrationDto;\nimport org.springframework.beans.factory.annotation.Qualifier;\n\n/**\n * Serialize and deserialize telemetry data to persistent storage.\n */\npublic class TelemetryLocalStorageManager {\n\n  private final FileStorageManager<TelemetryLocalStorage> fileStorageManager;\n  @Nullable\n  private final TelemetryMigrationDto telemetryMigration;\n\n  public TelemetryLocalStorageManager(@Qualifier(\"telemetryPath\") Path telemetryPath, InitializeParams initializeParams) {\n    fileStorageManager = new FileStorageManager<>(telemetryPath, TelemetryLocalStorage::new, TelemetryLocalStorage.class);\n    this.telemetryMigration = initializeParams.getTelemetryMigration();\n  }\n\n  @VisibleForTesting\n  TelemetryLocalStorage tryRead() {\n    return getStorage();\n  }\n\n  private TelemetryLocalStorage getStorage() {\n    var inMemoryStorage = fileStorageManager.getStorage();\n    applyTelemetryMigration(inMemoryStorage);\n    return inMemoryStorage;\n  }\n\n  private void applyTelemetryMigration(TelemetryLocalStorage inMemoryStorage) {\n    if (needToMigrateTelemetry(inMemoryStorage)) {\n      inMemoryStorage.setEnabled(telemetryMigration.isEnabled());\n      inMemoryStorage.setInstallTime(telemetryMigration.getInstallTime());\n      inMemoryStorage.setNumUseDays(telemetryMigration.getNumUseDays());\n    }\n  }\n\n  private boolean needToMigrateTelemetry(TelemetryLocalStorage inMemoryStorage) {\n    if (telemetryMigration == null) {\n      return false;\n    }\n    var duration = Duration.between(inMemoryStorage.installTime(), OffsetDateTime.now());\n    return duration.getSeconds() < 10 && inMemoryStorage.numUseDays() == 0;\n  }\n\n  public void tryUpdateAtomically(Consumer<TelemetryLocalStorage> updater) {\n    fileStorageManager.tryUpdateAtomically(updater);\n  }\n\n  public LocalDateTime lastUploadTime() {\n    return getStorage().lastUploadTime();\n  }\n\n  public boolean isEnabled() {\n    return getStorage().enabled();\n  }\n\n  public OffsetDateTime installTime() {\n    return getStorage().installTime();\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/TelemetryManager.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\nimport java.time.LocalDateTime;\nimport java.time.OffsetDateTime;\nimport java.util.function.Consumer;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.telemetry.common.TelemetryUserSetting;\n\n/**\n * Manage telemetry data and persistent storage, and stateful telemetry actions.\n * The single central point for clients to manage telemetry.\n */\npublic class TelemetryManager implements TelemetryUserSetting {\n\n  static final int MIN_HOURS_BETWEEN_UPLOAD = 5;\n\n  private final TelemetryLocalStorageManager storageManager;\n  private final TelemetryHttpClient client;\n\n  TelemetryManager(TelemetryLocalStorageManager storageManager, TelemetryHttpClient client) {\n    this.storageManager = storageManager;\n    this.client = client;\n  }\n\n  void enable(TelemetryLiveAttributes telemetryLiveAttributes) {\n    storageManager.tryUpdateAtomically(localStorage -> {\n      localStorage.setEnabled(true);\n      if (isGracePeriodElapsedAndDayChanged(localStorage.lastUploadTime())) {\n        uploadAndClearTelemetry(telemetryLiveAttributes, localStorage);\n      }\n    });\n  }\n\n  private static boolean isGracePeriodElapsedAndDayChanged(@Nullable LocalDateTime lastUploadTime) {\n    return TelemetryUtils.isGracePeriodElapsedAndDayChanged(lastUploadTime, MIN_HOURS_BETWEEN_UPLOAD);\n  }\n\n  private void uploadAndClearTelemetry(TelemetryLiveAttributes telemetryLiveAttributes, TelemetryLocalStorage localStorage) {\n    client.upload(localStorage, telemetryLiveAttributes);\n    localStorage.setLastUploadTime();\n    localStorage.clearAfterPing();\n  }\n\n  /**\n   * Disable telemetry (opt-out).\n   */\n  void disable(TelemetryLiveAttributes telemetryLiveAttributes) {\n    storageManager.tryUpdateAtomically(data -> {\n      data.setEnabled(false);\n      client.optOut(data, telemetryLiveAttributes);\n    });\n  }\n\n  /**\n   * Upload telemetry data, when all conditions are satisfied:\n   * - telemetry is enabled\n   * - the day is different from the last upload\n   * - the grace period has elapsed since the last upload\n   * To be called periodically once a day.\n   */\n  void uploadAndClearTelemetry(TelemetryLiveAttributes telemetryLiveAttributes) {\n    if (isTelemetryEnabledByUser() && isGracePeriodElapsedAndDayChanged(storageManager.lastUploadTime())) {\n      storageManager.tryUpdateAtomically(localStorage -> uploadAndClearTelemetry(telemetryLiveAttributes, localStorage));\n    }\n  }\n\n  public void updateTelemetry(Consumer<TelemetryLocalStorage> updater) {\n    if (isTelemetryEnabledByUser()) {\n      storageManager.tryUpdateAtomically(updater);\n    }\n  }\n\n  @Override\n  public boolean isTelemetryEnabledByUser() {\n    return storageManager.isEnabled();\n  }\n\n  public OffsetDateTime installTime() {\n    return storageManager.installTime();\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/TelemetryNotificationsCounter.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\npublic class TelemetryNotificationsCounter {\n  private int devNotificationsCount;\n  private int devNotificationsClicked;\n\n  public TelemetryNotificationsCounter() {\n  }\n\n  public TelemetryNotificationsCounter(int devNotificationsCount, int devNotificationsClicked) {\n    this.devNotificationsCount = devNotificationsCount;\n    this.devNotificationsClicked = devNotificationsClicked;\n  }\n\n  public int getDevNotificationsClicked() {\n    return devNotificationsClicked;\n  }\n\n  public int getDevNotificationsCount() {\n    return devNotificationsCount;\n  }\n\n  public void incrementDevNotificationsCount() {\n    this.devNotificationsCount++;\n  }\n\n  public void incrementDevNotificationsClicked() {\n    this.devNotificationsClicked++;\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/TelemetryServerAttributes.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\nimport java.util.List;\nimport javax.annotation.Nullable;\n\n/**\n * @param usesConnectedMode             At least one project in the IDE is bound to a SQ server or SC\n * @param usesSonarCloud                At least one project in the IDE is bound to SC\n * @param childBindingCount             Number of bindings for a child configuration scope\n * @param sonarQubeServerBindingCount   Number of bindings with SonarQube Server\n * @param sonarQubeCloudEUBindingCount  Number of bindings with SonarQube Cloud EU\n * @param sonarQubeCloudUSBindingCount  Number of bindings with SonarQube Cloud US\n * @param devNotificationsDisabled      Are dev notifications disabled (if multiple connections are configured, return true if feature is disabled for at least one connection)\n * @param nonDefaultEnabledRules        Rule keys for rules that disabled by default, but was enabled by user in settings.\n * @param defaultDisabledRules          Rule keys for rules that enabled by default, but was disabled by user in settings.\n * @param nodeVersion                   Node.js version used by analyzers (detected or configured by the user).\n *                                      Empty if no node present/detected/configured\n * @param connectionsAttributes         Information about the connections configured in the IDE\n */\npublic record TelemetryServerAttributes(boolean usesConnectedMode, boolean usesSonarCloud, int childBindingCount, int sonarQubeServerBindingCount,\n                                        int sonarQubeCloudEUBindingCount, int sonarQubeCloudUSBindingCount, boolean devNotificationsDisabled,\n                                        List<String> nonDefaultEnabledRules, List<String> defaultDisabledRules,\n                                        @Nullable String nodeVersion, List<TelemetryConnectionAttributes> connectionsAttributes) {\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/TelemetryUtils.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.temporal.ChronoUnit;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.BinaryOperator;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.telemetry.payload.TelemetryAnalyzerPerformancePayload;\nimport org.sonarsource.sonarlint.core.telemetry.payload.TelemetryFixSuggestionPayload;\nimport org.sonarsource.sonarlint.core.telemetry.payload.TelemetryFixSuggestionResolvedPayload;\nimport org.sonarsource.sonarlint.core.telemetry.payload.TelemetryNotificationsCounterPayload;\nimport org.sonarsource.sonarlint.core.telemetry.payload.TelemetryNotificationsPayload;\n\nclass TelemetryUtils {\n\n  private TelemetryUtils() {\n    // utility class, forbidden constructor\n  }\n\n  /**\n   * Check if \"now\" is a different day than the reference.\n   *\n   * @param date reference date\n   * @return true if it's a different day than the reference\n   */\n  static boolean isGracePeriodElapsedAndDayChanged(@Nullable LocalDate date) {\n    return date == null || !date.equals(LocalDate.now());\n  }\n\n  /**\n   * Transforms stored information about analyzers performance to payload to be sent to server.\n   */\n  static TelemetryAnalyzerPerformancePayload[] toPayload(Map<String, TelemetryAnalyzerPerformance> analyzers) {\n    return analyzers.entrySet().stream()\n      .map(TelemetryUtils::toPayload)\n      .toArray(TelemetryAnalyzerPerformancePayload[]::new);\n  }\n\n  private static TelemetryAnalyzerPerformancePayload toPayload(Map.Entry<String, TelemetryAnalyzerPerformance> entry) {\n    var analyzerPerformance = entry.getValue();\n    var language = entry.getKey();\n    var analysisCount = analyzerPerformance.analysisCount();\n    Map<String, BigDecimal> distribution = analyzerPerformance\n      .frequencies().entrySet().stream()\n      .collect(Collectors.toMap(Map.Entry::getKey, e -> {\n        if (analysisCount == 0) {\n          return BigDecimal.ZERO.setScale(2);\n        }\n        return BigDecimal.valueOf(100)\n          .multiply(BigDecimal.valueOf(e.getValue()))\n          .divide(BigDecimal.valueOf(analysisCount), 2, RoundingMode.HALF_EVEN);\n      }, throwingMerger(), LinkedHashMap::new));\n\n    return new TelemetryAnalyzerPerformancePayload(language, distribution);\n  }\n\n  static TelemetryNotificationsPayload toPayload(boolean devNotificationsDisabled, Map<String, TelemetryNotificationsCounter> notifications) {\n    return new TelemetryNotificationsPayload(devNotificationsDisabled, toNotifPayload(notifications));\n  }\n\n  private static Map<String, TelemetryNotificationsCounterPayload> toNotifPayload(Map<String, TelemetryNotificationsCounter> notifications) {\n    return notifications.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,\n      e -> new TelemetryNotificationsCounterPayload(e.getValue().getDevNotificationsCount(), e.getValue().getDevNotificationsClicked())));\n  }\n\n  static TelemetryFixSuggestionPayload[] toFixSuggestionResolvedPayload(\n    Map<String, TelemetryFixSuggestionReceivedCounter> fixSuggestionReceivedCounter,\n    Map<String, List<TelemetryFixSuggestionResolvedStatus>> fixSuggestionResolved\n  ) {\n    return fixSuggestionReceivedCounter.entrySet().stream().map(e -> {\n      var suggestionId = e.getKey();\n      var snippetsCount = e.getValue().snippetsCount();\n      var source = e.getValue().aiSuggestionsSource();\n      var resolvedSnippetStatus = fixSuggestionResolved.getOrDefault(suggestionId, List.of(new TelemetryFixSuggestionResolvedStatus(null, null)));\n      var resolvedSnippetPayload = resolvedSnippetStatus.stream()\n        .map(s -> new TelemetryFixSuggestionResolvedPayload(s.getFixSuggestionResolvedStatus(),\n        s.getFixSuggestionResolvedSnippetIndex())).toList();\n      var wasGeneratedFromIde = e.getValue().wasGeneratedFromIde();\n\n      return new TelemetryFixSuggestionPayload(suggestionId, snippetsCount, source, resolvedSnippetPayload, wasGeneratedFromIde);\n    }).toArray(TelemetryFixSuggestionPayload[]::new);\n  }\n\n  /**\n   * Check if \"now\" is a different day than the reference, and some hours have elapsed.\n   *\n   * @param dateTime reference date\n   * @param hours minimum hours that must have elapsed\n   * @return true if it's a different day than the reference and at least hours have elapsed\n   */\n  static boolean isGracePeriodElapsedAndDayChanged(@Nullable LocalDateTime dateTime, long hours) {\n    return dateTime == null ||\n      (!LocalDate.now().equals(dateTime.toLocalDate())\n        && (dateTime.until(LocalDateTime.now(), ChronoUnit.HOURS) >= hours));\n  }\n\n  private static <T> BinaryOperator<T> throwingMerger() {\n    return (u, v) -> {\n      throw new IllegalStateException(String.format(\"Duplicate key %s\", u));\n    };\n  }\n\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/ToolCallCounter.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\npublic class ToolCallCounter {\n  private int success;\n  private int error;\n\n  public ToolCallCounter() {\n  }\n\n  public ToolCallCounter(int success, int error) {\n    this.success = success;\n    this.error = error;\n  }\n\n  public void incrementCount(boolean succeeded) {\n    if (succeeded) {\n      success++;\n    } else {\n      error++;\n    }\n  }\n\n  public int getSuccess() {\n    return success;\n  }\n\n  public int getError() {\n    return error;\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/common/TelemetryUserSetting.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.common;\n\npublic interface TelemetryUserSetting {\n  boolean isTelemetryEnabledByUser();\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/common/package-info.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.telemetry.common;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/gessie/GessieHttpClient.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.gessie;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.gson.FieldNamingPolicy;\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport java.util.concurrent.CompletableFuture;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;\nimport org.sonarsource.sonarlint.core.http.HttpClient;\nimport org.sonarsource.sonarlint.core.http.HttpClientProvider;\nimport org.sonarsource.sonarlint.core.telemetry.InternalDebug;\nimport org.sonarsource.sonarlint.core.telemetry.gessie.event.GessieEvent;\nimport org.springframework.beans.factory.annotation.Qualifier;\n\npublic class GessieHttpClient {\n\n  private static final SonarLintLogger LOG = SonarLintLogger.get();\n\n  private final Gson gson = configureGson();\n  private final HttpClient client;\n  private final String endpoint;\n\n  public GessieHttpClient(HttpClientProvider httpClientProvider,\n    @Qualifier(\"gessieEndpoint\") String gessieEndpoint,\n    @Qualifier(\"gessieApiKey\") String gessieApiKey) {\n    this.client = httpClientProvider.getHttpClientWithXApiKeyAndRetries(gessieApiKey);\n    this.endpoint = gessieEndpoint;\n  }\n\n  public void postEvent(GessieEvent event) {\n    var json = gson.toJson(event);\n    logGessiePayload(json);\n    var futureResponse = client.postAsync(endpoint + \"/ide\", HttpClient.JSON_CONTENT_TYPE, json);\n    handleGessieResponse(futureResponse);\n  }\n\n  private void logGessiePayload(String json) {\n    if (isTelemetryLogEnabled()) {\n      LOG.info(\"Sending Gessie payload.\\n{}\", json);\n    }\n  }\n\n  private static Gson configureGson() {\n    return new GsonBuilder()\n      .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)\n      .serializeNulls()\n      .create();\n  }\n\n  private static void handleGessieResponse(CompletableFuture<HttpClient.Response> responseCompletableFuture) {\n    responseCompletableFuture.thenAccept(response -> {\n      if (!response.isSuccessful() && InternalDebug.isEnabled()) {\n        LOG.error(\"Failed to upload telemetry to Gessie: {} \\n{}\", response,\n          response.bodyAsString());\n      }\n    }).exceptionally(exception -> {\n      if (InternalDebug.isEnabled()) {\n        LOG.error(\"Failed to upload telemetry to Gessie\", exception);\n      }\n      return null;\n    });\n  }\n\n  @VisibleForTesting\n  boolean isTelemetryLogEnabled(){\n    return Boolean.parseBoolean(System.getenv(\"SONARLINT_TELEMETRY_LOG\"));\n  }\n\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/gessie/GessieService.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.gessie;\n\nimport jakarta.annotation.PostConstruct;\nimport java.time.Instant;\nimport java.util.UUID;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.TelemetryClientConstantAttributesDto;\nimport org.sonarsource.sonarlint.core.telemetry.common.TelemetryUserSetting;\nimport org.sonarsource.sonarlint.core.telemetry.gessie.event.GessieEvent;\nimport org.sonarsource.sonarlint.core.telemetry.gessie.event.GessieMetadata;\nimport org.sonarsource.sonarlint.core.telemetry.gessie.event.payload.MessagePayload;\n\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.GESSIE_TELEMETRY;\nimport static org.sonarsource.sonarlint.core.telemetry.gessie.event.GessieMetadata.SonarLintDomain;\n\npublic class GessieService {\n\n  private final boolean isGessieFeatureEnabled;\n  private final TelemetryClientConstantAttributesDto telemetryConstantAttributes;\n  private final GessieHttpClient client;\n  private final TelemetryUserSetting userSetting;\n\n  public GessieService(InitializeParams initializeParams, GessieHttpClient client, TelemetryUserSetting userSetting) {\n    this.isGessieFeatureEnabled = initializeParams.getBackendCapabilities().contains(GESSIE_TELEMETRY);\n    this.telemetryConstantAttributes = initializeParams.getTelemetryConstantAttributes();\n    this.client = client;\n    this.userSetting = userSetting;\n  }\n\n  @PostConstruct\n  public void onStartup() {\n    if (isGessieFeatureEnabled && userSetting.isTelemetryEnabledByUser()) {\n      client.postEvent(new GessieEvent(\n        new GessieMetadata(UUID.randomUUID(),\n          new GessieMetadata.GessieSource(SonarLintDomain.fromProductKey(telemetryConstantAttributes.getProductKey())),\n          \"Analytics.Editor.PluginActivated\",\n          Long.toString(Instant.now().toEpochMilli()),\n          \"0\"),\n        new MessagePayload(\"Gessie integration test event\", \"slcore_start\")\n      ));\n    }\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/gessie/event/GessieEvent.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.gessie.event;\n\npublic record GessieEvent(\n  GessieMetadata metadata,\n  Object eventPayload\n) {\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/gessie/event/GessieMetadata.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.gessie.event;\n\nimport com.google.gson.annotations.SerializedName;\nimport java.util.UUID;\n\npublic record GessieMetadata(\n  UUID eventId,\n  GessieSource source,\n  String eventType,\n  String eventTimestamp,\n  String eventVersion\n) {\n\n  public record GessieSource(SonarLintDomain domain) {\n  }\n\n  public enum SonarLintDomain {\n    @SerializedName(\"VSCode\")\n    VS_CODE,\n    @SerializedName(\"VisualStudio\")\n    VISUAL_STUDIO,\n    @SerializedName(\"Eclipse\")\n    ECLIPSE,\n    @SerializedName(\"IntelliJ\")\n    INTELLIJ,\n    @SerializedName(\"SLCore\")\n    SLCORE;\n\n    public static SonarLintDomain fromProductKey(String productKey) {\n      return switch (productKey) {\n        case \"idea\" -> INTELLIJ;\n        case \"eclipse\" -> ECLIPSE;\n        case \"visualstudio\" -> VISUAL_STUDIO;\n        case \"vscode\", \"cursor\", \"windsurf\" -> VS_CODE;\n        default -> SLCORE;\n      };\n    }\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/gessie/event/package-info.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.telemetry.gessie.event;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/gessie/event/payload/MessagePayload.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.gessie.event.payload;\n\npublic record MessagePayload(\n  String message,\n  String trigger\n) {\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/gessie/event/payload/package-info.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.telemetry.gessie.event.payload;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/gessie/package-info.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.telemetry.gessie;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/measures/payload/TelemetryMeasuresBuilder.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.measures.payload;\n\nimport com.google.gson.Gson;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.UUID;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryLiveAttributes;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryLocalStorage;\n\nimport static org.sonarsource.sonarlint.core.telemetry.measures.payload.TelemetryMeasuresValueGranularity.DAILY;\nimport static org.sonarsource.sonarlint.core.telemetry.measures.payload.TelemetryMeasuresValueType.BOOLEAN;\nimport static org.sonarsource.sonarlint.core.telemetry.measures.payload.TelemetryMeasuresValueType.INTEGER;\nimport static org.sonarsource.sonarlint.core.telemetry.measures.payload.TelemetryMeasuresValueType.STRING;\n\npublic class TelemetryMeasuresBuilder {\n\n  private static final String LINK_CLICKED_BASE_NAME = \"link_clicked_count_\";\n  private static final String FEEDBACK_CLICKED_BASE_NAME = \"feedback_link_clicked_count_\";\n\n  private final String platform;\n  private final String product;\n  private final TelemetryLocalStorage storage;\n  private final TelemetryLiveAttributes liveAttributes;\n\n  public TelemetryMeasuresBuilder(String platform, String product, TelemetryLocalStorage storage, TelemetryLiveAttributes liveAttributes) {\n    this.platform = platform;\n    this.product = product;\n    this.storage = storage;\n    this.liveAttributes = liveAttributes;\n  }\n\n  public TelemetryMeasuresPayload build() {\n    var values = new ArrayList<TelemetryMeasuresValue>();\n\n    addConnectedModeMeasures(values);\n\n    addNewBindingsMeasures(values);\n\n    addBindingSuggestionClueMeasures(values);\n\n    addHelpAndFeedbackMeasures(values);\n\n    addAnalysisReportingMeasures(values);\n\n    addQuickFixMeasures(values);\n\n    addIssuesMeasures(values);\n\n    addToolsMeasures(values);\n\n    addFindingsFilteredMeasures(values);\n\n    addPerformanceMeasures(values);\n\n    addFindingInvestigationMeasures(values);\n\n    addAutomaticAnalysisMeasures(values);\n\n    addMCPMeasures(values);\n\n    addLabsMeasures(values);\n\n    addAiHooksMeasures(values);\n\n    addCampaignsMeasures(values);\n\n    addSupportedLanguagesPanelMeasures(values);\n\n    return new TelemetryMeasuresPayload(UUID.randomUUID().toString(), platform, storage.installTime(), product, TelemetryMeasuresDimension.INSTALLATION, values);\n  }\n\n  private void addPerformanceMeasures(ArrayList<TelemetryMeasuresValue> values) {\n    values.add(new TelemetryMeasuresValue(\"performance.largest_file_count\", String.valueOf(storage.getBiggestNumberOfFilesInConfigScope()), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"performance.largest_file_count_ms\", String.valueOf(storage.getListingTimeForBiggestNumberConfigScopeFiles()), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"performance.longest_file_count_ms\", String.valueOf(storage.getLongestListingTimeForConfigScopeFiles()), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"performance.longest_file_count\", String.valueOf(storage.getNumberOfFilesForLongestFilesListingTimeConfigScope()), INTEGER, DAILY));\n  }\n\n  private void addFindingInvestigationMeasures(ArrayList<TelemetryMeasuresValue> values) {\n    values.add(new TelemetryMeasuresValue(\"findings_investigation.taints_locally\", String.valueOf(storage.getTaintInvestigatedLocallyCount()), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"findings_investigation.taints_remotely\", String.valueOf(storage.getTaintInvestigatedRemotelyCount()), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"findings_investigation.hotspots_locally\", String.valueOf(storage.getHotspotInvestigatedLocallyCount()), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"findings_investigation.hotspots_remotely\", String.valueOf(storage.getHotspotInvestigatedRemotelyCount()), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"findings_investigation.issues_locally\", String.valueOf(storage.getIssueInvestigatedLocallyCount()), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"findings_investigation.dependency_risks_locally\", String.valueOf(storage.getDependencyRiskInvestigatedLocallyCount()), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"findings_investigation.dependency_risks_remotely\",\n      String.valueOf(storage.getDependencyRiskInvestigatedRemotelyCount()), INTEGER, DAILY));\n  }\n\n  private void addConnectedModeMeasures(ArrayList<TelemetryMeasuresValue> values) {\n    if (liveAttributes.usesConnectedMode()) {\n      values.add(new TelemetryMeasuresValue(\"shared_connected_mode.manual\", String.valueOf(storage.getManualAddedBindingsCount()), INTEGER, DAILY));\n      values.add(new TelemetryMeasuresValue(\"shared_connected_mode.imported\", String.valueOf(storage.getImportedAddedBindingsCount()), INTEGER, DAILY));\n      values.add(new TelemetryMeasuresValue(\"shared_connected_mode.auto\", String.valueOf(storage.getAutoAddedBindingsCount()), INTEGER, DAILY));\n      values.add(new TelemetryMeasuresValue(\"shared_connected_mode.exported\", String.valueOf(storage.getExportedConnectedModeCount()), INTEGER, DAILY));\n\n      values.add(new TelemetryMeasuresValue(\"bindings.child_count\", String.valueOf(liveAttributes.countChildBindings()), INTEGER, DAILY));\n      values.add(new TelemetryMeasuresValue(\"bindings.server_count\", String.valueOf(liveAttributes.countSonarQubeServerBindings()), INTEGER, DAILY));\n      values.add(new TelemetryMeasuresValue(\"bindings.cloud_eu_count\", String.valueOf(liveAttributes.countSonarQubeCloudEUBindings()), INTEGER, DAILY));\n      values.add(new TelemetryMeasuresValue(\"bindings.cloud_us_count\", String.valueOf(liveAttributes.countSonarQubeCloudUSBindings()), INTEGER, DAILY));\n\n      if (!liveAttributes.getConnectionsAttributes().isEmpty()) {\n        values.add(new TelemetryMeasuresValue(\"connections.attributes\", new Gson().toJson(liveAttributes.getConnectionsAttributes()), STRING, DAILY));\n      }\n    }\n  }\n\n  private void addNewBindingsMeasures(ArrayList<TelemetryMeasuresValue> values) {\n    if (liveAttributes.usesConnectedMode()) {\n      values.add(new TelemetryMeasuresValue(\"new_bindings.manual\", String.valueOf(storage.getManualAddedBindingsCount()), INTEGER, DAILY));\n      values.add(new TelemetryMeasuresValue(\"new_bindings.accepted_suggestion_remote_url\", String.valueOf(storage.getNewBindingsRemoteUrlCount()), INTEGER, DAILY));\n      values.add(new TelemetryMeasuresValue(\"new_bindings.accepted_suggestion_properties_file\", String.valueOf(storage.getNewBindingsPropertiesFileCount()), INTEGER, DAILY));\n      values.add(new TelemetryMeasuresValue(\"new_bindings.accepted_suggestion_shared_config_file\",\n        String.valueOf(storage.getNewBindingsSharedConfigurationCount()), INTEGER, DAILY));\n      values.add(new TelemetryMeasuresValue(\"new_bindings.accepted_suggestion_project_name\", String.valueOf(storage.getNewBindingsProjectNameCount()), INTEGER, DAILY));\n    }\n  }\n\n  private void addBindingSuggestionClueMeasures(ArrayList<TelemetryMeasuresValue> values) {\n    values.add(new TelemetryMeasuresValue(\"binding_suggestion_clue.remote_url\", String.valueOf(storage.getSuggestedRemoteBindingsCount()), INTEGER, DAILY));\n  }\n\n  private void addHelpAndFeedbackMeasures(List<TelemetryMeasuresValue> values) {\n    storage.getHelpAndFeedbackLinkClickedCounter().entrySet().stream()\n      .filter(e -> e.getValue().getHelpAndFeedbackLinkClickedCount() > 0)\n      .map(e -> new TelemetryMeasuresValue(\n        \"help_and_feedback.\" + e.getKey().toLowerCase(Locale.ROOT),\n        String.valueOf(e.getValue().getHelpAndFeedbackLinkClickedCount()),\n        INTEGER,\n        DAILY\n      ))\n      .forEach(values::add);\n  }\n\n  private void addAnalysisReportingMeasures(List<TelemetryMeasuresValue> values) {\n    storage.getAnalysisReportingCountersByType().entrySet().stream()\n      .filter(e -> e.getValue().getAnalysisReportingCount() > 0)\n      .map(e -> new TelemetryMeasuresValue(\n        \"analysis_reporting.\" + e.getKey().getId(),\n        String.valueOf(e.getValue().getAnalysisReportingCount()),\n        INTEGER,\n        DAILY\n      ))\n      .forEach(values::add);\n  }\n\n  private void addQuickFixMeasures(List<TelemetryMeasuresValue> values) {\n    var allQuickFixCount = storage.getQuickFixCountByRuleKey().values().stream()\n      .mapToInt(Integer::intValue)\n      .sum();\n    if (allQuickFixCount > 0) {\n      values.add(new TelemetryMeasuresValue(\n        \"quick_fix.applied_count\",\n        Integer.toString(allQuickFixCount),\n        INTEGER,\n        DAILY\n      ));\n    }\n  }\n\n  private void addIssuesMeasures(List<TelemetryMeasuresValue> values) {\n    var newIssuesFound = storage.getNewIssuesFoundCount();\n    if (newIssuesFound > 0) {\n      values.add(new TelemetryMeasuresValue(\"ide_issues.found\", Long.toString(newIssuesFound), INTEGER, DAILY));\n    }\n    var issuesFixed = storage.getIssuesFixedCount();\n    if (issuesFixed > 0) {\n      values.add(new TelemetryMeasuresValue(\"ide_issues.fixed\", Long.toString(issuesFixed), INTEGER, DAILY));\n    }\n  }\n\n  private void addToolsMeasures(List<TelemetryMeasuresValue> values) {\n    var calledToolsByName = storage.getCalledToolsByName();\n    calledToolsByName.forEach((key, toolCallCounter) -> {\n      values.add(new TelemetryMeasuresValue(\"tools.\" + key + \"_success_count\", Integer.toString(toolCallCounter.getSuccess()), INTEGER, DAILY));\n      values.add(new TelemetryMeasuresValue(\"tools.\" + key + \"_error_count\", Integer.toString(toolCallCounter.getError()), INTEGER, DAILY));\n    });\n  }\n\n  private void addFindingsFilteredMeasures(List<TelemetryMeasuresValue> values) {\n    storage.getFindingsFilteredCountersByType().entrySet().stream()\n      .filter(e -> e.getValue().getFindingsFilteredCount() > 0)\n      .map(e -> new TelemetryMeasuresValue(\n        \"findings_filtered.\" + e.getKey().toLowerCase(Locale.ROOT),\n        String.valueOf(e.getValue().getFindingsFilteredCount()),\n        INTEGER,\n        DAILY\n      ))\n      .forEach(values::add);\n  }\n\n  private void addAutomaticAnalysisMeasures(List<TelemetryMeasuresValue> values) {\n    values.add(new TelemetryMeasuresValue(\"automatic_analysis.enabled\", String.valueOf(storage.isAutomaticAnalysisEnabled()), BOOLEAN, DAILY));\n    values.add(new TelemetryMeasuresValue(\"automatic_analysis.toggled_count\", String.valueOf(storage.getAutomaticAnalysisToggledCount()), INTEGER, DAILY));\n  }\n\n  private void addMCPMeasures(List<TelemetryMeasuresValue> values) {\n    values.add(new TelemetryMeasuresValue(\"mcp.configuration_requested\", String.valueOf(storage.getMcpServerConfigurationRequestedCount()), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"mcp.rule_file_requested\", String.valueOf(storage.getMcpRuleFileRequestedCount()), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"mcp.integration_enabled\", Boolean.toString(storage.isMcpIntegrationEnabled()), BOOLEAN, DAILY));\n    var mcpTransportModeUsed = storage.getMcpTransportModeUsed();\n    if (mcpTransportModeUsed != null) {\n      values.add(new TelemetryMeasuresValue(\"mcp.transport_mode\", mcpTransportModeUsed.name(), STRING, DAILY));\n    }\n  }\n\n  private void addLabsMeasures(ArrayList<TelemetryMeasuresValue> values) {\n    values.add(new TelemetryMeasuresValue(\"ide_labs.joined\", String.valueOf(liveAttributes.hasJoinedIdeLabs()), BOOLEAN, DAILY));\n    values.add(new TelemetryMeasuresValue(\"ide_labs.enabled\", String.valueOf(liveAttributes.hasEnabledIdeLabs()), BOOLEAN, DAILY));\n    addAll(storage.getLabsLinkClickedCount(), LINK_CLICKED_BASE_NAME, values);\n    addAll(storage.getLabsFeedbackLinkClickedCount(), FEEDBACK_CLICKED_BASE_NAME, values);\n  }\n\n  private static void addAll(Map<String, Integer> clickCounts, String baseName, List<TelemetryMeasuresValue> values) {\n    clickCounts.entrySet().stream()\n      .filter(entry -> entry.getValue() > 0)\n      .map(entry -> new TelemetryMeasuresValue(\n        \"ide_labs.\" + baseName + entry.getKey(),\n        String.valueOf(entry.getValue()),\n        INTEGER,\n        DAILY))\n      .forEach(values::add);\n  }\n\n  private void addAiHooksMeasures(ArrayList<TelemetryMeasuresValue> values) {\n    storage.getAiHooksInstalledCount().entrySet().stream()\n      .filter(entry -> entry.getValue() > 0)\n      .map(entry -> new TelemetryMeasuresValue(\n        \"ai_hooks.\" + entry.getKey().name().toLowerCase(Locale.ROOT) + \"_installed\",\n        String.valueOf(entry.getValue()),\n        INTEGER,\n        DAILY))\n      .forEach(values::add);\n  }\n\n  private void addSupportedLanguagesPanelMeasures(List<TelemetryMeasuresValue> values) {\n    values.add(new TelemetryMeasuresValue(\"supported_languages_panel.opened_count\",\n      String.valueOf(storage.getSupportedLanguagesPanelOpenedCount()), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"supported_languages_panel.cta_clicked_count\",\n      String.valueOf(storage.getSupportedLanguagesPanelCtaClickedCount()), INTEGER, DAILY));\n  }\n\n  private void addCampaignsMeasures(ArrayList<TelemetryMeasuresValue> values) {\n    storage.getCampaignsShown().entrySet().stream()\n      .filter(entry -> entry.getValue() > 0)\n      .map(campaignShown -> new TelemetryMeasuresValue(\n        \"campaigns.\"  + campaignShown.getKey() + \"_shown\",\n        String.valueOf(campaignShown.getValue()),\n        INTEGER,\n        DAILY))\n      .forEach(values::add);\n    storage.getCampaignsResolutions().entrySet().stream()\n      .map(campaignsResolution -> new TelemetryMeasuresValue(\n        \"campaigns.\"  + campaignsResolution.getKey() + \"_resolution\",\n        campaignsResolution.getValue(),\n        STRING,\n        DAILY))\n      .forEach(values::add);\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/measures/payload/TelemetryMeasuresDimension.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.measures.payload;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic enum TelemetryMeasuresDimension {\n  @SerializedName(\"language\")\n  LANGUAGE,\n  @SerializedName(\"installation\")\n  INSTALLATION\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/measures/payload/TelemetryMeasuresPayload.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.measures.payload;\n\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.annotations.SerializedName;\nimport java.time.OffsetDateTime;\nimport java.util.List;\nimport org.sonarsource.sonarlint.core.commons.storage.adapter.OffsetDateTimeAdapter;\n\npublic record TelemetryMeasuresPayload(@SerializedName(\"message_uuid\") String messageUuid,\n                                       @SerializedName(\"os\") String os,\n                                       @SerializedName(\"install_time\") OffsetDateTime installTime,\n                                       @SerializedName(\"sonarlint_product\") String product,\n                                       @SerializedName(\"dimension\") TelemetryMeasuresDimension dimension,\n                                       @SerializedName(\"metric_values\") List<TelemetryMeasuresValue> values) {\n\n  public String toJson() {\n    var gson = new GsonBuilder()\n      .registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter())\n      .serializeNulls()\n      .create();\n    var jsonPayload = gson.toJsonTree(this).getAsJsonObject();\n    return gson.toJson(jsonPayload);\n  }\n\n  public boolean hasMetrics() {\n    return !values.isEmpty();\n  }\n\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/measures/payload/TelemetryMeasuresValue.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.measures.payload;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic class TelemetryMeasuresValue {\n\n  private static final String KEY_PATTERN = \"^([a-z_][a-z0-9_]{1,126}\\\\.)[a-z_][a-z0-9_]{1,126}$\";\n\n  @SerializedName(\"key\")\n  private final String key;\n\n  @SerializedName(\"value\")\n  private final String value;\n\n  @SerializedName(\"type\")\n  private final TelemetryMeasuresValueType type;\n\n  @SerializedName(\"granularity\")\n  private final TelemetryMeasuresValueGranularity granularity;\n\n  public TelemetryMeasuresValue(String key, String value, TelemetryMeasuresValueType type, TelemetryMeasuresValueGranularity granularity) {\n    this.key = validateKey(key);\n    this.value = value;\n    this.type = type;\n    this.granularity = granularity;\n  }\n\n  /*\n   * From the telemetry measures specification:\n   *  - Entire key: ^([a-z_][a-z0-9_]{1,126}\\.)[a-z_][a-z0-9_]{1,126}$\n   *  - Group name: ^[a-z_][a-z0-9_]{1,126}\\.$\n   *  - Measure name: ^[a-z_][a-z0-9_]{1,126}$\n   */\n  private static String validateKey(String maybeKey) throws IllegalArgumentException {\n    if (maybeKey.matches(KEY_PATTERN)) {\n      return maybeKey;\n    }\n    throw new IllegalArgumentException(\"Invalid measure key: \" + maybeKey);\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/measures/payload/TelemetryMeasuresValueGranularity.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.measures.payload;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic enum TelemetryMeasuresValueGranularity {\n  @SerializedName(\"daily\")\n  DAILY,\n  @SerializedName(\"weekly\")\n  WEEKLY,\n  @SerializedName(\"monthly\")\n  MONTHLY\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/measures/payload/TelemetryMeasuresValueType.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.measures.payload;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic enum TelemetryMeasuresValueType {\n  @SerializedName(\"string\")\n  STRING,\n  @SerializedName(\"integer\")\n  INTEGER,\n  @SerializedName(\"boolean\")\n  BOOLEAN,\n  @SerializedName(\"float\")\n  FLOAT\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/measures/payload/package-info.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.telemetry.measures.payload;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/package-info.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.telemetry;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/payload/HotspotPayload.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.payload;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic record HotspotPayload(@SerializedName(\"open_in_browser_count\") int openInBrowserCount,\n                             @SerializedName(\"status_changed_count\") int statusChangedCount) {\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/payload/IssuePayload.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.payload;\n\nimport com.google.gson.annotations.SerializedName;\nimport java.util.Set;\n\npublic record IssuePayload(@SerializedName(\"status_changed_rule_keys\") Set<String> statusChangedRuleKeys,\n                           @SerializedName(\"status_changed_count\") int statusChangedCount) {\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/payload/ShareConnectedModePayload.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.payload;\n\nimport com.google.gson.annotations.SerializedName;\nimport javax.annotation.Nullable;\n\npublic record ShareConnectedModePayload(@SerializedName(\"manual_bindings_count\") @Nullable Integer manualAddedBindingsCount,\n                                        @SerializedName(\"imported_bindings_count\") @Nullable Integer importedAddedBindingsCount,\n                                        @SerializedName(\"auto_bindings_count\") @Nullable Integer autoAddedBindingsCount,\n                                        @SerializedName(\"exported_connected_mode_count\") @Nullable Integer exportedConnectedModeCount) {\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/payload/ShowHotspotPayload.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.payload;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic record ShowHotspotPayload(@SerializedName(\"requests_count\") int requestsCount) {\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/payload/ShowIssuePayload.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.payload;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic record ShowIssuePayload(@SerializedName(\"requests_count\") int requestsCount) {\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/payload/TaintVulnerabilitiesPayload.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.payload;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic record TaintVulnerabilitiesPayload(@SerializedName(\"investigated_locally_count\") int investigatedLocallyCount,\n                                          @SerializedName(\"investigated_remotely_count\") int investigatedRemotelyCount) {\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/payload/TelemetryAnalyzerPerformancePayload.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.payload;\n\nimport com.google.gson.annotations.SerializedName;\nimport java.math.BigDecimal;\nimport java.util.Map;\n\npublic record TelemetryAnalyzerPerformancePayload(String language,\n                                                  @SerializedName(\"rate_per_duration\") Map<String, BigDecimal> distribution) {\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/payload/TelemetryFixSuggestionPayload.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.payload;\n\nimport com.google.gson.annotations.SerializedName;\nimport java.util.List;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AiSuggestionSource;\n\npublic record TelemetryFixSuggestionPayload(@SerializedName(\"suggestion_id\") String suggestionId,\n                                            @SerializedName(\"count_snippets\") int countSnippets,\n                                            @SerializedName(\"ai_fix_suggestion_provider\") AiSuggestionSource aiFixSuggestionProvider,\n                                            @SerializedName(\"snippets\") List<TelemetryFixSuggestionResolvedPayload> snippets,\n                                            @SerializedName(\"was_ai_fix_suggestion_generated_from_ide\") boolean wasAiFixSuggestionGeneratedFromIde) {\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/payload/TelemetryFixSuggestionResolvedPayload.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.payload;\n\nimport com.google.gson.annotations.SerializedName;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.FixSuggestionStatus;\n\npublic record TelemetryFixSuggestionResolvedPayload(@SerializedName(\"status\") @Nullable FixSuggestionStatus status,\n                                                    @SerializedName(\"snippet_index\") @Nullable Integer snippetIndex) {\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/payload/TelemetryHelpAndFeedbackPayload.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.payload;\n\nimport com.google.gson.annotations.SerializedName;\nimport java.util.HashMap;\nimport java.util.Map;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryHelpAndFeedbackCounter;\n\npublic class TelemetryHelpAndFeedbackPayload {\n\n  @SerializedName(\"count_by_link\")\n  private final Map<String, Integer> counters;\n\n  public TelemetryHelpAndFeedbackPayload(Map<String, TelemetryHelpAndFeedbackCounter> counters) {\n    this.counters = new HashMap<>();\n    counters.forEach((link, count) -> this.counters.put(link, count.getHelpAndFeedbackLinkClickedCount()));\n  }\n\n  public Map<String, Integer> getCounters() {\n    return counters;\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/payload/TelemetryNotificationsCounterPayload.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.payload;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic record TelemetryNotificationsCounterPayload(@SerializedName(\"received\") int devNotificationsCount,\n                                                   @SerializedName(\"clicked\") int devNotificationsClicked) {\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/payload/TelemetryNotificationsPayload.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.payload;\n\nimport com.google.gson.annotations.SerializedName;\nimport java.util.Map;\n\npublic record TelemetryNotificationsPayload(boolean disabled,\n                                            @SerializedName(\"count_by_type\") Map<String, TelemetryNotificationsCounterPayload> counters) {\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/payload/TelemetryPayload.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.payload;\n\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport com.google.gson.annotations.SerializedName;\nimport com.google.gson.reflect.TypeToken;\nimport java.time.OffsetDateTime;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.storage.adapter.OffsetDateTimeAdapter;\nimport org.sonarsource.sonarlint.core.telemetry.payload.cayc.CleanAsYouCodePayload;\n\n/**\n * Models the usage data uploaded\n */\npublic class TelemetryPayload {\n  @SerializedName(\"days_since_installation\")\n  private final long daysSinceInstallation;\n\n  @SerializedName(\"days_of_use\")\n  private final long daysOfUse;\n\n  @SerializedName(\"sonarlint_version\")\n  private final String version;\n\n  @SerializedName(\"sonarlint_product\")\n  private final String product;\n\n  @SerializedName(\"ide_version\")\n  private final String ideVersion;\n\n  @SerializedName(\"platform\")\n  private final String platform;\n\n  @SerializedName(\"architecture\")\n  private final String architecture;\n\n  @SerializedName(\"connected_mode_used\")\n  private final boolean connectedMode;\n\n  @SerializedName(\"connected_mode_sonarcloud\")\n  private final boolean connectedModeSonarcloud;\n\n  @SerializedName(\"system_time\")\n  private final OffsetDateTime systemTime;\n\n  @SerializedName(\"install_time\")\n  private final OffsetDateTime installTime;\n\n  @SerializedName(\"os\")\n  private final String os;\n\n  @SerializedName(\"jre\")\n  private final String jre;\n\n  @SerializedName(\"nodejs\")\n  private final String nodejs;\n\n  @SerializedName(\"analyses\")\n  private final TelemetryAnalyzerPerformancePayload[] analyses;\n\n  @SerializedName(\"server_notifications\")\n  private final TelemetryNotificationsPayload notifications;\n\n  @SerializedName(\"show_hotspot\")\n  private final ShowHotspotPayload showHotspotPayload;\n\n  @SerializedName(\"show_issue\")\n  private final ShowIssuePayload showIssuePayload;\n\n  @SerializedName(\"taint_vulnerabilities\")\n  private final TaintVulnerabilitiesPayload taintVulnerabilitiesPayload;\n\n  @SerializedName(\"rules\")\n  private final TelemetryRulesPayload telemetryRulesPayload;\n\n  @SerializedName(\"hotspot\")\n  private final HotspotPayload hotspotPayload;\n\n  @SerializedName(\"issue\")\n  private final IssuePayload issuePayload;\n\n  @SerializedName(\"help_and_feedback\")\n  private final TelemetryHelpAndFeedbackPayload helpAndFeedbackPayload;\n\n  @SerializedName(\"ai_fix_suggestions\")\n  private final TelemetryFixSuggestionPayload[] aiFixSuggestionsPayload;\n\n  @SerializedName(\"count_issues_with_possible_ai_fix_from_ide\")\n  private final int countIssuesWithPossibleAiFixFromIde;\n\n  @SerializedName(\"cayc\")\n  private final CleanAsYouCodePayload cleanAsYouCodePayload;\n\n  @SerializedName(\"shared_connected_mode\")\n  private final ShareConnectedModePayload shareConnectedModePayload;\n\n  private final transient Map<String, Object> additionalAttributes;\n\n  public TelemetryPayload(long daysSinceInstallation, long daysOfUse, String product, String version, String ideVersion, @Nullable String platform, @Nullable String architecture,\n    boolean connectedMode, boolean connectedModeSonarcloud, OffsetDateTime systemTime, OffsetDateTime installTime, String os, String jre, @Nullable String nodejs,\n    TelemetryAnalyzerPerformancePayload[] analyses, TelemetryNotificationsPayload notifications, ShowHotspotPayload showHotspotPayload,\n    ShowIssuePayload showIssuePayload, TaintVulnerabilitiesPayload taintVulnerabilitiesPayload, TelemetryRulesPayload telemetryRulesPayload, HotspotPayload hotspotPayload,\n    IssuePayload issuePayload, TelemetryHelpAndFeedbackPayload helpAndFeedbackPayload, TelemetryFixSuggestionPayload[] aiFixSuggestionsPayload,\n    int countIssuesWithPossibleAiFixFromIde, CleanAsYouCodePayload cleanAsYouCodePayload, ShareConnectedModePayload shareConnectedModePayload,\n    Map<String, Object> additionalAttributes) {\n    this.daysSinceInstallation = daysSinceInstallation;\n    this.daysOfUse = daysOfUse;\n    this.product = product;\n    this.version = version;\n    this.ideVersion = ideVersion;\n    this.platform = platform;\n    this.architecture = architecture;\n    this.connectedMode = connectedMode;\n    this.connectedModeSonarcloud = connectedModeSonarcloud;\n    this.systemTime = systemTime;\n    this.installTime = installTime;\n    this.os = os;\n    this.jre = jre;\n    this.nodejs = nodejs;\n    this.analyses = analyses;\n    this.notifications = notifications;\n    this.showHotspotPayload = showHotspotPayload;\n    this.showIssuePayload = showIssuePayload;\n    this.taintVulnerabilitiesPayload = taintVulnerabilitiesPayload;\n    this.telemetryRulesPayload = telemetryRulesPayload;\n    this.hotspotPayload = hotspotPayload;\n    this.issuePayload = issuePayload;\n    this.helpAndFeedbackPayload = helpAndFeedbackPayload;\n    this.aiFixSuggestionsPayload = aiFixSuggestionsPayload;\n    this.countIssuesWithPossibleAiFixFromIde = countIssuesWithPossibleAiFixFromIde;\n    this.cleanAsYouCodePayload = cleanAsYouCodePayload;\n    this.shareConnectedModePayload = shareConnectedModePayload;\n    this.additionalAttributes = additionalAttributes;\n  }\n\n  public long daysSinceInstallation() {\n    return daysSinceInstallation;\n  }\n\n  public long daysOfUse() {\n    return daysOfUse;\n  }\n\n  public TelemetryAnalyzerPerformancePayload[] analyses() {\n    return analyses;\n  }\n\n  public String version() {\n    return version;\n  }\n\n  public String product() {\n    return product;\n  }\n\n  public boolean connectedMode() {\n    return connectedMode;\n  }\n\n  public boolean connectedModeSonarcloud() {\n    return connectedModeSonarcloud;\n  }\n\n  public String os() {\n    return os;\n  }\n\n  public String jre() {\n    return jre;\n  }\n\n  public String nodejs() {\n    return nodejs;\n  }\n\n  public OffsetDateTime systemTime() {\n    return systemTime;\n  }\n\n  public TelemetryNotificationsPayload notifications() {\n    return notifications;\n  }\n\n  public TelemetryHelpAndFeedbackPayload helpAndFeedbackPayload() {\n    return helpAndFeedbackPayload;\n  }\n\n  public CleanAsYouCodePayload cleanAsYouCodePayload() {\n    return cleanAsYouCodePayload;\n  }\n\n  public IssuePayload issuePayload() {\n    return issuePayload;\n  }\n\n  public Map<String, Object> additionalAttributes() {\n    return additionalAttributes;\n  }\n\n  public ShowHotspotPayload getShowHotspotPayload() {\n    return showHotspotPayload;\n  }\n\n  public ShowIssuePayload getShowIssuePayload() {\n    return showIssuePayload;\n  }\n\n  public TaintVulnerabilitiesPayload getTaintVulnerabilitiesPayload() {\n    return taintVulnerabilitiesPayload;\n  }\n\n  public TelemetryRulesPayload getTelemetryRulesPayload() {\n    return telemetryRulesPayload;\n  }\n\n  public HotspotPayload getHotspotPayload() {\n    return hotspotPayload;\n  }\n\n  public ShareConnectedModePayload getShareConnectedModePayload() {\n    return shareConnectedModePayload;\n  }\n\n  public TelemetryFixSuggestionPayload[] getAiFixSuggestionsPayload() {\n    return aiFixSuggestionsPayload;\n  }\n\n  public String getIdeVersion() {\n    return ideVersion;\n  }\n\n  public String getPlatform() {\n    return platform;\n  }\n\n  public String getArchitecture() {\n    return architecture;\n  }\n\n  public OffsetDateTime getInstallTime() {\n    return installTime;\n  }\n\n  public int getCountIssuesWithPossibleAiFixFromIde() {\n    return countIssuesWithPossibleAiFixFromIde;\n  }\n\n  public String toJson() {\n    var gson = new GsonBuilder()\n      .registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter())\n      .serializeNulls()\n      .create();\n    var jsonPayload = gson.toJsonTree(this).getAsJsonObject();\n    var jsonAdditional = gson.toJsonTree(additionalAttributes, new TypeToken<Map<String, Object>>() {\n    }.getType()).getAsJsonObject();\n    return gson.toJson(mergeObjects(jsonAdditional, jsonPayload));\n  }\n\n  static JsonObject mergeObjects(JsonObject source, JsonObject target) {\n    for (Entry<String, JsonElement> entry : source.entrySet()) {\n      var value = entry.getValue();\n      if (!target.has(entry.getKey())) {\n        // new value for \"key\":\n        target.add(entry.getKey(), value);\n      } else if (value.isJsonObject()) {\n        // existing value for \"key\" - recursively deep merge:\n        var valueJson = (JsonObject) value;\n        mergeObjects(valueJson, target.getAsJsonObject(entry.getKey()));\n      }\n      // Don't override value if it already exists in the target\n    }\n    return target;\n  }\n\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/payload/TelemetryRulesPayload.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.payload;\n\nimport com.google.gson.annotations.SerializedName;\nimport java.util.Collection;\n\npublic record TelemetryRulesPayload(@SerializedName(\"non_default_enabled\") Collection<String> nonDefaultEnabled,\n                                    @SerializedName(\"default_disabled\") Collection<String> defaultDisabled,\n                                    @SerializedName(\"raised_issues\") Collection<String> raisedIssues,\n                                    @SerializedName(\"quick_fix_applied\") Collection<String> quickFixesApplied) {\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/payload/cayc/CleanAsYouCodePayload.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.payload.cayc;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic record CleanAsYouCodePayload(@SerializedName(\"new_code_focus\") NewCodeFocusPayload newCodeFocusPayload) {\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/payload/cayc/NewCodeFocusPayload.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.payload.cayc;\n\npublic record NewCodeFocusPayload(boolean enabled, int changes) {\n}\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/payload/cayc/package-info.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.telemetry.payload.cayc;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/telemetry/src/main/java/org/sonarsource/sonarlint/core/telemetry/payload/package-info.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.telemetry.payload;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "backend/telemetry/src/test/java/org/sonarsource/sonarlint/core/telemetry/TelemetryHttpClientTests.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\nimport com.github.tomakehurst.wiremock.junit5.WireMockExtension;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Map;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.LogOutput.Level;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.http.HttpClientProvider;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.AiAgent;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.TelemetryClientConstantAttributesDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AnalysisReportingType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.McpTransportMode;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.TelemetryClientLiveAttributesResponse;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.delete;\nimport static com.github.tomakehurst.wiremock.client.WireMock.deleteRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;\nimport static com.github.tomakehurst.wiremock.client.WireMock.post;\nimport static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.when;\n\nclass TelemetryHttpClientTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  private static final String PLATFORM = SystemUtils.OS_NAME;\n  private static final String ARCHITECTURE = SystemUtils.OS_ARCH;\n\n  private TelemetryHttpClient underTest;\n\n  @RegisterExtension\n  static WireMockExtension telemetryMock = WireMockExtension.newInstance()\n    .options(wireMockConfig().dynamicPort())\n    .build();\n\n  @BeforeEach\n  void setUp() {\n    InitializeParams initializeParams = mock(InitializeParams.class);\n    when(initializeParams.getTelemetryConstantAttributes())\n      .thenReturn(new TelemetryClientConstantAttributesDto(null, \"product\", \"version\", \"ideversion\", Map.of(\"additionalKey\", \"additionalValue\")));\n\n    underTest = new TelemetryHttpClient(initializeParams, HttpClientProvider.forTesting(), telemetryMock.baseUrl());\n  }\n\n  @Test\n  void opt_out() {\n    telemetryMock.stubFor(delete(\"/\")\n      .willReturn(aResponse()));\n\n    underTest.optOut(new TelemetryLocalStorage(), getTelemetryLiveAttributesDto());\n\n    await().untilAsserted(() -> telemetryMock.verify(deleteRequestedFor(urlEqualTo(\"/\"))\n      .withRequestBody(\n        equalToJson(\n          \"{\\\"days_since_installation\\\":0,\\\"days_of_use\\\":0,\\\"sonarlint_version\\\":\\\"version\\\",\\\"sonarlint_product\\\":\\\"product\\\",\\\"ide_version\\\":\\\"ideversion\\\",\\\"platform\\\":\\\"\" + PLATFORM + \"\\\",\\\"architecture\\\":\\\"\" + ARCHITECTURE + \"\\\"}\",\n          true, true))));\n  }\n\n  @Test\n  void upload() {\n    await().untilAsserted(() -> {\n      assertTelemetryUploaded(false);\n      assertThat(logTester.logs(Level.INFO)).noneMatch(l -> l.matches(\"Sending telemetry payload.\"));\n    });\n  }\n\n  @Test\n  void upload_with_telemetry_debug_enabled() {\n    await().untilAsserted(() -> {\n      assertTelemetryUploaded(true);\n      assertThat(logTester.logs(Level.INFO)).anyMatch(l -> l.matches(\"Sending telemetry payload.\"));\n      assertThat(logTester.logs(Level.INFO)).anyMatch(l -> l.contains(\"{\\\"days_since_installation\\\":0,\\\"days_of_use\\\":1,\\\"sonarlint_version\\\":\\\"version\\\",\\\"sonarlint_product\\\":\\\"product\\\",\\\"ide_version\\\":\\\"ideversion\\\",\\\"platform\\\":\\\"\"+ PLATFORM +\"\\\",\\\"architecture\\\":\\\"\"+ ARCHITECTURE +\"\\\"\"));\n    });\n  }\n\n  private void assertTelemetryUploaded(boolean isDebugEnabled) {\n    var spy = spy(underTest);\n    doReturn(isDebugEnabled).when(spy).isTelemetryLogEnabled();\n    telemetryMock.stubFor(post(\"/\")\n      .willReturn(aResponse()));\n    var telemetryLocalStorage = new TelemetryLocalStorage();\n    telemetryLocalStorage.helpAndFeedbackLinkClicked(\"docs\");\n    telemetryLocalStorage.analysisReportingTriggered(AnalysisReportingType.PRE_COMMIT_ANALYSIS_TYPE);\n    telemetryLocalStorage.addQuickFixAppliedForRule(\"java:S107\");\n    telemetryLocalStorage.addQuickFixAppliedForRule(\"python:S107\");\n    telemetryLocalStorage.addNewlyFoundIssues(1);\n    telemetryLocalStorage.incrementToolCalledCount(\"tool_name\", true);\n    telemetryLocalStorage.incrementToolCalledCount(\"tool_name\", false);\n    telemetryLocalStorage.addFixedIssues(2);\n    telemetryLocalStorage.findingsFiltered(\"severity\");\n    telemetryLocalStorage.incrementMcpServerConfigurationRequestedCount();\n    telemetryLocalStorage.incrementMcpRuleFileRequestedCount();\n    telemetryLocalStorage.setMcpIntegrationEnabled(true);\n    telemetryLocalStorage.setMcpTransportModeUsed(McpTransportMode.STDIO);\n    telemetryLocalStorage.ideLabsLinkClicked(\"changed_file_analysis_doc\");\n    telemetryLocalStorage.ideLabsLinkClicked(\"privacy_policy\");\n    telemetryLocalStorage.ideLabsLinkClicked(\"privacy_policy\");\n    telemetryLocalStorage.ideLabsFeedbackLinkClicked(\"connected_mode\");\n    telemetryLocalStorage.ideLabsFeedbackLinkClicked(\"manage_dependency_risk\");\n    telemetryLocalStorage.ideLabsFeedbackLinkClicked(\"manage_dependency_risk\");\n    telemetryLocalStorage.aiHookInstalled(AiAgent.WINDSURF);\n    telemetryLocalStorage.aiHookInstalled(AiAgent.WINDSURF);\n    telemetryLocalStorage.campaignShown(\"feedback_2026_01\");\n    telemetryLocalStorage.campaignResolved(\"feedback_2026_01\", \"MAYBE_LATER\");\n    telemetryLocalStorage.campaignShown(\"feedback_2077_03\");\n    telemetryLocalStorage.campaignResolved(\"feedback_2077_03\", \"IGNORE\");\n    telemetryLocalStorage.incrementSupportedLanguagesPanelOpenedCount();\n    telemetryLocalStorage.incrementSupportedLanguagesPanelOpenedCount();\n    telemetryLocalStorage.incrementSupportedLanguagesPanelCtaClickedCount();\n    spy.upload(telemetryLocalStorage, getTelemetryLiveAttributesDto());\n\n    telemetryMock.verify(postRequestedFor(urlEqualTo(\"/\"))\n      .withRequestBody(\n        equalToJson(\n          \"{\\\"days_since_installation\\\":0,\\\"days_of_use\\\":1,\\\"sonarlint_version\\\":\\\"version\\\",\\\"sonarlint_product\\\":\\\"product\\\",\\\"ide_version\\\":\\\"ideversion\\\",\\\"platform\\\":\\\"\" + PLATFORM + \"\\\",\\\"architecture\\\":\\\"\"+ ARCHITECTURE + \"\\\",\\\"additionalKey\\\" : \\\"additionalValue\\\",\\\"help_and_feedback\\\":{\\\"count_by_link\\\":{\\\"docs\\\":1}}}\",\n          true, true)));\n\n    telemetryMock.verify(postRequestedFor(urlEqualTo(\"/metrics\"))\n      .withRequestBody(\n        equalToJson(\n          String.format(\"\"\"\n          {\"sonarlint_product\":\"product\",\"os\":\"%s\",\"dimension\":\"installation\", \"metric_values\": [\n            {\"key\":\"shared_connected_mode.manual\",\"value\":\"0\",\"type\":\"integer\",\"granularity\":\"daily\"},\n            {\"key\":\"help_and_feedback.docs\",\"value\":\"1\",\"type\":\"integer\",\"granularity\":\"daily\"},\n            {\"key\":\"analysis_reporting.trigger_count_pre_commit\",\"value\":\"1\",\"type\":\"integer\",\"granularity\":\"daily\"},\n            {\"key\":\"quick_fix.applied_count\",\"value\":\"2\",\"type\":\"integer\",\"granularity\":\"daily\"},\n            {\"key\":\"connections.attributes\",\"value\":\"[{\\\\\"userId\\\\\":\\\\\"user-id-sqc\\\\\",\\\\\"organizationId\\\\\":\\\\\"org-id\\\\\"},{\\\\\"serverId\\\\\":\\\\\"server-id\\\\\"}]\",\"type\":\"string\",\"granularity\":\"daily\"},\n            {\"key\":\"ide_issues.found\",\"value\":\"1\",\"type\":\"integer\",\"granularity\":\"daily\"},\n            {\"key\":\"ide_issues.fixed\",\"value\":\"2\",\"type\":\"integer\",\"granularity\":\"daily\"},\n            {\"key\":\"tools.tool_name_success_count\",\"value\":\"1\",\"type\":\"integer\",\"granularity\":\"daily\"},\n            {\"key\":\"tools.tool_name_error_count\",\"value\":\"1\",\"type\":\"integer\",\"granularity\":\"daily\"},\n            {\"key\":\"findings_filtered.severity\",\"value\":\"1\",\"type\":\"integer\",\"granularity\":\"daily\"},\n            {\"key\":\"mcp.configuration_requested\",\"value\":\"1\",\"type\":\"integer\",\"granularity\":\"daily\"},\n            {\"key\":\"mcp.rule_file_requested\",\"value\":\"1\",\"type\":\"integer\",\"granularity\":\"daily\"},\n            {\"key\":\"mcp.integration_enabled\",\"value\":\"true\",\"type\":\"boolean\",\"granularity\":\"daily\"},\n            {\"key\":\"mcp.transport_mode\",\"value\":\"STDIO\",\"type\":\"string\",\"granularity\":\"daily\"},\n            {\"key\":\"ide_labs.joined\",\"value\":\"true\",\"type\":\"boolean\",\"granularity\":\"daily\"},\n            {\"key\":\"ide_labs.enabled\",\"value\":\"false\",\"type\":\"boolean\",\"granularity\":\"daily\"},\n            {\"key\":\"ide_labs.link_clicked_count_changed_file_analysis_doc\",\"value\":\"1\",\"type\":\"integer\",\"granularity\":\"daily\"},\n            {\"key\":\"ide_labs.link_clicked_count_privacy_policy\",\"value\":\"2\",\"type\":\"integer\",\"granularity\":\"daily\"},\n            {\"key\":\"ide_labs.feedback_link_clicked_count_connected_mode\",\"value\":\"1\",\"type\":\"integer\",\"granularity\":\"daily\"},\n            {\"key\":\"ide_labs.feedback_link_clicked_count_manage_dependency_risk\",\"value\":\"2\",\"type\":\"integer\",\"granularity\":\"daily\"},\n            {\"key\":\"campaigns.feedback_2026_01_shown\", \"value\":\"1\", \"type\": \"integer\", \"granularity\":\"daily\"},\n            {\"key\":\"campaigns.feedback_2026_01_resolution\", \"value\":\"MAYBE_LATER\", \"type\": \"string\", \"granularity\":\"daily\"},\n            {\"key\":\"campaigns.feedback_2077_03_shown\", \"value\":\"1\", \"type\": \"integer\", \"granularity\":\"daily\"},\n            {\"key\":\"campaigns.feedback_2077_03_resolution\", \"value\":\"IGNORE\", \"type\": \"string\", \"granularity\":\"daily\"},\n            {\"key\":\"ai_hooks.windsurf_installed\",\"value\":\"2\",\"type\":\"integer\",\"granularity\":\"daily\"},\n            {\"key\":\"supported_languages_panel.opened_count\",\"value\":\"2\",\"type\":\"integer\",\"granularity\":\"daily\"},\n            {\"key\":\"supported_languages_panel.cta_clicked_count\",\"value\":\"1\",\"type\":\"integer\",\"granularity\":\"daily\"}\n          ]}\n          \"\"\", PLATFORM),\n          true, true)));\n  }\n\n  @Test\n  void should_not_crash_when_cannot_upload() {\n    telemetryMock.stubFor(post(\"/\")\n      .willReturn(aResponse().withStatus(500)));\n\n    underTest.upload(new TelemetryLocalStorage(), getTelemetryLiveAttributesDto());\n\n    await().untilAsserted(() -> telemetryMock.verify(postRequestedFor(urlEqualTo(\"/\"))));\n  }\n\n  @Test\n  void should_not_crash_when_cannot_opt_out() {\n    telemetryMock.stubFor(delete(\"/\")\n      .willReturn(aResponse().withStatus(500)));\n\n    underTest.optOut(new TelemetryLocalStorage(), getTelemetryLiveAttributesDto());\n\n    await().untilAsserted(() -> telemetryMock.verify(deleteRequestedFor(urlEqualTo(\"/\"))));\n  }\n\n  @Test\n  void failed_upload_should_log_if_debug() {\n    InternalDebug.setEnabled(true);\n\n    underTest.upload(new TelemetryLocalStorage(), getTelemetryLiveAttributesDto());\n\n    await().untilAsserted(() -> assertThat(logTester.logs(Level.ERROR)).anyMatch(l -> l.matches(\"Failed to upload telemetry data: .*404.*\")));\n  }\n\n  @Test\n  void failed_optout_should_log_if_debug() {\n    InternalDebug.setEnabled(true);\n\n    underTest.optOut(new TelemetryLocalStorage(), getTelemetryLiveAttributesDto());\n\n    await().untilAsserted(() -> assertThat(logTester.logs(Level.ERROR)).anyMatch(l -> l.matches(\"Failed to upload telemetry opt-out: .*404.*\")));\n  }\n\n  private static TelemetryLiveAttributes getTelemetryLiveAttributesDto() {\n    var connectionsAttributes = new ArrayList<TelemetryConnectionAttributes>();\n    connectionsAttributes.add(new TelemetryConnectionAttributes(\"user-id-sqc\", null, \"org-id\"));\n    connectionsAttributes.add(new TelemetryConnectionAttributes(null, \"server-id\", null));\n    var serverAttributes = new TelemetryServerAttributes(true, true, 1, 1, 1, 1, false, Collections.emptyList(), Collections.emptyList(), \"3.1.7\", connectionsAttributes);\n    var clientAttributes = new TelemetryClientLiveAttributesResponse(Map.of(\"joinedIdeLabs\", true, \"enabledIdeLabs\", false));\n    return new TelemetryLiveAttributes(serverAttributes, clientAttributes);\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/test/java/org/sonarsource/sonarlint/core/telemetry/TelemetryLocalStorageManagerTests.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\nimport java.nio.file.Path;\nimport java.time.LocalDate;\nimport java.time.OffsetDateTime;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Set;\nimport java.util.UUID;\nimport org.assertj.core.api.Condition;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.TelemetryMigrationDto;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatCode;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass TelemetryLocalStorageManagerTests {\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private final LocalDate today = LocalDate.now();\n  private Path filePath;\n\n  @BeforeEach\n  void setUp(@TempDir Path temp) {\n    filePath = temp.resolve(\"usage\");\n  }\n\n  @Test\n  void test_default_data() {\n    var storage = new TelemetryLocalStorageManager(filePath, mock(InitializeParams.class));\n\n    var data = storage.tryRead();\n    assertThat(filePath).doesNotExist();\n\n    assertThat(data.installTime()).is(within3SecOfNow);\n    assertThat(data.lastUseDate()).isNull();\n    assertThat(data.numUseDays()).isZero();\n    assertThat(data.enabled()).isTrue();\n  }\n\n  private final Condition<OffsetDateTime> within3SecOfNow = new Condition<>(p -> {\n    var now = OffsetDateTime.now();\n    return Math.abs(p.until(now, ChronoUnit.SECONDS)) < 3;\n  }, \"within3Sec\");\n\n  @Test\n  void should_update_data() {\n    var storage = new TelemetryLocalStorageManager(filePath, mock(InitializeParams.class));\n\n    storage.tryRead();\n    assertThat(filePath).doesNotExist();\n\n    storage.tryUpdateAtomically(TelemetryLocalStorage::setUsedAnalysis);\n    assertThat(filePath).exists();\n\n    var data2 = storage.tryRead();\n\n    assertThat(data2.lastUseDate()).isEqualTo(today);\n    assertThat(data2.numUseDays()).isEqualTo(1);\n  }\n\n  @Test\n  void should_fix_invalid_installTime() {\n    var storage = new TelemetryLocalStorageManager(filePath, mock(InitializeParams.class));\n\n    storage.tryUpdateAtomically(data -> {\n      data.setInstallTime(null);\n      data.setNumUseDays(100);\n    });\n\n    var data2 = storage.tryRead();\n    assertThat(data2.installTime()).is(within3SecOfNow);\n    assertThat(data2.lastUseDate()).isNull();\n    assertThat(data2.numUseDays()).isZero();\n  }\n\n  @Test\n  void should_fix_invalid_numDays() {\n    var storage = new TelemetryLocalStorageManager(filePath, mock(InitializeParams.class));\n\n    var tenDaysAgo = OffsetDateTime.now().minusDays(10);\n\n    storage.tryUpdateAtomically(data -> {\n      data.setInstallTime(tenDaysAgo);\n      data.setLastUseDate(today);\n      data.setNumUseDays(100);\n    });\n\n    var data2 = storage.tryRead();\n    assertThat(data2.installTime()).isEqualTo(tenDaysAgo);\n    assertThat(data2.lastUseDate()).isEqualTo(today);\n    assertThat(data2.numUseDays()).isEqualTo(11);\n  }\n\n  @Test\n  void should_fix_dates_in_future() {\n    var storage = new TelemetryLocalStorageManager(filePath, mock(InitializeParams.class));\n\n    storage.tryUpdateAtomically(data -> {\n      data.setInstallTime(OffsetDateTime.now().plusDays(5));\n      data.setLastUseDate(today.plusDays(7));\n      data.setNumUseDays(100);\n    });\n\n    var data2 = storage.tryRead();\n    assertThat(data2.installTime()).is(within3SecOfNow);\n    assertThat(data2.lastUseDate()).isEqualTo(today);\n    assertThat(data2.numUseDays()).isEqualTo(1);\n  }\n\n  @Test\n  void should_not_crash_when_cannot_read_storage(@TempDir Path temp) {\n    InternalDebug.setEnabled(false);\n    assertThatCode(() -> new TelemetryLocalStorageManager(temp, mock(InitializeParams.class)).tryRead())\n      .doesNotThrowAnyException();\n\n  }\n\n  @Test\n  void should_not_crash_when_cannot_write_storage(@TempDir Path temp) {\n    InternalDebug.setEnabled(false);\n    assertThatCode(() -> new TelemetryLocalStorageManager(temp, mock(InitializeParams.class)).tryUpdateAtomically(d -> {}))\n      .doesNotThrowAnyException();\n  }\n\n  @Test\n  void should_increment_open_hotspot_in_browser() {\n    var storage = new TelemetryLocalStorageManager(filePath, mock(InitializeParams.class));\n\n    storage.tryUpdateAtomically(TelemetryLocalStorage::incrementOpenHotspotInBrowserCount);\n    storage.tryUpdateAtomically(TelemetryLocalStorage::incrementOpenHotspotInBrowserCount);\n\n    var data2 = storage.tryRead();\n    assertThat(data2.openHotspotInBrowserCount()).isEqualTo(2);\n  }\n\n  @Test\n  void should_increment_hotspot_status_changed() {\n    var storage = new TelemetryLocalStorageManager(filePath, mock(InitializeParams.class));\n\n    storage.tryUpdateAtomically(TelemetryLocalStorage::incrementHotspotStatusChangedCount);\n    storage.tryUpdateAtomically(TelemetryLocalStorage::incrementHotspotStatusChangedCount);\n    storage.tryUpdateAtomically(TelemetryLocalStorage::incrementHotspotStatusChangedCount);\n\n    var data = storage.tryRead();\n    assertThat(data.hotspotStatusChangedCount()).isEqualTo(3);\n  }\n\n  @Test\n  void should_increment_issue_status_changed() {\n    var storage = new TelemetryLocalStorageManager(filePath, mock(InitializeParams.class));\n\n    storage.tryUpdateAtomically(telemetryLocalStorage -> telemetryLocalStorage.addIssueStatusChanged(\"ruleKey1\"));\n    storage.tryUpdateAtomically(telemetryLocalStorage -> telemetryLocalStorage.addIssueStatusChanged(\"ruleKey2\"));\n\n    var data = storage.tryRead();\n    assertThat(data.issueStatusChangedCount()).isEqualTo(2);\n    assertThat(data.issueStatusChangedRuleKeys()).containsExactlyInAnyOrder(\"ruleKey1\", \"ruleKey2\");\n  }\n\n  @Test\n  void should_increment_issue_ai_fixable() {\n    var storage = new TelemetryLocalStorageManager(filePath, mock(InitializeParams.class));\n    var uuid1 = UUID.randomUUID();\n    var uuid2 = UUID.randomUUID();\n    var uuid3 = UUID.randomUUID();\n    storage.tryUpdateAtomically(telemetryLocalStorage -> telemetryLocalStorage.addIssuesWithPossibleAiFixFromIde(Set.of(uuid1, uuid2)));\n    storage.tryUpdateAtomically(telemetryLocalStorage -> telemetryLocalStorage.addIssuesWithPossibleAiFixFromIde(Set.of(uuid1, uuid3)));\n\n    var data = storage.tryRead();\n    assertThat(data.getCountIssuesWithPossibleAiFixFromIde()).isEqualTo(3);\n  }\n\n  @Test\n  void should_migrate_telemetry() {\n    var initializeParams = mock(InitializeParams.class);\n    var expectedInstallTime = OffsetDateTime.now();\n    when(initializeParams.getTelemetryMigration()).thenReturn(new TelemetryMigrationDto(expectedInstallTime, 42, false));\n\n    var storageManager = new TelemetryLocalStorageManager(filePath, initializeParams);\n\n    var localStorage = storageManager.tryRead();\n    var actualInstallTime = localStorage.installTime();\n    var numUseDays = localStorage.numUseDays();\n    var enabled = localStorage.enabled();\n\n    assertThat(enabled).isFalse();\n    assertThat(numUseDays).isEqualTo(42);\n    assertThat(actualInstallTime).isEqualTo(expectedInstallTime);\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/test/java/org/sonarsource/sonarlint/core/telemetry/TelemetryLocalStorageTests.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.OffsetDateTime;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Map;\nimport java.util.UUID;\nimport org.assertj.core.api.Condition;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.FixSuggestionStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.McpTransportMode;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.entry;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.sonarsource.sonarlint.core.telemetry.TelemetryLocalStorage.isOlder;\n\nclass TelemetryLocalStorageTests {\n  @Test\n  void usedAnalysis_should_increment_num_days_on_first_run() {\n    var data = new TelemetryLocalStorage();\n    assertThat(data.numUseDays()).isZero();\n\n    data.setUsedAnalysis();\n    assertThat(data.numUseDays()).isEqualTo(1);\n  }\n\n  @Test\n  void usedAnalysis_should_not_increment_num_days_on_same_day() {\n    var data = new TelemetryLocalStorage();\n    assertThat(data.numUseDays()).isZero();\n\n    data.setUsedAnalysis();\n    assertThat(data.numUseDays()).isEqualTo(1);\n\n    data.setUsedAnalysis();\n    assertThat(data.numUseDays()).isEqualTo(1);\n  }\n\n  @Test\n  void usedAnalysis_with_duration_should_register_analyzer_performance() {\n    var data = new TelemetryLocalStorage();\n    assertThat(data.numUseDays()).isZero();\n    assertThat(data.analyzers()).isEmpty();\n\n    data.setUsedAnalysis(\"java\", 1000);\n    data.setUsedAnalysis(\"js\", 2000);\n\n    assertThat(data.numUseDays()).isEqualTo(1);\n\n    data.setUsedAnalysis();\n    assertThat(data.numUseDays()).isEqualTo(1);\n\n    assertThat(data.analyzers()).hasSize(2);\n    assertThat(data.analyzers().get(\"java\").analysisCount()).isEqualTo(1);\n    assertThat(data.analyzers().get(\"java\").frequencies()).containsOnly(\n      entry(\"0-300\", 0),\n      entry(\"300-500\", 0),\n      entry(\"500-1000\", 0),\n      entry(\"1000-2000\", 1),\n      entry(\"2000-4000\", 0),\n      entry(\"4000+\", 0));\n\n    assertThat(data.analyzers().get(\"js\").analysisCount()).isEqualTo(1);\n    assertThat(data.analyzers().get(\"js\").frequencies()).containsOnly(\n      entry(\"0-300\", 0),\n      entry(\"300-500\", 0),\n      entry(\"500-1000\", 0),\n      entry(\"1000-2000\", 0),\n      entry(\"2000-4000\", 1),\n      entry(\"4000+\", 0));\n  }\n\n  @Test\n  void usedAnalysis_should_increment_num_days_when_day_changed() {\n    var data = new TelemetryLocalStorage();\n    assertThat(data.numUseDays()).isZero();\n\n    data.setUsedAnalysis();\n    assertThat(data.numUseDays()).isEqualTo(1);\n\n    data.setUsedAnalysis();\n    assertThat(data.numUseDays()).isEqualTo(1);\n\n    data.setLastUseDate(LocalDate.now().minusDays(1));\n    data.setUsedAnalysis();\n    assertThat(data.numUseDays()).isEqualTo(2);\n  }\n\n  @Test\n  void test_isOlder_LocalDate() {\n    var date = LocalDate.now();\n\n    assertThat(isOlder((LocalDate) null, null)).isTrue();\n    assertThat(isOlder(null, date)).isTrue();\n    assertThat(isOlder(date, null)).isFalse();\n    assertThat(isOlder(date, date)).isFalse();\n    assertThat(isOlder(date, date.plusDays(1))).isTrue();\n  }\n\n  @Test\n  void test_isOlder_LocalDateTime() {\n    var date = LocalDateTime.now();\n\n    assertThat(isOlder((LocalDateTime) null, null)).isTrue();\n    assertThat(isOlder(null, date)).isTrue();\n    assertThat(isOlder(date, null)).isFalse();\n    assertThat(isOlder(date, date)).isFalse();\n    assertThat(isOlder(date, date.plusDays(1))).isTrue();\n  }\n\n  @Test\n  void validate_should_reset_installTime_if_in_future() {\n    var data = new TelemetryLocalStorage();\n    var now = OffsetDateTime.now();\n\n    data.validateAndMigrate();\n    assertThat(data.installTime()).is(within3SecOfNow);\n\n    data.setInstallTime(now.plusDays(1));\n    data.validateAndMigrate();\n    assertThat(data.installTime()).is(within3SecOfNow);\n  }\n\n  private final Condition<OffsetDateTime> within3SecOfNow = new Condition<>(p -> {\n    var now = OffsetDateTime.now();\n    return Math.abs(p.until(now, ChronoUnit.SECONDS)) < 3;\n  }, \"within3Sec\");\n\n  private final Condition<OffsetDateTime> about5DaysAgo = new Condition<>(p -> {\n    var fiveDaysAgo = OffsetDateTime.now().minusDays(5);\n    return Math.abs(p.until(fiveDaysAgo, ChronoUnit.SECONDS)) < 3;\n  }, \"about5DaysAgo\");\n\n  @Test\n  void validate_should_reset_lastUseDate_if_in_future() {\n    var data = new TelemetryLocalStorage();\n    var today = LocalDate.now();\n\n    data.setLastUseDate(today.plusDays(1));\n    data.validateAndMigrate();\n    assertThat(data.lastUseDate()).isEqualTo(today);\n  }\n\n  @Test\n  void should_migrate_installDate() {\n    var data = new TelemetryLocalStorage();\n    data.setInstallDate(LocalDate.now().minusDays(5));\n    data.validateAndMigrate();\n    assertThat(data.installTime()).is(about5DaysAgo);\n  }\n\n  @Test\n  void validate_should_reset_lastUseDate_if_before_installTime() {\n    var data = new TelemetryLocalStorage();\n    var now = OffsetDateTime.now();\n\n    data.setInstallTime(now);\n    data.setLastUseDate(now.minusDays(1).toLocalDate());\n    data.validateAndMigrate();\n    assertThat(data.lastUseDate()).isEqualTo(LocalDate.now());\n  }\n\n  @Test\n  void validate_should_reset_numDays_if_lastUseDate_unset() {\n    var data = new TelemetryLocalStorage();\n    data.setNumUseDays(3);\n\n    data.validateAndMigrate();\n    assertThat(data.lastUseDate()).isNull();\n    assertThat(data.numUseDays()).isZero();\n  }\n\n  @Test\n  void validate_should_fix_numDays_if_incorrect() {\n    var data = new TelemetryLocalStorage();\n    var installTime = OffsetDateTime.now().minusDays(10);\n    var lastUseDate = installTime.plusDays(3).toLocalDate();\n    data.setInstallTime(installTime);\n    data.setLastUseDate(lastUseDate);\n\n    var numUseDays = installTime.toLocalDate().until(lastUseDate, ChronoUnit.DAYS) + 1;\n    data.setNumUseDays(numUseDays * 2);\n\n    data.validateAndMigrate();\n    assertThat(data.numUseDays()).isEqualTo(numUseDays);\n    assertThat(data.installTime()).isEqualTo(installTime);\n    assertThat(data.lastUseDate()).isEqualTo(lastUseDate);\n  }\n\n  @Test\n  void should_replace_fix_suggestion_snippet_status() {\n    var data = new TelemetryLocalStorage();\n\n    var suggestionId = UUID.randomUUID().toString();\n    data.fixSuggestionResolved(suggestionId, FixSuggestionStatus.ACCEPTED, 0);\n\n    assertThat(data.getFixSuggestionResolved().get(suggestionId))\n      .extracting(TelemetryFixSuggestionResolvedStatus::getFixSuggestionResolvedStatus, TelemetryFixSuggestionResolvedStatus::getFixSuggestionResolvedSnippetIndex)\n      .containsExactly(tuple(FixSuggestionStatus.ACCEPTED, 0));\n\n    data.fixSuggestionResolved(suggestionId, FixSuggestionStatus.DECLINED, 0);\n    assertThat(data.getFixSuggestionResolved().get(suggestionId))\n      .extracting(TelemetryFixSuggestionResolvedStatus::getFixSuggestionResolvedStatus, TelemetryFixSuggestionResolvedStatus::getFixSuggestionResolvedSnippetIndex)\n      .containsExactly(tuple(FixSuggestionStatus.DECLINED, 0));\n  }\n\n  @Test\n  void should_track_findings_filtered_by_type() {\n    var data = new TelemetryLocalStorage();\n    assertThat(data.getFindingsFilteredCountersByType()).isEmpty();\n\n    data.findingsFiltered(\"severity\");\n\n    data.findingsFiltered(\"severity\");\n\n    data.findingsFiltered(\"location\");\n\n    data.findingsFiltered(\"fix_availability\");\n    assertThat(data.getFindingsFilteredCountersByType()).hasSize(3);\n    assertThat(data.getFindingsFilteredCountersByType().get(\"location\").getFindingsFilteredCount()).isEqualTo(1);\n    assertThat(data.getFindingsFilteredCountersByType().get(\"severity\").getFindingsFilteredCount()).isEqualTo(2);\n    assertThat(data.getFindingsFilteredCountersByType().get(\"fix_availability\").getFindingsFilteredCount()).isEqualTo(1);\n  }\n\n  @Test\n  void should_clear_findings_filtered_counters() {\n    var data = new TelemetryLocalStorage();\n\n    data.findingsFiltered(\"severity\");\n    data.findingsFiltered(\"location\");\n    assertThat(data.getFindingsFilteredCountersByType()).hasSize(2);\n\n    data.clearAfterPing();\n    assertThat(data.getFindingsFilteredCountersByType()).isEmpty();\n  }\n\n  @Test\n  void should_track_dependency_risk_investigated() {\n    var data = new TelemetryLocalStorage();\n    assertThat(data.getDependencyRiskInvestigatedRemotelyCount()).isZero();\n    assertThat(data.getDependencyRiskInvestigatedLocallyCount()).isZero();\n\n    data.incrementDependencyRiskInvestigatedRemotelyCount();\n    data.incrementDependencyRiskInvestigatedLocallyCount();\n    assertThat(data.getDependencyRiskInvestigatedRemotelyCount()).isEqualTo(1);\n    assertThat(data.getDependencyRiskInvestigatedLocallyCount()).isEqualTo(1);\n\n    data.incrementDependencyRiskInvestigatedRemotelyCount();\n    data.incrementDependencyRiskInvestigatedLocallyCount();\n    assertThat(data.getDependencyRiskInvestigatedRemotelyCount()).isEqualTo(2);\n    assertThat(data.getDependencyRiskInvestigatedLocallyCount()).isEqualTo(2);\n  }\n\n  @Test\n  void should_clear_dependency_risk_investigated_counts_after_ping() {\n    var data = new TelemetryLocalStorage();\n\n    data.incrementDependencyRiskInvestigatedRemotelyCount();\n    data.incrementDependencyRiskInvestigatedLocallyCount();\n    assertThat(data.getDependencyRiskInvestigatedRemotelyCount()).isEqualTo(1);\n    assertThat(data.getDependencyRiskInvestigatedLocallyCount()).isEqualTo(1);\n\n    data.clearAfterPing();\n    assertThat(data.getDependencyRiskInvestigatedRemotelyCount()).isZero();\n    assertThat(data.getDependencyRiskInvestigatedLocallyCount()).isZero();\n  }\n\n  @Test\n  void should_increment_new_bindings_counters_per_origin() {\n    var data = new TelemetryLocalStorage();\n\n    assertThat(data.getNewBindingsPropertiesFileCount()).isZero();\n    assertThat(data.getNewBindingsRemoteUrlCount()).isZero();\n    assertThat(data.getNewBindingsProjectNameCount()).isZero();\n    assertThat(data.getNewBindingsSharedConfigurationCount()).isZero();\n\n    data.incrementNewBindingsPropertiesFileCount();\n    data.incrementNewBindingsRemoteUrlCount();\n    data.incrementNewBindingsProjectNameCount();\n    data.incrementNewBindingsSharedConfigurationCount();\n\n    assertThat(data.getNewBindingsPropertiesFileCount()).isEqualTo(1);\n    assertThat(data.getNewBindingsRemoteUrlCount()).isEqualTo(1);\n    assertThat(data.getNewBindingsProjectNameCount()).isEqualTo(1);\n    assertThat(data.getNewBindingsSharedConfigurationCount()).isEqualTo(1);\n  }\n\n  @Test\n  void should_reset_new_bindings_counters_on_clear_after_ping() {\n    var data = new TelemetryLocalStorage();\n    data.incrementNewBindingsPropertiesFileCount();\n    data.incrementNewBindingsRemoteUrlCount();\n    data.incrementNewBindingsProjectNameCount();\n    data.incrementNewBindingsSharedConfigurationCount();\n\n    data.clearAfterPing();\n\n    assertThat(data.getNewBindingsPropertiesFileCount()).isZero();\n    assertThat(data.getNewBindingsRemoteUrlCount()).isZero();\n    assertThat(data.getNewBindingsProjectNameCount()).isZero();\n    assertThat(data.getNewBindingsSharedConfigurationCount()).isZero();\n  }\n\n  @Test\n  void should_increment_suggested_remote_bindings_count() {\n    var data = new TelemetryLocalStorage();\n    assertThat(data.getSuggestedRemoteBindingsCount()).isZero();\n    data.incrementSuggestedRemoteBindingsCount();\n    data.incrementSuggestedRemoteBindingsCount();\n    assertThat(data.getSuggestedRemoteBindingsCount()).isEqualTo(2);\n  }\n\n  @Test\n  void should_increment_mcp_server_settings_requested_count() {\n    var data = new TelemetryLocalStorage();\n    assertThat(data.getMcpServerConfigurationRequestedCount()).isZero();\n    data.incrementMcpServerConfigurationRequestedCount();\n    data.incrementMcpServerConfigurationRequestedCount();\n    assertThat(data.getMcpServerConfigurationRequestedCount()).isEqualTo(2);\n  }\n\n  @Test\n  void should_find_mcp_integration_enabled() {\n    var data = new TelemetryLocalStorage();\n    assertThat(data.isMcpIntegrationEnabled()).isFalse();\n    data.setMcpIntegrationEnabled(true);\n    assertThat(data.isMcpIntegrationEnabled()).isTrue();\n  }\n\n  @Test\n  void should_find_mcp_transport_mode_used() {\n    var data = new TelemetryLocalStorage();\n    assertThat(data.getMcpTransportModeUsed()).isNull();\n    data.setMcpTransportModeUsed(McpTransportMode.HTTP);\n    assertThat(data.getMcpTransportModeUsed()).isEqualTo(McpTransportMode.HTTP);\n  }\n\n  @Test\n  void should_increment_link_clicked_count_for_each_link_separately() {\n    var data = new TelemetryLocalStorage();\n    assertThat(data.getLabsLinkClickedCount()).isEmpty();\n\n    data.ideLabsLinkClicked(\"1\");\n    data.ideLabsLinkClicked(\"2\");\n    data.ideLabsLinkClicked(\"2\");\n\n    assertThat(data.getLabsLinkClickedCount())\n      .isEqualTo(Map.of(\n        \"1\", 1,\n        \"2\", 2));\n  }\n\n  @Test\n  void should_increment_feedback_link_clicked_count_for_each_link_separately() {\n    var data = new TelemetryLocalStorage();\n    assertThat(data.getLabsFeedbackLinkClickedCount()).isEmpty();\n\n    data.ideLabsFeedbackLinkClicked(\"1\");\n    data.ideLabsFeedbackLinkClicked(\"2\");\n    data.ideLabsFeedbackLinkClicked(\"2\");\n\n    assertThat(data.getLabsFeedbackLinkClickedCount())\n      .isEqualTo(Map.of(\n        \"1\", 1,\n        \"2\", 2));\n  }\n\n  @Test\n  void should_reset_link_clicked_data_after_ping() {\n    var data = new TelemetryLocalStorage();\n\n    data.ideLabsLinkClicked(\"1\");\n    data.ideLabsLinkClicked(\"2\");\n    data.ideLabsFeedbackLinkClicked(\"1\");\n    data.ideLabsFeedbackLinkClicked(\"2\");\n\n    data.clearAfterPing();\n\n    assertThat(data.getLabsLinkClickedCount()).isEmpty();\n    assertThat(data.getLabsFeedbackLinkClickedCount()).isEmpty();\n  }\n\n  @Test\n  void should_track_supported_languages_panel_opened() {\n    var data = new TelemetryLocalStorage();\n    assertThat(data.getSupportedLanguagesPanelOpenedCount()).isZero();\n\n    data.incrementSupportedLanguagesPanelOpenedCount();\n    data.incrementSupportedLanguagesPanelOpenedCount();\n    assertThat(data.getSupportedLanguagesPanelOpenedCount()).isEqualTo(2);\n  }\n\n  @Test\n  void should_track_supported_languages_panel_cta_clicked() {\n    var data = new TelemetryLocalStorage();\n    assertThat(data.getSupportedLanguagesPanelCtaClickedCount()).isZero();\n\n    data.incrementSupportedLanguagesPanelCtaClickedCount();\n    data.incrementSupportedLanguagesPanelCtaClickedCount();\n    data.incrementSupportedLanguagesPanelCtaClickedCount();\n    assertThat(data.getSupportedLanguagesPanelCtaClickedCount()).isEqualTo(3);\n  }\n\n  @Test\n  void should_clear_supported_languages_panel_counts_after_ping() {\n    var data = new TelemetryLocalStorage();\n\n    data.incrementSupportedLanguagesPanelOpenedCount();\n    data.incrementSupportedLanguagesPanelCtaClickedCount();\n    assertThat(data.getSupportedLanguagesPanelOpenedCount()).isEqualTo(1);\n    assertThat(data.getSupportedLanguagesPanelCtaClickedCount()).isEqualTo(1);\n\n    data.clearAfterPing();\n    assertThat(data.getSupportedLanguagesPanelOpenedCount()).isZero();\n    assertThat(data.getSupportedLanguagesPanelCtaClickedCount()).isZero();\n  }\n\n}\n"
  },
  {
    "path": "backend/telemetry/src/test/java/org/sonarsource/sonarlint/core/telemetry/TelemetryManagerTests.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\nimport java.nio.file.Path;\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.OffsetDateTime;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Collections;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.function.Consumer;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.mockito.stubbing.Answer;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AiSuggestionSource;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.FixSuggestionStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.McpTransportMode;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.TelemetryClientLiveAttributesResponse;\n\nimport static java.util.Collections.emptyMap;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\nimport static org.mockito.Mockito.when;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AnalysisReportingType.PRE_COMMIT_ANALYSIS_TYPE;\n\nclass TelemetryManagerTests {\n  private static final int DEFAULT_NOTIF_CLICKED = 5;\n  private static final int DEFAULT_NOTIF_COUNT = 10;\n  private static final int DEFAULT_HELP_AND_FEEDBACK_COUNT = 12;\n  private static final int DEFAULT_ANALYSIS_REPORTING_COUNT = 16;\n\n  private static final String FOO_EVENT = \"foo_event\";\n  private static final String SUGGEST_FEATURE = \"suggestFeature\";\n\n  private TelemetryHttpClient client;\n  private TelemetryManager telemetryManager;\n  private TelemetryLocalStorageManager storageManager;\n\n  @BeforeEach\n  void setUp(@TempDir Path temp) {\n    client = mock(TelemetryHttpClient.class);\n    storageManager = new TelemetryLocalStorageManager(temp.resolve(\"storage\"), mock(InitializeParams.class));\n    telemetryManager = new TelemetryManager(storageManager, client);\n  }\n\n  @Test\n  void enable_should_trigger_upload_once_per_day() {\n    var telemetryPayload = getTelemetryLiveAttributesDto();\n\n    telemetryManager.enable(telemetryPayload);\n    telemetryManager.enable(telemetryPayload);\n\n    verify(client).upload(any(TelemetryLocalStorage.class), eq(telemetryPayload));\n    verifyNoMoreInteractions(client);\n  }\n\n  @Test\n  void disable_should_trigger_optout() {\n    var mockStorageManager = mockTelemetryStorage();\n    var manager = new TelemetryManager(mockStorageManager, client);\n    var telemetryPayload = getTelemetryLiveAttributesDto();\n\n    manager.disable(telemetryPayload);\n\n    verify(client).optOut(any(TelemetryLocalStorage.class), eq(telemetryPayload));\n    verifyNoMoreInteractions(client);\n  }\n\n  @Test\n  void uploadAndClearTelemetry_should_trigger_upload_once_per_day() {\n    var telemetryPayload = getTelemetryLiveAttributesDto();\n\n    storageManager.tryUpdateAtomically(d -> d.setUsedAnalysis(\"java\", 1000));\n\n    var data = storageManager.tryRead();\n    assertThat(data.analyzers()).isNotEmpty();\n    assertThat(data.lastUploadTime()).isNull();\n\n    telemetryManager.uploadAndClearTelemetry(telemetryPayload);\n\n    var reloaded = storageManager.tryRead();\n\n    // should reset performance after upload\n    assertThat(reloaded.analyzers()).isEmpty();\n\n    var lastUploadTime = reloaded.lastUploadTime();\n    assertThat(lastUploadTime).isNotNull();\n\n    telemetryManager.uploadAndClearTelemetry(telemetryPayload);\n\n    reloaded = storageManager.tryRead();\n\n    assertThat(reloaded.lastUploadTime()).isEqualTo(lastUploadTime);\n    verify(client).upload(any(TelemetryLocalStorage.class), eq(telemetryPayload));\n    verifyNoMoreInteractions(client);\n  }\n\n  @Test\n  void uploadAndClearTelemetry_should_trigger_upload_if_day_changed_and_hours_elapsed() {\n    var telemetryPayload = getTelemetryLiveAttributesDto();\n\n    createAndSaveSampleData(storageManager);\n    storageManager.tryUpdateAtomically(telemetryLocalStorage -> telemetryLocalStorage.setEnabled(true));\n    telemetryManager.uploadAndClearTelemetry(telemetryPayload);\n\n    var data = storageManager.tryRead();\n\n    var lastUploadTime = data.lastUploadTime()\n      .minusDays(1)\n      .minusHours(TelemetryManager.MIN_HOURS_BETWEEN_UPLOAD);\n    storageManager.tryUpdateAtomically(d -> d.setLastUploadTime(lastUploadTime));\n\n    telemetryManager.uploadAndClearTelemetry(telemetryPayload);\n\n    verify(client, times(2)).upload(any(TelemetryLocalStorage.class), eq(telemetryPayload));\n    verifyNoMoreInteractions(client);\n  }\n\n  @Test\n  void uploadAndClearTelemetry_should_not_trigger_upload_if_telemetry_disabled_by_user() {\n    createAndSaveSampleData(storageManager);\n    TelemetryLiveAttributes telemetryLiveAttributesDto = getTelemetryLiveAttributesDto();\n\n    telemetryManager.uploadAndClearTelemetry(telemetryLiveAttributesDto);\n\n    assertThat(storageManager.isEnabled()).isFalse();\n    verify(client, never()).upload(any(TelemetryLocalStorage.class), eq(telemetryLiveAttributesDto));\n    verifyNoMoreInteractions(client);\n  }\n\n  @Test\n  void updateTelemetry_should_not_trigger_update_if_telemetry_disabled_by_user() {\n    createAndSaveSampleData(storageManager);\n\n    telemetryManager.updateTelemetry(telemetryLocalStorage -> telemetryLocalStorage.setNumUseDays(10));\n\n    TelemetryLocalStorage localStorage = storageManager.tryRead();\n    assertThat(localStorage.enabled()).isFalse();\n    assertThat(localStorage.numUseDays()).isEqualTo(5);\n  }\n\n  @Test\n  void enable_should_not_wipe_out_more_recent_data() {\n    var telemetryLiveAttributes = getTelemetryLiveAttributesDto();\n\n    createAndSaveSampleData(storageManager);\n\n    var data = storageManager.tryRead();\n    assertThat(data.enabled()).isFalse();\n\n    // note: the manager hasn't seen the saved data\n    telemetryManager.enable(telemetryLiveAttributes);\n\n    var reloaded = storageManager.tryRead();\n    assertThat(reloaded.enabled()).isTrue();\n    assertThat(reloaded.installTime()).isEqualTo(data.installTime().truncatedTo(ChronoUnit.MILLIS));\n    assertThat(reloaded.lastUseDate()).isEqualTo(data.lastUseDate());\n    assertThat(reloaded.numUseDays()).isEqualTo(data.numUseDays());\n  }\n\n  @Test\n  void disable_should_not_wipe_out_more_recent_data() {\n    var telemetryPayload = getTelemetryLiveAttributesDto();\n\n    createAndSaveSampleData(storageManager);\n    storageManager.tryUpdateAtomically(data -> data.setEnabled(true));\n\n    var data = storageManager.tryRead();\n    assertThat(data.enabled()).isTrue();\n\n    // note: the manager hasn't seen the saved data\n    telemetryManager.disable(telemetryPayload);\n\n    var reloaded = storageManager.tryRead();\n    assertThat(reloaded.enabled()).isFalse();\n    assertThat(reloaded.installTime()).isEqualTo(data.installTime());\n    assertThat(reloaded.lastUseDate()).isEqualTo(data.lastUseDate());\n    assertThat(reloaded.numUseDays()).isEqualTo(data.numUseDays());\n    assertThat(reloaded.lastUploadTime()).isEqualTo(data.lastUploadTime());\n    assertThat(reloaded.notifications().get(FOO_EVENT).getDevNotificationsCount()).isEqualTo(data.notifications().get(FOO_EVENT).getDevNotificationsCount());\n    assertThat(reloaded.getHelpAndFeedbackLinkClickedCounter().get(SUGGEST_FEATURE).getHelpAndFeedbackLinkClickedCount())\n      .isEqualTo(data.getHelpAndFeedbackLinkClickedCounter().get(SUGGEST_FEATURE).getHelpAndFeedbackLinkClickedCount());\n    assertThat(reloaded.getAnalysisReportingCountersByType().get(PRE_COMMIT_ANALYSIS_TYPE).getAnalysisReportingCount())\n      .isEqualTo(data.getAnalysisReportingCountersByType().get(PRE_COMMIT_ANALYSIS_TYPE).getAnalysisReportingCount());\n  }\n\n  @Test\n  void uploadAndClearTelemetry_should_clear_accumulated_data() {\n    var telemetryPayload = getTelemetryLiveAttributesDto();\n\n    createAndSaveSampleData(storageManager);\n    storageManager.tryUpdateAtomically(data -> {\n      data.setEnabled(true);\n      data.setUsedAnalysis(\"java\", 1000);\n      data.incrementHotspotStatusChangedCount();\n      data.incrementOpenHotspotInBrowserCount();\n      data.incrementShowHotspotRequestCount();\n      data.incrementShowIssueRequestCount();\n      data.addIssuesWithPossibleAiFixFromIde(Set.of(UUID.randomUUID(), UUID.randomUUID()));\n      data.fixSuggestionReceived(\"suggestionId\", AiSuggestionSource.SONARCLOUD, 2, true);\n      data.fixSuggestionResolved(\"suggestionId\", FixSuggestionStatus.ACCEPTED, 0);\n      data.incrementTaintVulnerabilitiesInvestigatedLocallyCount();\n      data.incrementTaintVulnerabilitiesInvestigatedRemotelyCount();\n      data.setLastUploadTime(LocalDateTime.now().minusDays(2));\n      data.setNumUseDays(5);\n      data.notifications().put(FOO_EVENT, new TelemetryNotificationsCounter(DEFAULT_NOTIF_COUNT, DEFAULT_NOTIF_CLICKED));\n      data.getHelpAndFeedbackLinkClickedCounter().put(SUGGEST_FEATURE, new TelemetryHelpAndFeedbackCounter(DEFAULT_HELP_AND_FEEDBACK_COUNT));\n      data.getAnalysisReportingCountersByType().put(PRE_COMMIT_ANALYSIS_TYPE, new TelemetryAnalysisReportingCounter(DEFAULT_ANALYSIS_REPORTING_COUNT));\n      data.findingsFiltered(\"severity\");\n      data.setMcpIntegrationEnabled(true);\n      data.setMcpTransportModeUsed(McpTransportMode.HTTPS);\n    });\n\n    telemetryManager.uploadAndClearTelemetry(telemetryPayload);\n\n    var reloaded = storageManager.tryRead();\n    assertThat(reloaded.analyzers()).isEmpty();\n    assertThat(reloaded.showHotspotRequestsCount()).isZero();\n    assertThat(reloaded.notifications()).isEmpty();\n    assertThat(reloaded.taintVulnerabilitiesInvestigatedLocallyCount()).isZero();\n    assertThat(reloaded.taintVulnerabilitiesInvestigatedRemotelyCount()).isZero();\n    assertThat(reloaded.hotspotStatusChangedCount()).isZero();\n    assertThat(reloaded.getShowIssueRequestsCount()).isZero();\n    assertThat(reloaded.getCountIssuesWithPossibleAiFixFromIde()).isZero();\n    assertThat(reloaded.getFixSuggestionReceivedCounter()).isEmpty();\n    assertThat(reloaded.getFixSuggestionResolved()).isEmpty();\n    assertThat(reloaded.openHotspotInBrowserCount()).isZero();\n    assertThat(reloaded.getHelpAndFeedbackLinkClickedCounter()).isEmpty();\n    assertThat(reloaded.getAnalysisReportingCountersByType()).isEmpty();\n    assertThat(reloaded.getFindingsFilteredCountersByType()).isEmpty();\n    assertThat(reloaded.isMcpIntegrationEnabled()).isFalse();\n    assertThat(reloaded.getMcpTransportModeUsed()).isNull();\n  }\n\n  private void createAndSaveSampleData(TelemetryLocalStorageManager storage) {\n    storage.tryUpdateAtomically(data -> {\n      data.setEnabled(false);\n      data.setInstallTime(OffsetDateTime.now().minusDays(10));\n      data.setLastUseDate(LocalDate.now().minusDays(3));\n      data.setLastUploadTime(LocalDateTime.now().minusDays(2));\n      data.setNumUseDays(5);\n      data.notifications().put(FOO_EVENT, new TelemetryNotificationsCounter(DEFAULT_NOTIF_COUNT, DEFAULT_NOTIF_CLICKED));\n      data.getHelpAndFeedbackLinkClickedCounter().put(SUGGEST_FEATURE, new TelemetryHelpAndFeedbackCounter(DEFAULT_HELP_AND_FEEDBACK_COUNT));\n      data.getAnalysisReportingCountersByType().put(PRE_COMMIT_ANALYSIS_TYPE, new TelemetryAnalysisReportingCounter(DEFAULT_ANALYSIS_REPORTING_COUNT));\n    });\n  }\n\n  private TelemetryLocalStorageManager mockTelemetryStorage() {\n    var storage = mock(TelemetryLocalStorageManager.class);\n    when(storage.tryRead()).thenReturn(new TelemetryLocalStorage());\n    doAnswer((Answer<Void>) invocation -> {\n      var args = invocation.getArguments();\n      ((Consumer) args[0]).accept(mock(TelemetryLocalStorage.class));\n      return null;\n    }).when(storage).tryUpdateAtomically(any(Consumer.class));\n    return storage;\n  }\n\n  private static TelemetryLiveAttributes getTelemetryLiveAttributesDto() {\n    var serverAttributes = new TelemetryServerAttributes(true, true, 1, 1, 1, 0, false, Collections.emptyList(), Collections.emptyList(), \"3.1.7\", Collections.emptyList());\n    var clientAttributes = new TelemetryClientLiveAttributesResponse(emptyMap());\n    return new TelemetryLiveAttributes(serverAttributes, clientAttributes);\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/test/java/org/sonarsource/sonarlint/core/telemetry/TelemetryUtilsTests.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry;\n\nimport java.math.BigDecimal;\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AiSuggestionSource;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.FixSuggestionStatus;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.entry;\nimport static org.sonarsource.sonarlint.core.telemetry.TelemetryUtils.isGracePeriodElapsedAndDayChanged;\n\nclass TelemetryUtilsTests {\n  @Test\n  void dayChanged_should_return_true_for_null() {\n    assertThat(isGracePeriodElapsedAndDayChanged(null)).isTrue();\n  }\n\n  @Test\n  void dayChanged_should_return_true_if_older() {\n    assertThat(isGracePeriodElapsedAndDayChanged(LocalDate.now().minusDays(1))).isTrue();\n  }\n\n  @Test\n  void should_create_telemetry_performance_payload() {\n    Map<String, TelemetryAnalyzerPerformance> analyzers = new HashMap<>();\n    var perf = new TelemetryAnalyzerPerformance();\n    perf.registerAnalysis(10);\n    perf.registerAnalysis(500);\n    perf.registerAnalysis(500);\n\n    analyzers.put(\"java\", perf);\n    var payload = TelemetryUtils.toPayload(analyzers);\n    assertThat(payload).hasSize(1);\n    assertThat(payload[0].language()).isEqualTo(\"java\");\n    assertThat(payload[0].distribution()).containsOnly(\n      entry(\"0-300\", new BigDecimal(\"33.33\")),\n      entry(\"300-500\", new BigDecimal(\"0.00\")),\n      entry(\"500-1000\", new BigDecimal(\"66.67\")),\n      entry(\"1000-2000\", new BigDecimal(\"0.00\")),\n      entry(\"2000-4000\", new BigDecimal(\"0.00\")),\n      entry(\"4000+\", new BigDecimal(\"0.00\")));\n  }\n\n  @Test\n  void dayChanged_should_return_false_if_same() {\n    assertThat(isGracePeriodElapsedAndDayChanged(LocalDate.now())).isFalse();\n  }\n\n  @Test\n  void dayChanged_with_hours_should_return_true_for_null() {\n    assertThat(TelemetryUtils.isGracePeriodElapsedAndDayChanged(null, 1)).isTrue();\n  }\n\n  @Test\n  void dayChanged_with_hours_should_return_false_if_day_same() {\n    assertThat(TelemetryUtils.isGracePeriodElapsedAndDayChanged(LocalDateTime.now(), 100)).isFalse();\n  }\n\n  @Test\n  void create_analyzer_performance_payload() {\n    var perf = new TelemetryAnalyzerPerformance();\n    for (var i = 0; i < 10; i++) {\n      perf.registerAnalysis(1000);\n    }\n    for (var i = 0; i < 20; i++) {\n      perf.registerAnalysis(2000);\n    }\n    for (var i = 0; i < 20; i++) {\n      perf.registerAnalysis(200);\n    }\n    assertThat(perf.analysisCount()).isEqualTo(50);\n    var payload = TelemetryUtils.toPayload(Collections.singletonMap(\"java\", perf));\n    assertThat(payload).hasSize(1);\n    assertThat(payload[0].language()).isEqualTo(\"java\");\n    assertThat(payload[0].distribution()).containsExactly(\n      entry(\"0-300\", new BigDecimal(\"40.00\")),\n      entry(\"300-500\", new BigDecimal(\"0.00\")),\n      entry(\"500-1000\", new BigDecimal(\"0.00\")),\n      entry(\"1000-2000\", new BigDecimal(\"20.00\")),\n      entry(\"2000-4000\", new BigDecimal(\"40.00\")),\n      entry(\"4000+\", new BigDecimal(\"0.00\")));\n\n  }\n\n  @Test\n  void dayChanged_with_hours_should_return_false_if_different_day_but_within_hours() {\n    var date = LocalDateTime.now().minusDays(1);\n    var hours = date.until(LocalDateTime.now(), ChronoUnit.HOURS);\n    assertThat(TelemetryUtils.isGracePeriodElapsedAndDayChanged(date, hours + 1)).isFalse();\n  }\n\n  @Test\n  void dayChanged_with_hours_should_return_true_if_different_day_and_beyond_hours() {\n    var date = LocalDateTime.now().minusDays(1);\n    var hours = date.until(LocalDateTime.now(), ChronoUnit.HOURS);\n    assertThat(TelemetryUtils.isGracePeriodElapsedAndDayChanged(date, hours)).isTrue();\n  }\n\n  @Test\n  void should_create_telemetry_fixSuggestions_payload() {\n    var suggestionId1 = UUID.randomUUID().toString();\n    var counter1 = new TelemetryFixSuggestionReceivedCounter(AiSuggestionSource.SONARCLOUD, 4, true);\n\n    var suggestionId2 = UUID.randomUUID().toString();\n    var counter2 = new TelemetryFixSuggestionReceivedCounter(AiSuggestionSource.SONARCLOUD, 2, true);\n\n    var suggestionId3 = UUID.randomUUID().toString();\n    var counter3 = new TelemetryFixSuggestionReceivedCounter(AiSuggestionSource.SONARCLOUD, 1, false);\n\n    var fixSuggestionReceivedCounter = Map.of(\n      suggestionId1, counter1,\n      suggestionId2, counter2,\n      suggestionId3, counter3\n    );\n    var fixSuggestionResolvedStatus1 = new TelemetryFixSuggestionResolvedStatus(FixSuggestionStatus.ACCEPTED, 0);\n    var fixSuggestionResolvedStatus2 = new TelemetryFixSuggestionResolvedStatus(FixSuggestionStatus.ACCEPTED, 1);\n    var fixSuggestionResolvedStatus3 = new TelemetryFixSuggestionResolvedStatus(FixSuggestionStatus.DECLINED, null);\n    var fixSuggestionResolved = Map.of(suggestionId1, List.of(fixSuggestionResolvedStatus1, fixSuggestionResolvedStatus2),\n      suggestionId3, List.of(fixSuggestionResolvedStatus3));\n\n    var result = TelemetryUtils.toFixSuggestionResolvedPayload(fixSuggestionReceivedCounter, fixSuggestionResolved);\n\n    assertThat(result).hasSize(3);\n    var resultingSuggestion1 = Arrays.stream(result).filter(s -> s.suggestionId().equals(suggestionId1)).findFirst().orElseThrow();\n    assertThat(resultingSuggestion1.suggestionId()).isEqualTo(suggestionId1);\n    assertThat(resultingSuggestion1.aiFixSuggestionProvider()).isEqualTo(AiSuggestionSource.SONARCLOUD);\n    assertThat(resultingSuggestion1.countSnippets()).isEqualTo(4);\n    assertThat(resultingSuggestion1.snippets()).hasSize(2);\n    assertThat(resultingSuggestion1.wasAiFixSuggestionGeneratedFromIde()).isTrue();\n\n    var resultingSuggestion2 = Arrays.stream(result).filter(s -> s.suggestionId().equals(suggestionId2)).findFirst().orElseThrow();\n    assertThat(resultingSuggestion2.suggestionId()).isEqualTo(suggestionId2);\n    assertThat(resultingSuggestion2.aiFixSuggestionProvider()).isEqualTo(AiSuggestionSource.SONARCLOUD);\n    assertThat(resultingSuggestion2.countSnippets()).isEqualTo(2);\n    assertThat(resultingSuggestion2.snippets()).hasSize(1);\n    assertThat(resultingSuggestion2.snippets().get(0).status()).isNull();\n    assertThat(resultingSuggestion2.snippets().get(0).snippetIndex()).isNull();\n    assertThat(resultingSuggestion2.wasAiFixSuggestionGeneratedFromIde()).isTrue();\n\n    var resultingSuggestion3 = Arrays.stream(result).filter(s -> s.suggestionId().equals(suggestionId3)).findFirst().orElseThrow();\n    assertThat(resultingSuggestion3.suggestionId()).isEqualTo(suggestionId3);\n    assertThat(resultingSuggestion3.aiFixSuggestionProvider()).isEqualTo(AiSuggestionSource.SONARCLOUD);\n    assertThat(resultingSuggestion3.countSnippets()).isEqualTo(1);\n    assertThat(resultingSuggestion3.snippets()).hasSize(1);\n    var telemetryFixSuggestionResolvedPayload3 = resultingSuggestion3.snippets().get(0);\n    assertThat(telemetryFixSuggestionResolvedPayload3.snippetIndex()).isNull();\n    assertThat(telemetryFixSuggestionResolvedPayload3.status()).isEqualTo(FixSuggestionStatus.DECLINED);\n    assertThat(resultingSuggestion3.wasAiFixSuggestionGeneratedFromIde()).isFalse();\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/test/java/org/sonarsource/sonarlint/core/telemetry/gessie/GessieHttpClientTests.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.gessie;\n\nimport com.github.tomakehurst.wiremock.junit5.WireMockExtension;\nimport com.github.tomakehurst.wiremock.matching.EqualToPattern;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Objects;\nimport java.util.UUID;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.http.HttpClientProvider;\nimport org.sonarsource.sonarlint.core.telemetry.gessie.event.GessieEvent;\nimport org.sonarsource.sonarlint.core.telemetry.gessie.event.GessieMetadata;\nimport org.sonarsource.sonarlint.core.telemetry.gessie.event.payload.MessagePayload;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;\nimport static com.github.tomakehurst.wiremock.client.WireMock.post;\nimport static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static org.awaitility.Awaitility.await;\nimport static org.sonarsource.sonarlint.core.telemetry.gessie.event.GessieMetadata.GessieSource;\nimport static org.sonarsource.sonarlint.core.telemetry.gessie.event.GessieMetadata.SonarLintDomain;\n\nclass GessieHttpClientTests {\n\n  private static final String IDE_ENDPOINT = \"/ide\";\n\n  private GessieHttpClient tested;\n\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @RegisterExtension\n  static WireMockExtension mockGessie = WireMockExtension.newInstance()\n    .options(wireMockConfig().dynamicPort())\n    .build();\n\n  @BeforeEach\n  void setUp() {\n    tested = new GessieHttpClient(HttpClientProvider.forTesting(), mockGessie.baseUrl(), \"value\");\n  }\n\n  @Test\n  void should_upload_accepted_payload() throws URISyntaxException, IOException {\n    mockGessie.stubFor(post(IDE_ENDPOINT)\n      .willReturn(aResponse().withStatus(202)));\n\n    tested.postEvent(getPayload());\n\n    var fileContent = getTestJson(\"GessieRequest\");\n    await().untilAsserted(() -> mockGessie.verify(postRequestedFor(urlEqualTo(IDE_ENDPOINT))\n        .withHeader(\"x-api-key\", new EqualToPattern(\"value\"))\n      .withRequestBody(equalToJson(fileContent))));\n  }\n\n  @Test\n  void should_handle_400_error_gracefully() throws URISyntaxException, IOException {\n    mockGessie.stubFor(post(IDE_ENDPOINT)\n      .willReturn(aResponse().withStatus(400)));\n\n    tested.postEvent(new GessieEvent(null, null));\n\n    var invalidRequest = getTestJson(\"InvalidRequest\");\n    await().untilAsserted(() -> mockGessie.verify(postRequestedFor(urlEqualTo(IDE_ENDPOINT))\n      .withHeader(\"x-api-key\", new EqualToPattern(\"value\"))\n      .withRequestBody(equalToJson(invalidRequest))));\n  }\n\n  @Test\n  void should_handle_403_error_gracefully() throws URISyntaxException, IOException {\n    mockGessie.stubFor(post(IDE_ENDPOINT)\n      .willReturn(aResponse().withStatus(403)));\n\n    tested.postEvent(getPayload());\n\n    var fileContent = getTestJson(\"GessieRequest\");\n    await().untilAsserted(() -> mockGessie.verify(postRequestedFor(urlEqualTo(IDE_ENDPOINT))\n      .withHeader(\"x-api-key\", new EqualToPattern(\"value\"))\n      .withRequestBody(equalToJson(fileContent))));\n  }\n\n  private String getTestJson(String fileName) throws URISyntaxException, IOException {\n    var resource = Objects.requireNonNull(getClass().getResource(\"/response/gessie/GessieHttpClientTest/\" + fileName + \".json\"))\n      .toURI();\n    return Files.readString(Path.of(resource));\n  }\n\n  private static GessieEvent getPayload() {\n    return new GessieEvent(\n      new GessieMetadata(UUID.fromString(\"a36e25e8-5a92-4b5d-93b4-ba0045947b4c\"),\n        new GessieSource(SonarLintDomain.INTELLIJ),\n        \"Analytics.Test.TestEvent\",\n        \"1761821877867\",\n        \"0\"),\n      new MessagePayload(\"Test event\", \"test\")\n    );\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/test/java/org/sonarsource/sonarlint/core/telemetry/gessie/event/GessieMetadataTests.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.gessie.event;\n\nimport java.util.stream.Stream;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThat;\nimport static org.sonarsource.sonarlint.core.telemetry.gessie.event.GessieMetadata.SonarLintDomain;\n\nclass GessieMetadataTests {\n\n  @ParameterizedTest\n  @MethodSource\n  void should_map_product_key_to_domain(String productKey, SonarLintDomain expected) {\n    var actual = SonarLintDomain.fromProductKey(productKey);\n\n    assertThat(actual).isEqualTo(expected);\n  }\n\n  public static Stream<Arguments> should_map_product_key_to_domain() {\n    return Stream.of(\n      Arguments.of(\"idea\", SonarLintDomain.INTELLIJ),\n      Arguments.of(\"eclipse\", SonarLintDomain.ECLIPSE),\n      Arguments.of(\"visualstudio\", SonarLintDomain.VISUAL_STUDIO),\n      Arguments.of(\"vscode\", SonarLintDomain.VS_CODE),\n      Arguments.of(\"cursor\", SonarLintDomain.VS_CODE),\n      Arguments.of(\"windsurf\", SonarLintDomain.VS_CODE),\n      Arguments.of(\"\", SonarLintDomain.SLCORE),\n      Arguments.of(\"test\", SonarLintDomain.SLCORE)\n    );\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/test/java/org/sonarsource/sonarlint/core/telemetry/payload/TelemetryMeasuresPayloadTests.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.payload;\n\nimport com.google.gson.Gson;\nimport java.time.OffsetDateTime;\nimport java.time.ZoneOffset;\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.McpTransportMode;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryConnectionAttributes;\nimport org.sonarsource.sonarlint.core.telemetry.measures.payload.TelemetryMeasuresDimension;\nimport org.sonarsource.sonarlint.core.telemetry.measures.payload.TelemetryMeasuresPayload;\nimport org.sonarsource.sonarlint.core.telemetry.measures.payload.TelemetryMeasuresValue;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.AssertionsForClassTypes.tuple;\nimport static org.sonarsource.sonarlint.core.telemetry.measures.payload.TelemetryMeasuresValueGranularity.DAILY;\nimport static org.sonarsource.sonarlint.core.telemetry.measures.payload.TelemetryMeasuresValueType.BOOLEAN;\nimport static org.sonarsource.sonarlint.core.telemetry.measures.payload.TelemetryMeasuresValueType.INTEGER;\nimport static org.sonarsource.sonarlint.core.telemetry.measures.payload.TelemetryMeasuresValueType.STRING;\n\nclass TelemetryMeasuresPayloadTests {\n\n  @Test\n  void testGenerationJson() {\n    var messageUuid = \"25318599-9aec-4e1d-a535-1bfa4f7fcf39\";\n    var installTime = OffsetDateTime.of(2017, 11, 10, 12, 1, 14, 984_123_123, ZoneOffset.ofHours(2));\n    var measures = generateMeasures();\n\n    var m = new TelemetryMeasuresPayload(\n      messageUuid,\n      \"Linux Ubuntu 24.04\",\n      installTime,\n      \"SonarQube for IDE\",\n      TelemetryMeasuresDimension.INSTALLATION,\n      measures\n    );\n\n    var s = m.toJson();\n\n    assertThat(s).isEqualTo(\"{\" +\n      \"\\\"message_uuid\\\":\\\"25318599-9aec-4e1d-a535-1bfa4f7fcf39\\\",\" +\n      \"\\\"os\\\":\\\"Linux Ubuntu 24.04\\\",\" +\n      \"\\\"install_time\\\":\\\"2017-11-10T12:01:14.984+02:00\\\",\" +\n      \"\\\"sonarlint_product\\\":\\\"SonarQube for IDE\\\",\" +\n      \"\\\"dimension\\\":\\\"installation\\\",\" +\n      \"\\\"metric_values\\\":[{\" +\n      \"\\\"key\\\":\\\"shared_connected_mode.manual\\\",\\\"value\\\":\\\"1\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"shared_connected_mode.imported\\\",\\\"value\\\":\\\"2\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"shared_connected_mode.auto\\\",\\\"value\\\":\\\"3\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"shared_connected_mode.exported\\\",\\\"value\\\":\\\"4\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"new_bindings.manual\\\",\\\"value\\\":\\\"1\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"new_bindings.accepted_suggestion_remote_url\\\",\\\"value\\\":\\\"2\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"new_bindings.accepted_suggestion_properties_file\\\",\\\"value\\\":\\\"3\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"new_bindings.accepted_suggestion_shared_config_file\\\",\\\"value\\\":\\\"4\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"new_bindings.accepted_suggestion_project_name\\\",\\\"value\\\":\\\"5\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"binding_suggestion_clue.remote_url\\\",\\\"value\\\":\\\"5\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"bindings.child_count\\\",\\\"value\\\":\\\"1\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"bindings.server_count\\\",\\\"value\\\":\\\"2\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"bindings.cloud_eu_count\\\",\\\"value\\\":\\\"0\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"bindings.cloud_us_count\\\",\\\"value\\\":\\\"0\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"connections.attributes\\\",\\\"value\\\":\\\"[{\\\\\\\"userId\\\\\\\":\\\\\\\"user-id\\\\\\\",\\\\\\\"organizationId\\\\\\\":\\\\\\\"org-id\\\\\\\"}]\\\",\\\"type\\\":\\\"string\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"help_and_feedback.doc_link\\\",\\\"value\\\":\\\"5\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"analysis_reporting.trigger_count_vcs_changed_files\\\",\\\"value\\\":\\\"7\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"performance.biggest_size_config_scope_files\\\",\\\"value\\\":\\\"12345\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"automatic_analysis.enabled\\\",\\\"value\\\":\\\"true\\\",\\\"type\\\":\\\"boolean\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"automatic_analysis.toggled_count\\\",\\\"value\\\":\\\"1\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"mcp.configuration_requested\\\",\\\"value\\\":\\\"3\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"mcp.rule_file_requested\\\",\\\"value\\\":\\\"4\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"mcp.integration_enabled\\\",\\\"value\\\":\\\"true\\\",\\\"type\\\":\\\"boolean\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"mcp.transport_mode\\\",\\\"value\\\":\\\"HTTP\\\",\\\"type\\\":\\\"string\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"ide_labs.joined\\\",\\\"value\\\":\\\"true\\\",\\\"type\\\":\\\"boolean\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"ide_labs.enabled\\\",\\\"value\\\":\\\"true\\\",\\\"type\\\":\\\"boolean\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"ide_labs.link_clicked_count_changed_file_analysis_doc\\\",\\\"value\\\":\\\"10\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"ide_labs.link_clicked_count_privacy_policy\\\",\\\"value\\\":\\\"20\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"ide_labs.feedback_link_clicked_count_connected_mode\\\",\\\"value\\\":\\\"1\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"ide_labs.feedback_link_clicked_count_manage_dependency_risk\\\",\\\"value\\\":\\\"2\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"ai_hooks.windsurf_installed\\\",\\\"value\\\":\\\"2\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"ai_hooks.cursor_installed\\\",\\\"value\\\":\\\"5\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"campaigns.feedback_2026_01_shown\\\",\\\"value\\\":\\\"1\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"campaigns.feedback_2026_01_resolution\\\",\\\"value\\\":\\\"MAYBE_LATER\\\",\\\"type\\\":\\\"string\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"campaigns.feedback_2077_03_shown\\\",\\\"value\\\":\\\"1\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"campaigns.feedback_2077_03_resolution\\\",\\\"value\\\":\\\"CLOSED\\\",\\\"type\\\":\\\"string\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"supported_languages_panel.opened_count\\\",\\\"value\\\":\\\"3\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"},\" +\n      \"{\\\"key\\\":\\\"supported_languages_panel.cta_clicked_count\\\",\\\"value\\\":\\\"7\\\",\\\"type\\\":\\\"integer\\\",\\\"granularity\\\":\\\"daily\\\"}\" +\n      \"]}\");\n\n    assertThat(m.messageUuid()).isEqualTo(messageUuid);\n    assertThat(m.os()).isEqualTo(\"Linux Ubuntu 24.04\");\n    assertThat(m.installTime()).isEqualTo(installTime);\n    assertThat(m.product()).isEqualTo(\"SonarQube for IDE\");\n    assertThat(m.dimension()).isEqualTo(TelemetryMeasuresDimension.INSTALLATION);\n    assertValues(m.values());\n  }\n\n  private List<TelemetryMeasuresValue> generateMeasures() {\n    var values = new ArrayList<TelemetryMeasuresValue>();\n\n    values.add(new TelemetryMeasuresValue(\"shared_connected_mode.manual\", String.valueOf(1), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"shared_connected_mode.imported\", String.valueOf(2), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"shared_connected_mode.auto\", String.valueOf(3), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"shared_connected_mode.exported\", String.valueOf(4), INTEGER, DAILY));\n\n    values.add(new TelemetryMeasuresValue(\"new_bindings.manual\", String.valueOf(1), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"new_bindings.accepted_suggestion_remote_url\", String.valueOf(2), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"new_bindings.accepted_suggestion_properties_file\", String.valueOf(3), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"new_bindings.accepted_suggestion_shared_config_file\", String.valueOf(4), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"new_bindings.accepted_suggestion_project_name\", String.valueOf(5), INTEGER, DAILY));\n\n    values.add(new TelemetryMeasuresValue(\"binding_suggestion_clue.remote_url\", String.valueOf(5), INTEGER, DAILY));\n\n    values.add(new TelemetryMeasuresValue(\"bindings.child_count\", String.valueOf(1), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"bindings.server_count\", String.valueOf(2), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"bindings.cloud_eu_count\", String.valueOf(0), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"bindings.cloud_us_count\", String.valueOf(0), INTEGER, DAILY));\n\n    values.add(new TelemetryMeasuresValue(\"connections.attributes\", new Gson().toJson(List.of(new TelemetryConnectionAttributes(\"user-id\", null, \"org-id\"))), STRING, DAILY));\n\n    values.add(new TelemetryMeasuresValue(\"help_and_feedback.doc_link\", String.valueOf(5), INTEGER, DAILY));\n\n    values.add(new TelemetryMeasuresValue(\"analysis_reporting.trigger_count_vcs_changed_files\", String.valueOf(7), INTEGER, DAILY));\n\n    values.add(new TelemetryMeasuresValue(\"performance.biggest_size_config_scope_files\", String.valueOf(12345), INTEGER, DAILY));\n\n    values.add(new TelemetryMeasuresValue(\"automatic_analysis.enabled\", String.valueOf(true), BOOLEAN, DAILY));\n    values.add(new TelemetryMeasuresValue(\"automatic_analysis.toggled_count\", String.valueOf(1), INTEGER, DAILY));\n\n    values.add(new TelemetryMeasuresValue(\"mcp.configuration_requested\", String.valueOf(3), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"mcp.rule_file_requested\", String.valueOf(4), INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"mcp.integration_enabled\", String.valueOf(true), BOOLEAN, DAILY));\n    values.add(new TelemetryMeasuresValue(\"mcp.transport_mode\", McpTransportMode.HTTP.name(), STRING, DAILY));\n\n    values.add(new TelemetryMeasuresValue(\"ide_labs.joined\", \"true\", BOOLEAN, DAILY));\n    values.add(new TelemetryMeasuresValue(\"ide_labs.enabled\", \"true\", BOOLEAN, DAILY));\n    values.add(new TelemetryMeasuresValue(\"ide_labs.link_clicked_count_changed_file_analysis_doc\", \"10\", INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"ide_labs.link_clicked_count_privacy_policy\", \"20\", INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"ide_labs.feedback_link_clicked_count_connected_mode\", \"1\", INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"ide_labs.feedback_link_clicked_count_manage_dependency_risk\", \"2\", INTEGER, DAILY));\n\n    values.add(new TelemetryMeasuresValue(\"ai_hooks.windsurf_installed\", \"2\", INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"ai_hooks.cursor_installed\", \"5\", INTEGER, DAILY));\n\n    values.add(new TelemetryMeasuresValue(\"campaigns.feedback_2026_01_shown\", \"1\", INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"campaigns.feedback_2026_01_resolution\", \"MAYBE_LATER\", STRING, DAILY));\n    values.add(new TelemetryMeasuresValue(\"campaigns.feedback_2077_03_shown\", \"1\", INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"campaigns.feedback_2077_03_resolution\", \"CLOSED\", STRING, DAILY));\n\n    values.add(new TelemetryMeasuresValue(\"supported_languages_panel.opened_count\", \"3\", INTEGER, DAILY));\n    values.add(new TelemetryMeasuresValue(\"supported_languages_panel.cta_clicked_count\", \"7\", INTEGER, DAILY));\n\n    return values;\n  }\n\n  private static void assertValues(List<TelemetryMeasuresValue> values) {\n    assertThat(values).extracting(\"key\", \"value\", \"type\", \"granularity\")\n      .contains(tuple(\"shared_connected_mode.manual\", \"1\", INTEGER, DAILY))\n      .contains(tuple(\"shared_connected_mode.imported\", \"2\", INTEGER, DAILY))\n      .contains(tuple(\"shared_connected_mode.auto\", \"3\", INTEGER, DAILY))\n      .contains(tuple(\"shared_connected_mode.exported\", \"4\", INTEGER, DAILY))\n      .contains(tuple(\"new_bindings.manual\", \"1\", INTEGER, DAILY))\n      .contains(tuple(\"new_bindings.accepted_suggestion_remote_url\", \"2\", INTEGER, DAILY))\n      .contains(tuple(\"new_bindings.accepted_suggestion_properties_file\", \"3\", INTEGER, DAILY))\n      .contains(tuple(\"new_bindings.accepted_suggestion_shared_config_file\", \"4\", INTEGER, DAILY))\n      .contains(tuple(\"new_bindings.accepted_suggestion_project_name\", \"5\", INTEGER, DAILY))\n      .contains(tuple(\"binding_suggestion_clue.remote_url\", \"5\", INTEGER, DAILY))\n      .contains(tuple(\"connections.attributes\", \"[{\\\"userId\\\":\\\"user-id\\\",\\\"organizationId\\\":\\\"org-id\\\"}]\", STRING, DAILY))\n      .contains(tuple(\"help_and_feedback.doc_link\", \"5\", INTEGER, DAILY))\n      .contains(tuple(\"analysis_reporting.trigger_count_vcs_changed_files\", \"7\", INTEGER, DAILY))\n      .contains(tuple(\"automatic_analysis.enabled\", \"true\", BOOLEAN, DAILY))\n      .contains(tuple(\"automatic_analysis.toggled_count\", \"1\", INTEGER, DAILY))\n      .contains(tuple(\"mcp.configuration_requested\", \"3\", INTEGER, DAILY))\n      .contains(tuple(\"mcp.rule_file_requested\", \"4\", INTEGER, DAILY))\n      .contains(tuple(\"mcp.integration_enabled\", \"true\", BOOLEAN, DAILY))\n      .contains(tuple(\"mcp.transport_mode\", \"HTTP\", STRING, DAILY))\n      .contains(tuple(\"ide_labs.joined\", \"true\", BOOLEAN, DAILY))\n      .contains(tuple(\"ide_labs.enabled\", \"true\", BOOLEAN, DAILY))\n      .contains(tuple(\"ide_labs.link_clicked_count_changed_file_analysis_doc\", \"10\", INTEGER, DAILY))\n      .contains(tuple(\"ide_labs.link_clicked_count_privacy_policy\", \"20\", INTEGER, DAILY))\n      .contains(tuple(\"ide_labs.feedback_link_clicked_count_connected_mode\", \"1\", INTEGER, DAILY))\n      .contains(tuple(\"ide_labs.feedback_link_clicked_count_manage_dependency_risk\", \"2\", INTEGER, DAILY))\n      .contains(tuple(\"ai_hooks.windsurf_installed\", \"2\", INTEGER, DAILY))\n      .contains(tuple(\"ai_hooks.cursor_installed\", \"5\", INTEGER, DAILY))\n      .contains(tuple(\"campaigns.feedback_2026_01_shown\", \"1\", INTEGER, DAILY))\n      .contains(tuple(\"campaigns.feedback_2026_01_resolution\", \"MAYBE_LATER\", STRING, DAILY))\n      .contains(tuple(\"campaigns.feedback_2077_03_shown\", \"1\", INTEGER, DAILY))\n      .contains(tuple(\"campaigns.feedback_2077_03_resolution\", \"CLOSED\", STRING, DAILY))\n      .contains(tuple(\"supported_languages_panel.opened_count\", \"3\", INTEGER, DAILY))\n      .contains(tuple(\"supported_languages_panel.cta_clicked_count\", \"7\", INTEGER, DAILY));\n  }\n\n}\n"
  },
  {
    "path": "backend/telemetry/src/test/java/org/sonarsource/sonarlint/core/telemetry/payload/TelemetryPayloadTests.java",
    "content": "/*\n * SonarLint Core - Telemetry\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.telemetry.payload;\n\nimport com.google.gson.Gson;\nimport com.google.gson.reflect.TypeToken;\nimport java.math.BigDecimal;\nimport java.time.OffsetDateTime;\nimport java.time.ZoneOffset;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AiSuggestionSource;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.FixSuggestionStatus;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryHelpAndFeedbackCounter;\nimport org.sonarsource.sonarlint.core.telemetry.payload.cayc.CleanAsYouCodePayload;\nimport org.sonarsource.sonarlint.core.telemetry.payload.cayc.NewCodeFocusPayload;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass TelemetryPayloadTests {\n\n  @Test\n  void testGenerationJson() {\n    var installTime = OffsetDateTime.of(2017, 11, 10, 12, 1, 14, 984_123_123, ZoneOffset.ofHours(2));\n    var systemTime = installTime.plusMinutes(1);\n    var perf = new TelemetryAnalyzerPerformancePayload[1];\n    Map<String, BigDecimal> distrib = new LinkedHashMap<>();\n    distrib.put(\"0-300\", BigDecimal.valueOf(9.90));\n    distrib.put(\"1000-2000\", BigDecimal.valueOf(90.10));\n    perf[0] = new TelemetryAnalyzerPerformancePayload(\"java\", distrib);\n    Map<String, TelemetryNotificationsCounterPayload> counters = new HashMap<>();\n    counters.put(\"QUALITY_GATE\", new TelemetryNotificationsCounterPayload(5, 3));\n    counters.put(\"NEW_ISSUES\", new TelemetryNotificationsCounterPayload(10, 1));\n    var notifPayload = new TelemetryNotificationsPayload(true, counters);\n    var showHotspotPayload = new ShowHotspotPayload(4);\n    var showIssuePayload = new ShowIssuePayload(3);\n    var hotspotPayload = new HotspotPayload(5, 3);\n    var taintVulnerabilitiesPayload = new TaintVulnerabilitiesPayload(6, 7);\n    var issuePayload = new IssuePayload(Set.of(\"java:S123\"), 1);\n    var rulesPayload = new TelemetryRulesPayload(Arrays.asList(\"enabledRuleKey1\", \"enabledRuleKey2\"), Arrays.asList(\"disabledRuleKey1\", \"disabledRuleKey2\"),\n      Arrays.asList(\"reportedRuleKey1\", \"reportedRuleKey2\"), Arrays.asList(\"quickFixedRuleKey1\", \"quickFixedRuleKey2\"));\n    Map<String, TelemetryHelpAndFeedbackCounter> helpAndFeedbackCounter = new HashMap<>();\n    helpAndFeedbackCounter.put(\"faq\", new TelemetryHelpAndFeedbackCounter(4));\n    helpAndFeedbackCounter.put(\"docs\", new TelemetryHelpAndFeedbackCounter(5));\n    var helpAndFeedbackPayload = new TelemetryHelpAndFeedbackPayload(helpAndFeedbackCounter);\n    var aiFixSuggestionsPayload = getTelemetryFixSuggestionPayloads();\n    Map<String, Object> additionalProps = new LinkedHashMap<>();\n    additionalProps.put(\"aString\", \"stringValue\");\n    additionalProps.put(\"aBool\", false);\n    additionalProps.put(\"aNumber\", 1.5);\n    var sharedConnectedModePayload = new ShareConnectedModePayload(3, 2, 1, 4);\n    Map<String, Object> additionalPropsSub = new LinkedHashMap<>();\n    additionalPropsSub.put(\"aSubNumber\", 2);\n    additionalProps.put(\"sub\", additionalPropsSub);\n    var cleanAsYouCodePayload = new CleanAsYouCodePayload(new NewCodeFocusPayload(true, 2));\n    var m = new TelemetryPayload(4, 15, \"SLI\", \"2.4\", \"Pycharm 3.2\", \"platform\", \"architecture\",\n      true, true, systemTime, installTime, \"Windows 10\", \"1.8.0\", \"10.5.2\", perf,\n      notifPayload, showHotspotPayload, showIssuePayload, taintVulnerabilitiesPayload, rulesPayload, hotspotPayload, issuePayload, helpAndFeedbackPayload,\n      aiFixSuggestionsPayload, 1, cleanAsYouCodePayload, sharedConnectedModePayload, additionalProps);\n    var s = m.toJson();\n\n    assertThat(s).isEqualTo(\"{\\\"days_since_installation\\\":4,\"\n      + \"\\\"days_of_use\\\":15,\"\n      + \"\\\"sonarlint_version\\\":\\\"2.4\\\",\"\n      + \"\\\"sonarlint_product\\\":\\\"SLI\\\",\"\n      + \"\\\"ide_version\\\":\\\"Pycharm 3.2\\\",\"\n      + \"\\\"platform\\\":\\\"platform\\\",\"\n      + \"\\\"architecture\\\":\\\"architecture\\\",\"\n      + \"\\\"connected_mode_used\\\":true,\"\n      + \"\\\"connected_mode_sonarcloud\\\":true,\"\n      + \"\\\"system_time\\\":\\\"2017-11-10T12:02:14.984+02:00\\\",\"\n      + \"\\\"install_time\\\":\\\"2017-11-10T12:01:14.984+02:00\\\",\"\n      + \"\\\"os\\\":\\\"Windows 10\\\",\"\n      + \"\\\"jre\\\":\\\"1.8.0\\\",\"\n      + \"\\\"nodejs\\\":\\\"10.5.2\\\",\"\n      + \"\\\"analyses\\\":[{\\\"language\\\":\\\"java\\\",\\\"rate_per_duration\\\":{\\\"0-300\\\":9.9,\\\"1000-2000\\\":90.1}}],\"\n      + \"\\\"server_notifications\\\":{\\\"disabled\\\":true,\\\"count_by_type\\\":{\\\"NEW_ISSUES\\\":{\\\"received\\\":10,\\\"clicked\\\":1},\\\"QUALITY_GATE\\\":{\\\"received\\\":5,\\\"clicked\\\":3}}},\"\n      + \"\\\"show_hotspot\\\":{\\\"requests_count\\\":4},\"\n      + \"\\\"show_issue\\\":{\\\"requests_count\\\":3},\"\n      + \"\\\"taint_vulnerabilities\\\":{\\\"investigated_locally_count\\\":6,\\\"investigated_remotely_count\\\":7},\"\n      + \"\\\"rules\\\":{\\\"non_default_enabled\\\":[\\\"enabledRuleKey1\\\",\\\"enabledRuleKey2\\\"],\\\"default_disabled\\\":[\\\"disabledRuleKey1\\\",\\\"disabledRuleKey2\\\"],\\\"raised_issues\\\":[\\\"reportedRuleKey1\\\",\\\"reportedRuleKey2\\\"],\\\"quick_fix_applied\\\":[\\\"quickFixedRuleKey1\\\",\\\"quickFixedRuleKey2\\\"]},\"\n      + \"\\\"hotspot\\\":{\\\"open_in_browser_count\\\":5,\\\"status_changed_count\\\":3},\"\n      + \"\\\"issue\\\":{\\\"status_changed_rule_keys\\\":[\\\"java:S123\\\"],\\\"status_changed_count\\\":1},\"\n      + \"\\\"help_and_feedback\\\":{\\\"count_by_link\\\":{\\\"docs\\\":5,\\\"faq\\\":4}},\"\n      + \"\\\"ai_fix_suggestions\\\":[{\\\"suggestion_id\\\":\\\"suggestionId1\\\",\\\"count_snippets\\\":1,\\\"ai_fix_suggestion_provider\\\":\\\"SONARCLOUD\\\",\\\"snippets\\\":[{\\\"status\\\":\\\"ACCEPTED\\\",\\\"snippet_index\\\":0},{\\\"status\\\":\\\"DECLINED\\\",\\\"snippet_index\\\":1}],\\\"was_ai_fix_suggestion_generated_from_ide\\\":true},{\\\"suggestion_id\\\":\\\"suggestionId2\\\",\\\"count_snippets\\\":2,\\\"ai_fix_suggestion_provider\\\":\\\"SONARCLOUD\\\",\\\"snippets\\\":[{\\\"status\\\":\\\"ACCEPTED\\\",\\\"snippet_index\\\":null}],\\\"was_ai_fix_suggestion_generated_from_ide\\\":true},{\\\"suggestion_id\\\":\\\"suggestionId3\\\",\\\"count_snippets\\\":3,\\\"ai_fix_suggestion_provider\\\":\\\"SONARCLOUD\\\",\\\"snippets\\\":[{\\\"status\\\":null,\\\"snippet_index\\\":null}],\\\"was_ai_fix_suggestion_generated_from_ide\\\":false}],\"\n      + \"\\\"count_issues_with_possible_ai_fix_from_ide\\\":1,\"\n      + \"\\\"cayc\\\":{\\\"new_code_focus\\\":{\\\"enabled\\\":true,\\\"changes\\\":2}},\"\n      + \"\\\"shared_connected_mode\\\":{\\\"manual_bindings_count\\\":3,\\\"imported_bindings_count\\\":2,\\\"auto_bindings_count\\\":1,\\\"exported_connected_mode_count\\\":4},\"\n      + \"\\\"aString\\\":\\\"stringValue\\\",\"\n      + \"\\\"aBool\\\":false,\"\n      + \"\\\"aNumber\\\":1.5,\"\n      + \"\\\"sub\\\":{\\\"aSubNumber\\\":2}}\");\n\n    assertThat(m.connectedMode()).isTrue();\n    assertThat(m.analyses()).hasSize(1);\n    assertThat(m.connectedModeSonarcloud()).isTrue();\n    assertThat(m.systemTime()).isEqualTo(systemTime);\n    assertThat(m.notifications().disabled()).isTrue();\n    assertThat(m.notifications().counters()).containsOnlyKeys(\"QUALITY_GATE\", \"NEW_ISSUES\");\n    assertThat(m.helpAndFeedbackPayload().getCounters()).containsOnlyKeys(\"docs\", \"faq\");\n    assertThat(m.getAiFixSuggestionsPayload()).hasSize(3);\n    assertThat(m.getCountIssuesWithPossibleAiFixFromIde()).isEqualTo(1);\n    assertThat(m.cleanAsYouCodePayload().newCodeFocusPayload())\n      .extracting(NewCodeFocusPayload::enabled, NewCodeFocusPayload::changes)\n      .containsExactly(true, 2);\n    assertThat(m.issuePayload().statusChangedRuleKeys()).isEqualTo(Set.of(\"java:S123\"));\n    assertThat(m.issuePayload().statusChangedCount()).isEqualTo(1);\n    assertThat(m.additionalAttributes()).containsExactlyEntriesOf(additionalProps);\n    assertThat(m.getShowHotspotPayload().requestsCount()).isEqualTo(4);\n    assertThat(m.getShowIssuePayload().requestsCount()).isEqualTo(3);\n    assertThat(m.getHotspotPayload().openInBrowserCount()).isEqualTo(5);\n    assertThat(m.getHotspotPayload().statusChangedCount()).isEqualTo(3);\n    assertThat(m.getTaintVulnerabilitiesPayload().investigatedLocallyCount()).isEqualTo(6);\n    assertThat(m.getTaintVulnerabilitiesPayload().investigatedRemotelyCount()).isEqualTo(7);\n    assertRulesPayload(m);\n    assertShareConnectedModePayload(m);\n    assertMetadata(m);\n  }\n\n  private static void assertMetadata(TelemetryPayload m) {\n    assertThat(m.getIdeVersion()).isEqualTo(\"Pycharm 3.2\");\n    assertThat(m.getPlatform()).isEqualTo(\"platform\");\n    assertThat(m.getArchitecture()).isEqualTo(\"architecture\");\n    assertThat(m.getInstallTime()).hasToString(\"2017-11-10T12:01:14.984123123+02:00\");\n    assertThat(m.os()).isEqualTo(\"Windows 10\");\n    assertThat(m.jre()).isEqualTo(\"1.8.0\");\n    assertThat(m.nodejs()).isEqualTo(\"10.5.2\");\n    assertThat(m.daysOfUse()).isEqualTo(15);\n    assertThat(m.daysSinceInstallation()).isEqualTo(4);\n    assertThat(m.product()).isEqualTo(\"SLI\");\n    assertThat(m.version()).isEqualTo(\"2.4\");\n  }\n\n  private static void assertShareConnectedModePayload(TelemetryPayload m) {\n    assertThat(m.getShareConnectedModePayload().manualAddedBindingsCount()).isEqualTo(3);\n    assertThat(m.getShareConnectedModePayload().importedAddedBindingsCount()).isEqualTo(2);\n    assertThat(m.getShareConnectedModePayload().autoAddedBindingsCount()).isEqualTo(1);\n    assertThat(m.getShareConnectedModePayload().exportedConnectedModeCount()).isEqualTo(4);\n  }\n\n  private static void assertRulesPayload(TelemetryPayload m) {\n    assertThat(m.getTelemetryRulesPayload().defaultDisabled()).containsExactly(\"disabledRuleKey1\", \"disabledRuleKey2\");\n    assertThat(m.getTelemetryRulesPayload().nonDefaultEnabled()).containsExactly(\"enabledRuleKey1\", \"enabledRuleKey2\");\n    assertThat(m.getTelemetryRulesPayload().raisedIssues()).containsExactly(\"reportedRuleKey1\", \"reportedRuleKey2\");\n    assertThat(m.getTelemetryRulesPayload().quickFixesApplied()).containsExactly(\"quickFixedRuleKey1\", \"quickFixedRuleKey2\");\n  }\n\n  private static TelemetryFixSuggestionPayload[] getTelemetryFixSuggestionPayloads() {\n    var fixSuggestionPayload1 = new TelemetryFixSuggestionPayload(\"suggestionId1\", 1,\n      AiSuggestionSource.SONARCLOUD,\n      List.of(new TelemetryFixSuggestionResolvedPayload(FixSuggestionStatus.ACCEPTED, 0),\n        new TelemetryFixSuggestionResolvedPayload(FixSuggestionStatus.DECLINED, 1)),\n      true);\n    var fixSuggestionPayload2 = new TelemetryFixSuggestionPayload(\"suggestionId2\", 2,\n      AiSuggestionSource.SONARCLOUD,\n      List.of(new TelemetryFixSuggestionResolvedPayload(FixSuggestionStatus.ACCEPTED, null)),\n      true);\n    var fixSuggestionPayload3 = new TelemetryFixSuggestionPayload(\"suggestionId3\", 3,\n      AiSuggestionSource.SONARCLOUD,\n      List.of(new TelemetryFixSuggestionResolvedPayload(null, null)),\n      false);\n    return new TelemetryFixSuggestionPayload[]{fixSuggestionPayload1, fixSuggestionPayload2, fixSuggestionPayload3};\n  }\n\n  @Test\n  void testMergeEmptyJson() {\n    Map<String, Object> source = new LinkedHashMap<>();\n    Map<String, Object> target = new LinkedHashMap<>();\n    var gson = new Gson();\n    var type = new TypeToken<Map<String, Object>>() {\n    }.getType();\n    var jsonSource = gson.toJsonTree(source, type).getAsJsonObject();\n    var jsonTarget = gson.toJsonTree(target, type).getAsJsonObject();\n\n    var merged = gson.toJson(TelemetryPayload.mergeObjects(jsonSource, jsonTarget));\n    assertThat(merged).isEqualTo(\"{}\");\n  }\n\n  @Test\n  void testMergeEmptyTargetJson() {\n    Map<String, Object> source = new LinkedHashMap<>();\n    source.put(\"keyInSource\", \"valueInSource\");\n    Map<String, Object> target = new LinkedHashMap<>();\n    var gson = new Gson();\n    var type = new TypeToken<Map<String, Object>>() {\n    }.getType();\n    var jsonSource = gson.toJsonTree(source, type).getAsJsonObject();\n    var jsonTarget = gson.toJsonTree(target, type).getAsJsonObject();\n\n    var merged = gson.toJson(TelemetryPayload.mergeObjects(jsonSource, jsonTarget));\n    assertThat(merged).isEqualTo(\"{\\\"keyInSource\\\":\\\"valueInSource\\\"}\");\n  }\n\n  @Test\n  void testMergeEmptySourceJson() {\n    Map<String, Object> source = new LinkedHashMap<>();\n    Map<String, Object> target = new LinkedHashMap<>();\n    target.put(\"keyInTarget\", \"valueInTarget\");\n    var gson = new Gson();\n    var type = new TypeToken<Map<String, Object>>() {\n    }.getType();\n    var jsonSource = gson.toJsonTree(source, type).getAsJsonObject();\n    var jsonTarget = gson.toJsonTree(target, type).getAsJsonObject();\n\n    var merged = gson.toJson(TelemetryPayload.mergeObjects(jsonSource, jsonTarget));\n    assertThat(merged).isEqualTo(\"{\\\"keyInTarget\\\":\\\"valueInTarget\\\"}\");\n  }\n\n  @Test\n  void testMergeJson() {\n    Map<String, Object> source = new LinkedHashMap<>();\n    source.put(\"keyInSource\", \"valueInSource\");\n    Map<String, Object> target = new LinkedHashMap<>();\n    target.put(\"keyInTarget\", \"valueInTarget\");\n    var gson = new Gson();\n    var type = new TypeToken<Map<String, Object>>() {\n    }.getType();\n    var jsonSource = gson.toJsonTree(source, type).getAsJsonObject();\n    var jsonTarget = gson.toJsonTree(target, type).getAsJsonObject();\n\n    var merged = gson.toJson(TelemetryPayload.mergeObjects(jsonSource, jsonTarget));\n    assertThat(merged).isEqualTo(\"{\\\"keyInTarget\\\":\\\"valueInTarget\\\",\\\"keyInSource\\\":\\\"valueInSource\\\"}\");\n  }\n\n  @Test\n  void testDeepMergeJson() {\n    Map<String, Object> source = new LinkedHashMap<>();\n    Map<String, Object> sourceSub = new LinkedHashMap<>();\n    source.put(\"key\", sourceSub);\n    sourceSub.put(\"sub2\", \"sub2Value\");\n    Map<String, Object> target = new LinkedHashMap<>();\n    Map<String, Object> targetSub = new LinkedHashMap<>();\n    target.put(\"key\", targetSub);\n    targetSub.put(\"sub1\", \"sub1Value\");\n    var gson = new Gson();\n    var type = new TypeToken<Map<String, Object>>() {\n    }.getType();\n    var jsonSource = gson.toJsonTree(source, type).getAsJsonObject();\n    var jsonTarget = gson.toJsonTree(target, type).getAsJsonObject();\n\n    var merged = gson.toJson(TelemetryPayload.mergeObjects(jsonSource, jsonTarget));\n    assertThat(merged).isEqualTo(\"{\\\"key\\\":{\\\"sub1\\\":\\\"sub1Value\\\",\\\"sub2\\\":\\\"sub2Value\\\"}}\");\n  }\n\n  @Test\n  void testMergeJsonDontOverrideExistingKey() {\n    Map<String, Object> source = new LinkedHashMap<>();\n    source.put(\"key\", \"valueInSource\");\n    Map<String, Object> target = new LinkedHashMap<>();\n    target.put(\"key\", \"valueInTarget\");\n    var gson = new Gson();\n    var type = new TypeToken<Map<String, Object>>() {\n    }.getType();\n    var jsonSource = gson.toJsonTree(source, type).getAsJsonObject();\n    var jsonTarget = gson.toJsonTree(target, type).getAsJsonObject();\n\n    var merged = gson.toJson(TelemetryPayload.mergeObjects(jsonSource, jsonTarget));\n    assertThat(merged).isEqualTo(\"{\\\"key\\\":\\\"valueInTarget\\\"}\");\n  }\n\n}\n"
  },
  {
    "path": "backend/telemetry/src/test/resources/response/gessie/GessieHttpClientTest/GessieRequest.json",
    "content": "{\n  \"metadata\": {\n    \"event_id\": \"a36e25e8-5a92-4b5d-93b4-ba0045947b4c\",\n    \"source\": {\n      \"domain\": \"IntelliJ\"\n    },\n    \"event_type\": \"Analytics.Test.TestEvent\",\n    \"event_timestamp\": \"1761821877867\",\n    \"event_version\": \"0\"\n  },\n  \"event_payload\": {\n    \"message\": \"Test event\",\n    \"trigger\": \"test\"\n  }\n}\n"
  },
  {
    "path": "backend/telemetry/src/test/resources/response/gessie/GessieHttpClientTest/InvalidRequest.json",
    "content": "{\n  \"metadata\": null,\n  \"event_payload\": null\n}\n"
  },
  {
    "path": "buildSrc/README.md",
    "content": "# SonarLint Core: Build dependencies & logic\n\nThis directory contains libraries, tools or scripts that are used by the build, e.g. as\ndependencies. These are not meant to be published but will be used / build for every build.\n\n## Maven Shade Plugin: Transformer for Bndtools\n\nThis is an extension to the [Maven Shade plug-in](https://maven.apache.org/plugins/maven-shade-plugin/)\nin order to work correctly on *MANIFEST.MF* files when used alongside the\n[Bndtools](https://github.com/bndtools/bnd/tree/master/maven-plugins/bnd-maven-plugin) on the same\nmodule. It provides a custom\n[Resource Transformer](https://maven.apache.org/plugins/maven-shade-plugin/examples/resource-transformers.html).\n\nThe transformer `org.sonarsource.sonarlint.maven.shade.ext.ManifestBndTransformer` is used in order\nto exchange the *MANIFEST.MF* file on normal and sources JAR archives if the configuration offers a\nreplacement. In the case of `sonarlint-java-client-osgi` this is used as the Bndtools are\ngenerating the *MANIFEST.MF* files and the OSGi content while the Maven Shade plug-in is used to\npack the necessary dependencies or relocate others.\n\nTo the console it logs all the dependencies it processing when shading / relocating in order to\ndetermine if shading / relocating is done on a normal or sources JAR archive. The log output is:\n> [maven-shade-ext-bnd-transformer : ManifestBndTransformer] Processing {Bundle-Name} / {Bundle-SymbolicName}\n\nper archive but both `Bundle-Name` and `Bundle-SymbolicName` can be null! It also logs once the\nMaven Shade plug-in comes to a close which *MANFIEST.MF* file will be used and moved into the JAR\narchive.\n"
  },
  {
    "path": "buildSrc/maven-shade-ext-bnd-transformer/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-core-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../../pom.xml</relativePath>\n  </parent>\n  <artifactId>maven-shade-ext-bnd-transformer</artifactId>\n  <name>Maven Shade Plugin: Transformer for Bndtools</name>\n  <description>Custom transformer used in combination with Bndtools</description>\n\n  <dependencies>\n    <dependency>\n      <groupId>org.apache.maven.plugins</groupId>\n      <artifactId>maven-shade-plugin</artifactId>\n      <version>${version.shade.plugin}</version>\n    </dependency>\n  </dependencies>\n</project>\n"
  },
  {
    "path": "buildSrc/maven-shade-ext-bnd-transformer/src/main/java/org/sonarsource/sonarlint/maven/shade/ext/ManifestBndTransformer.java",
    "content": "/*\n * Maven Shade Plugin: Transformer for Bndtools\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.maven.shade.ext;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.List;\nimport java.util.jar.JarEntry;\nimport java.util.jar.JarFile;\nimport java.util.jar.JarOutputStream;\nimport java.util.jar.Manifest;\nimport org.apache.maven.plugins.shade.relocation.Relocator;\nimport org.apache.maven.plugins.shade.resource.ManifestResourceTransformer;\nimport org.apache.maven.plugins.shade.resource.ReproducibleResourceTransformer;\n\n/**\n *  A custom Resource Transformer for the Maven Shade plug-in used in combination with the Bndtools for building OSGi bundles that are\n *  consumed by the Eclipse IDE. The Maven plug-in and Bndtools are not optimized to work together but we use them in order to not require\n *  different modules (one for shading, one for OSGi bundle, one for OSGi compliant source bundle) just to complete one operation.\n *\n *  @author tobias.hahnen\n */\npublic class ManifestBndTransformer implements ReproducibleResourceTransformer {\n  private static final String MANIFEST_ATTRIBUTE_BUNDLE_NAME = \"Bundle-Name\";\n  private static final String MANIFEST_ATTRIBUTE_BUNDLE_SYMBOLICNAME = \"Bundle-SymbolicName\";\n\n  // Configuration of the transformer in the pom.xml\n  private String normalJarManifestPath;\n  private String sourcesJarManifestPath;\n\n  // Private fields used by the transformer\n  private boolean isSourcesJarManifest = false;\n  private Manifest normalJarManifest;\n  private Manifest sourcesJarManifest;\n  private Manifest manifest;\n  private long time = Long.MIN_VALUE;\n\n\n  /** This is used inside the pom.xml for configuring the transformer! */\n  public void setNormalJarManifestPath(String normalJarManifestPath) {\n    this.normalJarManifestPath = normalJarManifestPath;\n  }\n\n\n  /** This is used inside the pom.xml for configuring the transformer! */\n  public void setSourcesJarManifestPath(String sourcesJarManifestPath) {\n    this.sourcesJarManifestPath = sourcesJarManifestPath;\n  }\n\n\n  @Override\n  public void processResource(String resource, InputStream is, List<Relocator> relocators, long time) throws IOException {\n    // i) Load manifest object from the input stream and try to get the information if it\n    manifest = new Manifest(is);\n    var attributes = manifest.getMainAttributes();\n\n    var bundleName = attributes.getValue(MANIFEST_ATTRIBUTE_BUNDLE_NAME);\n    var bundleSymbolicName = attributes.getValue(MANIFEST_ATTRIBUTE_BUNDLE_SYMBOLICNAME);\n    info(\"Processing \" + bundleName + \" / \" + bundleSymbolicName);\n\n    isSourcesJarManifest = isSourcesJarManifest ||\n      (bundleName != null && (bundleName.endsWith(\" Source\") || bundleName.endsWith(\" Sources\")));\n    isSourcesJarManifest = isSourcesJarManifest\n      || (bundleSymbolicName != null && (bundleSymbolicName.endsWith(\".source\") || bundleSymbolicName.endsWith(\".sources\")));\n\n    // ii) Set the time that is later used when saving the JAR archive entry!\n    if (time > this.time) {\n      this.time = time;\n    }\n  }\n\n\n  @Override\n  public void modifyOutputStream(JarOutputStream jos) throws IOException {\n    // i) the first time this is called we load the normal / sources JAR manifest file\n    tryLoadNormalJarManifest();\n    tryLoadSourcesJarManifest();\n\n    // ii) Setup the manifest object that should be written in the end\n    if (isSourcesJarManifest && sourcesJarManifest != null) {\n      manifest = sourcesJarManifest;\n      info(\"Exchanging META-INF/MANIFEST.MF (sources) with: \" + sourcesJarManifestPath);\n    } else if (!isSourcesJarManifest && normalJarManifest != null) {\n      manifest = normalJarManifest;\n      info(\"Exchanging META-INF/MANIFEST.MF (normal) with: \" + normalJarManifestPath);\n    } else {\n      manifest = manifest != null ? manifest : new Manifest();\n      info(\"Not exchanging META-INF/MANIFEST.MF\");\n    }\n\n    // iii) Propose output stream content for the META-INF/MANIFEST.MF\n    var jarEntry = new JarEntry(JarFile.MANIFEST_NAME);\n    jarEntry.setTime(time);\n    jos.putNextEntry(jarEntry);\n    manifest.write(jos);\n  }\n\n\n  /** Try to load the Manifest with attributes (normal) if not already loaded */\n  private void tryLoadNormalJarManifest() throws IOException {\n    if (normalJarManifestPath != null && normalJarManifest == null) {\n      var manifestFile = new File(normalJarManifestPath);\n      if (manifestFile.exists()) {\n        normalJarManifest = new Manifest(new FileInputStream(manifestFile));\n      } else {\n        error(\"Manifest with attributes (normal) does not exist at: \" + normalJarManifestPath);\n      }\n    }\n  }\n\n\n  /** Try to load the Manifest with attributes (sources) if not already loaded */\n  private void tryLoadSourcesJarManifest() throws IOException {\n    if (sourcesJarManifestPath != null && sourcesJarManifest == null) {\n      var manifestFile = new File(sourcesJarManifestPath);\n      if (manifestFile.exists()) {\n        sourcesJarManifest = new Manifest(new FileInputStream(manifestFile));\n      } else {\n        error(\"Manifest with attributes (sources) does not exist at: \" + sourcesJarManifestPath);\n      }\n    }\n  }\n\n\n  /** Log info to console */\n  private static void info(String message) {\n    System.err.println(\"[maven-shade-ext-bnd-transformer : ManifestBndTransformer] \" + message);\n  }\n\n\n  /** Log error to console */\n  private static void error(String message) {\n    System.err.println(\"[maven-shade-ext-bnd-transformer : ManifestBndTransformer] \" + message);\n  }\n\n\n  /**\n   *  Implementation is copied from the official implementation of\n   *  {@link ManifestResourceTransformer#processResource(String, InputStream, List)}\n   */\n  @Override\n  public void processResource(String resource, InputStream is, List<Relocator> relocators) throws IOException {\n    processResource(resource, is, relocators, 0);\n  }\n\n\n  /** Implementation is copied from the official implementation of {@link ManifestResourceTransformer#canTransformResource(String)} */\n  @Override\n  public boolean canTransformResource(String resource) {\n    return JarFile.MANIFEST_NAME.equalsIgnoreCase(resource);\n  }\n\n\n  /**\n   * Implementation is copied from the official implementation of {@link ManifestResourceTransformer#hasTransformedResource()}\n   */\n  @Override\n  public boolean hasTransformedResource() {\n    return true;\n  }\n}\n"
  },
  {
    "path": "client/java-client-dependencies/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-client-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../pom.xml</relativePath>\n  </parent>\n  <artifactId>sonarlint-java-client-dependencies</artifactId>\n  <name>SonarLint Core - Java Client dependencies</name>\n  <description>Dependencies used by the Java Clients, shaded and relocated together</description>\n  \n  <dependencies>\n    <dependency>\n      <groupId>com.google.code.gson</groupId>\n      <artifactId>gson</artifactId>\n      <version>${gson.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.eclipse.lsp4j</groupId>\n      <artifactId>org.eclipse.lsp4j.jsonrpc</artifactId>\n      <version>${lsp4j.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.eclipse.jgit</groupId>\n      <artifactId>org.eclipse.jgit</artifactId>\n      <version>${jgit6.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.slf4j</groupId>\n      <artifactId>slf4j-api</artifactId>\n      <version>${slf4j.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>io.sentry</groupId>\n      <artifactId>sentry</artifactId>\n      <version>${sentry.version}</version>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-jar-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>empty-javadoc-jar</id>\n            <phase>package</phase>\n            <goals>\n              <goal>jar</goal>\n            </goals>\n            <configuration>\n              <classifier>javadoc</classifier>\n              <classesDirectory>${basedir}/javadoc</classesDirectory>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-shade-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>shade</id>\n            <phase>package</phase>\n            <goals>\n              <goal>shade</goal>\n            </goals>\n          </execution>\n        </executions>\n        <configuration>\n          <createDependencyReducedPom>true</createDependencyReducedPom>\n          <minimizeJar>false</minimizeJar>\n          <createSourcesJar>true</createSourcesJar>\n\n          <!-- The dependencies we actually want to be shaded, without transitive ones! -->\n          <artifactSet>\n            <includes>\n              <include>com.google.code.gson:gson</include>\n              <include>org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc</include>\n              <include>org.eclipse.jgit:org.eclipse.jgit</include>\n              <include>org.slf4j:slf4j-api</include>\n              <include>io.sentry:sentry</include>\n            </includes>\n          </artifactSet>\n\n          <!-- The dependencies we want to relocate to not conflict with already existing bundles -->\n          <relocations>\n            <relocation>\n              <pattern />\n              <shadedPattern>org.sonarsource.sonarlint.shaded.</shadedPattern>\n              <includes>\n                <include>com.google.gson.**</include>\n                <include>org.eclipse.lsp4j.**</include>\n                <include>org.eclipse.jgit.**</include>\n                <include>org.slf4j.**</include>\n                <include>io.sentry.**</include>\n              </includes>\n            </relocation>\n          </relocations>\n\n          <!-- We don't want to have this files from all the dependencies shaded / relocated -->\n          <filters>\n            <filter>\n              <artifact>*:*</artifact>\n              <excludes>\n                <exclude>module-info.class</exclude>\n                <exclude>about.html</exclude>\n                <exclude>META-INF/*.SF</exclude>\n                <exclude>META-INF/*.DSA</exclude>\n                <exclude>META-INF/*.RSA</exclude>\n                <exclude>META-INF/LICENSE*</exclude>\n                <exclude>META-INF/NOTICE*</exclude>\n                <exclude>OSGI-INF/</exclude>\n                <exclude>LICENSE*</exclude>\n                <exclude>NOTICE*</exclude>\n                <exclude>*.proto</exclude>\n              </excludes>\n            </filter>\n          </filters>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "client/java-client-osgi/java-client-osgi-sources.bnd",
    "content": "# Include BND settings used for normal / sources JAR archives\n-include: shared.bnd\n\n# Manifest entries to configure the OSGi attributes for the sources JAR archive\nBundle-Name: ${project.name} Source\nBundle-Description: ${project.name} Source\nBundle-SymbolicName: ${project.groupId}.${project.artifactId}.source\nEclipse-SourceBundle: ${project.groupId}.${project.artifactId};version=\"${parsedVersion.osgiVersion}\";roots:=\".\"\nExport-Package: !*\nImport-Package: !*\n"
  },
  {
    "path": "client/java-client-osgi/java-client-osgi.bnd",
    "content": "# Include BND settings used for normal / sources JAR archives\n-include: shared.bnd\n\n# Manifest entries to configure the OSGi attributes for the normal JAR archive\nBundle-SymbolicName: ${project.groupId}.${project.artifactId}\nExport-Package: org.sonarsource.sonarlint.core.client.utils.*;version=\"${project.version}\",\\\n  org.sonarsource.sonarlint.core.rpc.client.*;version=\"${project.version}\",\\\n  org.sonarsource.sonarlint.core.rpc.protocol.*;version=\"${project.version}\",\\\n  org.sonarsource.sonarlint.shaded.com.google.gson.*;version=\"${gson.version}\",\\\n  org.sonarsource.sonarlint.shaded.org.eclipse.lsp4j.jsonrpc.*;version=\"${lsp4j.version}\",\\\n  org.sonarsource.sonarlint.shaded.org.eclipse.jgit.*;version=\"${jgit6.version}\",\\\n  org.sonarsource.sonarlint.shaded.org.slf4j.*;version=\"${slf4j.version}\",\\\n  org.sonarsource.sonarlint.shaded.io.sentry.*;version=\"${sentry.version}\",\nImport-Package: javax.annotation.*;resolution:=optional,\n"
  },
  {
    "path": "client/java-client-osgi/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-client-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../pom.xml</relativePath>\n  </parent>\n  <artifactId>sonarlint-java-client-osgi</artifactId>\n  <name>SonarLint Core - Java Client OSGi</name>\n  <description>Common SonarLint features bundled for OSGi</description>\n  \n<dependencies>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-java-client-dependencies</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-java-client-utils</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-rpc-java-client</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-rpc-protocol</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-jar-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>empty-javadoc-jar</id>\n            <phase>package</phase>\n            <goals>\n              <goal>jar</goal>\n            </goals>\n            <configuration>\n              <classifier>javadoc</classifier>\n              <classesDirectory>${basedir}/javadoc</classesDirectory>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.codehaus.mojo</groupId>\n        <artifactId>build-helper-maven-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>parse-version</id>\n            <goals>\n              <!-- Populate variables parsedVersion.osgiVersion -->\n              <goal>parse-version</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n\n      <plugin>\n        <groupId>biz.aQute.bnd</groupId>\n        <artifactId>bnd-maven-plugin</artifactId>\n        <extensions>true</extensions>\n        <executions>\n          <!-- Used to create the META-INF/MANIFEST.MF for the OSGi normal JAR archive -->\n          <execution>\n            <id>prepare-normal-MANIFEST.MF</id>\n            <goals>\n              <goal>bnd-process</goal>\n            </goals>\n            <configuration>\n              <bndfile>${project.basedir}/java-client-osgi.bnd</bndfile>\n              <manifestPath>${project.basedir}/target/normal-MANIFEST.MF</manifestPath>\n            </configuration>\n          </execution>\n\n          <!-- Used to create the META-INF/MANIFEST.MF for the OSGi sources JAR archive -->\n          <execution>\n            <id>prepare-sources-MANIFEST.MF</id>\n            <goals>\n              <goal>bnd-process</goal>\n            </goals>\n            <configuration>\n              <bndfile>${project.basedir}/java-client-osgi-sources.bnd</bndfile>\n              <manifestPath>${project.basedir}/target/sources-MANIFEST.MF</manifestPath>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-shade-plugin</artifactId>\n        <dependencies>\n          <dependency>\n            <groupId>${project.groupId}</groupId>\n            <artifactId>maven-shade-ext-bnd-transformer</artifactId>\n            <version>${project.version}</version>\n          </dependency>\n        </dependencies>\n        <executions>\n          <execution>\n            <id>shade</id>\n            <phase>package</phase>\n            <goals>\n              <goal>shade</goal>\n            </goals>\n          </execution>\n        </executions>\n        <configuration>\n          <createDependencyReducedPom>true</createDependencyReducedPom>\n          <minimizeJar>true</minimizeJar>\n          <createSourcesJar>true</createSourcesJar>\n\n          <!-- We have to use our own custom transformer as the Maven Shade and Bndtools plug-in collide on META-INF/MANIFEST.MF files -->\n          <transformers>\n            <transformer implementation=\"org.sonarsource.sonarlint.maven.shade.ext.ManifestBndTransformer\">\n              <normalJarManifestPath>${project.basedir}/target/normal-MANIFEST.MF</normalJarManifestPath>\n              <sourcesJarManifestPath>${project.basedir}/target/sources-MANIFEST.MF</sourcesJarManifestPath>\n            </transformer>\n          </transformers>\n\n          <!-- The dependencies we actually want to be shaded, without transitive ones! -->\n          <artifactSet>\n            <includes>\n              <include>${project.groupId}:sonarlint-java-client-dependencies</include>\n              <include>${project.groupId}:sonarlint-java-client-utils</include>\n              <include>${project.groupId}:sonarlint-rpc-java-client</include>\n              <include>${project.groupId}:sonarlint-rpc-protocol</include>\n            </includes>\n          </artifactSet>\n\n          <!-- The dependencies we want to relocate to not conflict with already existing bundles -->\n          <relocations>\n            <relocation>\n              <pattern />\n              <shadedPattern>org.sonarsource.sonarlint.shaded.</shadedPattern>\n              <includes>\n                <include>com.google.gson.**</include>\n                <include>org.eclipse.lsp4j.**</include>\n                <include>org.eclipse.jgit.**</include>\n                <include>org.slf4j.**</include>\n                <include>io.sentry.**</include>\n              </includes>\n            </relocation>\n          </relocations>\n\n          <!-- We don't want to have this files from all the dependencies shaded / relocated -->\n          <filters>\n            <filter>\n              <artifact>*:*</artifact>\n              <excludes>\n                <exclude>module-info.class</exclude>\n                <exclude>logback-shared.xml</exclude>\n                <exclude>sl_core_version.txt</exclude>\n                <exclude>META-INF/*.SF</exclude>\n                <exclude>META-INF/*.DSA</exclude>\n                <exclude>META-INF/*.RSA</exclude>\n                <exclude>META-INF/LICENSE*</exclude>\n                <exclude>META-INF/NOTICE*</exclude>\n                <exclude>LICENSE*</exclude>\n                <exclude>NOTICE*</exclude>\n                <exclude>*.proto</exclude>\n              </excludes>\n            </filter>\n\n            <!-- Shade only the classes from the specific artifacts as the other ones are provided using BND -->\n            <filter>\n              <artifact>${project.groupId}:*</artifact>\n              <includes>\n                <include>**</include>\n              </includes>\n            </filter>\n          </filters>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "client/java-client-osgi/shared.bnd",
    "content": "# Manifest entries to configure the OSGi attributes for the normal / sources JAR archive\nBundle-ManifestVersion: 2\nBundle-Version: ${parsedVersion.osgiVersion}\n\n# BND configuration to tweak generation of Manifest entries for the normal / sources JAR archive\n-removeheaders: Bnd-LastModified,Bundle-Developers,Bundle-DocURL,Bundle-SCM,Include-Resource,Private-Package\n-noimportjava: true\n# a new version of apache commons-io library has a new META-INF/versions/9/module-info.class file and that does not conform OSGi\n-fixupmessages \"Classes found in the wrong directory\"; restrict:=error; is:=warning\n"
  },
  {
    "path": "client/java-client-utils/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-client-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../pom.xml</relativePath>\n  </parent>\n  <artifactId>sonarlint-java-client-utils</artifactId>\n  <name>SonarLint Core - Java Client Utils</name>\n  <description>Utility classes for Java clients</description>\n  \n<dependencies>\n    <dependency>\n      <groupId>com.google.code.findbugs</groupId>\n      <artifactId>jsr305</artifactId>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.eclipse.jgit</groupId>\n      <artifactId>org.eclipse.jgit</artifactId>\n      <version>${jgit6.version}</version>\n      <!-- The core doesn't provide JGit, since some IDEs (like Eclipse) will provide it -->\n      <scope>provided</scope>\n      <optional>true</optional>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-rpc-protocol</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n\n    <!-- unit tests -->\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-engine</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.assertj</groupId>\n      <artifactId>assertj-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n</project>\n"
  },
  {
    "path": "client/java-client-utils/src/main/java/org/sonarsource/sonarlint/core/client/utils/CleanCodeAttribute.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\npublic enum CleanCodeAttribute {\n\n  CONVENTIONAL(\"Not conventional\", CleanCodeAttributeCategory.CONSISTENT),\n  FORMATTED(\"Not formatted\", CleanCodeAttributeCategory.CONSISTENT),\n  IDENTIFIABLE(\"Not identifiable\", CleanCodeAttributeCategory.CONSISTENT),\n\n  CLEAR(\"Not clear\", CleanCodeAttributeCategory.INTENTIONAL),\n  COMPLETE(\"Not complete\", CleanCodeAttributeCategory.INTENTIONAL),\n  EFFICIENT(\"Not efficient\", CleanCodeAttributeCategory.INTENTIONAL),\n  LOGICAL(\"Not logical\", CleanCodeAttributeCategory.INTENTIONAL),\n\n  DISTINCT(\"Not distinct\", CleanCodeAttributeCategory.ADAPTABLE),\n  FOCUSED(\"Not focused\", CleanCodeAttributeCategory.ADAPTABLE),\n  MODULAR(\"Not modular\", CleanCodeAttributeCategory.ADAPTABLE),\n  TESTED(\"Not tested\", CleanCodeAttributeCategory.ADAPTABLE),\n\n  LAWFUL(\"Not lawful\", CleanCodeAttributeCategory.RESPONSIBLE),\n  RESPECTFUL(\"Not respectful\", CleanCodeAttributeCategory.RESPONSIBLE),\n  TRUSTWORTHY(\"Not trustworthy\", CleanCodeAttributeCategory.RESPONSIBLE);\n\n\n  private final String label;\n  private final CleanCodeAttributeCategory category;\n\n  CleanCodeAttribute(String label, CleanCodeAttributeCategory category) {\n    this.label = label;\n    this.category = category;\n  }\n\n  public String getLabel() {\n    return label;\n  }\n\n  public CleanCodeAttributeCategory getCategory() {\n    return category;\n  }\n\n  public static CleanCodeAttribute fromDto(org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute rpcEnum) {\n    switch (rpcEnum) {\n      case CONVENTIONAL:\n        return CONVENTIONAL;\n      case FORMATTED:\n        return FORMATTED;\n      case IDENTIFIABLE:\n        return IDENTIFIABLE;\n      case CLEAR:\n        return CLEAR;\n      case COMPLETE:\n        return COMPLETE;\n      case EFFICIENT:\n        return EFFICIENT;\n      case LOGICAL:\n        return LOGICAL;\n      case DISTINCT:\n        return DISTINCT;\n      case FOCUSED:\n        return FOCUSED;\n      case MODULAR:\n        return MODULAR;\n      case TESTED:\n        return TESTED;\n      case LAWFUL:\n        return LAWFUL;\n      case RESPECTFUL:\n        return RESPECTFUL;\n      case TRUSTWORTHY:\n        return TRUSTWORTHY;\n      default:\n        throw new IllegalArgumentException(\"Unknown attribute: \" + rpcEnum);\n    }\n  }\n\n}\n"
  },
  {
    "path": "client/java-client-utils/src/main/java/org/sonarsource/sonarlint/core/client/utils/CleanCodeAttributeCategory.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\npublic enum CleanCodeAttributeCategory {\n  ADAPTABLE(\"Adaptability\"),\n  CONSISTENT(\"Consistency\"),\n  INTENTIONAL(\"Intentionality\"),\n  RESPONSIBLE(\"Responsibility\");\n\n  private final String label;\n\n  CleanCodeAttributeCategory(String label) {\n    this.label = label;\n  }\n\n  public String getLabel() {\n    return label;\n  }\n\n  public static CleanCodeAttributeCategory fromDto(org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttributeCategory rpcEnum) {\n    switch (rpcEnum) {\n      case ADAPTABLE:\n        return ADAPTABLE;\n      case CONSISTENT:\n        return CONSISTENT;\n      case INTENTIONAL:\n        return INTENTIONAL;\n      case RESPONSIBLE:\n        return RESPONSIBLE;\n      default:\n        throw new IllegalArgumentException(\"Unknown category: \" + rpcEnum);\n    }\n  }\n}\n"
  },
  {
    "path": "client/java-client-utils/src/main/java/org/sonarsource/sonarlint/core/client/utils/ClientFileExclusions.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\nimport java.io.File;\nimport java.nio.file.FileSystems;\nimport java.nio.file.Path;\nimport java.nio.file.PathMatcher;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.function.Predicate;\n\n/**\n * Exclusions configured on client side\n */\npublic class ClientFileExclusions implements Predicate<String> {\n  private static final String SYNTAX = \"glob\";\n\n  private final List<PathMatcher> matchers;\n  private final Set<String> directoryExclusions;\n  private final Set<String> fileExclusions;\n\n  public ClientFileExclusions(Set<String> fileExclusions, Set<String> directoryExclusions, Set<String> globPatterns) {\n    this.fileExclusions = fileExclusions;\n    this.directoryExclusions = directoryExclusions;\n    this.matchers = parseGlobPatterns(globPatterns);\n  }\n\n  private static List<PathMatcher> parseGlobPatterns(Set<String> globPatterns) {\n    var fs = FileSystems.getDefault();\n\n    List<PathMatcher> parsedMatchers = new ArrayList<>(globPatterns.size());\n    for (String pattern : globPatterns) {\n      try {\n        parsedMatchers.add(fs.getPathMatcher(SYNTAX + \":\" + pattern));\n      } catch (Exception e) {\n        // ignore invalid patterns, simply skip them\n      }\n    }\n    return parsedMatchers;\n  }\n\n  public boolean test(Path path) {\n    return testFileExclusions(path) || testDirectoryExclusions(path) || testGlob(path);\n  }\n\n  private boolean testGlob(Path path) {\n    return matchers.stream().anyMatch(matcher -> matcher.matches(path));\n  }\n\n  private boolean testFileExclusions(Path path) {\n    return hasOsIndependentExclusion(fileExclusions, path);\n  }\n\n  private boolean testDirectoryExclusions(Path path) {\n    var p = path;\n    while (p != null) {\n      if (hasOsIndependentExclusion(directoryExclusions, p)) {\n        return true;\n      }\n      p = p.getParent();\n    }\n    return false;\n  }\n\n  private static boolean hasOsIndependentExclusion(Set<String> exclusions, Path path) {\n    var pathStr = path.toString();\n    return exclusions.contains(pathStr) ||\n            exclusions.contains(pathStr.replace(File.separatorChar, '/')) ||\n            exclusions.contains(pathStr.replace(File.separatorChar, '\\\\'));\n  }\n\n  @Override\n  public boolean test(String string) {\n    return test(Paths.get(string));\n  }\n}\n"
  },
  {
    "path": "client/java-client-utils/src/main/java/org/sonarsource/sonarlint/core/client/utils/ClientLogOutput.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\n\n/**\n * Allow to redirect SonarLint logs to a custom output on client side\n */\npublic interface ClientLogOutput {\n\n  void log(String formattedMessage, Level level);\n\n  enum Level {\n    ERROR, WARN, INFO, DEBUG, TRACE\n  }\n\n  static String stackTraceToString(Throwable t) {\n    var stringWriter = new StringWriter();\n    var printWriter = new PrintWriter(stringWriter);\n    t.printStackTrace(printWriter);\n    return stringWriter.toString();\n  }\n}\n"
  },
  {
    "path": "client/java-client-utils/src/main/java/org/sonarsource/sonarlint/core/client/utils/DateUtils.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\nimport java.time.Instant;\nimport java.time.LocalDateTime;\nimport java.time.ZoneId;\nimport java.time.temporal.ChronoUnit;\n\npublic class DateUtils {\n\n  private DateUtils() {\n    // utility class, forbidden constructor\n  }\n\n  public static String toAge(long time) {\n    var creation = LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneId.systemDefault());\n    var now = LocalDateTime.now();\n\n    var years = ChronoUnit.YEARS.between(creation, now);\n    if (years > 0) {\n      return pluralize(years, \"year\");\n    }\n    var months = ChronoUnit.MONTHS.between(creation, now);\n    if (months > 0) {\n      return pluralize(months, \"month\");\n    }\n    var days = ChronoUnit.DAYS.between(creation, now);\n    if (days > 0) {\n      return pluralize(days, \"day\");\n    }\n    var hours = ChronoUnit.HOURS.between(creation, now);\n    if (hours > 0) {\n      return pluralize(hours, \"hour\");\n    }\n    var minutes = ChronoUnit.MINUTES.between(creation, now);\n    if (minutes > 0) {\n      return pluralize(minutes, \"minute\");\n    }\n\n    return \"few seconds ago\";\n  }\n\n  private static String pluralize(long strictlyPositiveCount, String singular) {\n    return pluralize(strictlyPositiveCount, singular, singular + \"s\");\n  }\n\n  private static String pluralize(long strictlyPositiveCount, String singular, String plural) {\n    if (strictlyPositiveCount == 1) {\n      return \"1 \" + singular + \" ago\";\n    }\n    return strictlyPositiveCount + \" \" + plural + \" ago\";\n  }\n}\n"
  },
  {
    "path": "client/java-client-utils/src/main/java/org/sonarsource/sonarlint/core/client/utils/DependencyRiskTransitionStatus.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.DependencyRiskDto;\n\npublic enum DependencyRiskTransitionStatus {\n  REOPEN(\"Open\", \"This finding has not yet been reviewed.\"),\n  CONFIRM(\"Confirmed\", \"This finding has been reviewed and the risk is valid.\"),\n  ACCEPT(\"Accepted\", \"This finding is valid, but it may not be fixed for a while.\"),\n  SAFE(\"Safe\", \"This finding does not pose a risk. No fix is needed.\"),\n  FIXED(\"Fixed\", \"This finding has been fixed.\");\n\n  private final String title;\n  private final String description;\n\n  DependencyRiskTransitionStatus(String title, String description) {\n    this.title = title;\n    this.description = description;\n  }\n\n  public String getTitle() {\n    return title;\n  }\n\n  public String getDescription() {\n    return description;\n  }\n\n  public static DependencyRiskTransitionStatus fromDto(DependencyRiskDto.Transition status) {\n    switch (status) {\n      case REOPEN:\n        return REOPEN;\n      case CONFIRM:\n        return CONFIRM;\n      case ACCEPT:\n        return ACCEPT;\n      case SAFE:\n        return SAFE;\n      case FIXED:\n        return FIXED;\n      default:\n        throw new IllegalArgumentException(\"Unknown status: \" + status);\n    }\n  }\n}\n"
  },
  {
    "path": "client/java-client-utils/src/main/java/org/sonarsource/sonarlint/core/client/utils/GitUtils.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.eclipse.jgit.lib.Constants;\nimport org.eclipse.jgit.lib.Ref;\nimport org.eclipse.jgit.lib.Repository;\nimport org.eclipse.jgit.lib.RepositoryBuilder;\nimport org.eclipse.jgit.revwalk.RevWalk;\nimport org.eclipse.jgit.revwalk.RevWalkUtils;\nimport org.eclipse.jgit.revwalk.filter.RevFilter;\n\nimport static java.util.Comparator.naturalOrder;\n\npublic class GitUtils {\n\n  private GitUtils() {\n    // util class\n  }\n\n  @CheckForNull\n  public static Repository getRepositoryForDir(Path projectDir, ClientLogOutput clientLogOutput) {\n    try {\n      var builder = new RepositoryBuilder()\n        .findGitDir(projectDir.toFile())\n        .setMustExist(true);\n      if (builder.getGitDir() == null) {\n        clientLogOutput.log(\"Not inside a Git work tree: \" + projectDir, ClientLogOutput.Level.DEBUG);\n        return null;\n      }\n      return builder.build();\n    } catch (IOException e) {\n      clientLogOutput.log(\"Couldn't access repository for path \" + projectDir, ClientLogOutput.Level.ERROR);\n      clientLogOutput.log(ClientLogOutput.stackTraceToString(e), ClientLogOutput.Level.ERROR);\n    }\n    return null;\n  }\n\n  @CheckForNull\n  public static String electBestMatchingServerBranchForCurrentHead(Repository repo, Set<String> serverCandidateNames, @Nullable String serverMainBranch,\n    ClientLogOutput clientLogOutput) {\n    try {\n\n      String currentBranch = repo.getBranch();\n      if (currentBranch != null && serverCandidateNames.contains(currentBranch)) {\n        return currentBranch;\n      }\n\n      var head = repo.exactRef(Constants.HEAD);\n      if (head == null) {\n        // Not sure if this is possible to not have a HEAD, but just in case\n        return null;\n      }\n\n      Map<Integer, Set<String>> branchesPerDistance = new HashMap<>();\n      for (String serverBranchName : serverCandidateNames) {\n        var shortBranchName = Repository.shortenRefName(serverBranchName);\n        var localFullBranchName = Constants.R_HEADS + shortBranchName;\n\n        var branchRef = repo.exactRef(localFullBranchName);\n        if (branchRef == null) {\n          continue;\n        }\n\n        int distance = distance(repo, head, branchRef);\n        branchesPerDistance.computeIfAbsent(distance, d -> new HashSet<>()).add(serverBranchName);\n      }\n      if (branchesPerDistance.isEmpty()) {\n        return null;\n      }\n\n      int minDistance = branchesPerDistance.keySet().stream().min(naturalOrder()).get();\n      var bestCandidates = branchesPerDistance.get(minDistance);\n      if (serverMainBranch != null && bestCandidates.contains(serverMainBranch)) {\n        // Favor the main branch when there are multiple candidates with the same distance\n        return serverMainBranch;\n      }\n      return bestCandidates.iterator().next();\n    } catch (IOException e) {\n      clientLogOutput.log(\"Couldn't find best matching branch\", ClientLogOutput.Level.ERROR);\n      clientLogOutput.log(ClientLogOutput.stackTraceToString(e), ClientLogOutput.Level.ERROR);\n      return null;\n    }\n  }\n\n  private static int distance(Repository repository, Ref from, Ref to) throws IOException {\n\n    try (var walk = new RevWalk(repository)) {\n\n      var fromCommit = walk.parseCommit(from.getObjectId());\n      var toCommit = walk.parseCommit(to.getObjectId());\n\n      walk.setRevFilter(RevFilter.MERGE_BASE);\n      walk.markStart(fromCommit);\n      walk.markStart(toCommit);\n      var mergeBase = walk.next();\n\n      walk.reset();\n      walk.setRevFilter(RevFilter.ALL);\n      int aheadCount = RevWalkUtils.count(walk, fromCommit, mergeBase);\n      int behindCount = RevWalkUtils.count(walk, toCommit,\n        mergeBase);\n\n      return aheadCount + behindCount;\n    }\n  }\n\n}\n"
  },
  {
    "path": "client/java-client-utils/src/main/java/org/sonarsource/sonarlint/core/client/utils/HotspotStatus.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\npublic enum HotspotStatus {\n  // order is important here, it will be applied in the UI\n  TO_REVIEW(\"To Review\", \"This Security Hotspot needs to be reviewed to assess whether the code poses a risk.\"),\n  ACKNOWLEDGED(\"Acknowledged\", \"The code has been reviewed and does pose a risk. A fix is required.\"),\n  FIXED(\"Fixed\", \"The code has been modified to follow recommended secure coding practices.\"),\n  SAFE(\"Safe\", \"The code has been reviewed and does not pose a risk. It does not need to be modified.\");\n\n  private final String title;\n  private final String description;\n\n  HotspotStatus(String title, String description) {\n    this.title = title;\n    this.description = description;\n  }\n\n  public String getTitle() {\n    return title;\n  }\n\n  public String getDescription() {\n    return description;\n  }\n\n  public static HotspotStatus fromDto(org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.HotspotStatus rpcEnum) {\n    switch (rpcEnum) {\n      case TO_REVIEW:\n        return TO_REVIEW;\n      case ACKNOWLEDGED:\n        return ACKNOWLEDGED;\n      case FIXED:\n        return FIXED;\n      case SAFE:\n        return SAFE;\n      default:\n        throw new IllegalArgumentException(\"Unknown status: \" + rpcEnum);\n    }\n  }\n}\n"
  },
  {
    "path": "client/java-client-utils/src/main/java/org/sonarsource/sonarlint/core/client/utils/ImpactSeverity.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\npublic enum ImpactSeverity {\n  INFO(\"Info\"),\n  LOW(\"Low\"),\n  MEDIUM(\"Medium\"),\n  HIGH(\"High\"),\n  BLOCKER(\"Blocker\");\n\n  private final String label;\n\n  ImpactSeverity(String label) {\n    this.label = label;\n  }\n\n  public String getLabel() {\n    return label;\n  }\n\n  public static ImpactSeverity fromDto(org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity rpcEnum) {\n    switch (rpcEnum) {\n      case INFO:\n        return INFO;\n      case LOW:\n        return LOW;\n      case MEDIUM:\n        return MEDIUM;\n      case HIGH:\n        return HIGH;\n      case BLOCKER:\n        return BLOCKER;\n      default:\n        throw new IllegalArgumentException(\"Unknown severity: \" + rpcEnum);\n    }\n  }\n}\n"
  },
  {
    "path": "client/java-client-utils/src/main/java/org/sonarsource/sonarlint/core/client/utils/IssueResolutionStatus.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ResolutionStatus;\n\npublic enum IssueResolutionStatus {\n  ACCEPT(\"Accepted\", \"The issue is valid but will not be fixed now. It represents accepted technical debt.\"),\n  WONT_FIX(\"Won't Fix\", \"The issue is valid but does not need fixing. It represents accepted technical debt.\"),\n  FALSE_POSITIVE(\"False Positive\", \"The issue is raised unexpectedly on code that should not trigger an issue.\");\n\n  private final String title;\n  private final String description;\n\n  IssueResolutionStatus(String title, String description) {\n    this.title = title;\n    this.description = description;\n  }\n\n  public String getTitle() {\n    return title;\n  }\n\n  public String getDescription() {\n    return description;\n  }\n\n  public static IssueResolutionStatus fromDto(ResolutionStatus status) {\n    switch (status) {\n      case ACCEPT:\n        return ACCEPT;\n      case WONT_FIX:\n        return WONT_FIX;\n      case FALSE_POSITIVE:\n        return FALSE_POSITIVE;\n      default:\n        throw new IllegalArgumentException(\"Unknown status: \" + status);\n    }\n  }\n}\n"
  },
  {
    "path": "client/java-client-utils/src/main/java/org/sonarsource/sonarlint/core/client/utils/Language.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\npublic enum Language {\n\n  ABAP(\"ABAP\"),\n  APEX(\"Apex\"),\n  C(\"C\"),\n  CPP(\"C++\"),\n  CS(\"C#\"),\n  CSS(\"CSS\"),\n  OBJC(\"Objective-C\"),\n  COBOL(\"COBOL\"),\n  HTML(\"HTML\"),\n  IPYTHON(\"IPython Notebooks\"),\n  JAVA(\"Java\"),\n  JCL(\"JCL\"),\n  JS(\"JavaScript\"),\n  KOTLIN(\"Kotlin\"),\n  PHP(\"PHP\"),\n  PLI(\"PL/I\"),\n  PLSQL(\"PL/SQL\"),\n  PYTHON(\"Python\"),\n  RPG(\"RPG\"),\n  RUBY(\"Ruby\"),\n  SCALA(\"Scala\"),\n  SECRETS(\"Secrets\"),\n  TEXT(\"Text\"),\n  SWIFT(\"Swift\"),\n  TSQL(\"T-SQL\"),\n  TS(\"TypeScript\"),\n  JSP(\"JSP\"),\n  VBNET(\"VB.NET\"),\n  XML(\"XML\"),\n  YAML(\"YAML\"),\n  JSON(\"JSON\"),\n  GO(\"Go\"),\n  CLOUDFORMATION(\"CloudFormation\"),\n  DOCKER(\"Docker\"),\n  KUBERNETES(\"Kubernetes\"),\n  TERRAFORM(\"Terraform\"),\n  AZURERESOURCEMANAGER(\"AzureResourceManager\"),\n  ANSIBLE(\"Ansible\"),\n  GITHUBACTIONS(\"GitHub Actions\");\n  private final String label;\n\n  Language(String label) {\n    this.label = label;\n  }\n\n  public String getLabel() {\n    return label;\n  }\n\n  public static Language fromDto(org.sonarsource.sonarlint.core.rpc.protocol.common.Language rpcEnum) {\n    switch (rpcEnum) {\n      case ABAP:\n        return ABAP;\n      case APEX:\n        return APEX;\n      case C:\n        return C;\n      case CPP:\n        return CPP;\n      case CS:\n        return CS;\n      case CSS:\n        return CSS;\n      case OBJC:\n        return OBJC;\n      case COBOL:\n        return COBOL;\n      case GITHUBACTIONS:\n        return GITHUBACTIONS;\n      case HTML:\n        return HTML;\n      case IPYTHON:\n        return IPYTHON;\n      case JAVA:\n        return JAVA;\n      case JCL:\n        return JCL;\n      case JS:\n        return JS;\n      case KOTLIN:\n        return KOTLIN;\n      case PHP:\n        return PHP;\n      case PLI:\n        return PLI;\n      case PLSQL:\n        return PLSQL;\n      case PYTHON:\n        return PYTHON;\n      case RPG:\n        return RPG;\n      case RUBY:\n        return RUBY;\n      case SCALA:\n        return SCALA;\n      case SECRETS:\n        return SECRETS;\n      case TEXT:\n        return TEXT;\n      case SWIFT:\n        return SWIFT;\n      case TSQL:\n        return TSQL;\n      case TS:\n        return TS;\n      case JSP:\n        return JSP;\n      case VBNET:\n        return VBNET;\n      case XML:\n        return XML;\n      case YAML:\n        return YAML;\n      case JSON:\n        return JSON;\n      case GO:\n        return GO;\n      case CLOUDFORMATION:\n        return CLOUDFORMATION;\n      case DOCKER:\n        return DOCKER;\n      case KUBERNETES:\n        return KUBERNETES;\n      case TERRAFORM:\n        return TERRAFORM;\n      case AZURERESOURCEMANAGER:\n        return AZURERESOURCEMANAGER;\n      case ANSIBLE:\n        return ANSIBLE;\n      default:\n        throw new IllegalArgumentException(\"Unknown language: \" + rpcEnum);\n    }\n  }\n\n}\n"
  },
  {
    "path": "client/java-client-utils/src/main/java/org/sonarsource/sonarlint/core/client/utils/SoftwareQuality.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\npublic enum SoftwareQuality {\n  MAINTAINABILITY(\"Maintainability\"),\n  RELIABILITY(\"Reliability\"),\n  SECURITY(\"Security\");\n\n  private final String label;\n\n  SoftwareQuality(String label) {\n    this.label = label;\n  }\n\n  public String getLabel() {\n    return label;\n  }\n\n  public static SoftwareQuality fromDto(org.sonarsource.sonarlint.core.rpc.protocol.common.SoftwareQuality rpcEnum) {\n    switch (rpcEnum) {\n      case MAINTAINABILITY:\n        return MAINTAINABILITY;\n      case RELIABILITY:\n        return RELIABILITY;\n      case SECURITY:\n        return SECURITY;\n      default:\n        throw new IllegalArgumentException(\"Unknown quality: \" + rpcEnum);\n    }\n  }\n}\n"
  },
  {
    "path": "client/java-client-utils/src/main/java/org/sonarsource/sonarlint/core/client/utils/package-info.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.client.utils;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "client/java-client-utils/src/test/java/org/sonarsource/sonarlint/core/client/utils/CleanCodeAttributeCategoryTests.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass CleanCodeAttributeCategoryTests {\n\n  @Test\n  void should_convert_all_enum_values() {\n    for (var rpcEnum : org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttributeCategory.values()) {\n      var converted = CleanCodeAttributeCategory.fromDto(rpcEnum);\n      assertEquals(rpcEnum.name(), converted.name());\n    }\n  }\n\n}"
  },
  {
    "path": "client/java-client-utils/src/test/java/org/sonarsource/sonarlint/core/client/utils/CleanCodeAttributeTests.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass CleanCodeAttributeTests {\n\n  @Test\n  void should_convert_all_enum_values() {\n    for (var rpcEnum : org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute.values()) {\n      var converted = CleanCodeAttribute.fromDto(rpcEnum);\n      assertEquals(rpcEnum.name(), converted.name());\n    }\n  }\n}\n"
  },
  {
    "path": "client/java-client-utils/src/test/java/org/sonarsource/sonarlint/core/client/utils/ClientFileExclusionsTests.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\nimport java.io.File;\nimport java.util.Collections;\nimport java.util.Set;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledOnOs;\nimport org.junit.jupiter.api.condition.OS;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ClientFileExclusionsTests {\n  ClientFileExclusions underTest;\n\n  @BeforeEach\n  void before() {\n    Set<String> glob = Collections.singleton(\"**/*.js\");\n\n    // Setup file exclusions with both separator styles\n    Set<String> files = Set.of(\n            new File(\"dir/file.java\").getAbsolutePath(),\n            \"dir/file-with-slash.java\",\n            \"other\\\\file-with-backslash.java\"\n    );\n\n    // Setup directory exclusions with both separator styles\n    Set<String> dirs = Set.of(\n            \"src\",\n            \"excluded/dir\",\n            \"another\\\\excluded\\\\dir\"\n    );\n\n    underTest = new ClientFileExclusions(files, dirs, glob);\n  }\n\n  @Test\n  void should_exclude_with_glob_relative_path() {\n    assertThat(underTest.test(new File(\"dir2/file.js\").getAbsolutePath())).isTrue();\n    assertThat(underTest.test(new File(\"dir2/file.java\").getAbsolutePath())).isFalse();\n  }\n\n  @Test\n  void should_exclude_with_glob_absolute_path() {\n    assertThat(underTest.test(new File(\"/absolute/dir/file.js\").getAbsolutePath())).isTrue();\n    assertThat(underTest.test(new File(\"/absolute/dir/file.java\").getAbsolutePath())).isFalse();\n  }\n\n  @Test\n  void should_exclude_with_file() {\n    assertThat(underTest.test(new File(\"dir/file2.java\").getAbsolutePath())).isFalse();\n    assertThat(underTest.test(new File(\"dir/file.java\").getAbsolutePath())).isTrue();\n  }\n\n  @Test\n  void should_exclude_with_dir() {\n    assertThat(underTest.test(new File(\"dir/class2.java\").getAbsolutePath())).isFalse();\n    assertThat(underTest.test(\"src/class.java\")).isTrue();\n  }\n\n  @Test\n  void should_handle_file_exclusions_with_different_separators() {\n    assertThat(underTest.test(\"dir/file-with-slash.java\")).isTrue();\n    assertThat(underTest.test(\"other/file-with-backslash.java\")).isTrue();\n\n    assertThat(underTest.test(\"different/dir/file-with-slash.java\")).isFalse();\n    assertThat(underTest.test(\"other2/file-with-backslash.java\")).isFalse();\n  }\n\n  @Test\n  void should_handle_directory_exclusions_with_different_separators() {\n    assertThat(underTest.test(\"excluded/dir/some-file.java\")).isTrue();\n    assertThat(underTest.test(\"another/excluded/dir/some-file.java\")).isTrue();\n\n    assertThat(underTest.test(\"different/excluded/some-file.java\")).isFalse();\n    assertThat(underTest.test(\"another2\\\\excluded\\\\dir\\\\some-file.java\")).isFalse();\n  }\n\n  @EnabledOnOs(OS.WINDOWS)\n  @Test\n  void testFileExclusionsWithBackslashes() {\n    assertThat(underTest.test(\"dir\\\\file-with-slash.java\")).isTrue();\n  }\n\n  @EnabledOnOs(OS.WINDOWS)\n  @Test\n  void testDirectoryExclusionsWithBackslashes() {\n    assertThat(underTest.test(\"excluded\\\\dir\\\\some-file.java\")).isTrue();\n  }\n}\n"
  },
  {
    "path": "client/java-client-utils/src/test/java/org/sonarsource/sonarlint/core/client/utils/DateUtilsTests.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\nimport java.time.LocalDateTime;\nimport java.time.ZoneId;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass DateUtilsTests {\n\n  @Test\n  void testAge() {\n    assertThat(DateUtils.toAge(System.currentTimeMillis() - 100)).isEqualTo(\"few seconds ago\");\n    assertThat(DateUtils.toAge(System.currentTimeMillis() - 65_000)).isEqualTo(\"1 minute ago\");\n    assertThat(DateUtils.toAge(System.currentTimeMillis() - 3_600_000 - 100_000)).isEqualTo(\"1 hour ago\");\n    assertThat(DateUtils.toAge(System.currentTimeMillis() - 2 * 3_600_000 - 100_000)).isEqualTo(\"2 hours ago\");\n    assertThat(DateUtils.toAge(System.currentTimeMillis() - 24 * 3_600_000 - 100_000)).isEqualTo(\"1 day ago\");\n    assertThat(DateUtils.toAge(LocalDateTime.now().minusMonths(5)\n      .atZone(ZoneId.systemDefault())\n      .toInstant()\n      .toEpochMilli())).isEqualTo(\"5 months ago\");\n    assertThat(DateUtils.toAge(LocalDateTime.now().minusMonths(15)\n      .atZone(ZoneId.systemDefault())\n      .toInstant()\n      .toEpochMilli())).isEqualTo(\"1 year ago\");\n  }\n}\n"
  },
  {
    "path": "client/java-client-utils/src/test/java/org/sonarsource/sonarlint/core/client/utils/DependencyRiskTransitionStatusTest.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.DependencyRiskDto;\n\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass DependencyRiskTransitionStatusTest {\n  @Test\n  void should_convert_all_enum_values() {\n    for (var rpcEnum : DependencyRiskDto.Transition.values()) {\n      var converted = DependencyRiskTransitionStatus.fromDto(rpcEnum);\n      assertEquals(rpcEnum.name(), converted.name());\n    }\n  }\n\n  @Test\n  void should_get_title() {\n    assertThat(DependencyRiskTransitionStatus.SAFE.getTitle()).isEqualTo(\"Safe\");\n  }\n\n  @Test\n  void should_get_description() {\n    assertThat(DependencyRiskTransitionStatus.FIXED.getDescription())\n      .isEqualTo(\"This finding has been fixed.\");\n  }\n}\n"
  },
  {
    "path": "client/java-client-utils/src/test/java/org/sonarsource/sonarlint/core/client/utils/GitUtilsTests.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Enumeration;\nimport java.util.Set;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\nimport org.eclipse.jgit.lib.RefDatabase;\nimport org.eclipse.jgit.lib.Repository;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport static java.lang.String.format;\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThat;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass GitUtilsTests {\n\n  private final ClientLogOutput fakeClientLogger = (m, l) -> {\n  };\n\n  @Test\n  void noGitRepoShouldBeNull(@TempDir File projectDir) throws IOException {\n    javaUnzip(\"no-git-repo.zip\", projectDir);\n    Path path = Paths.get(projectDir.getPath(), \"no-git-repo\");\n    Repository repo = GitUtils.getRepositoryForDir(path, fakeClientLogger);\n    assertThat(repo).isNull();\n  }\n\n  @Test\n  void gitRepoShouldBeNotNull(@TempDir File projectDir) throws IOException {\n    javaUnzip(\"dummy-git.zip\", projectDir);\n    Path path = Paths.get(projectDir.getPath(), \"dummy-git\");\n    try (Repository repo = GitUtils.getRepositoryForDir(path, fakeClientLogger)) {\n      Set<String> serverCandidateNames = Set.of(\"foo\", \"bar\", \"master\");\n\n      String branch = GitUtils.electBestMatchingServerBranchForCurrentHead(repo, serverCandidateNames, \"master\", fakeClientLogger);\n      assertThat(branch).isEqualTo(\"master\");\n    }\n  }\n\n  @Test\n  void shouldElectAnalyzedBranch(@TempDir File projectDir) throws IOException {\n    javaUnzip(\"analyzed-branch.zip\", projectDir);\n    Path path = Paths.get(projectDir.getPath(), \"analyzed-branch\");\n    try (Repository repo = GitUtils.getRepositoryForDir(path, fakeClientLogger)) {\n      Set<String> serverCandidateNames = Set.of(\"foo\", \"closest_branch\", \"master\");\n\n      String branch = GitUtils.electBestMatchingServerBranchForCurrentHead(repo, serverCandidateNames, \"master\", fakeClientLogger);\n      assertThat(branch).isEqualTo(\"closest_branch\");\n    }\n  }\n\n  @Test\n  void shouldReturnNullIfNonePresentInLocalGit(@TempDir File projectDir) throws IOException {\n    javaUnzip(\"analyzed-branch.zip\", projectDir);\n    Path path = Paths.get(projectDir.getPath(), \"analyzed-branch\");\n    try (Repository repo = GitUtils.getRepositoryForDir(path, fakeClientLogger)) {\n      Set<String> serverCandidateNames = Set.of(\"unknown1\", \"unknown2\", \"unknown3\");\n\n      String branch = GitUtils.electBestMatchingServerBranchForCurrentHead(repo, serverCandidateNames, \"master\", fakeClientLogger);\n      assertThat(branch).isNull();\n    }\n  }\n\n  @Test\n  void shouldElectClosestBranch(@TempDir File projectDir) throws IOException {\n    javaUnzip(\"closest-branch.zip\", projectDir);\n    Path path = Paths.get(projectDir.getPath(), \"closest-branch\");\n\n    try (Repository repo = GitUtils.getRepositoryForDir(path, fakeClientLogger)) {\n\n      Set<String> serverCandidateNames = Set.of(\"foo\", \"closest_branch\", \"master\");\n\n      String branch = GitUtils.electBestMatchingServerBranchForCurrentHead(repo, serverCandidateNames, \"master\", fakeClientLogger);\n      assertThat(branch).isEqualTo(\"closest_branch\");\n    }\n  }\n\n  @Test\n  void shouldElectClosestBranch_even_if_no_main_branch(@TempDir File projectDir) throws IOException {\n    javaUnzip(\"closest-branch.zip\", projectDir);\n    Path path = Paths.get(projectDir.getPath(), \"closest-branch\");\n\n    try (Repository repo = GitUtils.getRepositoryForDir(path, fakeClientLogger)) {\n\n      Set<String> serverCandidateNames = Set.of(\"foo\", \"closest_branch\", \"master\");\n\n      String branch = GitUtils.electBestMatchingServerBranchForCurrentHead(repo, serverCandidateNames, null, fakeClientLogger);\n      assertThat(branch).isEqualTo(\"closest_branch\");\n    }\n  }\n\n  @Test\n  void shouldElectMainBranchForNonAnalyzedChildBranch(@TempDir File projectDir) throws IOException {\n    javaUnzip(\"child-from-non-analyzed.zip\", projectDir);\n    Path path = Paths.get(projectDir.getPath(), \"child-from-non-analyzed\");\n    try (Repository repo = GitUtils.getRepositoryForDir(path, fakeClientLogger)) {\n\n      Set<String> serverCandidateNames = Set.of(\"foo\", \"branch_to_analyze\", \"master\");\n\n      String branch = GitUtils.electBestMatchingServerBranchForCurrentHead(repo, serverCandidateNames, \"master\", fakeClientLogger);\n      assertThat(branch).isEqualTo(\"master\");\n    }\n  }\n\n  @Test\n  void shouldReturnNullOnException() throws IOException {\n    Repository repo = mock(Repository.class);\n    RefDatabase db = mock(RefDatabase.class);\n    when(repo.getRefDatabase()).thenReturn(db);\n    when(db.exactRef(anyString())).thenThrow(new IOException());\n\n    String branch = GitUtils.electBestMatchingServerBranchForCurrentHead(repo, Set.of(\"foo\", \"bar\", \"master\"), \"master\", fakeClientLogger);\n\n    assertThat(branch).isNull();\n  }\n\n  @Test\n  void shouldFavorCurrentBranchIfMultipleCandidates(@TempDir File projectDir) throws IOException {\n    // Both main and same-as-master branches are pointing to HEAD, but same-as-master is the currently checked out branch\n    javaUnzip(\"two-branches-for-head.zip\", projectDir);\n    Path path = Paths.get(projectDir.getPath(), \"two-branches-for-head\");\n    try (Repository repo = GitUtils.getRepositoryForDir(path, fakeClientLogger)) {\n\n      Set<String> serverCandidateNames = Set.of(\"main\", \"same-as-master\", \"another\");\n\n      String branch = GitUtils.electBestMatchingServerBranchForCurrentHead(repo, serverCandidateNames, \"main\", fakeClientLogger);\n      assertThat(branch).isEqualTo(\"same-as-master\");\n    }\n  }\n\n  public void javaUnzip(String zipFileName, File toDir) throws IOException {\n    File testRepos = new File(\"src/test/test-repos\");\n    File zipFile = new File(testRepos, zipFileName);\n    javaUnzip(zipFile, toDir);\n  }\n\n  private static void javaUnzip(File zip, File toDir) {\n    try {\n      try (ZipFile zipFile = new ZipFile(zip)) {\n        Enumeration<? extends ZipEntry> entries = zipFile.entries();\n        while (entries.hasMoreElements()) {\n          ZipEntry entry = entries.nextElement();\n          File to = new File(toDir, entry.getName());\n          if (entry.isDirectory()) {\n            forceMkdir(to);\n          } else {\n            File parent = to.getParentFile();\n            forceMkdir(parent);\n\n            Files.copy(zipFile.getInputStream(entry), to.toPath());\n          }\n        }\n      }\n    } catch (Exception e) {\n      throw new IllegalStateException(format(\"Fail to unzip %s to %s\", zip, toDir), e);\n    }\n  }\n\n  private static void forceMkdir(final File directory) throws IOException {\n    if ((directory != null) && (!directory.mkdirs() && !directory.isDirectory())) {\n      throw new IOException(\"Cannot create directory '\" + directory + \"'.\");\n    }\n  }\n}\n"
  },
  {
    "path": "client/java-client-utils/src/test/java/org/sonarsource/sonarlint/core/client/utils/HotspotStatusTest.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass HotspotStatusTest {\n  @Test\n  void should_convert_all_enum_values() {\n    for (var rpcEnum : org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.HotspotStatus.values()) {\n      var converted = HotspotStatus.fromDto(rpcEnum);\n      assertEquals(rpcEnum.name(), converted.name());\n    }\n  }\n}\n"
  },
  {
    "path": "client/java-client-utils/src/test/java/org/sonarsource/sonarlint/core/client/utils/ImpactSeverityTests.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass ImpactSeverityTests {\n\n  @Test\n  void should_convert_all_enum_values() {\n    for (var rpcEnum : org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity.values()) {\n      var converted = ImpactSeverity.fromDto(rpcEnum);\n      assertEquals(rpcEnum.name(), converted.name());\n    }\n  }\n\n}\n"
  },
  {
    "path": "client/java-client-utils/src/test/java/org/sonarsource/sonarlint/core/client/utils/IssueResolutionStatusTest.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ResolutionStatus;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass IssueResolutionStatusTest {\n  @Test\n  void should_convert_all_enum_values() {\n    for (var rpcEnum : ResolutionStatus.values()) {\n      var converted = IssueResolutionStatus.fromDto(rpcEnum);\n      assertEquals(rpcEnum.name(), converted.name());\n    }\n  }\n}\n"
  },
  {
    "path": "client/java-client-utils/src/test/java/org/sonarsource/sonarlint/core/client/utils/LanguageTests.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass LanguageTests {\n\n  @Test\n  void should_convert_all_enum_values() {\n    for (var rpcEnum : org.sonarsource.sonarlint.core.rpc.protocol.common.Language.values()) {\n      var converted = Language.fromDto(rpcEnum);\n      assertEquals(rpcEnum.name(), converted.name());\n    }\n  }\n\n}\n"
  },
  {
    "path": "client/java-client-utils/src/test/java/org/sonarsource/sonarlint/core/client/utils/SoftwareQualityTests.java",
    "content": "/*\n * SonarLint Core - Java Client Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.client.utils;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass SoftwareQualityTests {\n\n  @Test\n  void should_convert_all_enum_values() {\n    for (var rpcEnum : org.sonarsource.sonarlint.core.rpc.protocol.common.SoftwareQuality.values()) {\n      var converted = SoftwareQuality.fromDto(rpcEnum);\n      assertEquals(rpcEnum.name(), converted.name());\n    }\n  }\n\n}\n"
  },
  {
    "path": "client/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-core-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../pom.xml</relativePath>\n  </parent>\n  <artifactId>sonarlint-client-parent</artifactId>\n  <packaging>pom</packaging>\n  <name>SonarLint Core - Client</name>\n\n  <properties>\n    <!--\n      client/ modules must compile with Java 11 because they are dependencies\n      for the SonarLint Core OSGi bundle used on SonarQube for Eclipse, which\n      itself compiles with Java 11.\n    -->\n    <maven.compiler.release>11</maven.compiler.release>\n  </properties>\n\n  <modules>\n    <module>java-client-dependencies</module>\n    <module>java-client-utils</module>\n    <module>java-client-osgi</module>\n    <module>rpc-java-client</module>\n  </modules>\n\n</project>\n"
  },
  {
    "path": "client/rpc-java-client/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-client-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../pom.xml</relativePath>\n  </parent>\n  <artifactId>sonarlint-rpc-java-client</artifactId>\n  <name>SonarLint Core - RPC Java Client</name>\n  <description>Java client for SonarLint RPC</description>\n  \n  <dependencies>\n    <dependency>\n      <groupId>com.google.code.findbugs</groupId>\n      <artifactId>jsr305</artifactId>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.eclipse.lsp4j</groupId>\n      <artifactId>org.eclipse.lsp4j.jsonrpc</artifactId>\n      <version>${lsp4j.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-rpc-protocol</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>io.sentry</groupId>\n      <artifactId>sentry</artifactId>\n      <version>${sentry.version}</version>\n    </dependency>\n\n    <!-- unit tests -->\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-engine</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.assertj</groupId>\n      <artifactId>assertj-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-lang3</artifactId>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <!--\n        This is required for the 'java-client-osgi' bundle in order to determine when a dependency is a source bundle. This is internally\n        used by the 'org.sonarsource.sonarlint.maven.shade.ext.ManifestBndTransformer'!\n      -->\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-source-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>attach-sources</id>\n            <phase>package</phase>\n            <goals>\n              <goal>jar-no-fork</goal>\n            </goals>\n            <configuration>\n              <archive>\n                <manifest>\n                  <addDefaultImplementationEntries>true</addDefaultImplementationEntries>\n                  <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>\n                </manifest>\n                <manifestEntries>\n                  <Bundle-Name>${project.name} Source</Bundle-Name>\n                  <Bundle-SymbolicName>${project.groupId}.${project.artifactId}.source</Bundle-SymbolicName>\n                </manifestEntries>\n              </archive>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "client/rpc-java-client/src/main/java/org/sonarsource/sonarlint/core/rpc/client/ClientJsonRpcLauncher.java",
    "content": "/*\n * SonarLint Core - RPC Java Client\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.client;\n\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.PrintWriter;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Paths;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport org.sonarsource.sonarlint.core.rpc.protocol.RpcErrorHandler;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SingleThreadedMessageConsumer;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintLauncherBuilder;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer;\n\npublic class ClientJsonRpcLauncher implements Closeable {\n  private final SonarLintRpcServer serverProxy;\n  private final Future<Void> future;\n  private final ExecutorService messageReaderExecutor;\n  private final ExecutorService messageWriterExecutor;\n  private final ExecutorService requestAndNotificationsSequentialExecutor;\n  private final ExecutorService requestsExecutor;\n\n  public ClientJsonRpcLauncher(InputStream in, OutputStream out, SonarLintRpcClientDelegate clientDelegate) {\n    messageReaderExecutor = Executors.newCachedThreadPool(r -> {\n      var t = new Thread(r);\n      t.setName(\"Client message reader\");\n      return t;\n    });\n    messageWriterExecutor = Executors.newCachedThreadPool(r -> {\n      var t = new Thread(r);\n      t.setName(\"Client message writer\");\n      return t;\n    });\n    this.requestAndNotificationsSequentialExecutor = Executors.newSingleThreadExecutor(r -> new Thread(r, \"SonarLint Client RPC sequential executor\"));\n    this.requestsExecutor = Executors.newCachedThreadPool(r -> new Thread(r, \"SonarLint Client RPC request executor\"));\n    var client = new SonarLintRpcClientImpl(clientDelegate, requestsExecutor, requestAndNotificationsSequentialExecutor);\n    var clientLauncher = new SonarLintLauncherBuilder<SonarLintRpcServer>()\n      .setLocalService(client)\n      .setRemoteInterface(SonarLintRpcServer.class)\n      .setInput(in)\n      .setOutput(out)\n      .setExecutorService(messageReaderExecutor)\n      .wrapMessages(m -> new SingleThreadedMessageConsumer(m, messageWriterExecutor,\n        ex -> client.logClientSideError(\"Error consuming RPC message\", ex)))\n      .traceMessages(getMessageTracer())\n      .setExceptionHandler(RpcErrorHandler::handleError)\n      .create();\n\n    this.serverProxy = clientLauncher.getRemoteProxy();\n    this.future = clientLauncher.startListening();\n  }\n\n  private static PrintWriter getMessageTracer() {\n    if (\"true\".equals(System.getProperty(\"sonarlint.debug.rpc\"))) {\n      try {\n        return new PrintWriter(Paths.get(System.getProperty(\"user.home\")).resolve(\".sonarlint\").resolve(\"rpc_client_session.log\").toFile(), StandardCharsets.UTF_8);\n      } catch (IOException e) {\n        System.err.println(\"Cannot write rpc debug logs file\");\n        e.printStackTrace();\n      }\n    }\n    return null;\n  }\n\n  public SonarLintRpcServer getServerProxy() {\n    return serverProxy;\n  }\n\n  @Override\n  public void close() {\n    requestsExecutor.shutdown();\n    requestAndNotificationsSequentialExecutor.shutdown();\n    // Stop the MessageProducer thread\n    future.cancel(true);\n    messageReaderExecutor.shutdownNow();\n    messageWriterExecutor.shutdownNow();\n    try {\n      if (!messageReaderExecutor.awaitTermination(10, TimeUnit.SECONDS)) {\n        throw new IllegalStateException(\"Unable to terminate the client message reader thread in a timely manner\");\n      }\n      if (!messageWriterExecutor.awaitTermination(10, TimeUnit.SECONDS)) {\n        throw new IllegalStateException(\"Unable to terminate the client message writer thread in a timely manner\");\n      }\n    } catch (InterruptedException e) {\n      Thread.currentThread().interrupt();\n      throw new IllegalStateException(\"Interrupted!\", e);\n    }\n  }\n}\n"
  },
  {
    "path": "client/rpc-java-client/src/main/java/org/sonarsource/sonarlint/core/rpc/client/ConfigScopeNotFoundException.java",
    "content": "/*\n * SonarLint Core - RPC Java Client\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.client;\n\npublic class ConfigScopeNotFoundException extends Exception {\n}\n"
  },
  {
    "path": "client/rpc-java-client/src/main/java/org/sonarsource/sonarlint/core/rpc/client/ConnectionNotFoundException.java",
    "content": "/*\n * SonarLint Core - RPC Java Client\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.client;\n\npublic class ConnectionNotFoundException extends Exception {\n}\n"
  },
  {
    "path": "client/rpc-java-client/src/main/java/org/sonarsource/sonarlint/core/rpc/client/Sloop.java",
    "content": "/*\n * SonarLint Core - RPC Java Client\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.client;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer;\n\npublic class Sloop {\n  private final SonarLintRpcServer rpcServer;\n  private final Process process;\n\n  public Sloop(SonarLintRpcServer rpcServer, Process process) {\n    this.rpcServer = rpcServer;\n    this.process = process;\n  }\n\n  public CompletableFuture<Void> shutdown() {\n    return rpcServer.shutdown();\n  }\n\n  public SonarLintRpcServer getRpcServer() {\n    return rpcServer;\n  }\n\n  /**\n   * @return a future that will be completed when the process exits, providing the exit value\n   */\n  public CompletableFuture<Integer> onExit() {\n    return process.onExit().thenApply(Process::exitValue);\n  }\n\n  public boolean isAlive() {\n    return process.isAlive();\n  }\n\n  public long getPid() {\n    return process.pid();\n  }\n}\n"
  },
  {
    "path": "client/rpc-java-client/src/main/java/org/sonarsource/sonarlint/core/rpc/client/SloopLauncher.java",
    "content": "/*\n * SonarLint Core - RPC Java Client\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.client;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.function.Supplier;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogLevel;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\n\npublic class SloopLauncher {\n  public static final String SLOOP_CLI_ENTRYPOINT_CLASS = \"org.sonarsource.sonarlint.core.backend.cli.SonarLintServerCli\";\n  private final SonarLintRpcClientDelegate rpcClient;\n  private final Function<List<String>, ProcessBuilder> processBuilderFactory;\n  private final Supplier<String> osNameSupplier;\n\n  public SloopLauncher(SonarLintRpcClientDelegate rpcClient) {\n    this(rpcClient, ProcessBuilder::new, () -> System.getProperty(\"os.name\"));\n  }\n\n  SloopLauncher(SonarLintRpcClientDelegate rpcClient, Function<List<String>, ProcessBuilder> processBuilderFactory, Supplier<String> osNameSupplier) {\n    this.rpcClient = rpcClient;\n    this.processBuilderFactory = processBuilderFactory;\n    this.osNameSupplier = osNameSupplier;\n  }\n\n  public Sloop start(Path distPath) {\n    return start(distPath, null);\n  }\n\n  public Sloop start(Path distPath, @Nullable Path jrePath) {\n    return start(distPath, jrePath, null);\n  }\n\n  /**\n   * @param jvmOpts Each argument should be separated by a space, such as '-XX:+UseG1GC -XX:MaxHeapFreeRatio=50'\n   */\n  public Sloop start(Path distPath, @Nullable Path jrePath, @Nullable String jvmOpts) {\n    try {\n      return execute(distPath, jrePath, jvmOpts);\n    } catch (Exception e) {\n      logToClient(LogLevel.ERROR, \"Unable to start the SonarLint backend\", stackTraceToString(e));\n      throw new IllegalStateException(\"Unable to start the SonarLint backend\", e);\n    }\n  }\n\n  private static String stackTraceToString(Throwable t) {\n    var stringWriter = new StringWriter();\n    var printWriter = new PrintWriter(stringWriter);\n    t.printStackTrace(printWriter);\n    return stringWriter.toString();\n  }\n\n  /**\n   * Inspired from Apache commons-lang3\n   */\n  private boolean isWindows() {\n    var osName = osNameSupplier.get();\n    if (osName == null) {\n      return false;\n    }\n    return osName.startsWith(\"Windows\");\n  }\n\n  private Sloop execute(Path distPath, @Nullable Path jrePath, @Nullable String jvmOpts) throws IOException {\n    var jreHomePath = jrePath == null ? distPath.resolve(\"jre\") : jrePath;\n    logToClient(LogLevel.INFO, \"Using JRE from \" + jreHomePath, null);\n    var binDirPath = jreHomePath.resolve(\"bin\");\n    var jreJavaExePath = binDirPath.resolve(\"java\" + (isWindows() ? \".exe\" : \"\"));\n    if (!Files.exists(jreJavaExePath)) {\n      throw new IllegalArgumentException(\"The provided JRE path does not exist: \" + jreJavaExePath);\n    }\n    var processBuilder = processBuilderFactory.apply(createCommand(distPath, jreJavaExePath, jvmOpts));\n    processBuilder.directory(binDirPath.toFile());\n    processBuilder.redirectOutput(ProcessBuilder.Redirect.PIPE);\n    processBuilder.redirectInput(ProcessBuilder.Redirect.PIPE);\n    processBuilder.redirectError(ProcessBuilder.Redirect.PIPE);\n\n    var process = processBuilder.start();\n\n    // redirect process.getErrorStream() to the client logs\n    new StreamGobbler(process.getErrorStream(), stdErrLogConsumer()).start();\n    // use process.getInputStream() as an input for the client\n    var serverToClientInputStream = process.getInputStream();\n    // use process.getOutputStream() as the standard input of a subprocess that can be written to\n    var clientToServerOutputStream = process.getOutputStream();\n    var clientLauncher = new ClientJsonRpcLauncher(serverToClientInputStream, clientToServerOutputStream, rpcClient);\n    process.onExit().thenAccept(p -> clientLauncher.close());\n\n    var serverProxy = clientLauncher.getServerProxy();\n    return new Sloop(serverProxy, process);\n  }\n\n  private static List<String> createCommand(Path distPath, Path jreJavaExePath, @Nullable String clientJvmOpts) {\n    var libFolderPath = distPath.resolve(\"lib\");\n    var classpath = libFolderPath.toAbsolutePath().normalize() + File.separator + '*';\n    List<String> commands = new ArrayList<>();\n    commands.add(jreJavaExePath.toAbsolutePath().normalize().toString());\n    var sonarlintEnvJvmOpts = System.getenv(\"SONARLINT_JVM_OPTS\");\n    if (sonarlintEnvJvmOpts != null) {\n      commands.addAll(Arrays.asList(sonarlintEnvJvmOpts.split(\" \")));\n    }\n    if (clientJvmOpts != null) {\n      commands.addAll(Arrays.asList(clientJvmOpts.split(\" \")));\n    }\n    // Avoid displaying the Java icon in the taskbar on Mac\n    commands.add(\"-Djava.awt.headless=true\");\n    commands.add(\"-classpath\");\n    commands.add(classpath);\n    commands.add(SLOOP_CLI_ENTRYPOINT_CLASS);\n    return commands;\n  }\n\n  private Consumer<String> stdErrLogConsumer() {\n    return s -> logToClient(LogLevel.ERROR, \"StdErr: \" + s, null);\n  }\n\n  private void logToClient(LogLevel level, @Nullable String message, @Nullable String stacktrace) {\n    rpcClient.log(new LogParams(level, message, null, Thread.currentThread().getName(),\n      SloopLauncher.class.getName(), stacktrace, Instant.now()));\n  }\n\n  private static class StreamGobbler extends Thread {\n    private final InputStream inputStream;\n    private final Consumer<String> consumer;\n\n    public StreamGobbler(InputStream inputStream, Consumer<String> consumer) {\n      this.inputStream = inputStream;\n      this.consumer = consumer;\n    }\n\n    @Override\n    public void run() {\n      new BufferedReader(new InputStreamReader(inputStream)).lines()\n        .forEach(consumer);\n    }\n  }\n}\n"
  },
  {
    "path": "client/rpc-java-client/src/main/java/org/sonarsource/sonarlint/core/rpc/client/SonarLintCancelChecker.java",
    "content": "/*\n * SonarLint Core - RPC Java Client\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.client;\n\nimport java.util.concurrent.CancellationException;\n\n/*\n * A class to use in place of {@link org.eclipse.lsp4j.jsonrpc.CancelChecker} to stop depending on lsp4j types in API\n * and services.\n * See SLCORE-663 for details.\n */\npublic class SonarLintCancelChecker {\n\n  private final org.eclipse.lsp4j.jsonrpc.CancelChecker lsp4JCancelChecker;\n\n  public SonarLintCancelChecker(org.eclipse.lsp4j.jsonrpc.CancelChecker cancelChecker) {\n    this.lsp4JCancelChecker = cancelChecker;\n  }\n\n  /**\n   * Throw a {@link CancellationException} if the currently processed request\n   * has been canceled.\n   */\n  public void checkCanceled() {\n    lsp4JCancelChecker.checkCanceled();\n  }\n\n  /**\n   * Check for cancellation without throwing an exception.\n   */\n  public boolean isCanceled() {\n    return lsp4JCancelChecker.isCanceled();\n  }\n}\n"
  },
  {
    "path": "client/rpc-java-client/src/main/java/org/sonarsource/sonarlint/core/rpc/client/SonarLintRpcClientDelegate.java",
    "content": "/*\n * SonarLint Core - RPC Java Client\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.client;\n\nimport java.net.URI;\nimport java.net.URL;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.CancellationException;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.PluginStatusDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.DependencyRiskDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.AssistBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.AssistBindingResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.NoBindingSuggestionFoundParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.ConnectionSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.embeddedserver.EmbeddedServerStartedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.event.DidReceiveServerHotspotEvent;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fix.FixSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.HotspotDetailsDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.RaisedHotspotDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.GetProxyPasswordAuthenticationResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.ProxyDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.X509CertificateDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.IssueDetailsDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.MessageActionItem;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.MessageType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowMessageRequestResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowSoonUnsupportedMessageParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.plugin.DidSkipLoadingPluginParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.progress.ReportProgressParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.progress.StartProgressParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.smartnotification.ShowSmartNotificationParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.TelemetryClientLiveAttributesResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\n\n/**\n * This is the interface that should be implemented by Java clients. We are trying to decouple from the RPC framework as much as possible,\n * but most of those methods should be pretty similar to {@link org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient}.\n * The \"delegation\" is made in {@link SonarLintRpcClientImpl}\n */\npublic interface SonarLintRpcClientDelegate {\n\n  /**\n   * Suggest a list of binding suggestions for each eligible configuration scope,\n   * based on registered connections, config scope, binding clues, and git remote URL.\n   * Scopes without any available suggestions are automatically excluded from the results.\n   */\n  void suggestBinding(Map<String, List<BindingSuggestionDto>> suggestionsByConfigScope);\n\n  void suggestConnection(Map<String, List<ConnectionSuggestionDto>> suggestionsByConfigScope);\n\n  void openUrlInBrowser(URL url);\n\n  /**\n   * Display a message to the user, usually in a small notification.\n   * The message is informative and does not imply applying an action.\n   */\n  void showMessage(MessageType type, String text);\n\n  /**\n   * Display a message to the user, usually in a small notification.\n   * This message has options that the user can pick from. Once user clicked on option, its String key is returned.\n   * If the user explicitly dismisses/closes the notification without clicking option, then it returns response with null selectedKey and closedByUser set to true.\n   * IMPORTANT: As users might not react to the notification at all, the returned future might block for an indefinite amount of time.\n   * So the caller should not block waiting for the result, but provide a callback instead.\n   */\n  default ShowMessageRequestResponse showMessageRequest(MessageType type, String text, List<MessageActionItem> actions) {\n    return null;\n  }\n\n  void log(LogParams params);\n\n  /**\n   * Display a one-time message to the user as a small notification.\n   * The message is informative and a link to the documentation should be available.\n   * The one-time mechanism should be handled on the client side (via a \"Don't show again\" button for example).\n   * There is an in-memory cache for the pair of connection ID + version that were already seen on the core side, but it is cleared after each restart.\n   */\n  void showSoonUnsupportedMessage(ShowSoonUnsupportedMessageParams params);\n\n  void showSmartNotification(ShowSmartNotificationParams params);\n\n  /**\n   * Return the client dynamic description.\n   * @see SonarLintRpcClient#getClientLiveInfo()\n   */\n  String getClientLiveDescription();\n\n  void showHotspot(String configurationScopeId, HotspotDetailsDto hotspotDetails);\n\n  /**\n   * Sends a notification to the client to show a specific issue in the IDE\n   */\n  void showIssue(String configurationScopeId, IssueDetailsDto issueDetails);\n\n  /**\n   * Sends a notification to the client to show a fix suggestion for a specific issue in the IDE\n   * The fix is only on a single files, but it may contain different locations\n   */\n  default void showFixSuggestion(String configurationScopeId, String issueKey, FixSuggestionDto fixSuggestion) {\n\n  }\n\n  /**\n   * Can be triggered by the backend when trying to handle a feature that needs a connection, e.g. open hotspot.\n   * @return the response to this connection creation assist request, that contains the new connection. The client can cancel the request if the user stops the creation process.\n   * @throws java.util.concurrent.CancellationException if the client cancels the process\n   */\n  AssistCreatingConnectionResponse assistCreatingConnection(AssistCreatingConnectionParams params, SonarLintCancelChecker cancelChecker) throws CancellationException;\n\n  /**\n   * Can be triggered by the backend when trying to handle a feature that needs a bound project, e.g. open hotspot.\n   * @return the response to this binding assist request, that contains the bound project. The client can cancel the request if the user stops the binding process.\n   * @throws java.util.concurrent.CancellationException if the client cancels the process\n   */\n  AssistBindingResponse assistBinding(AssistBindingParams params, SonarLintCancelChecker cancelChecker) throws CancellationException;\n\n  /**\n   * Requests the client to start showing progress to users.\n   * @throws UnsupportedOperationException if there is an error while creating the corresponding UI\n   */\n  void startProgress(StartProgressParams params) throws UnsupportedOperationException;\n\n  /**\n   * Reports progress to the client.\n   */\n  void reportProgress(ReportProgressParams params);\n\n  void didSynchronizeConfigurationScopes(Set<String> configurationScopeIds);\n\n  /**\n   * @throws ConnectionNotFoundException if the connection doesn't exist on the client side\n   * @return null if no credentials are available for this connection (backend may use unauthenticated HTTP requests)\n   */\n  @CheckForNull\n  Either<TokenDto, UsernamePasswordDto> getCredentials(String connectionId) throws ConnectionNotFoundException;\n\n  List<ProxyDto> selectProxies(URI uri);\n\n  GetProxyPasswordAuthenticationResponse getProxyPasswordAuthentication(String host, int port, String protocol, String prompt, String scheme, URL targetHost);\n\n  /**\n   * @param chain the peer certificate chain\n   * @param authType the key exchange algorithm used\n   */\n  boolean checkServerTrusted(List<X509CertificateDto> chain, String authType);\n\n  @Deprecated(since = \"10.3\")\n  default void didReceiveServerHotspotEvent(DidReceiveServerHotspotEvent params) {\n    // no-op\n  }\n\n  /**\n   * @return null if the client is unable to match the branch\n   */\n  @CheckForNull\n  String matchSonarProjectBranch(String configurationScopeId, String mainBranchName, Set<String> allBranchesNames,\n    SonarLintCancelChecker cancelChecker) throws ConfigScopeNotFoundException;\n\n  @Deprecated(since = \"10.23\", forRemoval = true)\n  default boolean matchProjectBranch(String configurationScopeId, String branchNameToMatch, SonarLintCancelChecker cancelChecker) {\n    return true;\n  }\n\n  void didChangeMatchedSonarProjectBranch(String configScopeId, String newMatchedBranchName);\n\n  TelemetryClientLiveAttributesResponse getTelemetryLiveAttributes();\n\n  void didChangeTaintVulnerabilities(String configurationScopeId, Set<UUID> closedTaintVulnerabilityIds, List<TaintVulnerabilityDto> addedTaintVulnerabilities,\n    List<TaintVulnerabilityDto> updatedTaintVulnerabilities);\n\n  default void didChangeDependencyRisks(String configurationScopeId, Set<UUID> closedDependencyRiskIds, List<DependencyRiskDto> addedDependencyRisks,\n    List<DependencyRiskDto> updatedDependencyRisks) {\n  }\n\n  default Path getBaseDir(String configurationScopeId) throws ConfigScopeNotFoundException {\n    return null;\n  }\n\n  List<ClientFileDto> listFiles(String configScopeId) throws ConfigScopeNotFoundException;\n\n  void noBindingSuggestionFound(NoBindingSuggestionFoundParams params);\n\n  void didChangeAnalysisReadiness(Set<String> configurationScopeIds, boolean areReadyForAnalysis);\n\n  default void raiseIssues(String configurationScopeId, Map<URI, List<RaisedIssueDto>> issuesByFileUri, boolean isIntermediatePublication, @Nullable UUID analysisId) {\n  }\n\n  default void raiseHotspots(String configurationScopeId, Map<URI, List<RaisedHotspotDto>> hotspotsByFileUri, boolean isIntermediatePublication, @Nullable UUID analysisId) {\n  }\n\n  default void didSkipLoadingPlugin(String configurationScopeId, Language language, DidSkipLoadingPluginParams.SkipReason reason, String minVersion,\n    @Nullable String currentVersion) {\n  }\n\n  default void didDetectSecret(String configurationScopeId) {\n  }\n\n  default void promoteExtraEnabledLanguagesInConnectedMode(String configurationScopeId, Set<Language> languagesToPromote) {\n  }\n\n  default Map<String, String> getInferredAnalysisProperties(String configurationScopeId, List<URI> filesToAnalyze) throws ConfigScopeNotFoundException {\n    return Map.of();\n  }\n\n  default Set<String> getFileExclusions(String configurationScopeId) throws ConfigScopeNotFoundException {\n    return Collections.emptySet();\n  }\n\n  default void invalidToken(String connectionId) {\n  }\n\n  default void embeddedServerStarted(EmbeddedServerStartedParams params) {\n  }\n\n  /**\n   * Called whenever the status of one or more analyzer plugins changes for a specific config scope.\n   * This can happen when plugins are loaded, unloaded, synced from a connection, or when a connection is removed.\n   * The parameters contain the full updated list of plugin statuses (one entry per known language) for the given scope.\n   * Clients should use this to refresh the \"Supported Languages\" panel for the corresponding project.\n   */\n  default void didChangePluginStatuses(String configScopeId, List<PluginStatusDto> pluginStatuses) {\n  }\n\n}\n"
  },
  {
    "path": "client/rpc-java-client/src/main/java/org/sonarsource/sonarlint/core/rpc/client/SonarLintRpcClientImpl.java",
    "content": "/*\n * SonarLint Core - RPC Java Client\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.client;\n\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.net.MalformedURLException;\nimport java.net.URI;\nimport java.net.URL;\nimport java.time.Instant;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.Executor;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport org.eclipse.lsp4j.jsonrpc.CancelChecker;\nimport org.eclipse.lsp4j.jsonrpc.CompletableFutures;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseError;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.OpenUrlInBrowserParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.DidChangeAnalysisReadinessParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.DidDetectSecretParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.GetFileExclusionsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.GetFileExclusionsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.GetInferredAnalysisPropertiesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.GetInferredAnalysisPropertiesResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.AssistBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.AssistBindingResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.NoBindingSuggestionFoundParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.SuggestBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.branch.DidChangeMatchedSonarProjectBranchParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.branch.MatchSonarProjectBranchParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.branch.MatchSonarProjectBranchResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.GetCredentialsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.GetCredentialsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.SuggestConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.embeddedserver.EmbeddedServerStartedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.event.DidReceiveServerHotspotEvent;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fix.ShowFixSuggestionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fs.GetBaseDirParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fs.GetBaseDirResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fs.ListFilesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fs.ListFilesResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.RaiseHotspotsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.ShowHotspotParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.CheckServerTrustedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.CheckServerTrustedResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.GetProxyPasswordAuthenticationParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.GetProxyPasswordAuthenticationResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.SelectProxiesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.SelectProxiesResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.info.GetClientLiveInfoResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaiseIssuesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.ShowIssueParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogLevel;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowMessageParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowMessageRequestParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowMessageRequestResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowSoonUnsupportedMessageParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.plugin.DidChangePluginStatusesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.plugin.DidSkipLoadingPluginParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.progress.ReportProgressParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.progress.StartProgressParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.promotion.PromoteExtraEnabledLanguagesInConnectedModeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.sca.DidChangeDependencyRisksParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.smartnotification.ShowSmartNotificationParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.sync.DidSynchronizeConfigurationScopeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.sync.InvalidTokenParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.taint.vulnerability.DidChangeTaintVulnerabilitiesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.TelemetryClientLiveAttributesResponse;\n\n/**\n * Implementation of {@link SonarLintRpcClient} that delegates to {@link SonarLintRpcClientDelegate} in order to simplify Java clients and avoid\n * leaking too many RPC-specific concept in each Java IDE.\n * In particular, this class attempt to:\n * <ul>\n *   <li>Hide the fact that RPC is asynchronous (don't let clients manipulate completable futures)</li>\n *   <li>Hide cancellation except if there is a functional need</li>\n *   <li>Convert Java exceptions to RPC error messages</li>\n * </ul>\n */\npublic class SonarLintRpcClientImpl implements SonarLintRpcClient {\n\n  private final SonarLintRpcClientDelegate delegate;\n  private final Executor requestsExecutor;\n  private final Executor requestAndNotificationsSequentialExecutor;\n\n  public SonarLintRpcClientImpl(SonarLintRpcClientDelegate delegate, Executor requestsExecutor, Executor requestAndNotificationsSequentialExecutor) {\n    this.delegate = delegate;\n    this.requestsExecutor = requestsExecutor;\n    this.requestAndNotificationsSequentialExecutor = requestAndNotificationsSequentialExecutor;\n  }\n\n  protected <R> CompletableFuture<R> requestAsync(Function<CancelChecker, R> code) {\n    CompletableFuture<CancelChecker> start = new CompletableFuture<>();\n    // First we schedule the processing of the request on the sequential executor, to maintain ordering of notifications, requests, responses,\n    // and cancellations\n    var sequentialFuture = start.thenApplyAsync(cancelChecker -> {\n      // We can maybe cancel early\n      cancelChecker.checkCanceled();\n      return cancelChecker;\n    }, requestAndNotificationsSequentialExecutor);\n    // Then requests are processed asynchronously to not block the processing of notifications, responses and cancellations\n    var requestFuture = sequentialFuture.thenApplyAsync(cancelChecker -> {\n      cancelChecker.checkCanceled();\n      return code.apply(cancelChecker);\n    }, requestsExecutor);\n    start.complete(new CompletableFutures.FutureCancelChecker(requestFuture));\n    return requestFuture;\n  }\n\n  protected CompletableFuture<Void> runAsync(Consumer<CancelChecker> code) {\n    CompletableFuture<CancelChecker> start = new CompletableFuture<>();\n    // First we schedule the processing of the request on the sequential executor, to maintain ordering of notifications, requests, responses,\n    // and cancellations\n    var sequentialFuture = start.thenApplyAsync(cancelChecker -> {\n      // We can maybe cancel early\n      cancelChecker.checkCanceled();\n      return cancelChecker;\n    }, requestAndNotificationsSequentialExecutor);\n    // Then requests are processed asynchronously to not block the processing of notifications, responses and cancellations\n    var requestFuture = sequentialFuture.<Void>thenApplyAsync(cancelChecker -> {\n      cancelChecker.checkCanceled();\n      code.accept(cancelChecker);\n      return null;\n    }, requestsExecutor);\n    start.complete(new CompletableFutures.FutureCancelChecker(requestFuture));\n    return requestFuture;\n  }\n\n  protected void notify(Runnable code) {\n    requestAndNotificationsSequentialExecutor.execute(() -> {\n      try {\n        code.run();\n      } catch (Throwable throwable) {\n        logClientSideError(\"Error when handling a notification\", throwable);\n      }\n    });\n  }\n\n  /**\n   * Client errors don't need to go over RPC, and can instead directly go through the delegate.\n   */\n  void logClientSideError(String message, Throwable throwable) {\n    delegate.log(new LogParams(LogLevel.ERROR, message, null, stackTraceToString(throwable), Instant.now()));\n  }\n\n  private static String stackTraceToString(Throwable t) {\n    var stringWriter = new StringWriter();\n    var printWriter = new PrintWriter(stringWriter);\n    t.printStackTrace(printWriter);\n    return stringWriter.toString();\n  }\n\n  @Override\n  public void suggestBinding(SuggestBindingParams params) {\n    notify(() -> delegate.suggestBinding(params.getSuggestions()));\n  }\n\n  @Override\n  public void suggestConnection(SuggestConnectionParams params) {\n    notify(() -> delegate.suggestConnection(params.getSuggestionsByConfigScopeId()));\n  }\n\n  @Override\n  public void openUrlInBrowser(OpenUrlInBrowserParams params) {\n    notify(() -> {\n      try {\n        delegate.openUrlInBrowser(URI.create(params.getUrl()).toURL());\n      } catch (MalformedURLException | IllegalArgumentException e) {\n        throw new ResponseErrorException(new ResponseError(ResponseErrorCode.InvalidParams, \"Not a valid URL: \" + params.getUrl(), params.getUrl()));\n      }\n    });\n  }\n\n  @Override\n  public void showMessage(ShowMessageParams params) {\n    notify(() -> delegate.showMessage(params.getType(), params.getText()));\n  }\n\n  @Override\n  public CompletableFuture<ShowMessageRequestResponse> showMessageRequest(ShowMessageRequestParams params) {\n    return requestAsync(cancelChecker -> delegate.showMessageRequest(params.getType(), params.getMessage(), params.getActions()));\n  }\n\n  @Override\n  public void log(LogParams params) {\n    notify(() -> delegate.log(params));\n  }\n\n  @Override\n  public void showSoonUnsupportedMessage(ShowSoonUnsupportedMessageParams params) {\n    notify(() -> delegate.showSoonUnsupportedMessage(params));\n  }\n\n  @Override\n  public void showSmartNotification(ShowSmartNotificationParams params) {\n    notify(() -> delegate.showSmartNotification(params));\n  }\n\n  @Override\n  public CompletableFuture<GetClientLiveInfoResponse> getClientLiveInfo() {\n    return requestAsync(cancelChecker -> new GetClientLiveInfoResponse(delegate.getClientLiveDescription()));\n  }\n\n  @Override\n  public void showHotspot(ShowHotspotParams params) {\n    notify(() -> delegate.showHotspot(params.getConfigurationScopeId(), params.getHotspotDetails()));\n  }\n\n  @Override\n  public void showIssue(ShowIssueParams params) {\n    notify(() -> delegate.showIssue(params.getConfigurationScopeId(), params.getIssueDetails()));\n  }\n\n  @Override\n  public void showFixSuggestion(ShowFixSuggestionParams params) {\n    notify(() -> delegate.showFixSuggestion(params.getConfigurationScopeId(), params.getIssueKey(), params.getFixSuggestion()));\n  }\n\n  @Override\n  public CompletableFuture<AssistCreatingConnectionResponse> assistCreatingConnection(AssistCreatingConnectionParams params) {\n    return requestAsync(cancelChecker -> delegate.assistCreatingConnection(params, new SonarLintCancelChecker(cancelChecker)));\n  }\n\n  @Override\n  public CompletableFuture<AssistBindingResponse> assistBinding(AssistBindingParams params) {\n    return requestAsync(cancelChecker -> delegate.assistBinding(params, new SonarLintCancelChecker(cancelChecker)));\n  }\n\n  @Override\n  public CompletableFuture<Void> startProgress(StartProgressParams params) {\n    return runAsync(cancelChecker -> {\n      try {\n        delegate.startProgress(params);\n      } catch (UnsupportedOperationException e) {\n        throw new ResponseErrorException(new ResponseError(SonarLintRpcErrorCode.PROGRESS_CREATION_FAILED, e.getMessage(), null));\n      }\n    });\n  }\n\n  @Override\n  public void reportProgress(ReportProgressParams params) {\n    notify(() -> delegate.reportProgress(params));\n  }\n\n  @Override\n  public void didSynchronizeConfigurationScopes(DidSynchronizeConfigurationScopeParams params) {\n    notify(() -> delegate.didSynchronizeConfigurationScopes(params.getConfigurationScopeIds()));\n  }\n\n  @Override\n  public CompletableFuture<GetCredentialsResponse> getCredentials(GetCredentialsParams params) {\n    return requestAsync(cancelChecker -> {\n      try {\n        return new GetCredentialsResponse(delegate.getCredentials(params.getConnectionId()));\n      } catch (ConnectionNotFoundException e) {\n        throw new ResponseErrorException(\n          new ResponseError(SonarLintRpcErrorCode.CONNECTION_NOT_FOUND, \"Unknown connection: \" + params.getConnectionId(), params.getConnectionId()));\n      }\n    });\n  }\n\n  @Override\n  public CompletableFuture<TelemetryClientLiveAttributesResponse> getTelemetryLiveAttributes() {\n    return requestAsync(cancelChecker -> delegate.getTelemetryLiveAttributes());\n  }\n\n  @Override\n  public CompletableFuture<SelectProxiesResponse> selectProxies(SelectProxiesParams params) {\n    return requestAsync(cancelChecker -> new SelectProxiesResponse(delegate.selectProxies(params.getUri())));\n  }\n\n  @Override\n  public CompletableFuture<GetProxyPasswordAuthenticationResponse> getProxyPasswordAuthentication(GetProxyPasswordAuthenticationParams params) {\n    return requestAsync(cancelChecker -> delegate.getProxyPasswordAuthentication(params.getHost(), params.getPort(), params.getProtocol(), params.getPrompt(), params.getScheme(),\n      params.getTargetHost()));\n  }\n\n  @Override\n  public CompletableFuture<CheckServerTrustedResponse> checkServerTrusted(CheckServerTrustedParams params) {\n    return requestAsync(cancelChecker -> new CheckServerTrustedResponse(delegate.checkServerTrusted(params.getChain(), params.getAuthType())));\n  }\n\n  @Override\n  public void didReceiveServerHotspotEvent(DidReceiveServerHotspotEvent params) {\n    notify(() -> delegate.didReceiveServerHotspotEvent(params));\n  }\n\n  @Override\n  public CompletableFuture<MatchSonarProjectBranchResponse> matchSonarProjectBranch(MatchSonarProjectBranchParams params) {\n    return requestAsync(cancelChecker -> {\n      try {\n        return new MatchSonarProjectBranchResponse(\n          delegate.matchSonarProjectBranch(params.getConfigurationScopeId(), params.getMainSonarBranchName(),\n            params.getAllSonarBranchesNames(), new SonarLintCancelChecker(cancelChecker)));\n      } catch (ConfigScopeNotFoundException e) {\n        throw configScopeNotFoundError(params.getConfigurationScopeId());\n      }\n    });\n  }\n\n  @Override\n  public void didChangeMatchedSonarProjectBranch(DidChangeMatchedSonarProjectBranchParams params) {\n    notify(() -> delegate.didChangeMatchedSonarProjectBranch(params.getConfigScopeId(), params.getNewMatchedBranchName()));\n  }\n\n  @Override\n  public CompletableFuture<GetBaseDirResponse> getBaseDir(GetBaseDirParams params) {\n    return requestAsync(cancelChecker -> {\n      try {\n        return new GetBaseDirResponse(delegate.getBaseDir(params.getConfigurationScopeId()));\n      } catch (ConfigScopeNotFoundException e) {\n        throw configScopeNotFoundError(params.getConfigurationScopeId());\n      }\n    });\n  }\n\n  @Override\n  public CompletableFuture<ListFilesResponse> listFiles(ListFilesParams params) {\n    return requestAsync(cancelChecker -> {\n      try {\n        return new ListFilesResponse(delegate.listFiles(params.getConfigScopeId()));\n      } catch (ConfigScopeNotFoundException e) {\n        throw configScopeNotFoundError(params.getConfigScopeId());\n      }\n    });\n  }\n\n  private static ResponseErrorException configScopeNotFoundError(String configScopeId) {\n    return new ResponseErrorException(\n      new ResponseError(SonarLintRpcErrorCode.CONFIG_SCOPE_NOT_FOUND, \"Unknown config scope: \" + configScopeId, configScopeId));\n  }\n\n  @Override\n  public void didChangeTaintVulnerabilities(DidChangeTaintVulnerabilitiesParams params) {\n    notify(() -> delegate.didChangeTaintVulnerabilities(params.getConfigurationScopeId(), params.getClosedTaintVulnerabilityIds(), params.getAddedTaintVulnerabilities(),\n      params.getUpdatedTaintVulnerabilities()));\n  }\n\n  @Override\n  public void didChangeDependencyRisks(DidChangeDependencyRisksParams params) {\n    notify(() -> delegate.didChangeDependencyRisks(params.getConfigurationScopeId(), params.getClosedDependencyRiskIds(), params.getAddedDependencyRisks(),\n      params.getUpdatedDependencyRisks()));\n  }\n\n  @Override\n  public void noBindingSuggestionFound(NoBindingSuggestionFoundParams params) {\n    notify(() -> delegate.noBindingSuggestionFound(params));\n  }\n\n  public void didChangeAnalysisReadiness(DidChangeAnalysisReadinessParams params) {\n    notify(() -> delegate.didChangeAnalysisReadiness(params.getConfigurationScopeIds(), params.areReadyForAnalysis()));\n  }\n\n  @Override\n  public void raiseIssues(RaiseIssuesParams params) {\n    notify(() -> delegate.raiseIssues(params.getConfigurationScopeId(), params.getIssuesByFileUri(), params.isIntermediatePublication(), params.getAnalysisId()));\n  }\n\n  @Override\n  public void raiseHotspots(RaiseHotspotsParams params) {\n    notify(() -> delegate.raiseHotspots(params.getConfigurationScopeId(), params.getHotspotsByFileUri(), params.isIntermediatePublication(), params.getAnalysisId()));\n  }\n\n  @Override\n  public void didSkipLoadingPlugin(DidSkipLoadingPluginParams params) {\n    notify(() -> delegate.didSkipLoadingPlugin(params.getConfigurationScopeId(), params.getLanguage(), params.getReason(), params.getMinVersion(), params.getCurrentVersion()));\n  }\n\n  @Override\n  public void didDetectSecret(DidDetectSecretParams params) {\n    notify(() -> delegate.didDetectSecret(params.getConfigurationScopeId()));\n  }\n\n  @Override\n  public void promoteExtraEnabledLanguagesInConnectedMode(PromoteExtraEnabledLanguagesInConnectedModeParams params) {\n    notify(() -> delegate.promoteExtraEnabledLanguagesInConnectedMode(params.getConfigurationScopeId(), params.getLanguagesToPromote()));\n  }\n\n  @Override\n  public CompletableFuture<GetInferredAnalysisPropertiesResponse> getInferredAnalysisProperties(GetInferredAnalysisPropertiesParams params) {\n    return requestAsync(cancelChecker -> {\n      try {\n        return new GetInferredAnalysisPropertiesResponse(delegate.getInferredAnalysisProperties(params.getConfigurationScopeId(), params.getFilesToAnalyze()));\n      } catch (ConfigScopeNotFoundException e) {\n        throw configScopeNotFoundError(params.getConfigurationScopeId());\n      }\n    });\n  }\n\n  @Override\n  public CompletableFuture<GetFileExclusionsResponse> getFileExclusions(GetFileExclusionsParams params) {\n    return requestAsync(cancelChecker -> {\n      try {\n        return new GetFileExclusionsResponse(delegate.getFileExclusions(params.getConfigurationScopeId()));\n      } catch (ConfigScopeNotFoundException e) {\n        throw configScopeNotFoundError(params.getConfigurationScopeId());\n      }\n    });\n  }\n\n  @Override\n  public void invalidToken(InvalidTokenParams params) {\n    notify(() -> delegate.invalidToken(params.getConnectionId()));\n  }\n\n  @Override\n  public void embeddedServerStarted(EmbeddedServerStartedParams params) {\n    notify(() -> delegate.embeddedServerStarted(params));\n  }\n\n  @Override\n  public void didChangePluginStatuses(DidChangePluginStatusesParams params) {\n    notify(() -> delegate.didChangePluginStatuses(params.getConfigScopeId(), params.getPluginStatuses()));\n  }\n\n}\n"
  },
  {
    "path": "client/rpc-java-client/src/main/java/org/sonarsource/sonarlint/core/rpc/client/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Java Client\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.client;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "client/rpc-java-client/src/test/java/org/sonarsource/sonarlint/core/rpc/client/SloopLauncherTests.java",
    "content": "/*\n * SonarLint Core - RPC Java Client\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.client;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.function.Function;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.mockito.ArgumentCaptor;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogLevel;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass SloopLauncherTests {\n  private Process mockProcess;\n  private SloopLauncher underTest;\n  private Sloop sloop;\n  private Function<List<String>, ProcessBuilder> mockPbFactory;\n  private SonarLintRpcClientDelegate rpcClient;\n  private String osName = \"Linux\";\n  private Path fakeJreHomePath;\n  private Path fakeJreJavaLinuxPath;\n  private Path fakeJreJavaWindowsPath;\n\n  @BeforeEach\n  void prepare(@TempDir Path fakeJreHomePath) throws IOException {\n    this.fakeJreHomePath = fakeJreHomePath;\n    var fakeJreBinFolder = this.fakeJreHomePath.resolve(\"bin\");\n    Files.createDirectories(fakeJreBinFolder);\n    fakeJreJavaLinuxPath = fakeJreBinFolder.resolve(\"java\");\n    Files.createFile(fakeJreJavaLinuxPath);\n    fakeJreJavaWindowsPath = fakeJreBinFolder.resolve(\"java.exe\");\n    Files.createFile(fakeJreJavaWindowsPath);\n    mockPbFactory = mock();\n    var mockProcessBuilder = mock(ProcessBuilder.class);\n    when(mockPbFactory.apply(any())).thenReturn(mockProcessBuilder);\n    mockProcess = mock(Process.class);\n    when(mockProcess.onExit()).thenReturn(new CompletableFuture<>());\n    doReturn(mockProcess).when(mockProcessBuilder).start();\n\n    when(mockProcess.getInputStream()).thenReturn(new ByteArrayInputStream(new byte[0]));\n    when(mockProcess.getErrorStream()).thenReturn(new ByteArrayInputStream(new byte[0]));\n    when(mockProcess.getOutputStream()).thenReturn(new ByteArrayOutputStream());\n\n    rpcClient = mock(SonarLintRpcClientDelegate.class);\n    underTest = new SloopLauncher(rpcClient, mockPbFactory, () -> osName);\n  }\n\n  @Test\n  void test_command_with_embedded_jre(@TempDir Path distPath) throws IOException {\n    var bundledJreBinPath = distPath.resolve(\"jre\").resolve(\"bin\");\n    Files.createDirectories(bundledJreBinPath);\n    var bundledJrejavaPath = bundledJreBinPath.resolve(\"java\");\n    Files.createFile(bundledJrejavaPath);\n\n    sloop = underTest.start(distPath);\n\n    verify(mockPbFactory).apply(List.of(bundledJrejavaPath.toString(), \"-Djava.awt.headless=true\",\n      \"-classpath\", distPath.resolve(\"lib\") + File.separator + '*', \"org.sonarsource.sonarlint.core.backend.cli.SonarLintServerCli\"));\n    assertThat(sloop.getRpcServer()).isNotNull();\n  }\n\n  @Test\n  void test_command_with_custom_jre_on_linux(@TempDir Path distPath) {\n    sloop = underTest.start(distPath, fakeJreHomePath);\n\n    verify(mockPbFactory)\n      .apply(List.of(fakeJreJavaLinuxPath.toString(), \"-Djava.awt.headless=true\",\n        \"-classpath\", distPath.resolve(\"lib\") + File.separator + '*', \"org.sonarsource.sonarlint.core.backend.cli.SonarLintServerCli\"));\n    assertThat(sloop.getRpcServer()).isNotNull();\n  }\n\n  @Test\n  void test_command_with_custom_jre_on_windows(@TempDir Path distPath) {\n    osName = \"Windows\";\n    sloop = underTest.start(distPath, fakeJreHomePath);\n\n    verify(mockPbFactory)\n      .apply(List.of(fakeJreJavaWindowsPath.toString(), \"-Djava.awt.headless=true\",\n        \"-classpath\", distPath.resolve(\"lib\") + File.separator + '*', \"org.sonarsource.sonarlint.core.backend.cli.SonarLintServerCli\"));\n    assertThat(sloop.getRpcServer()).isNotNull();\n  }\n\n  @Test\n  void test_redirect_stderr_to_client(@TempDir Path distPath) {\n    when(mockProcess.getErrorStream()).thenReturn(new ByteArrayInputStream(\"Some errors\\nSome other error\".getBytes()));\n\n    sloop = underTest.start(distPath, fakeJreHomePath);\n\n    ArgumentCaptor<LogParams> captor = ArgumentCaptor.captor();\n    verify(rpcClient, timeout(1000).times(3)).log(captor.capture());\n\n    assertThat(captor.getAllValues())\n      .filteredOn(m -> m.getLevel() == LogLevel.ERROR)\n      .extracting(LogParams::getMessage)\n      .containsExactly(\"StdErr: Some errors\", \"StdErr: Some other error\");\n  }\n\n  @Test\n  void test_log_stacktrace(@TempDir Path distPath) {\n    doThrow(new IllegalStateException(\"Some error\")).when(mockProcess).getInputStream();\n\n    assertThrows(IllegalStateException.class, () -> sloop = underTest.start(distPath, fakeJreHomePath));\n\n    ArgumentCaptor<LogParams> captor = ArgumentCaptor.captor();\n    verify(rpcClient, times(2)).log(captor.capture());\n\n    var log = captor.getValue();\n\n    assertThat(log.getMessage()).isEqualTo(\"Unable to start the SonarLint backend\");\n    assertThat(log.getStackTrace()).startsWith(\"java.lang.IllegalStateException: Some error\");\n  }\n\n  @Test\n  void test_throw_error_if_java_path_does_not_exist(@TempDir Path distPath) {\n    var wrongPath = Paths.get(\"wrongPath\");\n    assertThrows(IllegalStateException.class, () -> sloop = underTest.start(distPath, wrongPath));\n  }\n\n  @Test\n  void test_command_with_custom_jre_on_linux_and_jvm_option(@TempDir Path distPath) {\n    sloop = underTest.start(distPath, fakeJreHomePath, \"-XX:+UseG1GC -XX:MaxHeapFreeRatio=50\");\n\n    verify(mockPbFactory)\n      .apply(List.of(fakeJreJavaLinuxPath.toString(), \"-XX:+UseG1GC\", \"-XX:MaxHeapFreeRatio=50\", \"-Djava.awt.headless=true\",\n        \"-classpath\", distPath.resolve(\"lib\") + File.separator + '*', \"org.sonarsource.sonarlint.core.backend.cli.SonarLintServerCli\"));\n    assertThat(sloop.getRpcServer()).isNotNull();\n  }\n}\n"
  },
  {
    "path": "client/rpc-java-client/src/test/java/org/sonarsource/sonarlint/core/rpc/client/SonarLintRpcClientImplTest.java",
    "content": "/*\n * SonarLint Core - RPC Java Client\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.client;\n\nimport java.util.concurrent.ExecutionException;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.branch.MatchProjectBranchParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\n\nclass SonarLintRpcClientImplTest {\n  @Test\n  void it_should_print_notification_handling_errors_to_the_client_logs() {\n    var fakeClientDelegate = mock(SonarLintRpcClientDelegate.class);\n    var argumentCaptor = ArgumentCaptor.forClass(LogParams.class);\n\n    var rpcClient = new SonarLintRpcClientImpl(fakeClientDelegate, Runnable::run, Runnable::run);\n\n    rpcClient.notify(() -> {\n      throw new IllegalStateException(\"Kaboom\");\n    });\n\n    verify(fakeClientDelegate).log(argumentCaptor.capture());\n    assertThat(argumentCaptor.getAllValues())\n      .anySatisfy(logParam -> {\n        assertThat(logParam.getMessage()).contains(\"Error when handling a notification\");\n        assertThat(logParam.getStackTrace()).contains(\"java.lang.IllegalStateException: Kaboom\");\n      });\n  }\n\n  @Test\n  void it_should_match_project_branch() throws ExecutionException, InterruptedException {\n    var fakeClientDelegate = mock(SonarLintRpcClientDelegate.class);\n    var rpcClient = new SonarLintRpcClientImpl(fakeClientDelegate, Runnable::run, Runnable::run);\n    var params = new MatchProjectBranchParams(\"configScopeId\", \"branch\");\n\n    var response = rpcClient.matchProjectBranch(params);\n\n    assertThat(params.getConfigurationScopeId()).isEqualTo(\"configScopeId\");\n    assertThat(params.getServerBranchToMatch()).isEqualTo(\"branch\");\n    assertThat(response.get().isBranchMatched()).isTrue();\n  }\n}\n"
  },
  {
    "path": "doc/analyzer_management.md",
    "content": "# Artifact Management in SonarLint Core\n\nThis document explains how SQ:IDE manages the artifacts (plugins and plugin dependencies) required\nto analyze code. It covers where artifacts come from, how they are loaded, and how the loading\nstrategy adapts to standalone vs. connected mode.\n\n---\n\n## Glossary\n\n| Term | Meaning |\n|------|---------|\n| **Plugin** | A standard SonarSource analyzer packaged as a JAR. Loaded by the analysis engine via the plugin API. |\n| **Plugin dependency / sidecar** | An artifact required for a plugin to work but not itself a plugin (e.g. an OmniSharp distribution for the C# analyzer). Deployed on binaries.sonarsource.com alongside plugins. |\n| **Artifact** | Umbrella term for both plugins and plugin dependencies. |\n| **Artifact origin** | Where an artifact physically came from: `EMBEDDED`, `ON_DEMAND`, `SONARQUBE_SERVER`, `SONARQUBE_CLOUD`. Represented by the `ArtifactOrigin` enum. |\n\n---\n\n## Motivation\n\nHistorically SQ:IDE shipped every language analyzer bundled inside the IDE extension. This made\nthe extension large and required a release each time a new analyzer version was needed.\n\nTwo changes were made to address this:\n\n1. **On-demand source.** A curated set of artifacts (e.g. CFamily, C# OSS, OmniSharp) can be\n   downloaded at runtime from `binaries.sonarsource.com`, reducing the size of the shipped\n   extension. This also enables plugin _dependencies_ to be distributed alongside plugins on the\n   same infrastructure.\n\n2. **Policy-based loading.** Rather than having a single monolithic resolver, artifact loading is\n   now split between *where artifacts come from* (`ArtifactSource`) and *how sources are\n   combined* (`ArtifactsLoadingStrategy`).\n\n---\n\n## Core Abstractions\n\n### `ArtifactSource`\n\nRepresents **one place where artifacts can be obtained**. Every source exposes two methods:\n\n```\nlistAvailableArtifacts(Set<SonarLanguage> enabledLanguages) → List<AvailableArtifact>\n   Returns all artifacts known to this source for the given set of enabled languages, without triggering any downloads. This is a pure query.\n   Implementations should return artifacts corresponding to enabled languages, and artifacts that are not tied to a specific language.\n\nload(String artifactKey) → Optional<ResolvedArtifact>\n    Action. Ensures the artifact is available, scheduling a background\n    download if needed. Returns empty if this source does not handle\n    the given key.\n    May return a ResolvedArtifact in DOWNLOADING state.\n```\n\n`ResolvedArtifact` captures the outcome: `(ArtifactState state, Path path, ArtifactOrigin origin,\nVersion version)`. The state is one of: `ACTIVE`, `SYNCED`, `DOWNLOADING`, `FAILED`, `PREMIUM`,\n`UNSUPPORTED`.\n\nThere are three concrete implementations:\n\n#### `EmbeddedPluginSource`\n\nBacked by JARs physically bundled in the IDE extension. Never triggers downloads. Covers both\nlanguage plugins (e.g. `sonar-java-plugin.jar`) and companion plugins embedded by the client\n(e.g. `sonarlint-omnisharp-plugin.jar`).\n\nTwo factory methods select the right set of paths:\n- `EmbeddedPluginSource.forStandalone(params)` — standalone embedded paths + optional C# OSS standalone JAR\n- `EmbeddedPluginSource.forConnected(params)` — connected-mode embedded paths only\n\n#### `BinariesArtifactSource`\n\nBacked by `binaries.sonarsource.com`. Handles both **plugins** (CFamily, C# OSS) and **plugin\ndependencies** (OmniSharp distributions). Artifacts are cached under\n`<storage-root>/ondemand-plugins/`.\n\n- `listAvailableArtifacts(Set<SonarLanguage> enabledLanguages)` lists artifacts available on Binaries.\n- `load(key)` checks the cache first; if absent, schedules an async download and returns\n  `DOWNLOADING` immediately. A `PluginStatusUpdateEvent` is published when the download finishes\n  (with `ACTIVE` or `FAILED`).\n- Signature verification (`OnDemandPluginSignatureVerifier`) runs after every download.\n- Concurrent duplicate downloads for the same artifact are deduplicated (`UniqueTaskExecutor`).\n\n#### `ServerPluginSource`\n\nBacked by a specific SonarQube Server or SonarQube Cloud connection. One instance per connection,\ncached by `ConnectedArtifactsLoadingStrategyFactory`.\n\n- `listAvailableArtifacts(Set<SonarLanguage> enabledLanguages)` returns what the server currently exposes (converted from the server\n  plugin list). Falls back to an empty list if the server is unreachable.\n- `listServerPlugins()` returns the raw `ServerPlugin` list (richer metadata, used by the loading\n  policy for skip-list and companion decisions).\n- `load(key)` returns `SYNCED` if the artifact is on disk with a matching hash, or schedules an\n  async download (returns `DOWNLOADING`). Enterprise-variant resolution happens here.\n- `isAnyDownloadInProgress()` delegates to `ServerPluginDownloader`.\n\n---\n\n### `ArtifactsLoadingStrategy`\n\nRepresents **how sources are combined** to produce the full set of resolved artifacts for a given\ncontext (standalone or connected). The interface exposes:\n\n```\nresolveArtifacts() → ArtifactsLoadingResult\n    Resolves all artifacts from all managed sources, applying priority and\n    policy rules. The result holds the resolved artifact map (keyed by\n    artifact key) and the set of enabled languages.\n    May schedule background downloads.\n```\n\nThere are two implementations:\n\n#### `StandaloneArtifactsLoadingStrategy`\n\nUsed when the user has no server connection. Sources (in ascending priority):\n\n| Priority | Source                                    | Why                                          |\n|----------|-------------------------------------------|----------------------------------------------|\n| Lowest   | `BinariesArtifactSource`       | Fallback for on-demand artifacts             |\n| Highest  | `EmbeddedPluginSource` (standalone paths) | Overrides on-demand when embedded is present |\n\nLanguages available only in connected mode are reported as `PREMIUM` (no artifact path, just a\nstatus indicating the language requires a server connection).\n\nResolution uses a winner-map (ascending priority, last writer wins per key), then one post-processing pass:\n- For each `SonarLanguage` whose plugin key is still unresolved and is connected-only: mark as `PREMIUM`.\n\n#### `ConnectedArtifactsLoadingStrategy`\n\nUsed when the user has a server connection. One instance per connection, cached by\n`ConnectedArtifactsLoadingStrategyFactory`. Sources (in ascending priority):\n\n| Priority | Source | Why |\n|----------|--------|-----|\n| Lowest | `BinariesArtifactSource` | Fallback for on-demand artifacts |\n| Middle | `ServerPluginSource` | Server-specific analyzers override binaries |\n| Highest | `EmbeddedPluginSource` (connected paths) | JARs the client always carries (normally win) |\n\nResolution uses a winner-map (ascending priority, last writer wins per key), then two post-processing passes:\n1. **Enterprise-variant deduplication**: when a different-key enterprise variant (e.g. `csharpenterprise`) is present, the base key is removed so both are not loaded simultaneously.\n2. **Enterprise priority override**: when the server reports a plugin as enterprise (same-key enterprise editions such as Go or IaC), the server source is forced for that key even if the embedded source would normally win.\n\n---\n\n## Artifact State Machine\n\n```\n           load() called\n               │\n               ▼\n       ┌───────────────┐\n       │  On disk?     │──── Yes ──► ACTIVE / SYNCED\n       └───────────────┘\n               │ No\n               ▼\n       ┌───────────────┐\n       │  Schedule     │\n       │  download     │──────────► DOWNLOADING\n       └───────────────┘\n               │ (async)\n        ┌──────┴──────┐\n        ▼             ▼\n      ACTIVE        FAILED\n  (event fired)  (event fired)\n```\n\n`PluginStatusUpdateEvent` is published on every transition out of `DOWNLOADING`. The IDE listens\nto these events to update the status bar.\n\n---\n\n## Step-by-Step Flow\n\n### 1. Initialization\n\nWhen `SonarLintBackendImpl.initialize()` is called by the IDE extension, the Spring context boots.\n`PluginsService`, `BinariesArtifactSource`, and the connected-mode factory are\ninstantiated as singletons. No artifacts are loaded yet.\n\n### 2. Connection Sync (connected mode only)\n\nIf the workspace has bound projects, `PluginsSynchronizer` hits `api/plugins/installed` to learn\nwhat the server exposes. This updates the local storage with expected plugin paths and hashes.\n\n### 3. Artifact Resolution\n\nWhen analysis is requested (or when the IDE requests plugin statuses), `PluginsService` calls\n`resolveArtifacts()` on the appropriate `ArtifactsLoadingStrategy`. The strategy runs its\nresolution passes and returns an `ArtifactsLoadingResult`.\n\nArtifacts in `DOWNLOADING` state have a non-null `downloadFuture`. The caller uses\n`ArtifactsLoadingResult.whenAllArtifactsDownloaded()` to run a callback (publishing a\n`PluginsSynchronizedEvent`) once all pending downloads complete.\n\n### 4. Background Download\n\nWhen `load()` on a source cannot find the artifact locally, it enqueues a background download:\n- `BinariesArtifactSource` uses `UniqueTaskExecutor` + HTTP fetch from\n  `binaries.sonarsource.com`, followed by signature verification.\n- `ServerPluginSource` delegates to `ServerPluginDownloader`, which deduplicates concurrent\n  downloads for the same connection.\n\n### 5. Class Loading\n\nOnce all required artifacts are in `ACTIVE` or `SYNCED` state, `PluginsService` passes the\nresolved JAR paths to the core `PluginsLoader`, creating isolated `ClassLoader` instances per\nplugin. Analysis can then run.\n\n---\n"
  },
  {
    "path": "docs/PULL_REQUEST_TEMPLATE.md",
    "content": "\n# For SonarSourcers:\n\n- [ ] Prefix the commit message with the ticket number, i.e. `SLCORE-XXXX` if you already have a ticket in Jira\n- [ ] For standalone PRs without issue in Jira:\n    - [ ] Mention Epic ID in this descrition to create a new Task in Jira\n    - [ ] Mention Issue ID in this descrition to create a new Sub-Task in Jira\n    - [ ] Do not mention any Jira issue to create a new Task in Jira without a parent\n- [ ] When changing an API:\n    - [ ] Explain in the JavaDoc the purpose of the new API\n    - [ ] Document the change in [API_CHANGES.md](https://github.com/SonarSource/sonarlint-core/blob/master/API_CHANGES.md)\n    - [ ] If the change breaks the current API, explicitly communicate those to the impacted consumers prior to merging (eg. IDE squad)\n- [ ] Make sure the tests adhere to the convention:\n    - [ ] All test method names should use `snake_case`, for example: `test_validate_input`.\n- [ ] Make sure checks are green: build passes, Quality Gate is green\n\n# For external contributors:\n\nIn addition to the above, please review our [contribution guidelines](https://github.com/SonarSource/sonarlint-core/blob/master/docs/contributing.md) and ensure your pull request adheres to the following guidelines:\n\n- [ ] Please explain your motives to contribute this change: what problem you are trying to fix, what improvement you are trying to make\n- [ ] Use the following formatting style: [SonarSource/sonar-developer-toolset](https://github.com/SonarSource/sonar-developer-toolset#code-style)\n- [ ] Provide a unit test for any code you changed\n"
  },
  {
    "path": "docs/contributing.md",
    "content": "Contributing\n============\n\nIf you would like to see a new feature, please create a new thread in the forum [\"Suggest new features\"](https://community.sonarsource.com/c/suggestions/features).\n\nPlease be aware that we are not actively looking for feature contributions. The truth is that it's extremely difficult for someone outside SonarSource to comply with our roadmap and expectations. Therefore, we typically only accept minor cosmetic changes and typo fixes.\n\nWith that in mind, if you would like to submit a code contribution, please create a pull request for this repository. Please explain your motives to contribute this change: what problem you are trying to fix, what improvement you are trying to make.\n\nMake sure that you follow our [code style](https://github.com/SonarSource/sonar-developer-toolset#code-style) and all tests are passing (Travis build is executed for each pull request).\n\nThank You! The SonarSource Team\n"
  },
  {
    "path": "docs/decisions/0000-move-more-responsibilities-to-the-core.md",
    "content": "# Move more responsibilities from clients to the core\n\n# Why?\n\nSonarLint Core is a Java library, shared by the 3 \"Java based\" flavors of SonarLint (SLE, SLI, SLVSCODE). Its initial mission was to replace the scanner-engine: load and execute plugins. Then connected mode related code was added (WS + storage).\nA clear API for IDEs was initially maintained, but then we decided it was useless, since all IDEs were evolving under the control of the same people.\nThe 2 main entry points are StandaloneSonarLintEngine and ConnectedSonarLintEngine. IDEs are responsible to instantiate as many of those \"engines\" as they need (1 standalone, 1 connected per connection).\nThere are also a lot of small utility classes that are used by IDEs without using the \"engines\".\n\nThe problems:\n1. API STABILITY: different squads are consumer of SonarLint Core, having a very unstable API is not comfortable\n2. DUPLICATION: latest added features require an overall view of the \"engines\", so we had no choice but to put the logic in each IDEs. Exemple, when an open hotspot request arrives, we need to find the connection it refers to. This logic can't happen in the core today.\n  => lot of duplicated logic:\n    * open hotspots\n    * telemetry\n    * engine management (start/stop/sync/…) \n    * binding management (bind/unbind/compute prefixes/…)\n    * VCS branch management \n    * analysis scheduling \n    * local issue tracking\n3. RUNTIME / TECH STACK: relying on IDE runtime to execute most of our logic (including analysis) is a blocker for innovation.\npeople are still asking support for Java 8 runtime in Cobol IDEs\nhard to use Kotlin libraries in OSGi context\nhiring would be more attractive if a big part of the codebase was in modern Java (or another modern language)\nwe could kill IDE performances, and this is hard for us to measure\n4. TESTABILITY: SonarLint Core has always been intensively tested. Headless testing is much cheaper than IDE UI tests. On the IDE side, it was accepted to have low automated coverage test, relying on manual testing, dogfooding and community to find UI bugs. Now that we put more and more logic in IDEs, where the coverage is low (or absent), there is a higher risk of regressions.\n5. CONSISTENCY: We agreed a long time ago to not focus too much on having the same experience in all IDEs. It was considered better to have a \"native\" experience. Still we are now in a situation where the UX is different between IDEs, with no good reasons.\n6. OWNERSHIP of the project. In a situation where there will be one squad per IDE, \"consuming\" SLCORE, who would be responsible to maintain it (checking failing ITs, updating dependencies, …) and who is allowed to introduce functional changes? Today it is not clear.\n\n# Why Not?\n\nPossible reasons for not doing anything (or even putting more in IDEs):\n\n1. RIGIDITY: we will force the same behavior in all IDEs. In the past, it has been considered as bad. SonarLint should integrate \"natively\" is IDEs, which means following IDE \"patterns\".\nthe configuration and UI will stay under control of the IDE side\nthe part we should put in the core should be the \"original\" SonarLint experience (analysis, issue tracking, connected mode) so it should not suffer from comparison with existing IDE features\n2. SHARED RESPONSIBILITY: having a shared component between multiple teams or squads is difficult in terms of organization.\nI guess sonar-security or sonar-analyzer-common libraries are in the same situation, we could learn from the LT experience.\n3. PERFORMANCE: having SLCORE embedded into SLE/SLI allows to share Java objects/integrate into IDE VFS. By running out of process, we will probably have to duplicate objects in memory. The RPC communication will also be slower than \"in-JVM\" communication.\nwe should design the API to limit number of RPC calls\nwe should pay attention to memory consumption, but it will be easy to monitor since SonarLint backend will have its own process\n4. COMPLEXITY: SLCORE as a library is simpler to use (at least in Java IDEs) than having to manage a separate process\n5. INCREASING DISTRIBUTION SIZE: if we package our own runtime\n\n# What?\n1. API STABILITY: clear interface used by IDEs. Avoid breaking changes, document changes, deprecate/keep backward compatibility.\n2. DUPLICATION: move code into SLCORE, but not as a toolbox/library, more as a set of services, with a small and hopefully stable interface. IDEs should ideally only keep UI, settings, and IDE specific integration (extension points, …)\n3. RUNTIME: design SLCORE as a standalone process that communicates with IDE using RPC (like SLLS) . The API should support that (no more full Java objects leaking between IDE and Core, avoid too granular/frequent calls). IDE specific code can stay longer with old runtime compatibility.\n4. TESTABILITY: we should be able to write a lot of new tests, concerning advanced interactions between components (connected mode storage, VCS, …). For example: user switches its project branch, that should recompute the matching SQ branch, then sync issues, and report to IDE\n5. CONSISTENCY: find the right boundaries between IDE specific behavior and common part. Don't allow too much \"customization\" of the backend to avoid testability hell and no consistency. Document intended IDE specific behavior.\n6. OWNERSHIP: if we take inspiration from the SonarQube team organization, we don't think having a dedicated isolated squad owning SLCORE is a good solution. We think most of the functional changes should be driven by IDEs. We will form squads composed of IDE specialists (= frontend) + SLCORE specialists (= backend).\n\n## What should stay in each IDE?\nIDEs should keep control on the configuration:\n* connection definition\n* bindings\n* exclusion\n* rules configuration\n* user preferences (subscribe to dev notifications, …)\n\nThis is important, because configuration can be scoped/stored at different levels (user, ide instance, project, module, …) and benefit from IDE specific configuration synchronization. Also some settings should be stored in a secure storage, that is also IDE specific.\n\nIDEs are responsible for presentation (issues, flows, rule descriptions, …)\n\nShould IDEs keep responsibility of analysis scheduling? Maybe not.\n\nIDEs should be responsible for hooking into IDE editors to broadcast source file changes.\n\nIDEs should be responsible to collect analysis configuration (Java classpath, .NET version, …)\n\n# Target architecture\n\n![Target architecture](img/target-architecture.jpg \"Target architecture\")\n"
  },
  {
    "path": "docs/decisions/0001-implement-binding-suggestions-in-the-core.md",
    "content": "# Implement binding suggestions in the core\n\n## Why\n\nFollowing [ADR-0000](0000-move-more-responsibilities-to-the-core.md) decision, at the time of\nimplementing [MMF-2895](https://sonarsource.atlassian.net/browse/MMF-2895), we wondered if we should implement most of\nthe feature in the core. A first implementation was made in SLVSCODE\nfor [MMF-1431](https://sonarsource.atlassian.net/browse/MMF-1431) and the question of having a common implementation was\nraised.\n\n### Decision Drivers\n\n* Same drivers as [ADR-0000](0000-move-more-responsibilities-to-the-core.md)\n\n## What\n\n### Considered Options\n\n1. Implement the feature in SLI\n    * Pros:\n        * quicker\n    * Cons:\n        * again logic duplicated in clients\n2. Implement the feature in SLCORE and let it handle as much as possible of the feature\n    * Pros:\n        * no duplicated logic\n    * Cons:\n        * slower as the core will need to access more data (connections, projects)\n\n### Option Retained\n\nEven if the development would be slower we decided to go with option 2. We see it as an investment for the future. It\nalso\ngoes in the right direction of having more responsibilities in the core, to benefit all clients.\n\n### Consequences\n\n* Consequence 1: To be able to make suggestions the CORE will need to know about connections and about projects\n* Consequence 2: The suggestion algorithm might differ from the one used in SLVSCODE\n\n## How\n\nSome other technical decisions that were made:\n\n* Provide a `SonarLintBackend` interface as the entry point for clients. A single entrypoint is easier to use\n* Expose a `SonarLintClient` interface that should be implemented by clients to access some data that will remain their\n  responsibility. A single entrypoint is easier to implement\n* The backend is to be used as a library (in-process), running it out-of-process will be part of another effort\n* The backend relies on `lsp4j`, which prepares the ground for moving\n  out-of-process later. Also, the interfaces are designed to supported asynchronous messaging by\n  returning `CompletableFuture`s\n\nDecisions related to the `SonarLintBackend`:\n\n* only expose the `ConnectionService` and `ConfigurationService` services. More services could come later but were not\n  needed for this feature\n* those services are designed to be independent\n* it is the client's responsibility to store connections' data. It knows better how to store it (e.g. password safe)\n* the backend does not store on disk data under the client's responsibility. Connections and configurations are only\n  kept in memory\n* they are backed by repositories, `ConfigurationRepository` and `ConnectionConfigurationRepository` that hold the data\n  in-memory. Repositories can be used to write (usually from services) and read (usually from event handlers)\n* an `EventBus` has been implemented that let services raise events about things that happened. That also improves\n  decoupling\n* methods used to communicate with the backend should use `DTO`s to transfer data\n\nDecisions related to the `SonarLintClient`:\n\n* expose a method to get the `HttpClient`. This wouldn't work by going out-of-process, it is a shortcut taken to not\n  lose time and delay the decision about whose responsibility it is to manage HTTP requests (we let it in clients for\n  now)\n* it is also the client's responsibility for now to access files of the project. This responsibility might be moved to\n  the CORE at some point. For now clients are expected to implement `findFileByNamesInScope`, taking a list of filenames\n  to find to reduce back and forth\n* let the client display suggestions as they see fit. To let them choose the better UX, suggestions for all projects are\n  delivered at the same time. This might be more adapted for SLE, where there is a single window for all projects\n\nDecisions related to the `ConnectionService`:\n\n* An `initialize` method, to know what connections were already previously added by the user. We want to distinguish\n  already created connections and newly added ones: we don't want to trigger the same processing\n* when adding `ConfigurationScope`s several can be provided at the same time. This lets the backend group some\n  processing, e.g. binding suggestions to notify the client a single time with suggestions for all projects added\n* we have one method per connection event: `didAddConnection`, `didRemoveConnection` and `didUpdateConnection`. At some\n  point it could become a single replace instead, if it is more practical for clients\n* SonarQube and SonarCloud connections have been separated in 2 different objects, because they don't have exactly the\n  same data. It implies a bit of effort on the client side to make the right transformation. We can still re-evaluate\n  later\n\nDecisions related to the `ConfigurationService`:\n\n* no `initialize` method, it does not make sense to register `ConfigurationScope`s that are opened before starting the\n  backend. We suppose we will have the same processing for already opened scopes and new ones\n* when adding `ConfigurationScope`s several can be provided at the same time\n  in `ConfigurationService.configurationScopesAdded`. This lets the backend group some\n  processing, e.g. binding suggestions to notify the client a single time with suggestions for all projects added\n\nDecisions related to the `EventBus`:\n\n* events are dispatched and handled asynchronously. We don't want to delay the client while handling them\n* the bus is backed by a single thread executor service. Only one event can be handled at a given time. We think this\n  will help reduce race conditions, and it is simpler to reason about\n* as handling an event happens asynchronously, handlers should re-check if the conditions of the event are still valid\n* if event handlers have long processing they should move to a different thread, by being careful about race conditions\n\n## More Information\n\nThose decisions were taken in the context of implementing binding suggestions in SLI and trying to keep other IDEs in\nmind. The API is young and will very probably continue to evolve and break in the beginning\n"
  },
  {
    "path": "docs/decisions/0002-manage-http-client-in-core.md",
    "content": "# Manage the HTTPClient in the core\n\n## Why\n\nAt the moment, the core declares a Java interface for HttpClient, that should be implemented by each client, and passed to several APIs. It is the responsibility of the client side to implement this interface, by choosing an HTTPClient implementation, and configuring it (SSL, proxy, timeouts, credentials, ...).\nThe idea is to move the management of this HTTPClient to the core. \n\n### Decision Drivers\n\n* Consistent behavior for all IDEs (timeouts, cancellation, SSL, security ...)\n* More freedom in the core to do more advanced HTTP operations (SSE, websockets, ...) without having to ask each client to implement new code.\n* Intensive testing of the HTTPClient for our use cases is easier in SLCORE\n* The current API is not RPC-friendly (asking a Java client to implement an interface)\n\n## What\n\n### Considered Options\n\n* keep HTTPClient on IDE side, with an RPC-friendly protocol between client and core\n    * Pros:\n        * more freedom for each IDE\n        * once CORE will be a separate process, the requests will continue to appear as sent by the IDE (maybe important for firewalls)\n        * possibly simpler to integrate into IDE user experience regarding HTTP configuration (SSL, proxy, ...)\n        * possibility to use an HTTPClient provided by the IDE SDK (but not the case as of today) \n    * Cons:\n        * duplicate code in each IDE\n        * possibly different behavior\n        * need to design an RPC-friendly protocol for core to forward HTTP operations to the client. No easy for persistent connections (SSE, websocket)\n        * the core needs its own implementation of an HTTPClient for its medium tests/ITs\n* move HTTPClient to the core\n    * Pros:\n        * less code in each IDE\n        * consistent behavior\n        * fewer round-trips between client and core\n        * all core tests (medium tests/ITs) will use the \"production\" HTTPClient\n    * Cons:\n        * need to design an RPC-friendly protocol for core to get credentials, proxy settings, and SSL certificate validation\n\n\n### Option Retained\n\nWe retained \"move HTTPClient to the core\", mainly for the improved testability, and as it fits better with [ADR-0000](0000-move-more-responsibilities-to-the-core.md) decision.\n\n### Consequences\n\n* For SLVSCODE, there should be almost no impact\n* For SLE, it will change the HTTPClient from OkHttp to Apache HTTPClient, so it could have some side effects\n* For SLI, this will be the same library, but the integration with IntelliJ SSL and Proxy settings will need to be reworked with care\n* The HTTPClient interface will be deprecated, and should ultimately disappear\n\n## How\n\nNew requests will be added to the backend protocol:\n\n* `getCredentials` to ask client for credentials of a given connection\n* `selectProxies` to ask client for configured proxies for a given URL\n* `getProxyAuthentication` to ask client for proxy credentials\n* `isServerTrusted` to ask client if a given SSL certificate should be trusted"
  },
  {
    "path": "docs/decisions/template.md",
    "content": "# {short title of solved problem and solution}\n\n## Why\n\n{Describe the context and problem statement that require to make the decision, e.g., in free form using two to three sentences or in the form of an\nillustrative story}\n\n### Decision Drivers\n\n* {decision driver 1, e.g., a force, facing concern, …}\n* {decision driver 2, e.g., a force, facing concern, …}\n* … <!-- numbers of drivers can vary -->\n\n## What\n\n### Considered Options\n\n* {title of option 1}\n    * Pros:\n        * item 1\n        * item 2\n    * Cons:\n        * item 3\n        * item 4\n* {title of option 2}\n* … <!-- numbers of options can vary -->\n\n### Option Retained\n\nWe retained \"{title of option 1}\", because\n{justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | … | comes\nout best}.\n\n### Consequences\n\n{Explain what would be the consequences of the decision}\n* Consequence 1: ...\n* Consequence 2: ...\n\n\n## How\n\n{Get into more technical details about the change if needed}\n\n## More Information\n\n{Additional evidence/confidence for the option retained here}\n\n{Define when and how the decision should be realized and if/when it should be re-visited}\n\n{Links to other decisions and resources might here appear as well}\n"
  },
  {
    "path": "its/README.md",
    "content": "# Run integration tests\n\n# Prerequisites\n\n* Some integration tests load plugins relying on Node.js, so make sure the latest LTS version is installed and `node` is in the PATH.\n* SonarQube ITs need Java 17 to run and SonarCloud ITs need Java 11\n\n# First time configuration\n\n1. Make sure your Developer Box is properly setup (see xtranet)\n2. Configure Orchestrator settings as described [here](https://github.com/SonarSource/orchestrator#configuration). The Artifactory API key and GitHub token are the only mandatory options. The GH token is a Personal Access Token (classic) with the `repo` scope permission, and SSO properly configured\n3. For SonarCloud ITs, make sure the `SONARCLOUD_IT_TOKEN` env var is defined (you can find the value in our password management tool)\n4. Run `mvn clean install` a first time from this `its` folder so that test resources are built (like custom plugins)\n\n# Running ITs from IntelliJ\n\n1. From the root folder of the repository, first build sonarlint-core with `mvn clean install`\n2. Make sure the `its` profile is [activated in the Maven tool window](https://www.jetbrains.com/help/idea/work-with-maven-profiles.html#activate_maven_profiles)\n3. Reload the project with the top left button of the Maven tool window\n4. Open a test class and run it\n\n# Running ITs from command line\n\n1. From the root folder of the repository, first build sonarlint-core with `mvn clean install`\n2. Run `mvn verify -f its/pom.xml -Dsonar.runtimeVersion=<SQ server version>`\n"
  },
  {
    "path": "its/plugins/custom-sensor-plugin/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-core-its</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../../pom.xml</relativePath>\n  </parent>\n\n  <groupId>org.sonarsource.plugins.example</groupId>\n  <artifactId>custom-sensor-plugin</artifactId>\n  <packaging>sonar-plugin</packaging>\n  <version>11.2-SNAPSHOT</version>\n\n  <name>Example Plugin for SonarQube</name>\n  <description>Example of plugin for SonarQube</description>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <sonar.apiVersion>9.14.0.375</sonar.apiVersion>\n    <sonar.testing-harness>9.9.0.65466</sonar.testing-harness>\n    <!-- Always force Java 17 while SQ 9.9 is supported -->\n    <maven.compiler.release>17</maven.compiler.release>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>org.sonarsource.api.plugin</groupId>\n      <artifactId>sonar-plugin-api</artifactId>\n      <version>${sonar.apiVersion}</version>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <!-- packaged with the plugin -->\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-lang3</artifactId>\n      <version>3.20.0</version>\n    </dependency>\n\n\n    <!-- unit tests -->\n    <dependency>\n      <groupId>org.sonarsource.sonarqube</groupId>\n      <artifactId>sonar-testing-harness</artifactId>\n      <version>${sonar.testing-harness}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>junit</groupId>\n      <artifactId>junit</artifactId>\n      <version>4.13.2</version>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <finalName>${project.artifactId}</finalName>\n    <plugins>\n      <plugin>\n        <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>\n        <artifactId>sonar-packaging-maven-plugin</artifactId>\n        <version>1.17</version>\n        <extensions>true</extensions>\n        <configuration>\n          <pluginClass>org.sonarsource.plugins.example.ExamplePlugin</pluginClass>\n          <sonarLintSupported>true</sonarLintSupported>\n        </configuration>\n      </plugin>\n      <plugin>\n        <!-- UTF-8 bundles are not supported by Java, so they must be converted during build -->\n        <groupId>org.codehaus.mojo</groupId>\n        <artifactId>native2ascii-maven-plugin</artifactId>\n        <version>1.0-beta-1</version>\n        <executions>\n          <execution>\n            <goals>\n              <goal>native2ascii</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n    </plugins>\n  </build>\n\n</project>\n"
  },
  {
    "path": "its/plugins/custom-sensor-plugin/src/main/java/org/sonarsource/plugins/example/ExamplePlugin.java",
    "content": "/*\n * Example Plugin for SonarQube\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.plugins.example;\n\nimport org.sonar.api.Plugin;\n\n/**\n * This class is the entry point for all extensions. It is referenced in pom.xml.\n */\npublic class ExamplePlugin implements Plugin {\n\n  @Override\n  public void define(Context context) {\n    context.addExtensions(FooLintRulesDefinition.class, OneIssuePerLineSensor.class);\n  }\n}\n"
  },
  {
    "path": "its/plugins/custom-sensor-plugin/src/main/java/org/sonarsource/plugins/example/FooLintRulesDefinition.java",
    "content": "/*\n * Example Plugin for SonarQube\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.plugins.example;\n\nimport java.io.InputStream;\nimport java.nio.charset.StandardCharsets;\nimport org.sonar.api.server.rule.RulesDefinition;\nimport org.sonar.api.server.rule.RulesDefinitionXmlLoader;\n\npublic final class FooLintRulesDefinition implements RulesDefinition {\n\n  private static final String PATH_TO_RULES_XML = \"/example/foolint-rules.xml\";\n\n  static final String KEY = \"foolint\";\n  private static final String NAME = \"FooLint\";\n\n  private final RulesDefinitionXmlLoader xmlLoader;\n\n  public FooLintRulesDefinition(RulesDefinitionXmlLoader xmlLoader) {\n    this.xmlLoader = xmlLoader;\n  }\n\n  private String rulesDefinitionFilePath() {\n    return PATH_TO_RULES_XML;\n  }\n\n  @Override\n  public void define(Context context) {\n    NewRepository repository = context.createRepository(KEY, \"java\").setName(NAME);\n\n    InputStream rulesXml = this.getClass().getResourceAsStream(rulesDefinitionFilePath());\n    if (rulesXml != null) {\n      xmlLoader.load(repository, rulesXml, StandardCharsets.UTF_8.name());\n    }\n\n    repository.done();\n  }\n\n}\n"
  },
  {
    "path": "its/plugins/custom-sensor-plugin/src/main/java/org/sonarsource/plugins/example/OneIssuePerLineSensor.java",
    "content": "/*\n * Example Plugin for SonarQube\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.plugins.example;\n\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.sensor.Sensor;\nimport org.sonar.api.batch.sensor.SensorContext;\nimport org.sonar.api.batch.sensor.SensorDescriptor;\nimport org.sonar.api.batch.sensor.issue.NewIssue;\nimport org.sonar.api.rule.RuleKey;\n\npublic class OneIssuePerLineSensor implements Sensor {\n\n  @Override\n  public void describe(final SensorDescriptor descriptor) {\n    descriptor.name(\"One Issue Per Line\");\n  }\n\n  @Override\n  public void execute(final SensorContext context) {\n    for (InputFile f : context.fileSystem().inputFiles(context.fileSystem().predicates().all())) {\n      for (int i = 1; i < f.lines(); i++) {\n        NewIssue newIssue = context.newIssue();\n        newIssue\n          .forRule(RuleKey.of(FooLintRulesDefinition.KEY, \"ExampleRule1\"))\n          .at(newIssue.newLocation().on(f).at(f.selectLine(i)).message(\"Issue at line \" + i))\n          .save();\n      }\n    }\n  }\n\n}\n"
  },
  {
    "path": "its/plugins/custom-sensor-plugin/src/main/resources/example/foolint-rules.xml",
    "content": "<foolint-rules>\n\t<rule>\n\t\t<key>ExampleRule1</key>\n\t\t<name>Example Rule 1</name>\n\t\t<internalKey>ExampleRule1</internalKey>\n\t\t<description>This is an example of rule defined thru the XML.\n\t\t</description>\n\t\t<severity>BLOCKER</severity>\n\t\t<cardinality>SINGLE</cardinality>\n\t\t<status>READY</status>\n\t\t<tag>example</tag>\n\t\t<tag>bug</tag>\n\t\t<remediationFunction>CONSTANT_ISSUE</remediationFunction>\n        <remediationFunctionBaseEffort>2min</remediationFunctionBaseEffort>\n\t</rule>\n</foolint-rules>\n"
  },
  {
    "path": "its/plugins/global-extension-plugin/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-core-its</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../../pom.xml</relativePath>\n  </parent>\n\n  <groupId>org.sonarsource.plugins.example</groupId>\n  <artifactId>global-extension-plugin</artifactId>\n  <packaging>sonar-plugin</packaging>\n\n  <name>Example Plugin with global extension</name>\n  <description>Example Plugin with global extension</description>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <sonar.apiVersion>9.14.0.375</sonar.apiVersion>\n    <!-- Always force Java 17 while SQ 9.9 is supported -->\n    <maven.compiler.release>17</maven.compiler.release>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>org.sonarsource.api.plugin</groupId>\n      <artifactId>sonar-plugin-api</artifactId>\n      <version>${sonar.apiVersion}</version>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.slf4j</groupId>\n      <artifactId>slf4j-api</artifactId>\n      <version>1.7.36</version>\n    </dependency>\n    <dependency>\n      <!-- packaged with the plugin -->\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-lang3</artifactId>\n      <version>3.20.0</version>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <finalName>${project.artifactId}</finalName>\n    <plugins>\n      <plugin>\n        <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>\n        <artifactId>sonar-packaging-maven-plugin</artifactId>\n        <extensions>true</extensions>\n        <configuration>\n          <pluginClass>org.sonarsource.plugins.example.GlobalExtensionPlugin</pluginClass>\n          <!-- Need to be one part of the sensor whitelist -->\n          <pluginKey>cobol</pluginKey>\n          <skipDependenciesPackaging>true</skipDependenciesPackaging>\n          <sonarLintSupported>true</sonarLintSupported>\n          <pluginApiMinVersion>${sonar.apiVersion}</pluginApiMinVersion>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n\n</project>\n"
  },
  {
    "path": "its/plugins/global-extension-plugin/src/main/java/org/sonarsource/plugins/example/GlobalExtension.java",
    "content": "/*\n * Example Plugin with global extension\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.plugins.example;\n\nimport org.slf4j.LoggerFactory;\nimport org.sonar.api.Startable;\nimport org.sonar.api.config.Configuration;\nimport org.sonarsource.api.sonarlint.SonarLintSide;\n\nimport static org.sonarsource.api.sonarlint.SonarLintSide.MULTIPLE_ANALYSES;\n\n@SonarLintSide(lifespan = MULTIPLE_ANALYSES)\npublic class GlobalExtension implements Startable {\n\n  public static GlobalExtension getInstance() {\n    return instance;\n  }\n\n  private static final org.sonar.api.utils.log.Logger SONAR_API_LOG = org.sonar.api.utils.log.Loggers.get(GlobalExtension.class);\n  private static final org.slf4j.Logger SLF4J_LOG = LoggerFactory.getLogger(GlobalExtension.class);\n  private static GlobalExtension instance;\n\n  private int counter;\n\n  private final Configuration config;\n\n  public GlobalExtension(Configuration config) {\n    instance = this;\n    this.config = config;\n  }\n\n  @Override\n  public void start() {\n    SONAR_API_LOG.info(\"Start Global Extension \" + config.get(\"sonar.global.label\").orElse(\"MISSING\"));\n  }\n\n  @Override\n  public void stop() {\n    SLF4J_LOG.info(\"Stop Global Extension\");\n  }\n\n  public int getAndInc() {\n    return counter++;\n  }\n\n}\n"
  },
  {
    "path": "its/plugins/global-extension-plugin/src/main/java/org/sonarsource/plugins/example/GlobalExtensionPlugin.java",
    "content": "/*\n * Example Plugin with global extension\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.plugins.example;\n\nimport org.sonar.api.Plugin;\n\npublic class GlobalExtensionPlugin implements Plugin {\n\n  @Override\n  public void define(Context context) {\n    context.addExtensions(GlobalLanguage.class,\n      GlobalRulesDefinition.class,\n      GlobalSonarWayProfile.class,\n      GlobalSensor.class,\n      GlobalExtension.class);\n  }\n}\n"
  },
  {
    "path": "its/plugins/global-extension-plugin/src/main/java/org/sonarsource/plugins/example/GlobalLanguage.java",
    "content": "/*\n * Example Plugin with global extension\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.plugins.example;\n\nimport org.sonar.api.resources.AbstractLanguage;\n\npublic class GlobalLanguage extends AbstractLanguage {\n\n  // Need to use a key that is available in the Language enum\n  static final String LANGUAGE_KEY = \"cobol\";\n\n  public GlobalLanguage() {\n    super(LANGUAGE_KEY, \"Global\");\n  }\n\n  @Override\n  public String[] getFileSuffixes() {\n    return new String[] {\"glob\"};\n  }\n\n}\n"
  },
  {
    "path": "its/plugins/global-extension-plugin/src/main/java/org/sonarsource/plugins/example/GlobalRulesDefinition.java",
    "content": "/*\n * Example Plugin with global extension\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.plugins.example;\n\nimport org.sonar.api.server.rule.RuleParamType;\nimport org.sonar.api.server.rule.RulesDefinition;\n\npublic final class GlobalRulesDefinition implements RulesDefinition {\n\n  static final String RULE_KEY = \"inc\";\n  static final String KEY = \"global\";\n  static final String NAME = \"Global\";\n\n  @Override\n  public void define(Context context) {\n    NewRepository repository = context.createRepository(KEY, GlobalLanguage.LANGUAGE_KEY).setName(NAME);\n    NewRule rule = repository.createRule(RULE_KEY)\n      .setActivatedByDefault(true)\n      .setName(\"Increment\")\n      .setHtmlDescription(\"Increment message after every analysis\");\n    rule.createParam(\"stringParam\")\n      .setType(RuleParamType.STRING)\n      .setName(\"String parameter\")\n      .setDescription(\"An example of string parameter\");\n    rule.createParam(\"textParam\")\n      .setType(RuleParamType.TEXT)\n      .setDefaultValue(\"text\\nparameter\")\n      .setName(\"Text parameter\")\n      .setDescription(\"An example of text parameter\");\n    rule.createParam(\"intParam\")\n      .setType(RuleParamType.INTEGER)\n      .setDefaultValue(\"42\")\n      .setName(\"Int parameter\")\n      .setDescription(\"An example of int parameter\");\n    rule.createParam(\"boolParam\")\n      .setType(RuleParamType.BOOLEAN)\n      .setDefaultValue(\"true\")\n      .setName(\"Boolean parameter\")\n      .setDescription(\"An example boolean parameter\");\n    rule.createParam(\"floatParam\")\n      .setType(RuleParamType.FLOAT)\n      .setDefaultValue(\"3.14159265358\")\n      .setName(\"Float parameter\")\n      .setDescription(\"An example float parameter\");\n    rule.createParam(\"enumParam\")\n      .setType(RuleParamType.singleListOfValues(\"enum1\", \"enum2\", \"enum3\"))\n      .setDefaultValue(\"enum1\")\n      .setName(\"Enum parameter\")\n      .setDescription(\"An example enum parameter\");\n    rule.createParam(\"enumListParam\")\n      .setType(RuleParamType.multipleListOfValues(\"list1\", \"list2\", \"list3\"))\n      .setDefaultValue(\"list1,list2\")\n      .setName(\"Enum list parameter\")\n      .setDescription(\"An example enum list parameter\");\n    rule.createParam(\"multipleIntegersParam\")\n      .setType(RuleParamType.parse(\"INTEGER,multiple=true,values=\\\"80,120,160\\\"\"))\n      .setName(\"Enum list of integers\")\n      .setDescription(\"An example enum list of integers\");\n\n    repository.done();\n  }\n\n}\n"
  },
  {
    "path": "its/plugins/global-extension-plugin/src/main/java/org/sonarsource/plugins/example/GlobalSensor.java",
    "content": "/*\n * Example Plugin with global extension\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.plugins.example;\n\nimport java.time.Clock;\nimport java.util.Arrays;\nimport java.util.stream.Stream;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.rule.ActiveRule;\nimport org.sonar.api.batch.sensor.Sensor;\nimport org.sonar.api.batch.sensor.SensorContext;\nimport org.sonar.api.batch.sensor.SensorDescriptor;\nimport org.sonar.api.batch.sensor.issue.NewIssue;\nimport org.sonar.api.rule.RuleKey;\nimport org.sonar.api.utils.log.Logger;\nimport org.sonar.api.utils.log.Loggers;\n\npublic class GlobalSensor implements Sensor {\n\n  private static final Logger LOGGER = Loggers.get(GlobalSensor.class);\n\n  private final Clock clock;\n\n  public GlobalSensor(Clock clock) {\n    this.clock = clock;\n  }\n\n  @Override\n  public void describe(final SensorDescriptor descriptor) {\n    descriptor.name(\"Global\")\n      .onlyOnLanguage(GlobalLanguage.LANGUAGE_KEY);\n  }\n\n  @Override\n  public void execute(final SensorContext context) {\n    long timeBefore = clock.millis();\n    RuleKey globalRuleKey = RuleKey.of(GlobalRulesDefinition.KEY, GlobalRulesDefinition.RULE_KEY);\n    ActiveRule activeGlobalRule = context.activeRules().find(globalRuleKey);\n    if (activeGlobalRule != null) {\n      Stream.of(\"stringParam\", \"textParam\", \"intParam\", \"boolParam\", \"floatParam\", \"enumParam\", \"enumListParam\", \"multipleIntegersParam\")\n        .map(k -> Arrays.asList(k, activeGlobalRule.param(k)))\n        .forEach(kv -> LOGGER.info(\"Param {} has value {}\", kv.get(0), kv.get(1)));\n    } else {\n      LOGGER.error(\"Rule is not active\");\n    }\n    var inputFiles = context.fileSystem().inputFiles(context.fileSystem().predicates().all());\n    if (!inputFiles.iterator().hasNext()) {\n      LOGGER.error(\"File system is empty\");\n    } else {\n      for (InputFile f : inputFiles) {\n        NewIssue newIssue = context.newIssue();\n        newIssue\n          .forRule(globalRuleKey)\n          .at(newIssue.newLocation().on(f).message(\"Issue number \" + GlobalExtension.getInstance().getAndInc()))\n          .save();\n      }\n    }\n    long timeAfter = clock.millis();\n    LOGGER.info(String.format(\"Executed Global Sensor in %d ms\", timeAfter - timeBefore));\n  }\n\n}\n"
  },
  {
    "path": "its/plugins/global-extension-plugin/src/main/java/org/sonarsource/plugins/example/GlobalSonarWayProfile.java",
    "content": "/*\n * Example Plugin with global extension\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.plugins.example;\n\nimport org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;\n\npublic final class GlobalSonarWayProfile implements BuiltInQualityProfilesDefinition {\n\n  @Override\n  public void define(Context context) {\n    context.createBuiltInQualityProfile(\"Sonar Way\", GlobalLanguage.LANGUAGE_KEY)\n      .setDefault(true)\n      .done();\n  }\n\n}\n"
  },
  {
    "path": "its/plugins/java-custom-rules/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-core-its</artifactId>\n    <version>11.2-SNAPSHOT</version>\n    <relativePath>../../pom.xml</relativePath>\n  </parent>\n\n  <groupId>com.sonarsource.it.java</groupId>\n  <artifactId>java-custom-rules-plugin</artifactId>\n  <packaging>sonar-plugin</packaging>\n\n  <name>Java Custom Rules Plugin</name>\n  <description>Java Custom Rules</description>\n\n  <properties>\n    <sonarjava.version>7.16.0.30901</sonarjava.version>\n    <analyzer.commons.version>2.1.0.1111</analyzer.commons.version>\n    <sonar.apiVersion>9.14.0.375</sonar.apiVersion>\n    <!-- Always force Java 17 while SQ 9.9 is supported -->\n    <maven.compiler.release>17</maven.compiler.release>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>org.sonarsource.api.plugin</groupId>\n      <artifactId>sonar-plugin-api</artifactId>\n      <version>${sonar.apiVersion}</version>\n      <scope>provided</scope>\n    </dependency>\n\n    <dependency>\n      <groupId>org.sonarsource.java</groupId>\n      <artifactId>sonar-java-plugin</artifactId>\n      <type>sonar-plugin</type>\n      <version>${sonarjava.version}</version>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.analyzer-commons</groupId>\n      <artifactId>sonar-analyzer-commons</artifactId>\n      <version>${analyzer.commons.version}</version>\n    </dependency>\n\n    <dependency>\n      <groupId>junit</groupId>\n      <artifactId>junit</artifactId>\n      <version>4.13.2</version>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <finalName>${project.artifactId}</finalName>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-shade-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>shade</id>\n            <phase>package</phase>\n            <goals>\n              <goal>shade</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>\n        <artifactId>sonar-packaging-maven-plugin</artifactId>\n        <version>1.23.0.740</version>\n        <extensions>true</extensions>\n        <configuration>\n          <pluginClass>org.sonar.samples.java.MyJavaRulesPlugin</pluginClass>\n          <pluginApiMinVersion>${sonar.apiVersion}</pluginApiMinVersion>\n          <pluginKey>custom</pluginKey>\n          <sonarLintSupported>true</sonarLintSupported>\n          <skipDependenciesPackaging>true</skipDependenciesPackaging>\n          <requirePlugins>java:${sonarjava.version}</requirePlugins>\n          <requiredForLanguages>java</requiredForLanguages>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n\n</project>\n"
  },
  {
    "path": "its/plugins/java-custom-rules/src/main/java/org/sonar/samples/java/MyJavaFileCheckRegistrar.java",
    "content": "/*\n * Java Custom Rules Plugin\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonar.samples.java;\n\nimport java.util.List;\nimport org.sonar.plugins.java.api.CheckRegistrar;\nimport org.sonar.plugins.java.api.JavaCheck;\nimport org.sonarsource.api.sonarlint.SonarLintSide;\n\n/**\n * Provide the \"checks\" (implementations of rules) classes that are going be executed during\n * source code analysis.\n *\n * This class is a batch extension by implementing the {@link org.sonar.plugins.java.api.CheckRegistrar} interface.\n */\n@SonarLintSide\npublic class MyJavaFileCheckRegistrar implements CheckRegistrar {\n\n  /**\n   * Register the classes that will be used to instantiate checks during analysis.\n   */\n  @Override\n  public void register(RegistrarContext registrarContext) {\n    // Call to registerClassesForRepository to associate the classes with the correct repository key\n    registrarContext.registerClassesForRepository(MyJavaRulesDefinition.REPOSITORY_KEY, checkClasses(), testCheckClasses());\n  }\n\n  /**\n   * Lists all the main checks provided by the plugin\n   */\n  public static List<Class<? extends JavaCheck>> checkClasses() {\n    return RulesList.getJavaChecks();\n  }\n\n  /**\n   * Lists all the test checks provided by the plugin\n   */\n  public static List<Class<? extends JavaCheck>> testCheckClasses() {\n    return RulesList.getJavaTestChecks();\n  }\n}\n"
  },
  {
    "path": "its/plugins/java-custom-rules/src/main/java/org/sonar/samples/java/MyJavaRulesDefinition.java",
    "content": "/*\n * Java Custom Rules Plugin\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonar.samples.java;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Objects;\nimport java.util.Set;\nimport org.sonar.api.SonarEdition;\nimport org.sonar.api.SonarProduct;\nimport org.sonar.api.SonarQubeSide;\nimport org.sonar.api.SonarRuntime;\nimport org.sonar.api.server.rule.RulesDefinition;\nimport org.sonar.api.utils.Version;\nimport org.sonarsource.analyzer.commons.RuleMetadataLoader;\n\n/**\n * Declare rule metadata in server repository of rules.\n * That allows to list the rules in the page \"Rules\".\n */\npublic class MyJavaRulesDefinition implements RulesDefinition {\n\n  // don't change that because the path is hard coded in CheckVerifier\n  private static final String RESOURCE_BASE_PATH = \"org/sonar/l10n/java/rules/java\";\n\n  public static final String REPOSITORY_KEY = \"mycompany-java\";\n\n  // Add the rule keys of the rules which need to be considered as template-rules\n  private static final Set<String> RULE_TEMPLATES_KEY = Collections.emptySet();\n\n  @Override\n  public void define(Context context) {\n    NewRepository repository = context.createRepository(REPOSITORY_KEY, \"java\").setName(\"MyCompany Custom Repository\");\n\n    // The runtime version shouldn't matter. The analyzer is supposed to use it to change behavior based on its runtime version.\n    // But normally, they don't do it in the SonarLint context.\n    RuleMetadataLoader ruleMetadataLoader = new RuleMetadataLoader(RESOURCE_BASE_PATH, getSonarLintRuntime(Version.parse(\"10.3.0\")));\n\n    ruleMetadataLoader.addRulesByAnnotatedClass(repository, new ArrayList<>(RulesList.getChecks()));\n\n    setTemplates(repository);\n\n    repository.createRule(\"markdown\")\n      .setName(\"A rule with Markdown description\")\n      .setMarkdownDescription(\"  = Title\\n  * one\\n* two\");\n\n    repository.done();\n  }\n\n  private static SonarRuntime getSonarLintRuntime(Version version) {\n    return new SonarRuntime() {\n\n      @Override\n      public Version getApiVersion() {\n        return version;\n      }\n\n      @Override\n      public SonarProduct getProduct() {\n        return SonarProduct.SONARLINT;\n      }\n\n      @Override\n      public SonarQubeSide getSonarQubeSide() {\n        return null;\n      }\n\n      @Override\n      public SonarEdition getEdition() {\n        return null;\n      }\n    };\n  }\n\n  private static void setTemplates(NewRepository repository) {\n    RULE_TEMPLATES_KEY.stream()\n      .map(repository::rule)\n      .filter(Objects::nonNull)\n      .forEach(rule -> rule.setTemplate(true));\n  }\n}\n"
  },
  {
    "path": "its/plugins/java-custom-rules/src/main/java/org/sonar/samples/java/MyJavaRulesPlugin.java",
    "content": "/*\n * Java Custom Rules Plugin\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonar.samples.java;\n\nimport org.sonar.api.Plugin;\n\n/**\n * Entry point of your plugin containing your custom rules\n */\npublic class MyJavaRulesPlugin implements Plugin {\n\n  @Override\n  public void define(Context context) {\n    // server extensions -> objects are instantiated during server startup\n    context.addExtension(MyJavaRulesDefinition.class);\n\n    // batch extensions -> objects are instantiated during code analysis\n    context.addExtension(MyJavaFileCheckRegistrar.class);\n\n  }\n\n}\n"
  },
  {
    "path": "its/plugins/java-custom-rules/src/main/java/org/sonar/samples/java/RulesList.java",
    "content": "/*\n * Java Custom Rules Plugin\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonar.samples.java;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport org.sonar.plugins.java.api.JavaCheck;\nimport org.sonar.samples.java.checks.AvoidAnnotationRule;\n\npublic final class RulesList {\n\n  private RulesList() {\n  }\n\n  public static List<Class<? extends JavaCheck>> getChecks() {\n    List<Class<? extends JavaCheck>> checks = new ArrayList<>();\n    checks.addAll(getJavaChecks());\n    checks.addAll(getJavaTestChecks());\n    return Collections.unmodifiableList(checks);\n  }\n\n  /**\n   * These rules are going to target MAIN code only\n   */\n  public static List<Class<? extends JavaCheck>> getJavaChecks() {\n    return Collections.unmodifiableList(List.of(\n      AvoidAnnotationRule.class));\n  }\n\n  /**\n   * These rules are going to target TEST code only\n   */\n  public static List<Class<? extends JavaCheck>> getJavaTestChecks() {\n    return Collections.unmodifiableList(List.of());\n  }\n}\n"
  },
  {
    "path": "its/plugins/java-custom-rules/src/main/java/org/sonar/samples/java/checks/AvoidAnnotationRule.java",
    "content": "/*\n * Java Custom Rules Plugin\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonar.samples.java.checks;\n\nimport java.util.List;\nimport org.sonar.check.Rule;\nimport org.sonar.check.RuleProperty;\nimport org.sonar.plugins.java.api.JavaFileScanner;\nimport org.sonar.plugins.java.api.JavaFileScannerContext;\nimport org.sonar.plugins.java.api.tree.AnnotationTree;\nimport org.sonar.plugins.java.api.tree.BaseTreeVisitor;\nimport org.sonar.plugins.java.api.tree.IdentifierTree;\nimport org.sonar.plugins.java.api.tree.MethodTree;\nimport org.sonar.plugins.java.api.tree.Tree;\nimport org.sonar.plugins.java.api.tree.TypeTree;\n\n@Rule(key = \"AvoidAnnotation\")\npublic class AvoidAnnotationRule extends BaseTreeVisitor implements JavaFileScanner {\n\n  private static final String DEFAULT_VALUE = \"SuppressWarnings\";\n\n  private JavaFileScannerContext context;\n\n  /**\n   * Name of the annotation to avoid. Value can be set by users in Quality profiles.\n   * The key\n   */\n  @RuleProperty(\n    defaultValue = DEFAULT_VALUE,\n    description = \"Name of the annotation to avoid, without the prefix @, for instance 'Override'\")\n  protected String name;\n\n  @Override\n  public void scanFile(JavaFileScannerContext context) {\n    this.context = context;\n    scan(context.getTree());\n  }\n\n  @Override\n  public void visitMethod(MethodTree tree) {\n    List<AnnotationTree> annotations = tree.modifiers().annotations();\n    for (AnnotationTree annotationTree : annotations) {\n      TypeTree annotationType = annotationTree.annotationType();\n      if (annotationType.is(Tree.Kind.IDENTIFIER)) {\n        IdentifierTree identifier = (IdentifierTree) annotationType;\n        if (identifier.name().equals(name)) {\n          context.reportIssue(this, identifier, String.format(\"Avoid using annotation @%s\", name));\n        }\n      }\n    }\n\n    // The call to the super implementation allows to continue the visit of the AST.\n    // Be careful to always call this method to visit every node of the tree.\n    super.visitMethod(tree);\n  }\n}\n"
  },
  {
    "path": "its/plugins/java-custom-rules/src/main/resources/org/sonar/l10n/java/rules/java/AvoidAnnotation.html",
    "content": "<p>This rule detects usage of configured annotation</p>\n<h2>Noncompliant Code Example</h2>\n<pre>\nTO DO \n</pre>\n<h2>Compliant Solution</h2>\n<pre>\nTO DO \n</pre>"
  },
  {
    "path": "its/plugins/java-custom-rules/src/main/resources/org/sonar/l10n/java/rules/java/AvoidAnnotation.json",
    "content": "{\n  \"title\": \"Title of AvoidAnnotation\",\n  \"type\": \"CODE_SMELL\",\n  \"status\": \"ready\",\n  \"remediation\": {\n    \"func\": \"Constant\\/Issue\",\n    \"constantCost\": \"5min\"\n  },\n  \"tags\": [\n    \"pitfall\"\n  ],\n  \"defaultSeverity\": \"Minor\"\n}"
  },
  {
    "path": "its/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-core-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n  </parent>\n  <artifactId>sonarlint-core-its</artifactId>\n  <name>SonarLint Core - ITs</name>\n  <description>Integration tests parent</description>\n  <packaging>pom</packaging>\n\n  <modules>\n    <module>plugins/custom-sensor-plugin</module>\n    <module>plugins/java-custom-rules</module>\n    <module>plugins/global-extension-plugin</module>\n    <module>tests</module>\n  </modules>\n\n  <properties>\n    <maven.deploy.skip>true</maven.deploy.skip>\n  </properties>\n</project>\n"
  },
  {
    "path": "its/tests/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-core-its</artifactId>\n    <version>11.2-SNAPSHOT</version>\n  </parent>\n  <artifactId>sonarlint-core-its-tests</artifactId>\n  <name>SonarLint Core - ITs - Tests</name>\n  <description>Integration tests</description>\n\n  <dependencies>\n    <dependency>\n      <groupId>org.sonarsource.sonarlint.core</groupId>\n      <artifactId>sonarlint-analysis-engine</artifactId>\n      <version>${project.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.sonarlint.core</groupId>\n      <artifactId>sonarlint-commons</artifactId>\n      <version>${project.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.sonarlint.core</groupId>\n      <artifactId>sonarlint-core</artifactId>\n      <version>${project.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.sonarlint.core</groupId>\n      <artifactId>sonarlint-http</artifactId>\n      <version>${project.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <!-- This dependency needs to go first for the Loggers class from plugin-commons to be used before the one from the plugin-api -->\n    <dependency>\n      <groupId>org.sonarsource.sonarlint.core</groupId>\n      <artifactId>sonarlint-plugin-commons</artifactId>\n      <version>${project.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.sonarlint.core</groupId>\n      <artifactId>sonarlint-plugin-api</artifactId>\n      <version>${project.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.sonarlint.core</groupId>\n      <artifactId>sonarlint-rpc-impl</artifactId>\n      <version>${project.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.sonarlint.core</groupId>\n      <artifactId>sonarlint-rule-extractor</artifactId>\n      <version>${project.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.sonarlint.core</groupId>\n      <artifactId>sonarlint-server-api</artifactId>\n      <version>${project.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.sonarlint.core</groupId>\n      <artifactId>sonarlint-server-connection</artifactId>\n      <version>${project.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.sonarlint.core</groupId>\n      <artifactId>sonarlint-telemetry</artifactId>\n      <version>${project.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.sonarlint.core</groupId>\n      <artifactId>sonarlint-rpc-java-client</artifactId>\n      <version>${project.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.sonarqube</groupId>\n      <artifactId>sonar-ws</artifactId>\n      <version>9.9.9.104369</version>\n      <scope>test</scope>\n      <exclusions>\n        <exclusion>\n          <!-- sonar-plugin-api is shaded into SLCORE -->\n          <groupId>org.sonarsource.api.plugin</groupId>\n          <artifactId>sonar-plugin-api</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.orchestrator</groupId>\n      <artifactId>sonar-orchestrator-junit5</artifactId>\n      <version>6.0.3.3907</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.assertj</groupId>\n      <artifactId>assertj-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-engine</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.eclipse.jetty</groupId>\n      <artifactId>jetty-server</artifactId>\n      <version>11.0.25</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>com.google.guava</groupId>\n      <artifactId>guava</artifactId>\n      <version>32.1.1-jre</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.awaitility</groupId>\n      <artifactId>awaitility</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-lang3</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-compress</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>com.squareup.okhttp3</groupId>\n      <artifactId>okhttp-jvm</artifactId>\n      <version>${okhttp.version}</version>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-dependency-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>copy-plugins</id>\n            <phase>validate</phase>\n            <goals>\n              <goal>copy</goal>\n            </goals>\n            <configuration>\n              <artifactItems>\n                <artifactItem>\n                  <groupId>com.sonarsource.cpp</groupId>\n                  <artifactId>sonar-cfamily-plugin</artifactId>\n                  <version>6.75.1.93101</version>\n                  <type>jar</type>\n                </artifactItem>\n                <artifactItem>\n                  <groupId>org.sonarsource.go</groupId>\n                  <artifactId>sonar-go-plugin</artifactId>\n                  <version>1.31.0.4938</version>\n                  <type>jar</type>\n                </artifactItem>\n                <artifactItem>\n                  <groupId>org.sonarsource.iac</groupId>\n                  <artifactId>sonar-iac-plugin</artifactId>\n                  <version>2.2.0.18377</version>\n                  <type>jar</type>\n                </artifactItem>\n                <artifactItem>\n                  <groupId>org.sonarsource.javascript</groupId>\n                  <artifactId>sonar-javascript-plugin</artifactId>\n                  <version>11.7.1.36988</version>\n                  <type>jar</type>\n                </artifactItem>\n              </artifactItems>\n              <outputDirectory>${project.build.directory}/plugins</outputDirectory>\n              <overWriteReleases>false</overWriteReleases>\n              <overWriteSnapshots>true</overWriteSnapshots>\n              <stripVersion>false</stripVersion>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "its/tests/projects/sample-apex/src/file.cls",
    "content": "public class Foo {\n    static void MyFooMethod() {\n\t    Integer target = -5;\n\t\t\tInteger num = 3;\n\t\t\ttarget =- num;  // Noncompliant; target = -3. Is that really what's meant?\n    }\n}"
  },
  {
    "path": "its/tests/projects/sample-c/src/file.c",
    "content": "void end_of_preamble();\n\n#import \"foo.h\" // Noncompliant\n\nint function3(char* ptr) /* Noncompliant; two explicit returns */\n{\n  if (ptr == NULL) return -1;\n\n  return 7;\n}\n"
  },
  {
    "path": "its/tests/projects/sample-c/src/foo.h",
    "content": ""
  },
  {
    "path": "its/tests/projects/sample-cloudformation/src/sample.yaml",
    "content": "AWSTemplateFormatVersion: 2010-09-09\nResources:\n  S3Bucket:\n    Type: 'AWS::S3::Bucket'\n    Properties:\n      BucketName: \"mybucketname\"\n      Tags:\n        - Key: \"anycompany:cost-center\" # Noncompliant\n          Value: \"Accounting\""
  },
  {
    "path": "its/tests/projects/sample-cobol/copybooks/Attr.cpy",
    "content": "       01  ATTRIBUTE-DEFINITIONS.\r\n      *\r\n           05  ATTR-UNPROT                 PIC X   VALUE ' '.\r\n           05  ATTR-UNPROT-MDT             PIC X   VALUE X'C1'.\r\n           05  ATTR-UNPROT-BRT             PIC X   VALUE X'C8'.\r\n           05  ATTR-UNPROT-BRT-MDT         PIC X   VALUE X'C9'.\r\n           05  ATTR-UNPROT-DARK            PIC X   VALUE X'4C'.\r\n           05  ATTR-UNPROT-DARK-MDT        PIC X   VALUE X'4D'.\r\n           05  ATTR-UNPROT-NUM             PIC X   VALUE X'50'.\r\n           05  ATTR-UNPROT-NUM-MDT         PIC X   VALUE X'D1'.\r\n           05  ATTR-UNPROT-NUM-BRT         PIC X   VALUE X'D8'.\r\n           05  ATTR-UNPROT-NUM-BRT-MDT     PIC X   VALUE X'D9'.\r\n           05  ATTR-UNPROT-NUM-DARK        PIC X   VALUE X'5C'.\r\n           05  ATTR-UNPROT-NUM-DARK-MDT    PIC X   VALUE X'5D'.\r\n           05  ATTR-PROT                   PIC X   VALUE X'60'.\r\n           05  ATTR-PROT-MDT               PIC X   VALUE X'61'.\r\n           05  ATTR-PROT-BRT               PIC X   VALUE X'E8'.\r\n           05  ATTR-PROT-BRT-MDT           PIC X   VALUE X'E9'.\r\n           05  ATTR-PROT-DARK              PIC X   VALUE '%'.\r\n           05  ATTR-PROT-DARK-MDT          PIC X   VALUE X'6D'.\r\n           05  ATTR-PROT-SKIP              PIC X   VALUE X'F0'.\r\n           05  ATTR-PROT-SKIP-MDT          PIC X   VALUE X'F1'.\r\n           05  ATTR-PROT-SKIP-BRT          PIC X   VALUE X'F8'.\r\n           05  ATTR-PROT-SKIP-BRT-MDT      PIC X   VALUE X'F9'.\r\n           05  ATTR-PROT-SKIP-DARK         PIC X   VALUE X'7C'.\r\n           05  ATTR-PROT-SKIP-DARK-MDT     PIC X   VALUE X'7D'.\r\n      *\r\n           05  ATTR-NO-HIGHLIGHT           PIC X   VALUE X'00'.\r\n           05  ATTR-BLINK                  PIC X   VALUE '1'.\r\n           05  ATTR-REVERSE                PIC X   VALUE '2'.\r\n           05  ATTR-UNDERSCORE             PIC X   VALUE '4'.\r\n      *\r\n           05  ATTR-DEFAULT-COLOR          PIC X   VALUE X'00'.\r\n           05  ATTR-BLUE                   PIC X   VALUE '1'.\r\n           05  ATTR-RED                    PIC X   VALUE '2'.\r\n           05  ATTR-PINK                   PIC X   VALUE '3'.\r\n           05  ATTR-GREEN                  PIC X   VALUE '4'.\r\n           05  ATTR-TURQUOISE              PIC X   VALUE '5'.\r\n           05  ATTR-YELLOW                 PIC X   VALUE '6'.\r\n           05  ATTR-NEUTRAL                PIC X   VALUE '7'.\r\n"
  },
  {
    "path": "its/tests/projects/sample-cobol/copybooks/Custmas.cpy",
    "content": "       01  CUSTOMER-MASTER-RECORD.\r\n      *\r\n           05  CM-CUSTOMER-NUMBER      PIC X(6).\r\n           05  CM-FIRST-NAME           PIC X(20).\r\n           05  CM-LAST-NAME            PIC X(30).\r\n           05  CM-ADDRESS              PIC X(30).\r\n           05  CM-CITY                 PIC X(20).\r\n           05  CM-STATE                PIC X(2).\r\n           05  CM-ZIP-CODE             PIC X(10).\r\n"
  },
  {
    "path": "its/tests/projects/sample-cobol/copybooks/Errparm.cpy",
    "content": "       01  ERROR-PARAMETERS.\r\n      *\r\n           05  ERR-RESP          PIC S9(8)   COMP.\r\n           05  ERR-RESP2         PIC S9(8)   COMP.\r\n           05  ERR-TRNID         PIC X(4) VALUE IS 99.\r\n           05  ERR-RSRCE         PIC X(8).\r\n"
  },
  {
    "path": "its/tests/projects/sample-cobol/copybooks/MNTSET2.CPY",
    "content": "      *   Micro Focus BMS Screen Painter (ver MFBM 2.0.11)\r\n      *   MapSet Name   MNTSET2\r\n      *   Date Created  04/16/2001\r\n      *   Time Created  14:38:47\r\n\r\n      *  Input Data For Map MNTMAP1\r\n         01 MNTMAP1I.\r\n            03 FILLER                         PIC X(12).\r\n            03 TRANID1L                       PIC S9(4) COMP.\r\n            03 TRANID1F                       PIC X.\r\n            03 FILLER REDEFINES TRANID1F.\r\n               05 TRANID1A                       PIC X.\r\n            03 FILLER                         PIC X(2).\r\n            03 TRANID1I                       PIC X(4).\r\n            03 CUSTNO1L                       PIC S9(4) COMP.\r\n            03 CUSTNO1F                       PIC X.\r\n            03 FILLER REDEFINES CUSTNO1F.\r\n               05 CUSTNO1A                       PIC X.\r\n            03 FILLER                         PIC X(2).\r\n            03 CUSTNO1I                       PIC X(6).\r\n            03 ACTIONL                        PIC S9(4) COMP.\r\n            03 ACTIONF                        PIC X.\r\n            03 FILLER REDEFINES ACTIONF.\r\n               05 ACTIONA                        PIC X.\r\n            03 FILLER                         PIC X(2).\r\n            03 ACTIONI                        PIC X(1).\r\n            03 MSG1L                          PIC S9(4) COMP.\r\n            03 MSG1F                          PIC X.\r\n            03 FILLER REDEFINES MSG1F.\r\n               05 MSG1A                          PIC X.\r\n            03 FILLER                         PIC X(2).\r\n            03 MSG1I                          PIC X(79).\r\n            03 DUMMY1L                        PIC S9(4) COMP.\r\n            03 DUMMY1F                        PIC X.\r\n            03 FILLER REDEFINES DUMMY1F.\r\n               05 DUMMY1A                        PIC X.\r\n            03 FILLER                         PIC X(2).\r\n            03 DUMMY1I                        PIC X(1).\r\n\r\n      *  Output Data For Map MNTMAP1\r\n         01 MNTMAP1O REDEFINES MNTMAP1I.\r\n            03 FILLER                         PIC X(12).\r\n            03 FILLER                         PIC X(3).\r\n            03 TRANID1C                       PIC X.\r\n            03 TRANID1H                       PIC X.\r\n            03 TRANID1O                       PIC X(4).\r\n            03 FILLER                         PIC X(3).\r\n            03 CUSTNO1C                       PIC X.\r\n            03 CUSTNO1H                       PIC X.\r\n            03 CUSTNO1O                       PIC X(6).\r\n            03 FILLER                         PIC X(3).\r\n            03 ACTIONC                        PIC X.\r\n            03 ACTIONH                        PIC X.\r\n            03 ACTIONO                        PIC X(1).\r\n            03 FILLER                         PIC X(3).\r\n            03 MSG1C                          PIC X.\r\n            03 MSG1H                          PIC X.\r\n            03 MSG1O                          PIC X(79).\r\n            03 FILLER                         PIC X(3).\r\n            03 DUMMY1C                        PIC X.\r\n            03 DUMMY1H                        PIC X.\r\n            03 DUMMY1O                        PIC X(1).\r\n\r\n      *  Input Data For Map MNTMAP2\r\n         01 MNTMAP2I.\r\n            03 FILLER                         PIC X(12).\r\n            03 TRANID2L                       PIC S9(4) COMP.\r\n            03 TRANID2F                       PIC X.\r\n            03 FILLER REDEFINES TRANID2F.\r\n               05 TRANID2A                       PIC X.\r\n            03 FILLER                         PIC X(2).\r\n            03 TRANID2I                       PIC X(4).\r\n            03 INSTR2L                        PIC S9(4) COMP.\r\n            03 INSTR2F                        PIC X.\r\n            03 FILLER REDEFINES INSTR2F.\r\n               05 INSTR2A                        PIC X.\r\n            03 FILLER                         PIC X(2).\r\n            03 INSTR2I                        PIC X(79).\r\n            03 CUSTNO2L                       PIC S9(4) COMP.\r\n            03 CUSTNO2F                       PIC X.\r\n            03 FILLER REDEFINES CUSTNO2F.\r\n               05 CUSTNO2A                       PIC X.\r\n            03 FILLER                         PIC X(2).\r\n            03 CUSTNO2I                       PIC X(6).\r\n            03 LNAMEL                         PIC S9(4) COMP.\r\n            03 LNAMEF                         PIC X.\r\n            03 FILLER REDEFINES LNAMEF.\r\n               05 LNAMEA                         PIC X.\r\n            03 FILLER                         PIC X(2).\r\n            03 LNAMEI                         PIC X(30).\r\n            03 FNAMEL                         PIC S9(4) COMP.\r\n            03 FNAMEF                         PIC X.\r\n            03 FILLER REDEFINES FNAMEF.\r\n               05 FNAMEA                         PIC X.\r\n            03 FILLER                         PIC X(2).\r\n            03 FNAMEI                         PIC X(20).\r\n            03 ADDRL                          PIC S9(4) COMP.\r\n            03 ADDRF                          PIC X.\r\n            03 FILLER REDEFINES ADDRF.\r\n               05 ADDRA                          PIC X.\r\n            03 FILLER                         PIC X(2).\r\n            03 ADDRI                          PIC X(30).\r\n            03 CITYL                          PIC S9(4) COMP.\r\n            03 CITYF                          PIC X.\r\n            03 FILLER REDEFINES CITYF.\r\n               05 CITYA                          PIC X.\r\n            03 FILLER                         PIC X(2).\r\n            03 CITYI                          PIC X(20).\r\n            03 STATEL                         PIC S9(4) COMP.\r\n            03 STATEF                         PIC X.\r\n            03 FILLER REDEFINES STATEF.\r\n               05 STATEA                         PIC X.\r\n            03 FILLER                         PIC X(2).\r\n            03 STATEI                         PIC X(2).\r\n            03 ZIPCODEL                       PIC S9(4) COMP.\r\n            03 ZIPCODEF                       PIC X.\r\n            03 FILLER REDEFINES ZIPCODEF.\r\n               05 ZIPCODEA                       PIC X.\r\n            03 FILLER                         PIC X(2).\r\n            03 ZIPCODEI                       PIC X(10).\r\n            03 MSG2L                          PIC S9(4) COMP.\r\n            03 MSG2F                          PIC X.\r\n            03 FILLER REDEFINES MSG2F.\r\n               05 MSG2A                          PIC X.\r\n            03 FILLER                         PIC X(2).\r\n            03 MSG2I                          PIC X(79).\r\n            03 DUMMY2L                        PIC S9(4) COMP.\r\n            03 DUMMY2F                        PIC X.\r\n            03 FILLER REDEFINES DUMMY2F.\r\n               05 DUMMY2A                        PIC X.\r\n            03 FILLER                         PIC X(2).\r\n            03 DUMMY2I                        PIC X(1).\r\n\r\n      *  Output Data For Map MNTMAP2\r\n         01 MNTMAP2O REDEFINES MNTMAP2I.\r\n            03 FILLER                         PIC X(12).\r\n            03 FILLER                         PIC X(3).\r\n            03 TRANID2C                       PIC X.\r\n            03 TRANID2H                       PIC X.\r\n            03 TRANID2O                       PIC X(4).\r\n            03 FILLER                         PIC X(3).\r\n            03 INSTR2C                        PIC X.\r\n            03 INSTR2H                        PIC X.\r\n            03 INSTR2O                        PIC X(79).\r\n            03 FILLER                         PIC X(3).\r\n            03 CUSTNO2C                       PIC X.\r\n            03 CUSTNO2H                       PIC X.\r\n            03 CUSTNO2O                       PIC X(6).\r\n            03 FILLER                         PIC X(3).\r\n            03 LNAMEC                         PIC X.\r\n            03 LNAMEH                         PIC X.\r\n            03 LNAMEO                         PIC X(30).\r\n            03 FILLER                         PIC X(3).\r\n            03 FNAMEC                         PIC X.\r\n            03 FNAMEH                         PIC X.\r\n            03 FNAMEO                         PIC X(20).\r\n            03 FILLER                         PIC X(3).\r\n            03 ADDRC                          PIC X.\r\n            03 ADDRH                          PIC X.\r\n            03 ADDRO                          PIC X(30).\r\n            03 FILLER                         PIC X(3).\r\n            03 CITYC                          PIC X.\r\n            03 CITYH                          PIC X.\r\n            03 CITYO                          PIC X(20).\r\n            03 FILLER                         PIC X(3).\r\n            03 STATEC                         PIC X.\r\n            03 STATEH                         PIC X.\r\n            03 STATEO                         PIC X(2).\r\n            03 FILLER                         PIC X(3).\r\n            03 ZIPCODEC                       PIC X.\r\n            03 ZIPCODEH                       PIC X.\r\n            03 ZIPCODEO                       PIC X(10).\r\n            03 FILLER                         PIC X(3).\r\n            03 MSG2C                          PIC X.\r\n            03 MSG2H                          PIC X.\r\n            03 MSG2O                          PIC X(79).\r\n            03 FILLER                         PIC X(3).\r\n            03 DUMMY2C                        PIC X.\r\n            03 DUMMY2H                        PIC X.\r\n            03 DUMMY2O                        PIC X(1).\r\n\r\n"
  },
  {
    "path": "its/tests/projects/sample-cobol/src/Custmnt2.cbl",
    "content": "       IDENTIFICATION DIVISION.\r\n      *\r\n       PROGRAM-ID.  CUSTMNT2.\r\n      *\r\n       ENVIRONMENT DIVISION.\r\n      *\r\n       DATA DIVISION.\r\n      *\r\n       WORKING-STORAGE SECTION.\r\n      *\r\n       01  SWITCHES.\r\n      *\r\n           05  VALID-DATA-SW                 PIC X(01) VALUE 'Y'.\r\n               88  VALID-DATA                          VALUE 'Y'.\r\n      *\r\n       01  FLAGS.\r\n      *\r\n           05  SEND-FLAG                     PIC X(01).\r\n               88  SEND-ERASE                       VALUE '1'.\r\n               88  SEND-ERASE-ALARM                 VALUE '2'.\r\n               88  SEND-DATAONLY                    VALUE '3'.\r\n               88  SEND-DATAONLY-ALARM              VALUE '4'.\r\n      *\r\n       01  WORK-FIELDS.\r\n      *\r\n           05  RESPONSE-CODE                 PIC S9(08) COMP.\r\n      *\r\n       01  USER-INSTRUCTIONS.\r\n      *\r\n           05  ADD-INSTRUCTION               PIC X(79) VALUE\r\n               'Type information for new customer.  Then Press Enter.'.\r\n           05  CHANGE-INSTRUCTION            PIC X(79) VALUE\r\n               'Type changes.  Then press Enter.'.\r\n           05  DELETE-INSTRUCTION            PIC X(79) VALUE\r\n               'Press Enter to delete this customer or press F12 to canc\r\n      -        'el.'.\r\n      *\r\n       01  COMMUNICATION-AREA.\r\n      *\r\n           05  CA-CONTEXT-FLAG               PIC X(01).\r\n               88  PROCESS-KEY-MAP                  VALUE '1'.\r\n               88  PROCESS-ADD-CUSTOMER             VALUE '2'.\r\n               88  PROCESS-CHANGE-CUSTOMER          VALUE '3'.\r\n               88  PROCESS-DELETE-CUSTOMER          VALUE '4'.\r\n           05  CA-CUSTOMER-RECORD.\r\n               10  CA-CUSTOMER-NUMBER        PIC X(06).\r\n               10  FILLER                    PIC X(112).\r\n      *\r\n       COPY CUSTMAS.\r\n      *\r\n       COPY MNTSET2.\r\n      *\r\n       COPY DFHAID.\r\n      *\r\n       COPY ATTR.\r\n      *\r\n       COPY ERRPARM.\r\n      *\r\n       LINKAGE SECTION.\r\n      *\r\n       01  DFHCOMMAREA                       PIC X(119).\r\n      *\r\n       PROCEDURE DIVISION.\r\n      *\r\n       0000-PROCESS-CUSTOMER-MAINT.\r\n      *\r\n           IF EIBCALEN > ZERO\r\n               MOVE DFHCOMMAREA TO COMMUNICATION-AREA\r\n           END-IF.\r\n      *\r\n           EVALUATE TRUE\r\n      *\r\n               WHEN EIBCALEN = ZERO\r\n                   MOVE LOW-VALUE TO MNTMAP1O\r\n                   MOVE -1 TO CUSTNO1L\r\n                   SET SEND-ERASE TO TRUE\r\n                   PERFORM 1500-SEND-KEY-MAP\r\n                   SET PROCESS-KEY-MAP TO TRUE\r\n      *\r\n               WHEN EIBAID = DFHPF3\r\n                   EXEC CICS\r\n                       XCTL PROGRAM('INVMENU')\r\n                   END-EXEC\r\n      *\r\n               WHEN EIBAID = DFHPF12\r\n                   IF PROCESS-KEY-MAP\r\n                       EXEC CICS\r\n                           XCTL PROGRAM('INVMENU')\r\n                       END-EXEC\r\n                   ELSE\r\n                       MOVE LOW-VALUE TO MNTMAP1O\r\n                       MOVE -1 TO CUSTNO1L\r\n                       SET SEND-ERASE TO TRUE\r\n                       PERFORM 1500-SEND-KEY-MAP\r\n                       SET PROCESS-KEY-MAP TO TRUE\r\n                   END-IF\r\n      *\r\n               WHEN EIBAID = DFHCLEAR\r\n                   IF PROCESS-KEY-MAP\r\n                       MOVE LOW-VALUE TO MNTMAP1O\r\n                       MOVE -1 TO CUSTNO1L\r\n                       SET SEND-ERASE TO TRUE\r\n                       PERFORM 1500-SEND-KEY-MAP\r\n                   ELSE\r\n                       MOVE LOW-VALUE TO MNTMAP2O\r\n                       MOVE CA-CUSTOMER-NUMBER TO CUSTNO2O\r\n                       EVALUATE TRUE\r\n                           WHEN PROCESS-ADD-CUSTOMER\r\n                               MOVE ADD-INSTRUCTION    TO INSTR2O\r\n                           WHEN PROCESS-CHANGE-CUSTOMER\r\n                               MOVE CHANGE-INSTRUCTION TO INSTR2O\r\n                           WHEN PROCESS-DELETE-CUSTOMER\r\n                               MOVE DELETE-INSTRUCTION TO INSTR2O\r\n                       END-EVALUATE\r\n                       MOVE -1 TO LNAMEL\r\n                       SET SEND-ERASE TO TRUE\r\n                       PERFORM 1400-SEND-DATA-MAP\r\n                   END-IF\r\n      *\r\n               WHEN EIBAID = DFHPA1 OR DFHPA2 OR DFHPA3\r\n                   CONTINUE\r\n      *\r\n               WHEN EIBAID = DFHENTER\r\n                   EVALUATE TRUE\r\n                       WHEN PROCESS-KEY-MAP\r\n                           PERFORM 1000-PROCESS-KEY-MAP\r\n                       WHEN PROCESS-ADD-CUSTOMER\r\n                           PERFORM 2000-PROCESS-ADD-CUSTOMER\r\n                       WHEN PROCESS-CHANGE-CUSTOMER\r\n                           PERFORM 3000-PROCESS-CHANGE-CUSTOMER\r\n                       WHEN PROCESS-DELETE-CUSTOMER\r\n                           PERFORM 4000-PROCESS-DELETE-CUSTOMER\r\n                   END-EVALUATE\r\n      *\r\n               WHEN OTHER\r\n                   IF PROCESS-KEY-MAP\r\n                       MOVE LOW-VALUE TO MNTMAP1O\r\n                       MOVE 'That key is unassigned.' TO MSG1O\r\n                       MOVE -1 TO CUSTNO1L\r\n                       SET SEND-DATAONLY-ALARM TO TRUE\r\n                       PERFORM 1500-SEND-KEY-MAP\r\n                   ELSE\r\n                       MOVE LOW-VALUE TO MNTMAP2O\r\n                       MOVE 'That key is unassigned.' TO MSG2O\r\n                       MOVE -1 TO LNAMEL\r\n                       SET SEND-DATAONLY-ALARM TO TRUE\r\n                       PERFORM 1400-SEND-DATA-MAP\r\n                   END-IF\r\n      *\r\n           END-EVALUATE.\r\n\r\n           EXEC CICS\r\n               RETURN TRANSID('MNT2')\r\n                      COMMAREA(COMMUNICATION-AREA)\r\n           END-EXEC.\r\n      *\r\n       1000-PROCESS-KEY-MAP.\r\n      *\r\n           PERFORM 1100-RECEIVE-KEY-MAP.\r\n           PERFORM 1200-EDIT-KEY-DATA.\r\n           IF VALID-DATA\r\n               IF NOT PROCESS-DELETE-CUSTOMER\r\n                   INSPECT CUSTOMER-MASTER-RECORD\r\n                       REPLACING ALL SPACE BY '_'\r\n               END-IF\r\n               MOVE CUSTNO1I      TO CUSTNO2O\r\n               MOVE CM-LAST-NAME  TO LNAMEO\r\n               MOVE CM-FIRST-NAME TO FNAMEO\r\n               MOVE CM-ADDRESS    TO ADDRO\r\n               MOVE CM-CITY       TO CITYO\r\n               MOVE CM-STATE      TO STATEO\r\n               MOVE CM-ZIP-CODE   TO ZIPCODEO\r\n               MOVE -1 TO LNAMEL\r\n               SET SEND-ERASE TO TRUE\r\n               PERFORM 1400-SEND-DATA-MAP\r\n           ELSE\r\n               MOVE LOW-VALUE TO CUSTNO1O\r\n                                 ACTIONO\r\n               SET SEND-DATAONLY-ALARM TO TRUE\r\n               PERFORM 1500-SEND-KEY-MAP\r\n           END-IF.\r\n      *\r\n       1100-RECEIVE-KEY-MAP.\r\n      *\r\n           EXEC CICS\r\n               RECEIVE MAP('MNTMAP1')\r\n                       MAPSET('MNTSET2')\r\n                       INTO(MNTMAP1I)\r\n           END-EXEC.\r\n      *\r\n           INSPECT MNTMAP1I\r\n               REPLACING ALL '_' BY SPACE.\r\n      *\r\n       1200-EDIT-KEY-DATA.\r\n      *\r\n           MOVE ATTR-NO-HIGHLIGHT TO ACTIONH\r\n                                     CUSTNO1H.\r\n      *\r\n           IF ACTIONI NOT = '1' AND '2' AND '3' AND '4' AND '5'\r\n               MOVE ATTR-REVERSE TO ACTIONH\r\n               MOVE -1 TO ACTIONL\r\n               MOVE 'Action must be 1, 2, or 3.' TO MSG1O\r\n               MOVE 'N' TO VALID-DATA-SW\r\n           END-IF.\r\n      *\r\n           IF       CUSTNO1L = ZERO\r\n                 OR CUSTNO1I = SPACE\r\n               MOVE ATTR-REVERSE TO CUSTNO1H\r\n               MOVE -1 TO CUSTNO1L\r\n               MOVE 'You must enter a customer number.' TO MSG1O\r\n               MOVE 'N' TO VALID-DATA-SW\r\n           END-IF.\r\n      *\r\n           IF VALID-DATA\r\n               MOVE LOW-VALUE TO MNTMAP2O\r\n               EVALUATE ACTIONI\r\n                   WHEN '1'\r\n                       PERFORM 1300-READ-CUSTOMER-RECORD\r\n                       IF RESPONSE-CODE = DFHRESP(NOTFND)\r\n                           MOVE ADD-INSTRUCTION TO INSTR2O\r\n                           SET PROCESS-ADD-CUSTOMER TO TRUE\r\n                           MOVE SPACE TO CUSTOMER-MASTER-RECORD\r\n                       ELSE\r\n                           IF RESPONSE-CODE = DFHRESP(NORMAL)\r\n                               MOVE 'That customer already exists.'\r\n                                    TO MSG1O\r\n                               MOVE 'N' TO VALID-DATA-SW\r\n                           END-IF\r\n                       END-IF\r\n                   WHEN '2'\r\n                       PERFORM 1300-READ-CUSTOMER-RECORD\r\n                       IF RESPONSE-CODE = DFHRESP(NORMAL)\r\n                           MOVE CUSTOMER-MASTER-RECORD TO\r\n                               CA-CUSTOMER-RECORD\r\n                           MOVE CHANGE-INSTRUCTION TO INSTR2O\r\n                           SET PROCESS-CHANGE-CUSTOMER TO TRUE\r\n                       ELSE\r\n                           IF RESPONSE-CODE = DFHRESP(NOTFND)\r\n                               MOVE 'That customer does not exist.' TO\r\n                                   MSG1O\r\n                               MOVE 'N' TO VALID-DATA-SW\r\n                           END-IF\r\n                       END-IF\r\n                   WHEN '3'\r\n                       PERFORM 1300-READ-CUSTOMER-RECORD\r\n                       IF RESPONSE-CODE = DFHRESP(NORMAL)\r\n                           MOVE CUSTOMER-MASTER-RECORD TO\r\n                               CA-CUSTOMER-RECORD\r\n                           MOVE DELETE-INSTRUCTION TO INSTR2O\r\n                           SET PROCESS-DELETE-CUSTOMER TO TRUE\r\n                           MOVE ATTR-PROT TO LNAMEA\r\n                                             FNAMEA\r\n                                             ADDRA\r\n                                             CITYA\r\n                                             STATEA\r\n                                             ZIPCODEA\r\n                       ELSE\r\n                           IF RESPONSE-CODE = DFHRESP(NOTFND)\r\n                               MOVE 'That customer does not exist.' TO\r\n                                   MSG1O\r\n                               MOVE 'N' TO VALID-DATA-SW\r\n                           END-IF\r\n                       END-IF\r\n               END-EVALUATE.\r\n      *\r\n       1300-READ-CUSTOMER-RECORD.\r\n      *\r\n           EXEC CICS\r\n               READ FILE('CUSTMAS')\r\n                    INTO(CUSTOMER-MASTER-RECORD)\r\n                    RIDFLD(CUSTNO1I)\r\n                    RESP(RESPONSE-CODE)\r\n           END-EXEC.\r\n      *\r\n           IF      RESPONSE-CODE NOT = DFHRESP(NORMAL)\r\n               AND RESPONSE-CODE NOT = DFHRESP(NOTFND)\r\n               PERFORM 9999-TERMINATE-PROGRAM\r\n           END-IF.\r\n      *\r\n       1400-SEND-DATA-MAP.\r\n      *\r\n           MOVE 'MNT2' TO TRANID2O.\r\n      *\r\n           EVALUATE TRUE\r\n               WHEN SEND-ERASE\r\n                   EXEC CICS\r\n                       SEND MAP('MNTMAP2')\r\n                            MAPSET('MNTSET2')\r\n                            FROM(MNTMAP2O)\r\n                            ERASE\r\n                            CURSOR\r\n                   END-EXEC\r\n               WHEN SEND-DATAONLY-ALARM\r\n                   EXEC CICS\r\n                       SEND MAP('MNTMAP2')\r\n                            MAPSET('MNTSET2')\r\n                            FROM(MNTMAP2O)\r\n                            DATAONLY\r\n                            ALARM\r\n                            CURSOR\r\n               END-EXEC\r\n           END-EVALUATE.\r\n      *\r\n       1500-SEND-KEY-MAP.\r\n      *\r\n           MOVE 'MNT2' TO TRANID1O.\r\n      *\r\n           EVALUATE TRUE\r\n               WHEN SEND-ERASE\r\n                   EXEC CICS\r\n                       SEND MAP('MNTMAP1')\r\n                            MAPSET('MNTSET2')\r\n                            FROM(MNTMAP1O)\r\n                            ERASE\r\n                            CURSOR\r\n                   END-EXEC\r\n               WHEN SEND-ERASE-ALARM\r\n                   EXEC CICS\r\n                       SEND MAP('MNTMAP1')\r\n                            MAPSET('MNTSET2')\r\n                            FROM(MNTMAP1O)\r\n                            ERASE\r\n                            ALARM\r\n                            CURSOR\r\n                   END-EXEC\r\n               WHEN SEND-DATAONLY-ALARM\r\n                   EXEC CICS\r\n                       SEND MAP('MNTMAP1')\r\n                            MAPSET('MNTSET2')\r\n                            FROM(MNTMAP1O)\r\n                            DATAONLY\r\n                            ALARM\r\n                            CURSOR\r\n                   END-EXEC\r\n           END-EVALUATE.\r\n      *\r\n       2000-PROCESS-ADD-CUSTOMER.\r\n      *\r\n           PERFORM 2100-RECEIVE-DATA-MAP.\r\n           PERFORM 2200-EDIT-CUSTOMER-DATA.\r\n           IF VALID-DATA\r\n               PERFORM 2300-WRITE-CUSTOMER-RECORD\r\n               IF RESPONSE-CODE = DFHRESP(NORMAL)\r\n                   MOVE 'Customer record added.' TO MSG1O\r\n                   SET SEND-ERASE TO TRUE\r\n               ELSE\r\n                   IF RESPONSE-CODE = DFHRESP(DUPREC)\r\n                       MOVE 'Another user has added a record with that c\r\n      -                    'ustomer number.' TO MSG1O\r\n                       SET SEND-ERASE-ALARM TO TRUE\r\n                   END-IF\r\n               END-IF\r\n               MOVE -1 TO CUSTNO1L\r\n               PERFORM 1500-SEND-KEY-MAP\r\n               SET PROCESS-KEY-MAP TO TRUE\r\n           ELSE\r\n               MOVE LOW-VALUE TO LNAMEO\r\n                                 FNAMEO\r\n                                 ADDRO\r\n                                 CITYO\r\n                                 STATEO\r\n                                 ZIPCODEO\r\n               SET SEND-DATAONLY-ALARM TO TRUE\r\n               PERFORM 1400-SEND-DATA-MAP\r\n           END-IF.\r\n      *\r\n       2100-RECEIVE-DATA-MAP.\r\n      *\r\n           EXEC CICS\r\n               RECEIVE MAP('MNTMAP2')\r\n                       MAPSET('MNTSET2')\r\n                       INTO(MNTMAP2I)\r\n           END-EXEC.\r\n      *\r\n           INSPECT MNTMAP2I\r\n               REPLACING ALL '_' BY SPACE.\r\n      *\r\n       2200-EDIT-CUSTOMER-DATA.\r\n      *\r\n           MOVE ATTR-NO-HIGHLIGHT TO ZIPCODEH\r\n                                     STATEH\r\n                                     CITYH\r\n                                     ADDRH\r\n                                     FNAMEH\r\n                                     LNAMEH.\r\n\r\n           IF       ZIPCODEI = SPACE\r\n                 OR ZIPCODEL = ZERO\r\n               MOVE ATTR-REVERSE TO ZIPCODEH\r\n               MOVE -1 TO ZIPCODEL\r\n               MOVE 'You must enter a zip code.' TO MSG2O\r\n               MOVE 'N' TO VALID-DATA-SW\r\n           END-IF.\r\n\r\n           IF       STATEI = SPACE\r\n                 OR STATEL = ZERO\r\n               MOVE ATTR-REVERSE TO STATEH\r\n               MOVE -1 TO STATEL\r\n               MOVE 'You must enter a state.' TO MSG2O\r\n               MOVE 'N' TO VALID-DATA-SW\r\n           END-IF.\r\n\r\n           IF       CITYI = SPACE\r\n                 OR CITYL = ZERO\r\n               MOVE ATTR-REVERSE TO CITYH\r\n               MOVE -1 TO CITYL\r\n               MOVE 'You must enter a city.' TO MSG2O\r\n               MOVE 'N' TO VALID-DATA-SW\r\n           END-IF.\r\n\r\n           IF       ADDRI = SPACE\r\n                 OR ADDRL = ZERO\r\n               MOVE ATTR-REVERSE TO ADDRH\r\n               MOVE -1 TO ADDRL\r\n               MOVE 'You must enter an address.' TO MSG2O\r\n               MOVE 'N' TO VALID-DATA-SW\r\n           END-IF.\r\n\r\n           IF       FNAMEI = SPACE\r\n                 OR FNAMEL = ZERO\r\n               MOVE ATTR-REVERSE TO FNAMEH\r\n               MOVE -1 TO FNAMEL\r\n               MOVE 'You must enter a first name.' TO MSG2O\r\n               MOVE 'N' TO VALID-DATA-SW\r\n           END-IF.\r\n\r\n           IF       LNAMEI = SPACE\r\n                 OR LNAMEL = ZERO\r\n               MOVE ATTR-REVERSE TO LNAMEH\r\n               MOVE -1 TO LNAMEL\r\n               MOVE 'You must enter a last name.' TO MSG2O\r\n               MOVE 'N' TO VALID-DATA-SW\r\n           END-IF.\r\n      *\r\n       2300-WRITE-CUSTOMER-RECORD.\r\n      *\r\n           MOVE CUSTNO2I TO CM-CUSTOMER-NUMBER.\r\n           MOVE LNAMEI   TO CM-LAST-NAME.\r\n           MOVE FNAMEI   TO CM-FIRST-NAME.\r\n           MOVE ADDRI    TO CM-ADDRESS.\r\n           MOVE CITYI    TO CM-CITY.\r\n           MOVE STATEI   TO CM-STATE.\r\n           MOVE ZIPCODEI TO CM-ZIP-CODE.\r\n      *\r\n           EXEC CICS\r\n               WRITE FILE('CUSTMAS')\r\n                     FROM(CUSTOMER-MASTER-RECORD)\r\n                     RIDFLD(CM-CUSTOMER-NUMBER)\r\n                     RESP(RESPONSE-CODE)\r\n           END-EXEC.\r\n      *\r\n           IF      RESPONSE-CODE NOT = DFHRESP(NORMAL)\r\n               AND RESPONSE-CODE NOT = DFHRESP(DUPREC)\r\n               PERFORM 9999-TERMINATE-PROGRAM\r\n           END-IF.\r\n      *\r\n       3000-PROCESS-CHANGE-CUSTOMER.\r\n      *\r\n           PERFORM 2100-RECEIVE-DATA-MAP.\r\n           PERFORM 2200-EDIT-CUSTOMER-DATA.\r\n           IF VALID-DATA\r\n               MOVE CUSTNO2I TO CM-CUSTOMER-NUMBER\r\n               PERFORM 3100-READ-CUSTOMER-FOR-UPDATE\r\n               IF RESPONSE-CODE = DFHRESP(NORMAL)\r\n                   IF CUSTOMER-MASTER-RECORD = CA-CUSTOMER-RECORD\r\n      * Introduce extra nested if as an example of rule violation \r\n                       IF VALID-DATA\r\n                           IF RESPONSE-CODE = DFHRESP(NORMAL)\r\n                               PERFORM 3200-REWRITE-CUSTOMER-RECORD\r\n                               MOVE 'Customer record updated.' TO MSG1O\r\n                               SET SEND-ERASE TO TRUE\r\n                           END-IF\r\n                       END-IF\r\n                   ELSE\r\n                       MOVE 'Another user has updated the record.  Try a\r\n      -                     'gain.' TO MSG1O\r\n                       SET SEND-ERASE-ALARM TO TRUE\r\n                   END-IF\r\n               ELSE\r\n                   IF RESPONSE-CODE = DFHRESP(NOTFND)\r\n                       MOVE 'Another user has deleted the record.' TO\r\n                           MSG1O\r\n                       SET SEND-ERASE-ALARM TO TRUE\r\n                   END-IF\r\n               END-IF\r\n               MOVE -1 TO CUSTNO1L\r\n               PERFORM 1500-SEND-KEY-MAP\r\n               SET PROCESS-KEY-MAP TO TRUE\r\n           ELSE\r\n               MOVE LOW-VALUE TO LNAMEO\r\n                                 FNAMEO\r\n                                 ADDRO\r\n                                 CITYO\r\n                                 STATEO\r\n                                 ZIPCODEO\r\n               SET SEND-DATAONLY-ALARM TO TRUE\r\n               PERFORM 1400-SEND-DATA-MAP\r\n           END-IF.\r\n      *\r\n       3100-READ-CUSTOMER-FOR-UPDATE.\r\n      *\r\n           EXEC CICS\r\n               READ FILE('CUSTMAS')\r\n                    INTO(CUSTOMER-MASTER-RECORD)\r\n                    RIDFLD(CM-CUSTOMER-NUMBER)\r\n                    UPDATE\r\n                    RESP(RESPONSE-CODE)\r\n           END-EXEC.\r\n      *\r\n           IF      RESPONSE-CODE NOT = DFHRESP(NORMAL)\r\n               AND RESPONSE-CODE NOT = DFHRESP(NOTFND)\r\n               PERFORM 9999-TERMINATE-PROGRAM\r\n           END-IF.\r\n      *\r\n       3200-REWRITE-CUSTOMER-RECORD.\r\n      *\r\n           MOVE LNAMEI   TO CM-LAST-NAME.\r\n           MOVE FNAMEI   TO CM-FIRST-NAME.\r\n           MOVE ADDRI    TO CM-ADDRESS.\r\n           MOVE CITYI    TO CM-CITY.\r\n           MOVE STATEI   TO CM-STATE.\r\n           MOVE ZIPCODEI TO CM-ZIP-CODE.\r\n      *\r\n           EXEC CICS\r\n               REWRITE FILE('CUSTMAS')\r\n                       FROM(CUSTOMER-MASTER-RECORD)\r\n                       RESP(RESPONSE-CODE)\r\n           END-EXEC.\r\n      *\r\n           IF RESPONSE-CODE NOT = DFHRESP(NORMAL)\r\n               PERFORM 9999-TERMINATE-PROGRAM\r\n           END-IF.\r\n      *\r\n       4000-PROCESS-DELETE-CUSTOMER.\r\n      *\r\n           MOVE CA-CUSTOMER-NUMBER TO CM-CUSTOMER-NUMBER.\r\n           PERFORM 3100-READ-CUSTOMER-FOR-UPDATE.\r\n           IF RESPONSE-CODE = DFHRESP(NORMAL)\r\n               ALTER X TO PROCEED TO Y\r\n               IF CUSTOMER-MASTER-RECORD = CA-CUSTOMER-RECORD\r\n                   PERFORM 4100-DELETE-CUSTOMER-RECORD\r\n                   MOVE 'Customer deleted.' TO MSG1O\r\n                   SET SEND-ERASE TO TRUE\r\n               ELSE\r\n                   MOVE 'Another user has updated the record.  Try again\r\n      -                 '.' TO MSG1O\r\n                   SET SEND-ERASE-ALARM TO TRUE\r\n               END-IF\r\n           ELSE\r\n               IF RESPONSE-CODE = DFHRESP(NOTFND)\r\n                   MOVE 'Another user has deleted the record.' TO\r\n                       MSG1O\r\n                   SET SEND-ERASE-ALARM TO TRUE\r\n               END-IF\r\n           END-IF.\r\n           MOVE -1 TO CUSTNO1L.\r\n           PERFORM 1500-SEND-KEY-MAP.\r\n           SET PROCESS-KEY-MAP TO TRUE.\r\n      *\r\n       4100-DELETE-CUSTOMER-RECORD.\r\n      * TODO Some comment\r\n           EXEC CICS\r\n               DELETE FILE('CUSTMAS')\r\n                      RESP(RESPONSE-CODE)\r\n           END-EXEC.\r\n      *\r\n           IF  RESPONSE-CODE NOT = DFHRESP(NORMAL)\r\n               PERFORM 9999-TERMINATE-PROGRAM\r\n           END-IF.\r\n      *\r\n       9999-TERMINATE-PROGRAM.\r\n      *\r\n           MOVE EIBRESP  TO ERR-RESP.\r\n           MOVE EIBRESP2 TO ERR-RESP2.\r\n           MOVE EIBTRNID TO ERR-TRNID.\r\n           MOVE EIBRSRCE TO ERR-RSRCE.\r\n      *\r\n           EXEC CICS\r\n               XCTL PROGRAM('SYSERR')\r\n                    COMMAREA(ERROR-PARAMETERS)\r\n           END-EXEC.\r\n"
  },
  {
    "path": "its/tests/projects/sample-custom-secrets/src/file.md",
    "content": "# README\n\nSecret is YaYYaYYaY\n"
  },
  {
    "path": "its/tests/projects/sample-dbd/src/hello.py",
    "content": "def out_of_bounds_access():\n    my_list = []\n    print(my_list[0])\n"
  },
  {
    "path": "its/tests/projects/sample-docker/src/Dockerfile",
    "content": "from ubuntu:22.04 as jammy\n"
  },
  {
    "path": "its/tests/projects/sample-global-extension/src/foo.glob",
    "content": "Foo"
  },
  {
    "path": "its/tests/projects/sample-go/src/sample.go",
    "content": "package main\nimport (\n    \"crypto/rand\"\n    \"crypto/rsa\"\n    \"fmt\"\n)\n\nfunc encrypt(plaintext []byte) []byte {\n    random := rand.Reader\n    privateKey, _ := rsa.GenerateKey(random, 4096)\n    ciphertext, _ := rsa.EncryptPKCS1v15(random, &privateKey.PublicKey, plaintext)\n    return ciphertext\n}\n\nfunc add(x, y int) int {\n\treturn x + y\n\tz := x + y\n}\n"
  },
  {
    "path": "its/tests/projects/sample-java/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <groupId>org.sonarsource.sonarlint.core</groupId>\n  <artifactId>sample-java</artifactId>\n  <version>1.0-SNAPSHOT</version>\n  <name>Sample Java</name>\n  <description>Sample project for ITs</description>\n\n  <properties>\n    <maven.compiler.source>17</maven.compiler.source>\n    <maven.compiler.target>17</maven.compiler.target>\n  </properties>\n\n</project>\n"
  },
  {
    "path": "its/tests/projects/sample-java/src/main/java/foo/Foo.java",
    "content": "package foo;\n\npublic class Foo {\n  public void call_echo() {\n    echo(3);\n  }\n  \n  public void echo(int i) {\n    should_be_static();\n  }\n  \n  // invalid\n  private void should_be_static() {\n    System.out.println(\"Foo\");\n  }\n  \n}\n"
  },
  {
    "path": "its/tests/projects/sample-java-custom/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <groupId>org.sonarsource.sonarlint.core</groupId>\n  <artifactId>sample-java</artifactId>\n  <version>1.0-SNAPSHOT</version>\n  <name>Sample Java</name>\n  <description>Sample project for ITs</description>\n\n  <properties>\n    <maven.compiler.source>17</maven.compiler.source>\n    <maven.compiler.target>17</maven.compiler.target>\n  </properties>\n\n</project>\n"
  },
  {
    "path": "its/tests/projects/sample-java-custom/src/main/java/foo/Foo.java",
    "content": "package foo;\n\npublic class Foo {\n  public void call_echo() {\n    echo(3);\n  }\n  \n  public void echo(int i) {\n    should_be_static();\n  }\n  \n  @SuppressWarnings(\"\")\n  private void should_be_static() {\n    System.out.println(\"Foo\");\n  }\n  \n}\n"
  },
  {
    "path": "its/tests/projects/sample-java-hotspot/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <groupId>org.sonarsource.sonarlint.core</groupId>\n  <artifactId>sample-java-hotspot</artifactId>\n  <version>1.0-SNAPSHOT</version>\n  <name>Sample Java Hotspot</name>\n  <description>Sample project for ITs</description>\n\n  <properties>\n    <maven.compiler.source>17</maven.compiler.source>\n    <maven.compiler.target>17</maven.compiler.target>\n  </properties>\n\n</project>"
  },
  {
    "path": "its/tests/projects/sample-java-hotspot/src/main/java/foo/Foo.java",
    "content": "package foo;\n\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\npublic class Foo {\n\n  public static void configureLogging() {\n    Logger.getGlobal().setLevel(Level.FINEST);\n  }\n}\n"
  },
  {
    "path": "its/tests/projects/sample-java-taint/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <groupId>org.sonarsource.sonarlint.core</groupId>\n  <artifactId>sample-java-taint</artifactId>\n  <version>1.0-SNAPSHOT</version>\n  <name>Sample Java Taint</name>\n  <description>Sample project for ITs</description>\n\n  <dependencies>\n    <dependency>\n      <groupId>javax.servlet</groupId>\n      <artifactId>javax.servlet-api</artifactId>\n      <version>3.0.1</version>\n      <scope>provided</scope>\n    </dependency>\n  </dependencies>\n\n  <properties>\n    <maven.compiler.source>17</maven.compiler.source>\n    <maven.compiler.target>17</maven.compiler.target>\n  </properties>\n</project>"
  },
  {
    "path": "its/tests/projects/sample-java-taint/src/main/java/foo/DbHelper.java",
    "content": "package foo;\n\nimport java.sql.SQLException;\n\npublic class DbHelper {\n\n  static boolean executeQuery(java.sql.Connection connection, String user, String pass) throws SQLException {\n    String query = \"SELECT * FROM users WHERE user = '\" + user + \"' AND pass = '\" + pass + \"'\"; // Unsafe\n\n    java.sql.Statement statement = connection.createStatement();\n    java.sql.ResultSet resultSet = statement.executeQuery(query); // Noncompliant\n    return resultSet.next();\n  }\n\n}\n"
  },
  {
    "path": "its/tests/projects/sample-java-taint/src/main/java/foo/Endpoint.java",
    "content": "package foo;\n\nimport java.sql.SQLException;\n\npublic class Endpoint {\n\n  public boolean authenticate(javax.servlet.http.HttpServletRequest request, java.sql.Connection connection) throws SQLException {\n    String user = request.getParameter(\"user\");\n    String pass = request.getParameter(\"pass\");\n\n    return DbHelper.executeQuery(connection, user, pass);\n  }\n\n}\n"
  },
  {
    "path": "its/tests/projects/sample-javascript/src/Person.js",
    "content": "var Person = function(first, last, middle) {\n    this.first = first;\n    this.middle = middle; this.last = last;\n};\n\nPerson.prototype = {\n\n    whoAreYou : function() {\n        fullName = [this.first, this.middle, this.last].filter(x => x).join(' ');\n        return fullName;\n    }\n\n};\n"
  },
  {
    "path": "its/tests/projects/sample-jcl/GAM0VCDB.jcl",
    "content": "//* Noncompliant@+2 {{Replace this implicit SYSIN DD * statement with an explicit one.}}\n//\nthis is some value\n//* ^[sc=1;ec=18]\n//\n//* Noncompliant@+2\n//\nimplicit dd * with concatenated statement,\nonly this datastream should be highlighted\n//* ^[sc=1;el=+1;ec=42]@-1\n// DD DSN=CONCATENATED-STATEMENT\n//*\n//* Noncompliant@+2\n//MYDD DD DNS=TEST\nthis is some value\n\n//SYSIN DD *\nsome data\n/*\n//*\n//SYSIN DD * DLM=AA\nsome data\nAA\n//*\n//* Noncompliant@+2\n//MYJOB JOB\nsome data\n\n//* Noncompliant@+2\n// CNTL\nsome data\n// ENDCNTL\n//*\n//* Noncompliant@+2\n// PROC\nsome data\n// ENDPROC"
  },
  {
    "path": "its/tests/projects/sample-kotlin/src/hello.kt",
    "content": "\nfun foo() {\n    val a = 1\n    a = a\n}\n"
  },
  {
    "path": "its/tests/projects/sample-kubernetes/src/sample.yaml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  name: test\nspec:\n  containers:\n    - image: k8s.gcr.io/test-webserver\n      name: test-container\n      volumeMounts:\n        - mountPath: /data\n          name: test-volume\n  volumes:\n    - name: test-volume\n      hostPath:\n        path: /etc # Sensitive\n"
  },
  {
    "path": "its/tests/projects/sample-language-mix/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <groupId>org.sonarsource.sonarlint.core</groupId>\n  <artifactId>sample-language-mix</artifactId>\n  <version>1.0-SNAPSHOT</version>\n  <name>Sample Language Mix</name>\n  <description>Sample project for ITs</description>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <version>3.5</version>\n        <configuration>\n          <source>1.7</source>\n          <target>1.7</target>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n\n</project>\n"
  },
  {
    "path": "its/tests/projects/sample-language-mix/src/main/java/foo/Foo.java",
    "content": "package foo;\n\npublic class Foo {\n  public void call_echo() {\n    echo(3);\n  }\n  \n  public void echo(int i) {\n    should_be_static();\n  }\n  \n  // invalid\n  private void should_be_static() {\n    System.out.println(\"Foo\");\n  }\n  \n}\n"
  },
  {
    "path": "its/tests/projects/sample-language-mix/src/main/java/foo/main.py",
    "content": "def my_function(name):\n    print \"Hello world!\"\n"
  },
  {
    "path": "its/tests/projects/sample-misra/foo.cpp",
    "content": "extern int * f();\n\nint func()\n{\n  if ( f == nullptr ) {// Non-compliant\n  }\n}"
  },
  {
    "path": "its/tests/projects/sample-php/src/Math.php",
    "content": "<?php\n/**\n * This file is part of phpUnderControl.\n *\n * Copyright (c) 2007-2009, Manuel Pichler <mapi@phpundercontrol.org>.\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *   * Redistributions of source code must retain the above copyright\n *     notice, this list of conditions and the following disclaimer.\n *\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in\n *     the documentation and/or other materials provided with the\n *     distribution.\n *\n *   * Neither the name of Manuel Pichler nor the names of his\n *     contributors may be used to endorse or promote products derived\n *     from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS\n * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\n * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\n * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n *\n * @package   Example\n * @author    Manuel Pichler <mapi@phpundercontrol.org>\n * @copyright 2007-2009 Manuel Pichler. All rights reserved.\n * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License\n * @version   SVN: $Id: Math.php 4429 2009-01-04 15:39:45Z mapi $\n * @link      http://www.phpundercontrol.org/\n */\nfunction add($v1 , $v2)\n    {\n        return ($v1 + $v2);\n    }\n\n/**\n * Simple math class.\n *\n * @package   Example\n * @author    Manuel Pichler <mapi@phpundercontrol.org>\n * @copyright 2007-2009 Manuel Pichler. All rights reserved.\n * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License\n * @version   Release: 0.5.0\n * @link      http://www.phpundercontrol.org/\n */\nclass PhpUnderControl_Example_Math\n{\n    /**\n     * Adds the two given values.\n     *\n     * @param integer $v1 Value one.\n     * @param integer $v2 Value two.\n     *\n     * @return integer.\n     */\n    public function add($v1 , $v2)\n    {\n        //TODO add something else\n        return ($v1 + $v2);\n    }\n\n    /**\n     * Subtract param two from param one\n     *\n     * @param integer $v1 Value one.\n     * @param integer $v2 Value two.\n     *\n     * @return integer.\n     */\n    public function sub($v1, $v2)\n    {\n        return $v1 - $v2;\n    }\n\n    /**\n     * Not tested method that should be visible with low coverage.\n     */\n    public function div($v1, $v2)\n    {\n        $v3 = $v1 / ($v2 + $v1); \n        if ($v3 > 14)\n        {\n            $v4 = 0;\n            for ($i = 0; $i < $v3; $i++)\n            {\n                $v4 += ($v2 * $i);\n            }\n        }\n        $v5 = ($v4 < $v3 ? ($v3 - $v4) : ($v4 - $v3));\n\n        $v6 = ($v1 * $v2 * $v3 * $v4 * $v5);\n\n        $d = array($v1, $v2, $v3, $v4, $v5, $v6);\n\n        $v7 = 1;\n        for ($i = 0; $i < $v6; $i++)\n        {\n            shuffle( $d );\n            $v7 = $v7 + $i * end($d);\n        }\n\n        $v8 = $v7;\n        foreach ( $d as $x )\n        {\n            $v8 *= $x;\n        }\n        \n        $v3 = $v1 / ($v2 + $v1);\n        if ($v3 > 14)\n        {\n            $v4 = 0;\n            for ($i = 0; $i < $v3; $i++)\n            {\n                $v4 += ($v2 * $i);\n            }\n        }\n        $v5 = ($v4 < $v3 ? ($v3 - $v4) : ($v4 - $v3));\n\n        $v6 = ($v1 * $v2 * $v3 * $v4 * $v5);\n\n        $d = array($v1, $v2, $v3, $v4, $v5, $v6);\n\n        $v7 = 1;\n        for ($i = 0; $i < $v6; $i++)\n        {\n            shuffle( $d );\n            $v7 = $v7 + $i * end($d);\n        }\n\n        $v8 = $v7;\n        foreach ( $d as $x )\n        {\n            $v8 *= $x;\n        }\n\n        return $v8;\n    }\n\n    /**\n     * Simple copy for cpd detection.\n     */\n    public function complex($v1, $v2)\n    {\n        $v3 = $v1 / ($v2 + $v1);\n        if ($v3 > 14)\n        {\n            $v4 = 0;\n            for ($i = 0; $i < $v3; $i++)\n            {\n                $v4 += ($v2 * $i);\n            }\n        }\n        $v5 = ($v4 < $v3 ? ($v3 - $v4) : ($v4 - $v3));\n\n        $v6 = ($v1 * $v2 * $v3 * $v4 * $v5);\n\n        $d = array($v1, $v2, $v3, $v4, $v5, $v6);\n\n        $v7 = 1;\n        for ($i = 0; $i < $v6; $i++)\n        {\n            shuffle( $d );\n            $v7 = $v7 + $i * end( $d );\n        }\n\n        $v8 = $v7;\n        foreach ( $d as $x )\n        {\n            $v8 *= $x;\n        }\n        \n        $v3 = $v1 / ($v2 + $v1);\n        if ($v3 > 14)\n        {\n            $v4 = 0;\n            for ($i = 0; $i < $v3; $i++)\n            {\n                $v4 += ($v2 * $i);\n            }\n        }\n        $v5 = ($v4 < $v3 ? ($v3 - $v4) : ($v4 - $v3));\n\n        $v6 = ($v1 * $v2 * $v3 * $v4 * $v5);\n\n        $d = array($v1, $v2, $v3, $v4, $v5, $v6);\n\n        $v7 = 1;\n        for ($i = 0; $i < $v6; $i++)\n        {\n            shuffle( $d );\n            $v7 = $v7 + $i * end($d);\n        }\n\n        $v8 = $v7;\n        foreach ( $d as $x )\n        {\n            $v8 *= $x;\n        }\n\n        return $v8;\n    }\n}\n"
  },
  {
    "path": "its/tests/projects/sample-python/src/hello.py",
    "content": "def my_function(name):\n    print \"Hello world!\"\n"
  },
  {
    "path": "its/tests/projects/sample-ruby/src/hello.rb",
    "content": "\ndef fun\n  a = 1\n  a = a\nend\n"
  },
  {
    "path": "its/tests/projects/sample-sca/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <groupId>org</groupId>\n  <artifactId>artifact</artifactId>\n  <version>0.1</version>\n  <packaging>jar</packaging>\n  <name>Name</name>\n  <description>Description</description>\n\n  <dependencies>\n    <!-- Vulnerable dependency -->\n    <dependency>\n      <groupId>com.fasterxml.woodstox</groupId>\n      <artifactId>woodstox-core</artifactId>\n      <version>6.2.7</version>\n    </dependency>\n  </dependencies>\n</project>\n"
  },
  {
    "path": "its/tests/projects/sample-scala/src/Hello.scala",
    "content": "object HelloWorld extends App {\n    var a = 1\n    a = a\n}\n"
  },
  {
    "path": "its/tests/projects/sample-terraform/src/sample.tf",
    "content": "resource \"aws_s3_bucket\" \"mynoncompliantbucket\" {\n  bucket = \"mybucketname\"\n\n  tags = {\n    \"anycompany:cost-center\" = \"Accounting\" # Noncompliant\n  }\n}\n"
  },
  {
    "path": "its/tests/projects/sample-tsql/src/file.tsql",
    "content": "UPDATE books\nSET title = 'unknown'\nWHERE title = NULL -- Noncompliant\n"
  },
  {
    "path": "its/tests/projects/sample-typescript/.gitignore",
    "content": "node_modules/\npackage-lock.json\n"
  },
  {
    "path": "its/tests/projects/sample-typescript/package.json",
    "content": "{\n\t\"devDependencies\": {\n\t\t\"typescript\": \"3.2.1\"\n\t}\n}\n"
  },
  {
    "path": "its/tests/projects/sample-typescript/src/Person.ts",
    "content": "function foo(bar) {\n  if (bar == 'howdy') {\n    return 42;\n  }\n}\n"
  },
  {
    "path": "its/tests/projects/sample-typescript/tsconfig.json",
    "content": "{}"
  },
  {
    "path": "its/tests/projects/sample-web/src/file.html",
    "content": "<html>\n\n</html>"
  },
  {
    "path": "its/tests/projects/sample-xml/src/foo.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<foo>\n  <bar>RAAAAAAAAAAAAAAAAAAAAAaaaaaaaaaaaaaaaaaAAAAAAAAAAAaaaaaaaaaaaaaaaaAAaaaaaaaaaaaaaaAAAAAAAAAAAAAaaaaaaaaaAAAAAAAGNAROK</bar>\n</foo>\n"
  },
  {
    "path": "its/tests/src/test/java/its/AbstractConnectedTests.java",
    "content": "/*\n * SonarLint Core - ITs - Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage its;\n\nimport com.google.common.base.Preconditions;\nimport com.google.common.collect.Maps;\nimport com.sonar.orchestrator.Orchestrator;\nimport com.sonar.orchestrator.build.MavenBuild;\nimport com.sonar.orchestrator.http.HttpMethod;\nimport its.utils.LogOnTestFailure;\nimport java.nio.file.Paths;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Queue;\nimport java.util.concurrent.ConcurrentLinkedQueue;\nimport java.util.regex.Pattern;\nimport org.apache.commons.codec.digest.DigestUtils;\nimport org.assertj.core.internal.Failures;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarqube.ws.Issues;\nimport org.sonarqube.ws.Qualityprofiles.SearchWsResponse.QualityProfile;\nimport org.sonarqube.ws.client.HttpConnector;\nimport org.sonarqube.ws.client.PostRequest;\nimport org.sonarqube.ws.client.WsClient;\nimport org.sonarqube.ws.client.WsClientFactories;\nimport org.sonarqube.ws.client.qualityprofiles.SearchRequest;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.ClientConstantInfoDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.TelemetryClientConstantAttributesDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic abstract class AbstractConnectedTests {\n  public static final TelemetryClientConstantAttributesDto IT_TELEMETRY_ATTRIBUTES = new TelemetryClientConstantAttributesDto(\"SonarLint ITs\", \"SonarLint ITs\",\n    \"1.2.3\", \"4.5.6\", Collections.emptyMap());\n  protected static final Queue<LogParams> rpcClientLogs = new ConcurrentLinkedQueue<>();\n\n  @RegisterExtension\n  static LogOnTestFailure logOnTestFailure = new LogOnTestFailure(rpcClientLogs);\n\n  public static final ClientConstantInfoDto IT_CLIENT_INFO = new ClientConstantInfoDto(\"clientName\", \"integrationTests\");\n  protected static final String SONARLINT_USER = \"sonarlint\";\n  protected static final String SONARLINT_PWD = \"sonarlintpwd\";\n  protected static final String MAIN_BRANCH_NAME = \"master\";\n\n  protected static WsClient newAdminWsClient(Orchestrator orchestrator) {\n    var server = orchestrator.getServer();\n    return WsClientFactories.getDefault().newClient(HttpConnector.newBuilder()\n      .url(server.getUrl())\n      .credentials(com.sonar.orchestrator.container.Server.ADMIN_LOGIN, com.sonar.orchestrator.container.Server.ADMIN_PASSWORD)\n      .build());\n  }\n\n  static Map<String, String> toMap(String[] keyValues) {\n    Preconditions.checkArgument(keyValues.length % 2 == 0, \"Must be an even number of key/values\");\n    Map<String, String> map = Maps.newHashMap();\n    var index = 0;\n    while (index < keyValues.length) {\n      var key = keyValues[index++];\n      var value = keyValues[index++];\n      map.put(key, value);\n    }\n    return map;\n  }\n\n  private static final Pattern MATCH_ALL_WHITESPACES = Pattern.compile(\"\\\\s\");\n\n  protected static String hash(String codeSnippet) {\n    String codeSnippetWithoutWhitespaces = MATCH_ALL_WHITESPACES.matcher(codeSnippet).replaceAll(\"\");\n    return DigestUtils.md5Hex(codeSnippetWithoutWhitespaces);\n  }\n\n  protected static void analyzeMavenProject(Orchestrator orchestrator, String projectDirName) {\n    analyzeMavenProject(orchestrator, projectDirName, Map.of());\n  }\n\n  protected static void analyzeMavenProject(Orchestrator orchestrator, String projectDirName, Map<String, String> extraProperties) {\n    var projectDir = Paths.get(\"projects/\" + projectDirName).toAbsolutePath();\n    var pom = projectDir.resolve(\"pom.xml\");\n    var mavenBuild = MavenBuild.create(pom.toFile())\n      .setCleanPackageSonarGoals()\n      .setProperties(extraProperties);\n\n    if (orchestrator.getServer().version().isGreaterThanOrEquals(10, 2)) {\n      mavenBuild\n        .setProperty(\"sonar.token\", orchestrator.getDefaultAdminToken())\n        .setProperties(extraProperties);\n    } else {\n      // sonar.token is not supported for 9.9\n      mavenBuild\n        .setProperty(\"sonar.login\", com.sonar.orchestrator.container.Server.ADMIN_LOGIN)\n        .setProperty(\"sonar.password\", com.sonar.orchestrator.container.Server.ADMIN_PASSWORD);\n    }\n\n    orchestrator.executeBuild(mavenBuild);\n  }\n\n  protected QualityProfile getQualityProfile(WsClient adminWsClient, String qualityProfileName) {\n    var searchReq = new SearchRequest();\n    searchReq.setQualityProfile(qualityProfileName);\n    searchReq.setDefaults(\"false\");\n    var search = adminWsClient.qualityprofiles().search(searchReq);\n    for (QualityProfile profile : search.getProfilesList()) {\n      if (profile.getName().equals(qualityProfileName)) {\n        return profile;\n      }\n    }\n    throw Failures.instance().failure(\"Unable to get quality profile \" + qualityProfileName);\n  }\n\n  protected void deactivateRule(WsClient adminWsClient, QualityProfile qualityProfile, String ruleKey) {\n    var request = new PostRequest(\"/api/qualityprofiles/deactivate_rule\")\n      .setParam(\"key\", qualityProfile.getKey())\n      .setParam(\"rule\", ruleKey);\n    try (var response = adminWsClient.wsConnector().call(request)) {\n      assertTrue(response.isSuccessful(), \"Unable to deactivate rule\");\n    }\n  }\n\n  protected static List<String> getIssueKeys(WsClient adminWsClient, String ruleKey) {\n    var searchReq = new org.sonarqube.ws.client.issues.SearchRequest();\n    searchReq.setRules(List.of(ruleKey));\n    var response = adminWsClient.issues().search(searchReq);\n    return response.getIssuesList().stream().map(Issues.Issue::getKey).toList();\n  }\n\n  protected static void resolveIssueAsWontFix(WsClient adminWsClient, String issueKey) {\n    changeIssueStatus(adminWsClient, issueKey, \"wontfix\");\n  }\n\n  protected static void reopenIssue(WsClient adminWsClient, String issueKey) {\n    changeIssueStatus(adminWsClient, issueKey, \"reopen\");\n  }\n\n  protected static void changeIssueStatus(WsClient adminWsClient, String issueKey, String status) {\n    var request = new PostRequest(\"/api/issues/do_transition\")\n      .setParam(\"issue\", issueKey)\n      .setParam(\"transition\", status);\n    try (var response = adminWsClient.wsConnector().call(request)) {\n      assertTrue(response.isSuccessful(), \"Unable to resolve issue\");\n    }\n  }\n\n  protected static void resolveHotspotAsSafe(WsClient adminWsClient, String hotspotKey) {\n    var request = new PostRequest(\"/api/hotspots/change_status\")\n      .setParam(\"hotspot\", hotspotKey)\n      .setParam(\"status\", \"REVIEWED\")\n      .setParam(\"resolution\", \"SAFE\");\n    try (var response = adminWsClient.wsConnector().call(request)) {\n      assertTrue(response.isSuccessful(), \"Unable to resolve hotspot\");\n    }\n  }\n\n  protected static void provisionProject(Orchestrator orchestrator, String projectKey, String projectName) {\n    orchestrator.getServer()\n      .newHttpCall(\"/api/projects/create\")\n      .setMethod(HttpMethod.POST)\n      .setAdminCredentials()\n      .setParam(\"project\", projectKey)\n      .setParam(\"name\", projectName)\n      .setParam(\"mainBranch\", MAIN_BRANCH_NAME)\n      .execute();\n  }\n}\n"
  },
  {
    "path": "its/tests/src/test/java/its/FileExclusionTests.java",
    "content": "/*\n * SonarLint Core - ITs - Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage its;\n\nimport com.sonar.orchestrator.junit5.OrchestratorExtension;\nimport com.sonar.orchestrator.locator.FileLocation;\nimport its.utils.OrchestratorUtils;\nimport java.io.IOException;\nimport java.io.PipedInputStream;\nimport java.io.PipedOutputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ExecutionException;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarqube.ws.client.WsClient;\nimport org.sonarqube.ws.client.settings.ResetRequest;\nimport org.sonarqube.ws.client.settings.SetRequest;\nimport org.sonarqube.ws.client.users.CreateRequest;\nimport org.sonarsource.sonarlint.core.rpc.client.ClientJsonRpcLauncher;\nimport org.sonarsource.sonarlint.core.rpc.client.ConnectionNotFoundException;\nimport org.sonarsource.sonarlint.core.rpc.client.SonarLintRpcClientDelegate;\nimport org.sonarsource.sonarlint.core.rpc.impl.BackendJsonRpcLauncher;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarQubeConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidUpdateFileSystemParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.GetFilesStatusParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.HttpConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\n\nimport static java.util.Collections.singletonList;\nimport static java.util.concurrent.TimeUnit.MINUTES;\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JAVA;\n\nclass FileExclusionTests extends AbstractConnectedTests {\n  @RegisterExtension\n  static OrchestratorExtension ORCHESTRATOR = OrchestratorUtils.defaultEnvBuilder()\n    .addPlugin(FileLocation.of(\"../plugins/java-custom-rules/target/java-custom-rules-plugin.jar\"))\n    .setServerProperty(\"sonar.projectCreation.mainBranchName\", MAIN_BRANCH_NAME)\n    .build();\n\n  private static final String CONNECTION_ID = \"orchestrator\";\n\n  @TempDir\n  private static Path sonarUserHome;\n  private static WsClient adminWsClient;\n  private static SonarLintRpcServer backend;\n  private static final Map<String, Boolean> analysisReadinessByConfigScopeId = new ConcurrentHashMap<>();\n  private static BackendJsonRpcLauncher serverLauncher;\n\n  @BeforeAll\n  static void startBackend() throws IOException {\n    System.setProperty(\"sonarlint.internal.synchronization.initialDelay\", \"3\");\n    System.setProperty(\"sonarlint.internal.synchronization.period\", \"5\");\n    System.setProperty(\"sonarlint.internal.synchronization.scope.period\", \"3\");\n\n    var clientToServerOutputStream = new PipedOutputStream();\n    var clientToServerInputStream = new PipedInputStream(clientToServerOutputStream);\n\n    var serverToClientOutputStream = new PipedOutputStream();\n    var serverToClientInputStream = new PipedInputStream(serverToClientOutputStream);\n\n    serverLauncher = new BackendJsonRpcLauncher(clientToServerInputStream, serverToClientOutputStream);\n    var clientLauncher = new ClientJsonRpcLauncher(serverToClientInputStream, clientToServerOutputStream, newDummySonarLintClient());\n\n    backend = clientLauncher.getServerProxy();\n    try {\n      var enabledLanguages = Set.of(JAVA);\n      backend.initialize(\n        new InitializeParams(IT_CLIENT_INFO,\n          IT_TELEMETRY_ATTRIBUTES, HttpConfigurationDto.defaultConfig(), null, Set.of(BackendCapability.FULL_SYNCHRONIZATION, BackendCapability.PROJECT_SYNCHRONIZATION),\n          sonarUserHome.resolve(\"storage\"),\n          sonarUserHome.resolve(\"work\"),\n          Collections.emptySet(), Collections.emptyMap(), enabledLanguages, Collections.emptySet(), Collections.emptySet(),\n          List.of(new SonarQubeConnectionConfigurationDto(CONNECTION_ID, ORCHESTRATOR.getServer().getUrl(), true)),\n          Collections.emptyList(),\n          sonarUserHome.toString(),\n          Map.of(), false, null, false, null))\n        .get();\n    } catch (Exception e) {\n      throw new IllegalStateException(\"Cannot initialize the backend\", e);\n    }\n\n    adminWsClient = newAdminWsClient(ORCHESTRATOR);\n    adminWsClient.users().create(new CreateRequest().setLogin(SONARLINT_USER).setPassword(SONARLINT_PWD).setName(\"SonarLint\"));\n  }\n\n  @AfterAll\n  static void stop() throws ExecutionException, InterruptedException {\n    backend.shutdown().get();\n    System.clearProperty(\"sonarlint.internal.synchronization.initialDelay\");\n    System.clearProperty(\"sonarlint.internal.synchronization.period\");\n    System.clearProperty(\"sonarlint.internal.synchronization.scope.period\");\n  }\n\n  @AfterEach\n  void cleanup_after_each() {\n    analysisReadinessByConfigScopeId.clear();\n    rpcClientLogs.clear();\n  }\n\n  @Test\n  void should_respect_exclusion_settings_on_SQ() {\n    var configScopeId = \"should_respect_exclusion_settings_on_SQ\";\n    var projectKey = \"sample-java\";\n    var projectName = \"my-sample-java\";\n    provisionProject(ORCHESTRATOR, projectKey, projectName);\n\n    backend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(\n      List.of(new ConfigurationScopeDto(configScopeId, null, true, projectName, new BindingConfigurationDto(CONNECTION_ID, projectKey,\n        true)))));\n    await().atMost(1, MINUTES).untilAsserted(() -> assertThat(analysisReadinessByConfigScopeId).containsEntry(configScopeId, true));\n\n    var filePath = Path.of(\"src/main/java/foo/Foo.java\");\n    var clientFileDto = new ClientFileDto(filePath.toUri(), filePath, configScopeId, null, StandardCharsets.UTF_8.name(),\n      filePath.toAbsolutePath(), null, null, true);\n    var didUpdateFileSystemParams = new DidUpdateFileSystemParams(List.of(clientFileDto), List.of(), List.of());\n    backend.getFileService().didUpdateFileSystem(didUpdateFileSystemParams);\n\n    // Firstly check file is included\n    var getFilesStatusParams = new GetFilesStatusParams(Map.of(configScopeId, List.of(filePath.toUri())));\n    await().atMost(10, SECONDS)\n      .untilAsserted(() -> assertThat(backend.getFileService().getFilesStatus(getFilesStatusParams).get().getFileStatuses().get(filePath.toUri()).isExcluded()).isFalse());\n\n    // Change file exclusion settings on SQ which should affect Foo.java\n    adminWsClient.settings().set(new SetRequest()\n      .setKey(\"sonar.exclusions\")\n      .setValues(singletonList(\"**/*.java\"))\n      .setComponent(projectKey));\n\n    forceBackendToPullSettings(configScopeId, projectKey);\n\n    // Check Foo.java is excluded\n    await().atMost(30, SECONDS)\n      .untilAsserted(() -> assertThat(backend.getFileService().getFilesStatus(getFilesStatusParams).get().getFileStatuses().get(filePath.toUri()).isExcluded()).isTrue());\n\n    // Change file exclusion settings on SQ which should not affect Foo.java\n    adminWsClient.settings().set(new SetRequest()\n      .setKey(\"sonar.exclusions\")\n      .setValues(singletonList(\"**/*.js\"))\n      .setComponent(projectKey));\n\n    forceBackendToPullSettings(configScopeId, projectKey);\n\n    // Check Foo.java is included\n    await().atMost(30, SECONDS)\n      .untilAsserted(() -> assertThat(backend.getFileService().getFilesStatus(getFilesStatusParams).get().getFileStatuses().get(filePath.toUri()).isExcluded()).isFalse());\n\n    // Change file inclusion settings on SQ to include only .js files\n    adminWsClient.settings().set(new SetRequest()\n      .setKey(\"sonar.inclusions\")\n      .setValues(singletonList(\"**/*.js\"))\n      .setComponent(projectKey));\n\n    forceBackendToPullSettings(configScopeId, projectKey);\n\n    // Check Foo.java is excluded\n    await().atMost(30, SECONDS)\n      .untilAsserted(() -> assertThat(backend.getFileService().getFilesStatus(getFilesStatusParams).get().getFileStatuses().get(filePath.toUri()).isExcluded()).isTrue());\n\n    // Reset file inclusions/exclusion settings on SQ\n    adminWsClient.settings().reset(new ResetRequest()\n      .setKeys(List.of(\"sonar.exclusions\", \"sonar.inclusions\"))\n      .setComponent(projectKey));\n\n    forceBackendToPullSettings(configScopeId, projectKey);\n\n    // Check Foo.java is included again\n    await().atMost(30, SECONDS)\n      .untilAsserted(() -> assertThat(backend.getFileService().getFilesStatus(getFilesStatusParams).get().getFileStatuses().get(filePath.toUri()).isExcluded()).isFalse());\n  }\n\n  private static void forceBackendToPullSettings(String configScopeId, String projectKey) {\n    // The only way to force a sync of the storage is to unbind/rebind\n    backend.getConfigurationService().didUpdateBinding(new DidUpdateBindingParams(configScopeId, new BindingConfigurationDto(null, null, true)));\n    backend.getConfigurationService().didUpdateBinding(new DidUpdateBindingParams(configScopeId, new BindingConfigurationDto(CONNECTION_ID, projectKey, true)));\n  }\n\n  private static SonarLintRpcClientDelegate newDummySonarLintClient() {\n    return new MockSonarLintRpcClientDelegate() {\n\n      @Override\n      public Either<TokenDto, UsernamePasswordDto> getCredentials(String connectionId) throws ConnectionNotFoundException {\n        if (connectionId.equals(CONNECTION_ID)) {\n          return Either.forRight(new UsernamePasswordDto(SONARLINT_USER, SONARLINT_PWD));\n        }\n        return super.getCredentials(connectionId);\n      }\n\n      @Override\n      public void didChangeAnalysisReadiness(Set<String> configurationScopeIds, boolean areReadyForAnalysis) {\n        analysisReadinessByConfigScopeId.putAll(configurationScopeIds.stream().collect(Collectors.toMap(Function.identity(), k -> areReadyForAnalysis)));\n      }\n\n      @Override\n      public void log(LogParams params) {\n        System.out.println(params);\n        rpcClientLogs.add(params);\n      }\n    };\n  }\n}\n"
  },
  {
    "path": "its/tests/src/test/java/its/MockSonarLintRpcClientDelegate.java",
    "content": "/*\n * SonarLint Core - ITs - Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage its;\n\nimport java.net.URI;\nimport java.net.URL;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ConcurrentHashMap;\nimport org.jetbrains.annotations.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.client.ConfigScopeNotFoundException;\nimport org.sonarsource.sonarlint.core.rpc.client.ConnectionNotFoundException;\nimport org.sonarsource.sonarlint.core.rpc.client.SonarLintCancelChecker;\nimport org.sonarsource.sonarlint.core.rpc.client.SonarLintRpcClientDelegate;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.AssistBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.AssistBindingResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.NoBindingSuggestionFoundParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.ConnectionSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.event.DidReceiveServerHotspotEvent;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fix.FixSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.HotspotDetailsDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.RaisedHotspotDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.GetProxyPasswordAuthenticationResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.ProxyDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.X509CertificateDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.IssueDetailsDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.MessageType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowSoonUnsupportedMessageParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.progress.ReportProgressParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.progress.StartProgressParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.smartnotification.ShowSmartNotificationParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.TelemetryClientLiveAttributesResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\n\npublic class MockSonarLintRpcClientDelegate implements SonarLintRpcClientDelegate {\n\n  private final Map<String, Map<URI, List<RaisedIssueDto>>> raisedIssues = new ConcurrentHashMap<>();\n  private final Map<String, Map<URI, List<RaisedHotspotDto>>> raisedHotspots = new ConcurrentHashMap<>();\n\n  /**\n   * @return null when raiseIssues was never called for the provided configurationScopeId.\n   * Returns empty map if raiseIssues was called with no issues\n   */\n  @Nullable\n  public Map<URI, List<RaisedIssueDto>> takeRaisedIssues(String configurationScopeId) {\n    return raisedIssues.remove(configurationScopeId);\n  }\n\n  /**\n   * @return null when raiseHotspots was never called for the provided configurationScopeId.\n   * Returns empty map if raiseHotspots was called with no hotspots\n   */\n  @Nullable\n  public Map<URI, List<RaisedHotspotDto>> takeRaisedHotspots(String configurationScopeId) {\n    return raisedHotspots.remove(configurationScopeId);\n  }\n\n  @Override\n  public void suggestBinding(Map<String, List<BindingSuggestionDto>> suggestionsByConfigScope) {\n\n  }\n\n  @Override\n  public void suggestConnection(Map<String, List<ConnectionSuggestionDto>> suggestionsByConfigScope) {\n\n  }\n\n  @Override\n  public void openUrlInBrowser(URL url) {\n\n  }\n\n  @Override\n  public void showMessage(MessageType type, String text) {\n\n  }\n\n  @Override\n  public void log(LogParams params) {\n\n  }\n\n  @Override\n  public void showSoonUnsupportedMessage(ShowSoonUnsupportedMessageParams params) {\n\n  }\n\n  @Override\n  public void showSmartNotification(ShowSmartNotificationParams params) {\n\n  }\n\n  @Override\n  public String getClientLiveDescription() {\n    return \"\";\n  }\n\n  @Override\n  public void showHotspot(String configurationScopeId, HotspotDetailsDto hotspotDetails) {\n\n  }\n\n  @Override\n  public void showIssue(String configurationScopeId, IssueDetailsDto issueDetails) {\n\n  }\n\n  @Override\n  public void showFixSuggestion(String configurationScopeId, String issueKey, FixSuggestionDto fixSuggestion) {\n\n  }\n\n  @Override\n  public AssistCreatingConnectionResponse assistCreatingConnection(AssistCreatingConnectionParams params, SonarLintCancelChecker cancelChecker) throws CancellationException {\n    throw new CancellationException(\"Unsupported in ITS\");\n  }\n\n  @Override\n  public AssistBindingResponse assistBinding(AssistBindingParams params, SonarLintCancelChecker cancelChecker) throws CancellationException {\n    throw new CancellationException(\"Unsupported in ITS\");\n  }\n\n  @Override\n  public void startProgress(StartProgressParams params) throws UnsupportedOperationException {\n\n  }\n\n  @Override\n  public void reportProgress(ReportProgressParams params) {\n\n  }\n\n  @Override\n  public void didSynchronizeConfigurationScopes(Set<String> configurationScopeIds) {\n\n  }\n\n  @Override\n  public Either<TokenDto, UsernamePasswordDto> getCredentials(String connectionId) throws ConnectionNotFoundException {\n    throw new ConnectionNotFoundException();\n  }\n\n  @Override\n  public List<ProxyDto> selectProxies(URI uri) {\n    return List.of(ProxyDto.NO_PROXY);\n  }\n\n  @Override\n  public GetProxyPasswordAuthenticationResponse getProxyPasswordAuthentication(String host, int port, String protocol, String prompt, String scheme, URL targetHost) {\n    return new GetProxyPasswordAuthenticationResponse(\"\", \"\");\n  }\n\n  @Override\n  public boolean checkServerTrusted(List<X509CertificateDto> chain, String authType) {\n    return false;\n  }\n\n  @Override\n  public void didReceiveServerHotspotEvent(DidReceiveServerHotspotEvent params) {\n\n  }\n\n  @Override\n  public String matchSonarProjectBranch(String configurationScopeId, String mainBranchName, Set<String> allBranchesNames, SonarLintCancelChecker cancelChecker)\n    throws ConfigScopeNotFoundException {\n    return mainBranchName;\n  }\n\n  @Override\n  public void didChangeMatchedSonarProjectBranch(String configScopeId, String newMatchedBranchName) {\n\n  }\n\n  @Override\n  public TelemetryClientLiveAttributesResponse getTelemetryLiveAttributes() {\n    System.err.println(\"Telemetry should be disabled in ITs\");\n    throw new CancellationException(\"Telemetry should be disabled in ITs\");\n  }\n\n  @Override\n  public void didChangeTaintVulnerabilities(String configurationScopeId, Set<UUID> closedTaintVulnerabilityIds, List<TaintVulnerabilityDto> addedTaintVulnerabilities,\n    List<TaintVulnerabilityDto> updatedTaintVulnerabilities) {\n\n  }\n\n  @Override\n  public List<ClientFileDto> listFiles(String configScopeId) {\n    return List.of();\n  }\n\n  @Override\n  public void noBindingSuggestionFound(NoBindingSuggestionFoundParams params) {\n  }\n\n  @Override\n  public void didChangeAnalysisReadiness(Set<String> configurationScopeIds, boolean areReadyForAnalysis) {\n\n  }\n\n  @Override\n  public void raiseIssues(String configurationScopeId, Map<URI, List<RaisedIssueDto>> issuesByFileUri, boolean isIntermediatePublication, @Nullable UUID analysisId) {\n    if (!isIntermediatePublication) {\n      raisedIssues.put(configurationScopeId, issuesByFileUri);\n    }\n  }\n\n  @Override\n  public void raiseHotspots(String configurationScopeId, Map<URI, List<RaisedHotspotDto>> hotspotsByFileUri, boolean isIntermediatePublication, @Nullable UUID analysisId) {\n    if (!isIntermediatePublication) {\n      raisedHotspots.put(configurationScopeId, hotspotsByFileUri);\n    }\n  }\n\n  public void clear() {\n    raisedIssues.clear();\n    raisedHotspots.clear();\n  }\n\n}\n"
  },
  {
    "path": "its/tests/src/test/java/its/SonarCloudTests.java",
    "content": "/*\n * SonarLint Core - ITs - Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage its;\n\nimport its.utils.PluginLocator;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.PipedInputStream;\nimport java.io.PipedOutputStream;\nimport java.net.URI;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Collections;\nimport java.util.EnumMap;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.apache.commons.exec.CommandLine;\nimport org.apache.commons.exec.DefaultExecutor;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestInstance;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarqube.ws.Issues;\nimport org.sonarqube.ws.MediaTypes;\nimport org.sonarqube.ws.client.GetRequest;\nimport org.sonarqube.ws.client.HttpConnector;\nimport org.sonarqube.ws.client.PostRequest;\nimport org.sonarqube.ws.client.WsClient;\nimport org.sonarqube.ws.client.WsClientFactories;\nimport org.sonarqube.ws.client.WsRequest;\nimport org.sonarqube.ws.client.WsResponse;\nimport org.sonarqube.ws.client.issues.SearchRequest;\nimport org.sonarqube.ws.client.settings.ResetRequest;\nimport org.sonarqube.ws.client.settings.SetRequest;\nimport org.sonarqube.ws.client.sources.RawRequest;\nimport org.sonarsource.sonarlint.core.rpc.client.ClientJsonRpcLauncher;\nimport org.sonarsource.sonarlint.core.rpc.client.ConnectionNotFoundException;\nimport org.sonarsource.sonarlint.core.rpc.impl.BackendJsonRpcLauncher;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.ShouldUseEnterpriseCSharpAnalyzerParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.branch.GetMatchedSonarProjectBranchParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidRemoveConfigurationScopeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarCloudConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarCloudConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.GetOrganizationParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.ListUserOrganizationsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.GetAllProjectsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.SonarProjectDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.validate.ValidateConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.HotspotStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.HttpConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.SonarCloudAlternativeEnvironmentDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.SonarQubeCloudRegionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.GetEffectiveRuleDetailsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.ListAllParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.RaisedHotspotDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.RuleType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\n\nimport static its.utils.AnalysisUtils.analyzeAndAwaitHotspots;\nimport static its.utils.AnalysisUtils.analyzeAndAwaitIssues;\nimport static java.util.Collections.emptyList;\nimport static java.util.Collections.emptyMap;\nimport static java.util.Collections.emptySet;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.awaitility.Awaitility.waitAtMost;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.HTML;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JAVA;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JS;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.KOTLIN;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.PHP;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.PYTHON;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.RUBY;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.SCALA;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.XML;\n\n@Tag(\"SonarCloud\")\nclass SonarCloudTests extends AbstractConnectedTests {\n  private static final String SONAR_JAVA_FILE_SUFFIXES = \"sonar.java.file.suffixes\";\n  private static final Map<SonarCloudRegion, SonarQubeCloudRegionDto> SONARCLOUD_STAGING_URIS = new EnumMap<>(SonarCloudRegion.class);\n  static {\n    SONARCLOUD_STAGING_URIS.put(SonarCloudRegion.EU, new SonarQubeCloudRegionDto(URI.create(\"https://sc-staging.io\"), URI.create(\"https://api.sc-staging.io\"),\n      URI.create(\"wss://events-api.sc-staging.io/\")));\n    SONARCLOUD_STAGING_URIS.put(SonarCloudRegion.US, new SonarQubeCloudRegionDto(URI.create(\"https://us-sc-staging.io\"), URI.create(\"https://api.us-sc-staging.io\"),\n      URI.create(\"wss://events-api.us-sc-staging.io/\")));\n  }\n  private static final SonarCloudRegion region = StringUtils.isNotBlank(System.getenv(\"SONARCLOUD_REGION\")) ? SonarCloudRegion.valueOf(System.getenv(\"SONARCLOUD_REGION\"))\n    : SonarCloudRegion.EU;\n  private static final URI SONARCLOUD_STAGING_URL = SONARCLOUD_STAGING_URIS.get(region).getUri();\n  private static final String SONARCLOUD_ORGANIZATION = \"sonarlint-it\";\n  private static final String SONARCLOUD_TOKEN = System.getenv(\"SONARCLOUD_IT_TOKEN\");\n\n  private static final String PROJECT_KEY_JAVA = \"sample-java\";\n\n  public static final String CONNECTION_ID = \"sonarcloud\";\n\n  private static WsClient adminWsClient;\n  @TempDir\n  private static Path sonarUserHome;\n\n  private static int randomPositiveInt;\n\n  private static SonarLintRpcServer backend;\n  private static MockSonarLintRpcClientDelegate client;\n  private static final Set<String> openedConfigurationScopeIds = new HashSet<>();\n  private static final Map<String, Boolean> analysisReadinessByConfigScopeId = new ConcurrentHashMap<>();\n\n  @BeforeAll\n  static void prepare() throws Exception {\n    var clientToServerOutputStream = new PipedOutputStream();\n    var clientToServerInputStream = new PipedInputStream(clientToServerOutputStream);\n\n    var serverToClientOutputStream = new PipedOutputStream();\n    var serverToClientInputStream = new PipedInputStream(serverToClientOutputStream);\n\n    new BackendJsonRpcLauncher(clientToServerInputStream, serverToClientOutputStream);\n    client = newDummySonarLintClient();\n    var clientLauncher = new ClientJsonRpcLauncher(serverToClientInputStream, clientToServerOutputStream, client);\n\n    backend = clientLauncher.getServerProxy();\n    var languages = Set.of(JAVA, PHP, JS, PYTHON, HTML, RUBY, KOTLIN, SCALA, XML);\n    backend.initialize(\n      new InitializeParams(IT_CLIENT_INFO, IT_TELEMETRY_ATTRIBUTES, HttpConfigurationDto.defaultConfig(),\n        new SonarCloudAlternativeEnvironmentDto(SONARCLOUD_STAGING_URIS),\n        Set.of(BackendCapability.FULL_SYNCHRONIZATION, BackendCapability.PROJECT_SYNCHRONIZATION, BackendCapability.SECURITY_HOTSPOTS, BackendCapability.SERVER_SENT_EVENTS),\n        sonarUserHome.resolve(\"storage\"),\n        sonarUserHome.resolve(\"work\"), emptySet(), PluginLocator.getEmbeddedPluginsByKeyForTests(), languages, emptySet(), emptySet(), emptyList(),\n        List.of(new SonarCloudConnectionConfigurationDto(CONNECTION_ID, SONARCLOUD_ORGANIZATION, SonarCloudRegion.valueOf(region.name()), true)), sonarUserHome.toString(),\n        emptyMap(), false, null, false, null));\n    randomPositiveInt = new Random().nextInt() & Integer.MAX_VALUE;\n\n    adminWsClient = newAdminWsClient();\n\n    restoreProfile(\"java-sonarlint.xml\");\n    provisionProject(PROJECT_KEY_JAVA, \"Sample Java\");\n    associateProjectToQualityProfile(PROJECT_KEY_JAVA, \"java\", \"SonarLint IT Java\");\n\n    // Build project to have bytecode\n    runMaven(Paths.get(\"projects/sample-java\"), \"clean\", \"compile\");\n\n    Map<String, String> globalProps = new HashMap<>();\n    globalProps.put(\"sonar.global.label\", \"It works\");\n  }\n\n  @AfterAll\n  static void cleanup() throws Exception {\n    var request = new PostRequest(\"api/projects/bulk_delete\");\n    request.setParam(\"q\", \"-\" + randomPositiveInt);\n    request.setParam(\"organization\", SONARCLOUD_ORGANIZATION);\n    try (var response = adminWsClient.wsConnector().call(request)) {\n      assertIsOk(response);\n    }\n    client.clear();\n    backend.shutdown().get();\n  }\n\n  private static void associateProjectToQualityProfile(String projectKey, String language, String profileName) {\n    var request = new PostRequest(\"api/qualityprofiles/add_project\");\n    request.setParam(\"language\", language);\n    request.setParam(\"project\", projectKey(projectKey));\n    request.setParam(\"qualityProfile\", profileName);\n    request.setParam(\"organization\", SONARCLOUD_ORGANIZATION);\n    try (var response = adminWsClient.wsConnector().call(request)) {\n      assertIsOk(response);\n    }\n  }\n\n  private static void restoreProfile(String profile) {\n    var backupFile = new File(\"src/test/resources/\" + profile);\n    // XXX can't use RestoreRequest because of a bug\n    var request = new PostRequest(\"api/qualityprofiles/restore\");\n    request.setParam(\"organization\", SONARCLOUD_ORGANIZATION);\n    request.setPart(\"backup\", new PostRequest.Part(MediaTypes.XML, backupFile));\n    try (var response = adminWsClient.wsConnector().call(request)) {\n      assertIsOk(response);\n    }\n  }\n\n  private static String provisionProject(String key, String name) {\n    var projectKey = projectKey(key);\n    var request = new PostRequest(\"api/projects/create\");\n    request.setParam(\"name\", name);\n    request.setParam(\"project\", projectKey);\n    request.setParam(\"organization\", SONARCLOUD_ORGANIZATION);\n    try (var response = adminWsClient.wsConnector().call(request)) {\n      assertIsOk(response);\n    }\n    return projectKey;\n  }\n\n  private static String projectKey(String key) {\n    return \"sonarlint-its-\" + key + \"-\" + randomPositiveInt;\n  }\n\n  @AfterEach\n  void cleanup_after_each() {\n    openedConfigurationScopeIds.forEach(configScopeId -> backend.getConfigurationService().didRemoveConfigurationScope(new DidRemoveConfigurationScopeParams(configScopeId)));\n    openedConfigurationScopeIds.clear();\n    analysisReadinessByConfigScopeId.clear();\n    rpcClientLogs.clear();\n  }\n\n  @Test\n  void match_main_branch_by_default() throws ExecutionException, InterruptedException {\n    var configScopeId = \"match_main_branch_by_default\";\n    openBoundConfigurationScope(configScopeId, PROJECT_KEY_JAVA);\n    waitForAnalysisToBeReady(configScopeId);\n\n    var sonarProjectBranch = backend.getSonarProjectBranchService().getMatchedSonarProjectBranch(new GetMatchedSonarProjectBranchParams(configScopeId)).get();\n\n    await().untilAsserted(() -> assertThat(sonarProjectBranch.getMatchedSonarProjectBranch()).isEqualTo(MAIN_BRANCH_NAME));\n  }\n\n  @Test\n  void should_use_enterprise_csharp_analyzer_with_sonarcloud() {\n    // the project and config scope names do not matter\n    var configScopeId = \"match_main_branch_by_default\";\n    openBoundConfigurationScope(configScopeId, PROJECT_KEY_JAVA);\n    waitForAnalysisToBeReady(configScopeId);\n\n    var shouldUseEnterpriseAnalyzer = backend.getAnalysisService().shouldUseEnterpriseCSharpAnalyzer(new ShouldUseEnterpriseCSharpAnalyzerParams(configScopeId)).join();\n\n    await().untilAsserted(() -> assertThat(shouldUseEnterpriseAnalyzer.shouldUseEnterpriseAnalyzer()).isTrue());\n  }\n\n  @Test\n  void getAllProjects() {\n    provisionProject(\"foo-bar\", \"Foo\");\n    var getAllProjectsParams = new GetAllProjectsParams(new TransientSonarCloudConnectionDto(SONARCLOUD_ORGANIZATION,\n      Either.forLeft(new TokenDto(SONARCLOUD_TOKEN)), region));\n\n    waitAtMost(1, TimeUnit.MINUTES).untilAsserted(() -> assertThat(backend.getConnectionService().getAllProjects(getAllProjectsParams).get().getSonarProjects())\n      .extracting(SonarProjectDto::getKey)\n      .contains(projectKey(\"foo-bar\")));\n  }\n\n  @Test\n  void testRuleDescription() throws Exception {\n    openBoundConfigurationScope(\"testRuleDescription\", PROJECT_KEY_JAVA);\n    waitForAnalysisToBeReady(\"testRuleDescription\");\n\n    var ruleDetails = backend.getRulesService().getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams(\"testRuleDescription\", \"java:S106\", null)).get();\n\n    assertThat(ruleDetails.details().getDescription().getRight().getTabs().get(0).getContent().getLeft().getHtmlContent())\n      .contains(\"logs serve as a record of events within an application\");\n  }\n\n  @Test\n  void verifyExtendedDescription() throws Exception {\n    var configScopeId = \"verifyExtendedDescription\";\n    var ruleKey = \"java:S106\";\n\n    var extendedDescription = \"my dummy extended description\";\n\n    WsRequest request = new PostRequest(\"/api/rules/update\")\n      .setParam(\"key\", ruleKey)\n      .setParam(\"organization\", SONARCLOUD_ORGANIZATION)\n      .setParam(\"markdown_note\", extendedDescription);\n    try (var response = adminWsClient.wsConnector().call(request)) {\n      assertThat(response.code()).isEqualTo(200);\n    }\n\n    openBoundConfigurationScope(configScopeId, PROJECT_KEY_JAVA);\n    waitForAnalysisToBeReady(configScopeId);\n    var ruleDetails = backend.getRulesService().getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams(configScopeId, \"java\" +\n      \":S106\", null)).get();\n    assertThat(ruleDetails.details().getDescription().getRight().getTabs().get(1).getContent().getLeft().getHtmlContent()).contains(extendedDescription);\n  }\n\n  @Test\n  void analysisJavascript() {\n    var configScopeId = \"analysisJavascript\";\n    restoreProfile(\"javascript-sonarlint.xml\");\n    var projectKeyJs = \"sample-javascript\";\n    provisionProject(projectKeyJs, \"Sample Javascript\");\n    associateProjectToQualityProfile(projectKeyJs, \"js\", \"SonarLint IT Javascript\");\n\n    openBoundConfigurationScope(configScopeId, projectKeyJs);\n    waitForAnalysisToBeReady(configScopeId);\n    var issues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", projectKeyJs), \"src/Person.js\");\n    assertThat(issues).hasSize(1);\n  }\n\n  @Test\n  void analysisPHP() {\n    var configScopeId = \"analysisPHP\";\n    restoreProfile(\"php-sonarlint.xml\");\n    var projectKeyPhp = \"sample-php\";\n    provisionProject(projectKeyPhp, \"Sample PHP\");\n    associateProjectToQualityProfile(projectKeyPhp, \"php\", \"SonarLint IT PHP\");\n\n    openBoundConfigurationScope(configScopeId, projectKeyPhp);\n    waitForAnalysisToBeReady(configScopeId);\n\n    var issues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", projectKeyPhp), \"src/Math.php\");\n    assertThat(issues).hasSize(1);\n  }\n\n  @Test\n  void analysisPython() {\n    var configScopeId = \"analysisPython\";\n    restoreProfile(\"python-sonarlint.xml\");\n    var projectKeyPython = \"sample-python\";\n    provisionProject(projectKeyPython, \"Sample Python\");\n    associateProjectToQualityProfile(projectKeyPython, \"py\", \"SonarLint IT Python\");\n\n    openBoundConfigurationScope(configScopeId, projectKeyPython);\n    waitForAnalysisToBeReady(configScopeId);\n\n    var issues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", projectKeyPython), \"src/hello.py\");\n    assertThat(issues).hasSize(1);\n  }\n\n  @Test\n  void analysisWeb() {\n    var configScopeId = \"analysisWeb\";\n    restoreProfile(\"web-sonarlint.xml\");\n    var projectKey = \"sample-web\";\n    provisionProject(projectKey, \"Sample Web\");\n    associateProjectToQualityProfile(projectKey, \"web\", \"SonarLint IT Web\");\n\n    openBoundConfigurationScope(configScopeId, projectKey);\n    waitForAnalysisToBeReady(configScopeId);\n\n    var issues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", projectKey), \"src/file.html\");\n\n    assertThat(issues).hasSize(1);\n  }\n\n  @Test\n  @Disabled(\"Reaction to settings changes is not fully implemented in the new backend, see SLCORE-650\")\n  void analysisUseConfiguration() {\n    var configScopeId = \"analysisUseConfiguration\";\n    openUnboundConfigurationScope(configScopeId);\n    var issues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", PROJECT_KEY_JAVA), \"src/main/java/foo/Foo.java\",\n      \"sonar.java.binaries\", new File(\"projects/sample-java/target/classes\").getAbsolutePath());\n    assertThat(issues).hasSize(2);\n\n    try {\n      // Override default file suffixes in project props so that input file is not considered as a Java file\n      setSettingsMultiValue(projectKey(PROJECT_KEY_JAVA), SONAR_JAVA_FILE_SUFFIXES, \".foo\");\n\n      backend.getConfigurationService().didUpdateBinding(new DidUpdateBindingParams(configScopeId, new BindingConfigurationDto(CONNECTION_ID, projectKey(PROJECT_KEY_JAVA), true)));\n      waitForAnalysisToBeReady(configScopeId);\n\n      issues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", PROJECT_KEY_JAVA), \"src/main/java/foo/Foo.java\",\n        \"sonar.java.binaries\", new File(\"projects/sample-java/target/classes\").getAbsolutePath());\n      assertThat(issues).isEmpty();\n    } finally {\n      adminWsClient.settings().reset(new ResetRequest()\n        .setKeys(Collections.singletonList(SONAR_JAVA_FILE_SUFFIXES))\n        .setComponent(projectKey(PROJECT_KEY_JAVA)));\n    }\n  }\n\n  @Test\n  void downloadUserOrganizations() throws ExecutionException, InterruptedException {\n    var response = backend.getConnectionService()\n      .listUserOrganizations(new ListUserOrganizationsParams(Either.forLeft(new TokenDto(SONARCLOUD_TOKEN)), region)).get();\n    assertThat(response.getUserOrganizations()).hasSize(1);\n  }\n\n  @Test\n  void getOrganization() throws ExecutionException, InterruptedException {\n    var response = backend.getConnectionService()\n      .getOrganization(new GetOrganizationParams(Either.forLeft(new TokenDto(SONARCLOUD_TOKEN)), SONARCLOUD_ORGANIZATION, region)).get();\n    var org = response.getOrganization();\n    assertThat(org).isNotNull();\n    assertThat(org.getKey()).isEqualTo(SONARCLOUD_ORGANIZATION);\n    assertThat(org.getName()).isEqualTo(\"SonarLint IT Tests\");\n  }\n\n  @Test\n  void analysisRuby() {\n    var configScopeId = \"analysisRuby\";\n    restoreProfile(\"ruby-sonarlint.xml\");\n    var projectKeyRuby = \"sample-ruby\";\n    provisionProject(projectKeyRuby, \"Sample Ruby\");\n    associateProjectToQualityProfile(projectKeyRuby, \"ruby\", \"SonarLint IT Ruby\");\n\n    openBoundConfigurationScope(configScopeId, projectKeyRuby);\n    waitForAnalysisToBeReady(configScopeId);\n\n    var issues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", projectKeyRuby), \"src/hello.rb\");\n    assertThat(issues).hasSize(1);\n  }\n\n  @Test\n  void analysisKotlin() {\n    var configScopeId = \"analysisKotlin\";\n    restoreProfile(\"kotlin-sonarlint.xml\");\n    var projectKeyKotlin = \"sample-kotlin\";\n    provisionProject(projectKeyKotlin, \"Sample Kotlin\");\n    associateProjectToQualityProfile(projectKeyKotlin, \"kotlin\", \"SonarLint IT Kotlin\");\n\n    openBoundConfigurationScope(configScopeId, projectKeyKotlin);\n    waitForAnalysisToBeReady(configScopeId);\n\n    var issues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", projectKeyKotlin), \"src/hello.kt\");\n    assertThat(issues).hasSize(1);\n  }\n\n  @Test\n  void analysisScala() {\n    var configScopeId = \"analysisScala\";\n    restoreProfile(\"scala-sonarlint.xml\");\n    var projectKeyScala = \"sample-scala\";\n    provisionProject(projectKeyScala, \"Sample Scala\");\n    associateProjectToQualityProfile(projectKeyScala, \"scala\", \"SonarLint IT Scala\");\n\n    openBoundConfigurationScope(configScopeId, projectKeyScala);\n    waitForAnalysisToBeReady(configScopeId);\n\n    var issues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", projectKeyScala), \"src/Hello.scala\");\n    assertThat(issues).hasSize(1);\n  }\n\n  @Test\n  void analysisXml() {\n    var configScopeId = \"analysisXml\";\n    restoreProfile(\"xml-sonarlint.xml\");\n    var projectKeyXml = \"sample-xml\";\n    provisionProject(projectKeyXml, \"Sample XML\");\n    associateProjectToQualityProfile(projectKeyXml, \"xml\", \"SonarLint IT XML\");\n\n    openBoundConfigurationScope(configScopeId, projectKeyXml);\n    waitForAnalysisToBeReady(configScopeId);\n\n    var issues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", projectKeyXml), \"src/foo.xml\");\n    assertThat(issues).hasSize(1);\n  }\n\n  @Test\n  void testConnection() throws ExecutionException, InterruptedException {\n    var successResponse = backend.getConnectionService()\n      .validateConnection(\n        new ValidateConnectionParams(new TransientSonarCloudConnectionDto(SONARCLOUD_ORGANIZATION, Either.forLeft(new TokenDto(SONARCLOUD_TOKEN)), region)))\n      .get();\n    assertThat(successResponse.isSuccess()).isTrue();\n    assertThat(successResponse.getMessage()).isEqualTo(\"Authentication successful\");\n\n    var failIfWrongOrg = backend.getConnectionService().validateConnection(\n      new ValidateConnectionParams(new TransientSonarCloudConnectionDto(\"not-exists\", Either.forLeft(new TokenDto(SONARCLOUD_TOKEN)), region))).get();\n    assertThat(failIfWrongOrg.isSuccess()).isFalse();\n    assertThat(failIfWrongOrg.getMessage()).isEqualTo(\"No organizations found for key: not-exists\");\n\n    var failIfWrongCredentials = backend.getConnectionService()\n      .validateConnection(new ValidateConnectionParams(new TransientSonarCloudConnectionDto(SONARCLOUD_ORGANIZATION, Either.forLeft(new TokenDto(\"foo\")), region))).get();\n    assertThat(failIfWrongCredentials.isSuccess()).isFalse();\n    assertThat(failIfWrongCredentials.getMessage()).isEqualTo(\"Authentication failed\");\n  }\n\n  @Nested\n  // TODO Can be removed when switching to Java 16+ and changing prepare() to static\n  @TestInstance(TestInstance.Lifecycle.PER_CLASS)\n  class Hotspots {\n    private static final String PROJECT_KEY_JAVA_HOTSPOT = \"sample-java-hotspot\";\n\n    @BeforeAll\n    void prepare() throws Exception {\n      restoreProfile(\"java-sonarlint-with-hotspot.xml\");\n      provisionProject(PROJECT_KEY_JAVA_HOTSPOT, \"Sample Java Hotspot\");\n      associateProjectToQualityProfile(PROJECT_KEY_JAVA_HOTSPOT, \"java\", \"SonarLint IT Java Hotspot\");\n      analyzeMavenProject(projectKey(PROJECT_KEY_JAVA_HOTSPOT), PROJECT_KEY_JAVA_HOTSPOT);\n    }\n\n    @Test\n    void reportHotspots() {\n      var configScopeId = \"reportHotspots\";\n      openBoundConfigurationScope(configScopeId, PROJECT_KEY_JAVA_HOTSPOT);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var issues = analyzeAndAwaitHotspots(backend, client, configScopeId, Path.of(\"projects\", PROJECT_KEY_JAVA_HOTSPOT), \"src/main/java/foo/Foo.java\");\n      assertThat(issues)\n        .extracting(RaisedHotspotDto::getRuleKey, h -> h.getSeverityMode().getLeft().getType())\n        .containsExactly(tuple(\"java:S4792\", RuleType.SECURITY_HOTSPOT));\n    }\n\n    @Test\n    void loadHotspotRuleDescription() throws Exception {\n      openBoundConfigurationScope(\"loadHotspotRuleDescription\", PROJECT_KEY_JAVA_HOTSPOT);\n\n      var ruleDetails = backend.getRulesService().getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams(\"loadHotspotRuleDescription\", \"java:S4792\", null)).get();\n      assertThat(ruleDetails.details().getName()).isEqualTo(\"Configuring loggers is security-sensitive\");\n      assertThat(ruleDetails.details().getDescription().getRight().getTabs().get(2).getContent().getLeft().getHtmlContent())\n        .contains(\"Check that your production deployment doesn’t have its loggers in \\\"debug\\\" mode\");\n    }\n\n    @Test\n    void shouldMatchServerSecurityHotspots() {\n      var configScopeId = \"shouldMatchServerSecurityHotspots\";\n      openBoundConfigurationScope(configScopeId, PROJECT_KEY_JAVA_HOTSPOT);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var raisedHotspots = analyzeAndAwaitHotspots(backend, client, configScopeId, Path.of(\"projects\", PROJECT_KEY_JAVA_HOTSPOT), \"src/main/java/foo/Foo.java\");\n\n      assertThat(raisedHotspots).hasSize(1);\n      assertThat(raisedHotspots.get(0).getStatus()).isEqualTo(HotspotStatus.TO_REVIEW);\n    }\n  }\n\n  private static void openBoundConfigurationScope(String configScopeId, String projectKey) {\n    openedConfigurationScopeIds.add(configScopeId);\n    backend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(\n      List.of(new ConfigurationScopeDto(configScopeId, null, true, \"My \" + configScopeId, new BindingConfigurationDto(CONNECTION_ID, projectKey(projectKey), true)))));\n  }\n\n  private static void openUnboundConfigurationScope(String configScopeId) {\n    backend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(\n      List.of(new ConfigurationScopeDto(configScopeId, null, true, \"My \" + configScopeId, new BindingConfigurationDto(null, null, true)))));\n  }\n\n  @Nested\n  // TODO Can be removed when switching to Java 16+ and changing prepare() to static\n  @TestInstance(TestInstance.Lifecycle.PER_CLASS)\n  class TaintVulnerabilities {\n    private static final String PROJECT_KEY_JAVA_TAINT = \"sample-java-taint\";\n    private String projectKey;\n\n    @BeforeAll\n    void prepare() throws Exception {\n      restoreProfile(\"java-sonarlint-with-taint.xml\");\n      this.projectKey = provisionProject(PROJECT_KEY_JAVA_TAINT, \"Java With Taint Vulnerabilities\");\n      associateProjectToQualityProfile(PROJECT_KEY_JAVA_TAINT, \"java\", \"SonarLint Taint Java\");\n      analyzeMavenProject(projectKey(PROJECT_KEY_JAVA_TAINT), PROJECT_KEY_JAVA_TAINT);\n    }\n\n    @Test\n    void download_taint_vulnerabilities_for_project() throws ExecutionException, InterruptedException {\n      var configScopeId = \"download_taint_vulnerabilities_for_project\";\n      openBoundConfigurationScope(configScopeId, PROJECT_KEY_JAVA_TAINT);\n      waitForAnalysisToBeReady(configScopeId);\n\n      // Ensure a vulnerability has been reported on server side\n      AtomicReference<Issues.Issue> issue = new AtomicReference<>();\n      await().untilAsserted(() -> {\n        var issuesList = adminWsClient.issues().search(new SearchRequest().setTypes(List.of(\"VULNERABILITY\")).setComponentKeys(List.of(projectKey(PROJECT_KEY_JAVA_TAINT))))\n          .getIssuesList();\n        assertThat(issuesList).hasSize(1);\n        issue.set(issuesList.get(0));\n      });\n      // Ensure the source is available, it can take some time to propagate after the analysis, especially on SQC US\n      await().untilAsserted(() -> {\n        try {\n          var rawSource = adminWsClient.sources().raw(new RawRequest().setKey(projectKey + \":src/main/java/foo/DbHelper.java\"));\n          assertThat(rawSource).isNotEmpty();\n        } catch (Exception e) {\n          fail(\"The source is not yet available\", e);\n        }\n      });\n\n      var issueKey = issue.get().getKey();\n\n      var taintVulnerabilities = backend.getTaintVulnerabilityTrackingService().listAll(new ListAllParams(configScopeId, true)).get().getTaintVulnerabilities();\n\n      assertThat(taintVulnerabilities).hasSize(1);\n      var taintVulnerability = taintVulnerabilities.get(0);\n      assertThat(taintVulnerability.getSonarServerKey()).isEqualTo(issueKey);\n      assertThat(taintVulnerability.getRuleKey()).isEqualTo(\"javasecurity:S3649\");\n      assertThat(taintVulnerability.getTextRange().getHash()).isEqualTo(hash(\"statement.executeQuery(query)\"));\n      assertThat(taintVulnerability.getRuleDescriptionContextKey()).isNull();\n      assertThat(taintVulnerability.getSeverityMode().isRight()).isTrue();\n      assertThat(taintVulnerability.getSeverityMode().getRight().getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.COMPLETE);\n      assertThat(taintVulnerability.getSeverityMode().getRight().getImpacts().get(0)).extracting(\"softwareQuality\", \"impactSeverity\").containsExactly(SoftwareQuality.SECURITY,\n        ImpactSeverity.BLOCKER);\n      assertThat(taintVulnerability.getFlows()).isNotEmpty();\n      assertThat(taintVulnerability.isOnNewCode()).isTrue();\n      // the feature is not enabled for our org\n      assertThat(taintVulnerability.isAiCodeFixable()).isFalse();\n      var flow = taintVulnerability.getFlows().get(0);\n      assertThat(flow.getLocations()).isNotEmpty();\n      assertThat(flow.getLocations().get(0).getTextRange().getHash()).isEqualTo(hash(\"statement.executeQuery(query)\"));\n      assertThat(flow.getLocations().get(flow.getLocations().size() - 1).getTextRange().getHash()).isIn(hash(\"request.getParameter(\\\"user\\\")\"),\n        hash(\"request.getParameter(\\\"pass\\\")\"));\n    }\n  }\n\n  private static void waitForAnalysisToBeReady(String configScopeId) {\n    await().atMost(1, TimeUnit.MINUTES).untilAsserted(() -> assertThat(analysisReadinessByConfigScopeId).containsEntry(configScopeId, true));\n  }\n\n  private void setSettingsMultiValue(@Nullable String moduleKey, String key, String value) {\n    adminWsClient.settings().set(new SetRequest()\n      .setKey(key)\n      .setValues(Collections.singletonList(value))\n      .setComponent(moduleKey));\n  }\n\n  public static WsClient newAdminWsClient() {\n    return WsClientFactories.getDefault().newClient(HttpConnector.newBuilder()\n      .url(SONARCLOUD_STAGING_URL.toString())\n      .token(SONARCLOUD_TOKEN)\n      .build());\n  }\n\n  private static void analyzeMavenProject(String projectKey, String projectDirName) throws IOException {\n    var projectDir = Paths.get(\"projects/\" + projectDirName).toAbsolutePath();\n    runMaven(projectDir, \"clean\", \"package\", \"sonar:sonar\",\n      \"-Dsonar.projectKey=\" + projectKey,\n      \"-Dsonar.host.url=\" + SONARCLOUD_STAGING_URL,\n      \"-Dsonar.organization=\" + SONARCLOUD_ORGANIZATION,\n      \"-Dsonar.token=\" + SONARCLOUD_TOKEN,\n      \"-Dsonar.scm.disabled=true\",\n      \"-Dsonar.branch.autoconfig.disabled=true\");\n\n    waitAtMost(1, TimeUnit.MINUTES).until(() -> {\n      var request = new GetRequest(\"api/analysis_reports/is_queue_empty\");\n      try (var response = adminWsClient.wsConnector().call(request)) {\n        return \"true\".equals(response.content());\n      }\n    });\n  }\n\n  private static void runMaven(Path workDir, String... args) throws IOException {\n    CommandLine cmdLine;\n    if (SystemUtils.IS_OS_WINDOWS) {\n      cmdLine = CommandLine.parse(\"cmd.exe\");\n      cmdLine.addArguments(\"/c\");\n      cmdLine.addArguments(\"mvn\");\n    } else {\n      cmdLine = CommandLine.parse(\"mvn\");\n    }\n\n    cmdLine.addArguments(new String[] {\"--batch-mode\", \"--show-version\", \"--errors\"});\n    cmdLine.addArguments(args);\n    var executor = new DefaultExecutor();\n    executor.setWorkingDirectory(workDir.toFile());\n    var exitValue = executor.execute(cmdLine);\n    assertThat(exitValue).isZero();\n  }\n\n  private static void assertIsOk(WsResponse response) {\n    var code = response.code();\n    assertThat(code)\n      .withFailMessage(() -> \"Expected an HTTP call to have an OK code, got: \" + code)\n      // This is an approximation for \"non error codes\" - 200, 201, 204... + possible redirects\n      .isBetween(200, 399);\n  }\n\n  private static MockSonarLintRpcClientDelegate newDummySonarLintClient() {\n    return new MockSonarLintRpcClientDelegate() {\n\n      @Override\n      public Either<TokenDto, UsernamePasswordDto> getCredentials(String connectionId) throws ConnectionNotFoundException {\n        if (connectionId.equals(CONNECTION_ID)) {\n          return Either.forLeft(new TokenDto(SONARCLOUD_TOKEN));\n        }\n        return super.getCredentials(connectionId);\n      }\n\n      @Override\n      public void didChangeAnalysisReadiness(Set<String> configurationScopeIds, boolean areReadyForAnalysis) {\n        analysisReadinessByConfigScopeId.putAll(configurationScopeIds.stream().collect(Collectors.toMap(Function.identity(), k -> areReadyForAnalysis)));\n      }\n\n      @Override\n      public void log(LogParams params) {\n        System.out.println(params);\n        rpcClientLogs.add(params);\n      }\n    };\n  }\n\n}\n"
  },
  {
    "path": "its/tests/src/test/java/its/SonarQubeCommunityEditionTests.java",
    "content": "/*\n * SonarLint Core - ITs - Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage its;\n\nimport com.sonar.orchestrator.junit5.OrchestratorExtension;\nimport com.sonar.orchestrator.locator.FileLocation;\nimport its.utils.OrchestratorUtils;\nimport java.io.IOException;\nimport java.io.PipedInputStream;\nimport java.io.PipedOutputStream;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ExecutionException;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarqube.ws.client.WsClient;\nimport org.sonarqube.ws.client.users.CreateRequest;\nimport org.sonarsource.sonarlint.core.rpc.client.ClientJsonRpcLauncher;\nimport org.sonarsource.sonarlint.core.rpc.client.ConnectionNotFoundException;\nimport org.sonarsource.sonarlint.core.rpc.client.SonarLintRpcClientDelegate;\nimport org.sonarsource.sonarlint.core.rpc.impl.BackendJsonRpcLauncher;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarQubeConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.HttpConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\n\nimport static java.util.Collections.emptySet;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JAVA;\n\nclass SonarQubeCommunityEditionTests extends AbstractConnectedTests {\n\n  private static final String CONNECTION_ID = \"orchestrator\";\n\n  @RegisterExtension\n  static final OrchestratorExtension ORCHESTRATOR = OrchestratorUtils.defaultEnvBuilder()\n    .addPlugin(FileLocation.of(\"../plugins/java-custom-rules/target/java-custom-rules-plugin.jar\"))\n    .setServerProperty(\"sonar.projectCreation.mainBranchName\", MAIN_BRANCH_NAME)\n    .build();\n\n  @TempDir\n  private static Path sonarUserHome;\n  private static WsClient adminWsClient;\n  private static SonarLintRpcServer backend;\n  private static BackendJsonRpcLauncher serverLauncher;\n\n  @BeforeAll\n  static void startBackend() throws IOException {\n    var clientToServerOutputStream = new PipedOutputStream();\n    var clientToServerInputStream = new PipedInputStream(clientToServerOutputStream);\n\n    var serverToClientOutputStream = new PipedOutputStream();\n    var serverToClientInputStream = new PipedInputStream(serverToClientOutputStream);\n    var client = newDummySonarLintClient();\n    serverLauncher = new BackendJsonRpcLauncher(clientToServerInputStream, serverToClientOutputStream);\n    var clientLauncher = new ClientJsonRpcLauncher(serverToClientInputStream, clientToServerOutputStream, client);\n\n    backend = clientLauncher.getServerProxy();\n    try {\n      var enabledLanguages = Set.of(JAVA);\n      backend.initialize(\n        new InitializeParams(IT_CLIENT_INFO,\n          IT_TELEMETRY_ATTRIBUTES, HttpConfigurationDto.defaultConfig(), null, Set.of(), sonarUserHome.resolve(\"storage\"),\n          sonarUserHome.resolve(\"work\"),\n          Collections.emptySet(), Collections.emptyMap(), enabledLanguages, emptySet(), emptySet(),\n          List.of(new SonarQubeConnectionConfigurationDto(CONNECTION_ID, ORCHESTRATOR.getServer().getUrl(), true)),\n          Collections.emptyList(),\n          sonarUserHome.toString(),\n          Map.of(), false, null, false, null))\n        .get();\n    } catch (Exception e) {\n      throw new IllegalStateException(\"Cannot initialize the backend\", e);\n    }\n  }\n\n  @BeforeAll\n  static void createSonarLintUser() {\n    adminWsClient = newAdminWsClient(ORCHESTRATOR);\n    adminWsClient.users().create(new CreateRequest().setLogin(SONARLINT_USER).setPassword(SONARLINT_PWD).setName(\"SonarLint\"));\n  }\n\n  @AfterAll\n  static void stopBackend() throws ExecutionException, InterruptedException {\n    serverLauncher.getServer().shutdown().get();\n  }\n\n  private static SonarLintRpcClientDelegate newDummySonarLintClient() {\n    return new MockSonarLintRpcClientDelegate() {\n\n      @Override\n      public Either<TokenDto, UsernamePasswordDto> getCredentials(String connectionId) throws ConnectionNotFoundException {\n        if (connectionId.equals(CONNECTION_ID)) {\n          return Either.forRight(new UsernamePasswordDto(SONARLINT_USER, SONARLINT_PWD));\n        }\n        return super.getCredentials(connectionId);\n      }\n\n      @Override\n      public void log(LogParams params) {\n        rpcClientLogs.add(params);\n      }\n\n    };\n  }\n}\n"
  },
  {
    "path": "its/tests/src/test/java/its/SonarQubeDeveloperEditionTests.java",
    "content": "/*\n * SonarLint Core - ITs - Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage its;\n\nimport com.google.protobuf.InvalidProtocolBufferException;\nimport com.sonar.orchestrator.build.SonarScanner;\nimport com.sonar.orchestrator.container.Edition;\nimport com.sonar.orchestrator.junit5.OnlyOnSonarQube;\nimport com.sonar.orchestrator.junit5.OrchestratorExtension;\nimport com.sonar.orchestrator.locator.FileLocation;\nimport its.utils.OrchestratorUtils;\nimport its.utils.PluginLocator;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.PipedInputStream;\nimport java.io.PipedOutputStream;\nimport java.net.URI;\nimport java.net.URLEncoder;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestInstance;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.mockito.ArgumentCaptor;\nimport org.sonarqube.ws.client.PostRequest;\nimport org.sonarqube.ws.client.WsClient;\nimport org.sonarqube.ws.client.WsRequest;\nimport org.sonarqube.ws.client.issues.DoTransitionRequest;\nimport org.sonarqube.ws.client.issues.SearchRequest;\nimport org.sonarqube.ws.client.settings.ResetRequest;\nimport org.sonarqube.ws.client.settings.SetRequest;\nimport org.sonarqube.ws.client.users.CreateRequest;\nimport org.sonarsource.sonarlint.core.rpc.client.ClientJsonRpcLauncher;\nimport org.sonarsource.sonarlint.core.rpc.client.ConfigScopeNotFoundException;\nimport org.sonarsource.sonarlint.core.rpc.client.ConnectionNotFoundException;\nimport org.sonarsource.sonarlint.core.rpc.client.SonarLintCancelChecker;\nimport org.sonarsource.sonarlint.core.rpc.impl.BackendJsonRpcLauncher;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.branch.DidVcsRepositoryChangeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.branch.GetMatchedSonarProjectBranchParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidRemoveConfigurationScopeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarQubeConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarQubeConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.GetAllProjectsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.SonarProjectDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.HotspotStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.HttpConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.GetEffectiveRuleDetailsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleDescriptionTabDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.ListAllParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.HotspotDetailsDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.RaisedHotspotDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedFindingDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.taint.vulnerability.DidChangeTaintVulnerabilitiesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.RuleType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\n\nimport static its.utils.AnalysisUtils.analyzeAndAwaitHotspots;\nimport static its.utils.AnalysisUtils.analyzeAndAwaitIssues;\nimport static java.util.Collections.emptyList;\nimport static java.util.Collections.emptySet;\nimport static java.util.Collections.singletonList;\nimport static org.apache.commons.lang3.StringUtils.abbreviate;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.awaitility.Awaitility.waitAtMost;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.verify;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.CLOUDFORMATION;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.COBOL;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.DOCKER;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.GO;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.HTML;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JAVA;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JS;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.KOTLIN;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.KUBERNETES;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.PHP;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.PYTHON;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.RUBY;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.SCALA;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.TERRAFORM;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.XML;\n\nclass SonarQubeDeveloperEditionTests extends AbstractConnectedTests {\n\n  public static final String CONNECTION_ID = \"orchestrator\";\n  public static final String CONNECTION_ID_WRONG_CREDENTIALS = \"wrong-credentials\";\n\n  @RegisterExtension\n  static OrchestratorExtension ORCHESTRATOR = OrchestratorUtils.defaultEnvBuilder()\n    .setEdition(Edition.DEVELOPER)\n    .activateLicense()\n    .addPlugin(FileLocation.of(\"../plugins/global-extension-plugin/target/global-extension-plugin.jar\"))\n    .addPlugin(FileLocation.of(\"../plugins/custom-sensor-plugin/target/custom-sensor-plugin.jar\"))\n    .addPlugin(FileLocation.of(\"../plugins/java-custom-rules/target/java-custom-rules-plugin.jar\"))\n    // Ensure SSE are processed correctly just after SQ startup\n    .setServerProperty(\"sonar.pushevents.polling.initial.delay\", \"2\")\n    .setServerProperty(\"sonar.pushevents.polling.period\", \"1\")\n    .setServerProperty(\"sonar.pushevents.polling.last.timestamp\", \"1\")\n    .setServerProperty(\"sonar.projectCreation.mainBranchName\", MAIN_BRANCH_NAME)\n    .build();\n\n  private static WsClient adminWsClient;\n  private static MockSonarLintRpcClientDelegate client;\n\n  @BeforeAll\n  static void createSonarLintUser() {\n    adminWsClient = newAdminWsClient(ORCHESTRATOR);\n    adminWsClient.users().create(new CreateRequest().setLogin(SONARLINT_USER).setPassword(SONARLINT_PWD).setName(\"SonarLint\"));\n  }\n\n  private static SonarLintRpcServer backend;\n\n  private static BackendJsonRpcLauncher serverLauncher;\n\n  @TempDir\n  private static Path sonarUserHome;\n\n  private static final List<DidChangeTaintVulnerabilitiesParams> didChangeTaintVulnerabilitiesEvents = new CopyOnWriteArrayList<>();\n  private static final List<String> allBranchNamesForProject = new CopyOnWriteArrayList<>();\n  private static String matchedBranchNameForProject = null;\n  private static final List<String> didSynchronizeConfigurationScopes = new CopyOnWriteArrayList<>();\n  private static final Map<String, Boolean> analysisReadinessByConfigScopeId = new ConcurrentHashMap<>();\n\n  @BeforeAll\n  static void start() throws IOException {\n    var clientToServerOutputStream = new PipedOutputStream();\n    var clientToServerInputStream = new PipedInputStream(clientToServerOutputStream);\n\n    var serverToClientOutputStream = new PipedOutputStream();\n    var serverToClientInputStream = new PipedInputStream(serverToClientOutputStream);\n\n    serverLauncher = new BackendJsonRpcLauncher(clientToServerInputStream, serverToClientOutputStream);\n    client = spy(newDummySonarLintClient());\n    var clientLauncher = new ClientJsonRpcLauncher(serverToClientInputStream, clientToServerOutputStream, client);\n\n    backend = clientLauncher.getServerProxy();\n    try {\n      var languages = Set.of(JAVA, GO, PHP, JS, PYTHON, HTML, RUBY, KOTLIN, SCALA, XML, COBOL, CLOUDFORMATION, DOCKER, KUBERNETES, TERRAFORM);\n      backend.initialize(\n        new InitializeParams(IT_CLIENT_INFO, IT_TELEMETRY_ATTRIBUTES, HttpConfigurationDto.defaultConfig(), null,\n          Set.of(BackendCapability.FULL_SYNCHRONIZATION, BackendCapability.PROJECT_SYNCHRONIZATION, BackendCapability.SERVER_SENT_EVENTS, BackendCapability.SECURITY_HOTSPOTS,\n            BackendCapability.DATAFLOW_BUG_DETECTION),\n          sonarUserHome.resolve(\"storage\"),\n          sonarUserHome.resolve(\"work\"),\n          emptySet(), PluginLocator.getEmbeddedPluginsByKeyForTests(),\n          languages, emptySet(), emptySet(),\n          List.of(new SonarQubeConnectionConfigurationDto(CONNECTION_ID, ORCHESTRATOR.getServer().getUrl(), false),\n            new SonarQubeConnectionConfigurationDto(CONNECTION_ID_WRONG_CREDENTIALS, ORCHESTRATOR.getServer().getUrl(), false)),\n          emptyList(),\n          sonarUserHome.toString(),\n          Map.of(), false, null, false, null))\n        .get();\n    } catch (Exception e) {\n      throw new IllegalStateException(\"Cannot initialize the backend\", e);\n    }\n  }\n\n  @BeforeEach\n  void clearState() {\n    rpcClientLogs.clear();\n    didSynchronizeConfigurationScopes.clear();\n    analysisReadinessByConfigScopeId.clear();\n    allBranchNamesForProject.clear();\n    matchedBranchNameForProject = null;\n  }\n\n  @AfterAll\n  static void stop() throws ExecutionException, InterruptedException {\n    backend.shutdown().get();\n  }\n\n  @Nested\n  class AnalysisTests {\n\n    @BeforeEach\n    void start() {\n      Map<String, String> globalProps = new HashMap<>();\n      globalProps.put(\"sonar.global.label\", \"It works\");\n\n      // This profile is altered in a test\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/java-sonarlint.xml\"));\n    }\n\n    @AfterEach\n    void stop() {\n      adminWsClient.settings().reset(new ResetRequest().setKeys(singletonList(\"sonar.java.file.suffixes\")));\n      client.clear();\n    }\n\n    // TODO should be moved to a separate class, not related to analysis\n    @Test\n    void shouldRaiseIssuesOnAJavaScriptProject() {\n      var configScopeId = \"shouldRaiseIssuesOnAJavaScriptProject\";\n      var projectKey = \"sample-javascript\";\n      provisionProject(ORCHESTRATOR, projectKey, \"Sample Javascript\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/javascript-sonarlint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"js\", \"SonarLint IT Javascript\");\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var rawIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-javascript\"), \"src/Person.js\");\n\n      assertThat(rawIssues).hasSize(1);\n    }\n\n    @Test\n    void shouldRaiseIssuesFromACustomRule() {\n      var configScopeId = \"shouldRaiseIssuesFromACustomRule\";\n      var projectKey = \"sample-java-custom\";\n      provisionProject(ORCHESTRATOR, projectKey, \"Sample Java Custom\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/java-custom.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"java\", \"SonarLint IT Java Custom\");\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var rawIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-java-custom\"), \"src/main/java/foo/Foo.java\");\n\n      assertThat(rawIssues).extracting(\"ruleKey\", \"textRange\")\n        .usingRecursiveFieldByFieldElementComparator()\n        .containsOnly(tuple(\"mycompany-java:AvoidAnnotation\", new TextRangeDto(12, 3, 12, 19)));\n    }\n\n    @Test\n    void shouldRaiseIssuesOnAPhpProject() {\n      var configScopeId = \"shouldRaiseIssuesOnAPhpProject\";\n      var projectKey = \"sample-php\";\n      provisionProject(ORCHESTRATOR, projectKey, \"Sample PHP\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/php-sonarlint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"php\", \"SonarLint IT PHP\");\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var rawIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-php\"), \"src/Math.php\");\n\n      assertThat(rawIssues).hasSize(1);\n    }\n\n    @Test\n    void shouldRaiseIssuesOnAPythonProject() {\n      var configScopeId = \"shouldRaiseIssuesOnAPythonProject\";\n      var projectKey = \"sample-python\";\n      var projectName = \"Sample Python\";\n      provisionProject(ORCHESTRATOR, projectKey, projectName);\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/python-sonarlint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"py\", \"SonarLint IT Python\");\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var rawIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-python\"), \"src/hello.py\");\n\n      assertThat(rawIssues).hasSize(1);\n    }\n\n    @Test\n    void shouldRaiseIssuesOnAHtmlProject() {\n      var configScopeId = \"shouldRaiseIssuesOnAHtmlProject\";\n      var projectKey = \"sample-web\";\n      provisionProject(ORCHESTRATOR, projectKey, \"Sample Web\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/web-sonarlint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"web\", \"SonarLint IT Web\");\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var rawIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-web\"), \"src/file.html\");\n\n      assertThat(rawIssues).hasSize(1);\n    }\n\n    @Test\n    void shouldRaiseIssuesOnAGoProject() {\n      var configScopeId = \"shouldRaiseIssuesOnAGoProject\";\n      var projectKey = \"sample-go\";\n      provisionProject(ORCHESTRATOR, projectKey, \"Sample Go\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/go-sonarlint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"go\", \"SonarLint IT Go\");\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var rawIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-go\"), \"src/sample.go\");\n\n      // S5542 was introduced with Go Enterprise in 2025.2\n      var expectedIssues = ORCHESTRATOR.getServer().version().isGreaterThanOrEquals(2025, 2) ? 2 : 1;\n      assertThat(rawIssues).hasSize(expectedIssues);\n    }\n\n    @Test\n    @OnlyOnSonarQube(from = \"9.9\")\n    void shouldRaiseIssuesOnACloudFormationProject() {\n      var configScopeId = \"shouldRaiseIssuesOnACloudFormationProject\";\n      var projectKey = \"sample-cloudformation\";\n      provisionProject(ORCHESTRATOR, projectKey, \"Sample CloudFormation\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/cloudformation-sonarlint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"cloudformation\", \"SonarLint IT CloudFormation\");\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var rawIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-cloudformation\"), \"src/sample.yaml\");\n\n      assertThat(rawIssues).hasSize(1);\n    }\n\n    @Test\n    @OnlyOnSonarQube(from = \"9.9\")\n    void shouldRaiseIssuesOnADockerProject() {\n      var configScopeId = \"shouldRaiseIssuesOnADockerProject\";\n      var projectKey = \"sample-docker\";\n      provisionProject(ORCHESTRATOR, projectKey, \"Sample Docker\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/docker-sonarlint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"docker\", \"SonarLint IT Docker\");\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var rawIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-docker\"), \"src/Dockerfile\");\n\n      assertThat(rawIssues).hasSize(2);\n    }\n\n    @Test\n    @OnlyOnSonarQube(from = \"9.9\")\n    void shouldRaiseIssuesOnAKubernetesProject() {\n      var configScopeId = \"shouldRaiseIssuesOnAKubernetesProject\";\n      var projectKey = \"sample-kubernetes\";\n      provisionProject(ORCHESTRATOR, projectKey, \"Sample Kubernetes\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/kubernetes-sonarlint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"kubernetes\", \"SonarLint IT Kubernetes\");\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var rawIssues = analyzeAndAwaitHotspots(backend, client, configScopeId, Path.of(\"projects\", \"sample-kubernetes\"), \"src/sample.yaml\");\n\n      assertThat(rawIssues).hasSize(1);\n    }\n\n    @Test\n    @OnlyOnSonarQube(from = \"9.9\")\n    void shouldRaiseIssuesOnATerraformProject() {\n      var configScopeId = \"shouldRaiseIssuesOnATerraformProject\";\n      var projectKey = \"sample-terraform\";\n      provisionProject(ORCHESTRATOR, projectKey, \"Sample Terraform\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/terraform-sonarlint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"terraform\", \"SonarLint IT Terraform\");\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var rawIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-terraform\"), \"src/sample.tf\");\n\n      assertThat(rawIssues).hasSize(1);\n    }\n\n    @Test\n    @OnlyOnSonarQube(from = \"10.4\")\n    void shouldRaiseDataflowIssuesOnAPythonProject() {\n      var configScopeId = \"shouldRaiseDataflowIssuesOnAPythonProject\";\n      var projectKey = \"sample-dbd\";\n      provisionProject(ORCHESTRATOR, projectKey, \"Sample DBD\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/dbd-sonarlint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"py\", \"SonarLint IT DBD\");\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForSync(configScopeId);\n\n      var rawIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-dbd\"), \"src/hello.py\");\n\n      assertThat(rawIssues)\n        .extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getPrimaryMessage)\n        .containsOnly(tuple(\"pythonbugs:S6466\", \"Fix this access on a collection that may trigger an 'IndexError'.\"));\n    }\n\n    @Test\n    void customSensorsShouldNotBeExecuted() {\n      var configScopeId = \"customSensorsShouldNotBeExecuted\";\n      var projectKey = \"sample-java-custom-sensor\";\n      provisionProject(ORCHESTRATOR, projectKey, \"Sample Java Custom\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/custom-sensor.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"java\", \"SonarLint IT Custom Sensor\");\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      assertThat(analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-java\"), \"src/main/java/foo/Foo.java\")).isEmpty();\n    }\n\n    // TODO should be moved to a medium test\n    @Test\n    void globalExtension() {\n      var configScopeId = \"globalExtension\";\n      var projectKey = \"sample-global-extension\";\n      provisionProject(ORCHESTRATOR, projectKey, \"Sample Global Extension\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/global-extension.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"cobol\", \"SonarLint IT Global Extension\");\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var rawIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-global-extension\"), \"src/foo.glob\", \"sonar.cobol.file.suffixes\", \"glob\");\n      assertThat(rawIssues).extracting(\"ruleKey\", \"primaryMessage\").containsOnly(\n        tuple(\"global:inc\", \"Issue number 0\"));\n\n      rawIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-global-extension\"), \"src/foo.glob\", \"sonar.cobol.file.suffixes\", \"glob\");\n      assertThat(rawIssues).extracting(\"ruleKey\", \"primaryMessage\").containsOnly(\n        tuple(\"global:inc\", \"Issue number 1\"));\n    }\n\n    @Test\n    void shouldRaiseIssuesFromATemplateRule() throws Exception {\n      var configScopeId = \"shouldRaiseIssuesFromATemplateRule\";\n      var projectKey = \"sample-java-template-rule\";\n      provisionProject(ORCHESTRATOR, projectKey, \"Sample Java\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/java-sonarlint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"java\", \"SonarLint IT Java\");\n\n      var qp = getQualityProfile(adminWsClient, \"SonarLint IT Java\");\n\n      PostRequest request = new PostRequest(\"/api/rules/create\")\n        .setParam(\"params\", \"methodName=echo;className=foo.Foo;argumentTypes=int\")\n        .setParam(\"name\", \"myrule\")\n        .setParam(\"severity\", \"MAJOR\");\n      if (ORCHESTRATOR.getServer().version().isGreaterThanOrEquals(10, 0)) {\n        request.setParam(\"customKey\", \"myrule\")\n          .setParam(\"markdownDescription\", \"my_rule_description\")\n          .setParam(\"templateKey\", javaRuleKey(\"S2253\"));\n      } else {\n        request.setParam(\"custom_key\", \"myrule\")\n          .setParam(\"markdown_description\", \"my_rule_description\")\n          .setParam(\"template_key\", javaRuleKey(\"S2253\"))\n          .setParam(\"type\", \"VULNERABILITY\");\n      }\n\n      try (var response = adminWsClient.wsConnector().call(request)) {\n        assertTrue(response.isSuccessful());\n      }\n\n      request = new PostRequest(\"/api/qualityprofiles/activate_rule\")\n        .setParam(\"key\", qp.getKey())\n        .setParam(\"rule\", javaRuleKey(\"myrule\"));\n      try (var response = adminWsClient.wsConnector().call(request)) {\n        assertTrue(response.isSuccessful(), \"Unable to activate custom rule\");\n      }\n\n      try {\n        openBoundConfigurationScope(configScopeId, projectKey, true);\n        waitForAnalysisToBeReady(configScopeId);\n\n        var rawIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-java\"), \"src/main/java/foo/Foo.java\");\n        assertThat(rawIssues).hasSize(3);\n\n        var ruleDetails = backend.getRulesService().getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams(configScopeId, javaRuleKey(\"myrule\"), null)).get();\n        var details = ruleDetails.details();\n        assertThat(details.getDescription().getLeft().getHtmlContent()).contains(\"my_rule_description\");\n        assertThat(details.getName()).isEqualTo(\"myrule\");\n\n        if (!ORCHESTRATOR.getServer().version().isGreaterThanOrEquals(10, 0)) {\n          assertThat(details.getType()).isEqualTo(RuleType.VULNERABILITY);\n        }\n\n      } finally {\n\n        request = new PostRequest(\"/api/rules/delete\")\n          .setParam(\"key\", javaRuleKey(\"myrule\"));\n        try (var response = adminWsClient.wsConnector().call(request)) {\n          assertTrue(response.isSuccessful(), \"Unable to delete custom rule\");\n        }\n      }\n    }\n\n    @Test\n    void shouldHonorServerSideSettings() {\n      var configScopeId = \"shouldHonorServerSideSettings\";\n      var projectKey = \"sample-java-configured\";\n      var projectName = \"Sample Java\";\n      provisionProject(ORCHESTRATOR, projectKey, projectName);\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/java-sonarlint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"java\", \"SonarLint IT Java\");\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var rawIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-java\"), \"src/main/java/foo/Foo.java\");\n      assertThat(rawIssues).hasSize(2);\n\n      rpcClientLogs.clear();\n      didSynchronizeConfigurationScopes.clear();\n      // Override default file suffixes in global props so that input file is not considered as a Java file\n      setSettingsMultiValue(null, \"sonar.java.file.suffixes\", \".foo\");\n      backend.getConfigurationService().didUpdateBinding(new DidUpdateBindingParams(configScopeId, new BindingConfigurationDto(CONNECTION_ID, projectKey, false)));\n      await().untilAsserted(() -> assertThat(rpcClientLogs.stream().anyMatch(s -> s.getMessage().equals(\"Stored project analyzer configuration\"))).isTrue());\n\n      assertThat(analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-java\"), \"src/main/java/foo/Foo.java\")).isEmpty();\n\n      rpcClientLogs.clear();\n      // Override default file suffixes in project props so that input file is considered as a Java file again\n      setSettingsMultiValue(projectKey, \"sonar.java.file.suffixes\", \".java\");\n      backend.getConfigurationService().didUpdateBinding(new DidUpdateBindingParams(configScopeId, new BindingConfigurationDto(CONNECTION_ID, projectKey, true)));\n      await().untilAsserted(() -> assertThat(rpcClientLogs.stream().anyMatch(s -> s.getMessage().equals(\"Stored project analyzer configuration\"))).isTrue());\n\n      rawIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-java\"), \"src/main/java/foo/Foo.java\");\n      assertThat(rawIssues).hasSize(2);\n    }\n\n    @Test\n    void shouldRaiseIssuesOnARubyProject() {\n      var configScopeId = \"shouldRaiseIssuesOnARubyProject\";\n      var projectKey = \"sample-ruby\";\n      provisionProject(ORCHESTRATOR, projectKey, \"Sample Ruby\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/ruby-sonarlint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"ruby\", \"SonarLint IT Ruby\");\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var rawIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-ruby\"), \"src/hello.rb\");\n\n      assertThat(rawIssues).hasSize(1);\n    }\n\n    @Test\n    void shouldRaiseIssuesOnAKotlinProject() {\n      var configScopeId = \"shouldRaiseIssuesOnAKotlinProject\";\n      var projectKey = \"sample-kotlin\";\n      provisionProject(ORCHESTRATOR, projectKey, \"Sample Kotlin\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/kotlin-sonarlint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"kotlin\", \"SonarLint IT Kotlin\");\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var rawIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-kotlin\"), \"src/hello.kt\");\n\n      assertThat(rawIssues).hasSize(1);\n    }\n\n    @Test\n    void shouldRaiseIssuesOnAScalaProject() {\n      var configScopeId = \"shouldRaiseIssuesOnAScalaProject\";\n      var projectKey = \"sample-scala\";\n      provisionProject(ORCHESTRATOR, projectKey, \"Sample Scala\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/scala-sonarlint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"scala\", \"SonarLint IT Scala\");\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var rawIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-scala\"), \"src/Hello.scala\");\n\n      assertThat(rawIssues).hasSize(1);\n    }\n\n    @Test\n    void shouldRaiseIssuesOnAnXmlProject() {\n      var configScopeId = \"shouldRaiseIssuesOnAnXmlProject\";\n      var projectKey = \"sample-xml\";\n      provisionProject(ORCHESTRATOR, projectKey, \"Sample XML\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/xml-sonarlint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"xml\", \"SonarLint IT XML\");\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var rawIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-xml\"), \"src/foo.xml\");\n\n      assertThat(rawIssues).hasSize(1);\n    }\n  }\n\n  @Nested\n  class ServerSentEvents {\n\n    @Test\n    @OnlyOnSonarQube(from = \"9.9\")\n    void shouldUpdateQualityProfileInLocalStorageWhenProfileChangedOnServer() {\n      var configScopeId = \"shouldUpdateQualityProfileInLocalStorageWhenProfileChangedOnServer\";\n      var projectKey = \"projectKey-sse\";\n      provisionProject(ORCHESTRATOR, projectKey, \"Sample Java\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/java-sonarlint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"java\", \"SonarLint IT Java\");\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var qualityProfile = getQualityProfile(adminWsClient, \"SonarLint IT Java\");\n      deactivateRule(adminWsClient, qualityProfile, \"java:S106\");\n      waitAtMost(1, TimeUnit.MINUTES).pollDelay(Duration.ofSeconds(10)).untilAsserted(() -> {\n        var rawIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-java\"), \"src/main/java/foo/Foo.java\");\n\n        assertThat(rawIssues)\n          .extracting(RaisedIssueDto::getRuleKey)\n          .containsOnly(\"java:S2325\");\n      });\n    }\n\n    @Test\n    void shouldUpdateIssueInLocalStorageWhenIssueResolvedOnServer() {\n      var configScopeId = \"shouldUpdateIssueInLocalStorageWhenIssueResolvedOnServer\";\n      var projectKey = \"projectKey-sse2\";\n      provisionProject(ORCHESTRATOR, projectKey, \"Sample Java\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/java-sonarlint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"java\", \"SonarLint IT Java\");\n\n      analyzeMavenProject(\"sample-java\", projectKey);\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var issueKey = getIssueKeys(adminWsClient, \"java:S106\").get(0);\n      resolveIssueAsWontFix(adminWsClient, issueKey);\n\n      waitAtMost(1, TimeUnit.MINUTES).untilAsserted(() -> {\n        var fooIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-java\"), \"src/main/java/foo/Foo.java\");\n\n        assertThat(fooIssues)\n          .extracting(RaisedFindingDto::getRuleKey, RaisedFindingDto::isResolved)\n          .contains(tuple(\"java:S106\", true));\n      });\n    }\n  }\n\n  @Nested\n  class BranchTests {\n\n    @Test\n    void should_sync_branches_from_server() throws ExecutionException, InterruptedException {\n      var configScopeId = \"should_sync_branches_from_server\";\n      var short_branch = \"feature/short_living\";\n      var long_branch = \"branch-1.x\";\n      var projectKey = \"sample-branch\";\n      provisionProject(ORCHESTRATOR, projectKey, \"Sample Branch\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/xml-sonarlint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"xml\", \"SonarLint IT XML\");\n      // Use the pattern of long living branches in SQ 9.9, else we only have issues on changed files\n\n      // main branch\n      analyzeProject(\"sample-xml\", projectKey);\n      // short living branch\n      analyzeProject(\"sample-xml\", projectKey, \"sonar.branch.name\", short_branch);\n      // long living branch\n      analyzeProject(\"sample-xml\", projectKey, \"sonar.branch.name\", long_branch);\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForSync(configScopeId);\n\n      var sonarProjectBranch = backend.getSonarProjectBranchService().getMatchedSonarProjectBranch(new GetMatchedSonarProjectBranchParams(configScopeId)).get();\n      assertThat(sonarProjectBranch.getMatchedSonarProjectBranch()).isEqualTo(MAIN_BRANCH_NAME);\n\n      matchedBranchNameForProject = short_branch;\n      backend.getSonarProjectBranchService().didVcsRepositoryChange(new DidVcsRepositoryChangeParams(configScopeId));\n\n      await().untilAsserted(() -> assertThat(backend.getSonarProjectBranchService()\n        .getMatchedSonarProjectBranch(new GetMatchedSonarProjectBranchParams(configScopeId))\n        .get().getMatchedSonarProjectBranch())\n          .isEqualTo(short_branch));\n\n      await().untilAsserted(() -> assertThat(allBranchNamesForProject).contains(MAIN_BRANCH_NAME, short_branch, long_branch));\n    }\n\n    @Test\n    void should_match_issues_from_branch() {\n      var configScopeId = \"should_match_issues_from_branch\";\n      var projectKey = \"sample-java\";\n      var projectName = \"my-sample-java\";\n      var featureBranch = \"branch-1.x\";\n\n      provisionProject(ORCHESTRATOR, projectKey, projectName);\n      analyzeProject(projectKey, projectKey);\n      analyzeProject(projectKey, projectKey, \"sonar.branch.name\", featureBranch);\n\n      var issuesBranch = adminWsClient.issues().search(new SearchRequest().setBranch(featureBranch).setComponentKeys(List.of(projectKey)));\n      var issueToMarkFP = issuesBranch.getIssuesList().stream().filter(issue -> issue.getRule().equals(\"java:S1172\")).findFirst().orElseThrow();\n      adminWsClient.issues().doTransition(new DoTransitionRequest().setIssue(issueToMarkFP.getKey()).setTransition(\"falsepositive\"));\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var raisedIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-java\"), \"src/main/java/foo/Foo.java\");\n\n      assertThat(raisedIssues)\n        .extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::isResolved)\n        .contains(tuple(\"java:S1172\", false));\n\n      didSynchronizeConfigurationScopes.clear();\n      matchedBranchNameForProject = featureBranch;\n      backend.getSonarProjectBranchService().didVcsRepositoryChange(new DidVcsRepositoryChangeParams(configScopeId));\n      await().untilAsserted(() -> assertThat(backend.getSonarProjectBranchService()\n        .getMatchedSonarProjectBranch(new GetMatchedSonarProjectBranchParams(configScopeId))\n        .get().getMatchedSonarProjectBranch())\n          .isEqualTo(featureBranch));\n      waitForSync(configScopeId);\n\n      raisedIssues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", \"sample-java\"), \"src/main/java/foo/Foo.java\");\n\n      assertThat(raisedIssues)\n        .extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::isResolved)\n        .contains(tuple(\"java:S1172\", true));\n    }\n  }\n\n  @Nested\n  class TaintVulnerabilities {\n\n    private static final String PROJECT_KEY_JAVA_TAINT = \"sample-java-taint\";\n    private static final String CONFIG_SCOPE_ID = \"sample-java-taint-in-ide\";\n\n    @BeforeEach\n    void prepare() {\n      provisionProject(ORCHESTRATOR, PROJECT_KEY_JAVA_TAINT, \"Java With Taint Vulnerabilities\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/java-sonarlint-with-taint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(PROJECT_KEY_JAVA_TAINT, \"java\", \"SonarLint Taint Java\");\n    }\n\n    @AfterEach\n    void stop() {\n      backend.getConfigurationService().didRemoveConfigurationScope(new DidRemoveConfigurationScopeParams(CONFIG_SCOPE_ID));\n      var request = new PostRequest(\"api/projects/bulk_delete\");\n      request.setParam(\"projects\", PROJECT_KEY_JAVA_TAINT);\n      try (var response = adminWsClient.wsConnector().call(request)) {\n      }\n    }\n\n    @Test\n    void shouldSyncTaintVulnerabilities() throws ExecutionException, InterruptedException {\n      openBoundConfigurationScope(CONFIG_SCOPE_ID, PROJECT_KEY_JAVA_TAINT, true);\n      waitForAnalysisToBeReady(CONFIG_SCOPE_ID);\n\n      analyzeMavenProject(\"sample-java-taint\", PROJECT_KEY_JAVA_TAINT);\n\n      // Ensure a vulnerability has been reported on server side\n      var issuesList = adminWsClient.issues().search(new SearchRequest().setTypes(List.of(\"VULNERABILITY\")).setComponentKeys(List.of(PROJECT_KEY_JAVA_TAINT))).getIssuesList();\n      assertThat(issuesList).hasSize(1);\n\n      var taintVulnerabilities = backend.getTaintVulnerabilityTrackingService().listAll(new ListAllParams(CONFIG_SCOPE_ID, true)).get().getTaintVulnerabilities();\n\n      assertThat(taintVulnerabilities).hasSize(1);\n\n      var taintVulnerability = taintVulnerabilities.get(0);\n      assertThat(taintVulnerability.getTextRange().getHash()).isEqualTo(hash(\"statement.executeQuery(query)\"));\n      var serverVersion = ORCHESTRATOR.getServer().version();\n      var ruleDescriptionContextKey = serverVersion.isGreaterThanOrEquals(2025, 3) ? \"java_jdbc_api\" : \"java_se\";\n      assertThat(taintVulnerability.getRuleDescriptionContextKey()).isEqualTo(ruleDescriptionContextKey);\n      if (serverVersion.isGreaterThanOrEquals(10, 8)) {\n        assertThat(taintVulnerability.getSeverityMode().isRight()).isTrue();\n        // In SQ 10.8+, old MAJOR severity maps to overridden MEDIUM impact\n        assertThat(taintVulnerability.getSeverityMode().getRight().getImpacts().get(0)).extracting(\"softwareQuality\", \"impactSeverity\").containsExactly(SoftwareQuality.SECURITY,\n          ImpactSeverity.MEDIUM);\n        assertThat(taintVulnerability.getSeverityMode().getRight().getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.COMPLETE);\n      } else if (serverVersion.isGreaterThanOrEquals(10, 2)) {\n        // In 10.2 <= SQ < 10.8, the impact severity is not overridden\n        assertThat(taintVulnerability.getSeverityMode().isRight()).isTrue();\n        assertThat(taintVulnerability.getSeverityMode().getRight().getImpacts().get(0)).extracting(\"softwareQuality\", \"impactSeverity\").containsExactly(SoftwareQuality.SECURITY,\n          ImpactSeverity.HIGH);\n        assertThat(taintVulnerability.getSeverityMode().getRight().getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.COMPLETE);\n      } else {\n        assertThat(taintVulnerability.getSeverityMode().isLeft()).isTrue();\n        assertThat(taintVulnerability.getSeverityMode().getLeft().getSeverity()).isEqualTo(org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity.MAJOR);\n        assertThat(taintVulnerability.getSeverityMode().getLeft().getType()).isEqualTo(org.sonarsource.sonarlint.core.rpc.protocol.common.RuleType.VULNERABILITY);\n      }\n      assertThat(taintVulnerability.getFlows()).isNotEmpty();\n      assertThat(taintVulnerability.isOnNewCode()).isTrue();\n      var flow = taintVulnerability.getFlows().get(0);\n      assertThat(flow.getLocations()).isNotEmpty();\n      assertThat(flow.getLocations().get(0).getTextRange().getHash()).isEqualTo(hash(\"statement.executeQuery(query)\"));\n      assertThat(flow.getLocations().get(flow.getLocations().size() - 1).getTextRange().getHash()).isIn(hash(\"request.getParameter(\\\"user\\\")\"),\n        hash(\"request.getParameter(\\\"pass\\\")\"));\n    }\n\n    @Test\n    @OnlyOnSonarQube(from = \"9.9\")\n    void shouldUpdateTaintVulnerabilityInLocalStorageWhenChangedOnServer() throws ExecutionException, InterruptedException {\n      openBoundConfigurationScope(CONFIG_SCOPE_ID, PROJECT_KEY_JAVA_TAINT, true);\n      waitForAnalysisToBeReady(CONFIG_SCOPE_ID);\n\n      assertThat(backend.getTaintVulnerabilityTrackingService().listAll(new ListAllParams(CONFIG_SCOPE_ID)).get().getTaintVulnerabilities()).isEmpty();\n\n      // check TaintVulnerabilityRaised is received\n      analyzeMavenProject(\"sample-java-taint\", PROJECT_KEY_JAVA_TAINT);\n\n      waitAtMost(1, TimeUnit.MINUTES).until(() -> !didChangeTaintVulnerabilitiesEvents.isEmpty());\n      var issues = getIssueKeys(adminWsClient, \"javasecurity:S3649\");\n      assertThat(issues).isNotEmpty();\n      var issueKey = issues.get(0);\n      var firstTaintChangedEvent = didChangeTaintVulnerabilitiesEvents.remove(0);\n      assertThat(firstTaintChangedEvent)\n        .extracting(DidChangeTaintVulnerabilitiesParams::getConfigurationScopeId, DidChangeTaintVulnerabilitiesParams::getClosedTaintVulnerabilityIds,\n          DidChangeTaintVulnerabilitiesParams::getUpdatedTaintVulnerabilities)\n        .containsExactly(CONFIG_SCOPE_ID, emptySet(), emptyList());\n      assertThat(firstTaintChangedEvent.getAddedTaintVulnerabilities())\n        .extracting(TaintVulnerabilityDto::getSonarServerKey, TaintVulnerabilityDto::isResolved, TaintVulnerabilityDto::getRuleKey, TaintVulnerabilityDto::getMessage,\n          TaintVulnerabilityDto::getIdeFilePath, TaintVulnerabilityDto::isOnNewCode)\n        .containsExactly(tuple(issueKey, false, \"javasecurity:S3649\", \"Change this code to not construct SQL queries directly from user-controlled data.\",\n          Paths.get(\"src/main/java/foo/DbHelper.java\"), true));\n      assertThat(firstTaintChangedEvent.getAddedTaintVulnerabilities())\n        .flatExtracting(\"flows\")\n        .flatExtracting(\"locations\")\n        .extracting(\"message\", \"filePath\", \"textRange.startLine\", \"textRange.startLineOffset\", \"textRange.endLine\", \"textRange.endLineOffset\", \"textRange.hash\")\n        .contains(\n          // flow 1 (don't assert intermediate locations as they change frequently between versions)\n          tuple(\"Sink: this invocation is not safe; a malicious value can be used as argument\", Paths.get(\"src/main/java/foo/DbHelper.java\"), 11, 35, 11, 64,\n            \"d123d615e9ea7cc7e78c784c768f2941\"),\n          tuple(\"Source: a user can craft an HTTP request with malicious content\", Paths.get(\"src/main/java/foo/Endpoint.java\"), 9, 18, 9, 46, \"a2b69949119440a24e900f15c0939c30\"),\n          // flow 2 (don't assert intermediate locations as they change frequently between versions)\n          tuple(\"Sink: this invocation is not safe; a malicious value can be used as argument\", Paths.get(\"src/main/java/foo/DbHelper.java\"), 11, 35, 11, 64,\n            \"d123d615e9ea7cc7e78c784c768f2941\"),\n          tuple(\"Source: a user can craft an HTTP request with malicious content\", Paths.get(\"src/main/java/foo/Endpoint.java\"), 8, 18, 8, 46, \"2ef54227b849e317e7104dc550be8146\"));\n      var raisedIssueId = firstTaintChangedEvent.getAddedTaintVulnerabilities().get(0).getId();\n\n      var taintIssues = backend.getTaintVulnerabilityTrackingService().listAll(new ListAllParams(CONFIG_SCOPE_ID)).get().getTaintVulnerabilities();\n      assertThat(taintIssues)\n        .extracting(TaintVulnerabilityDto::getSonarServerKey, TaintVulnerabilityDto::isResolved, TaintVulnerabilityDto::getRuleKey, TaintVulnerabilityDto::getMessage,\n          TaintVulnerabilityDto::getIdeFilePath, TaintVulnerabilityDto::isOnNewCode)\n        .containsExactly(tuple(issueKey, false, \"javasecurity:S3649\", \"Change this code to not construct SQL queries directly from user-controlled data.\",\n          Paths.get(\"src/main/java/foo/DbHelper.java\"), true));\n      assertThat(taintIssues)\n        .flatExtracting(\"flows\")\n        .flatExtracting(\"locations\")\n        .extracting(\"message\", \"filePath\", \"textRange.startLine\", \"textRange.startLineOffset\", \"textRange.endLine\", \"textRange.endLineOffset\", \"textRange.hash\")\n        .contains(\n          // flow 1 (don't assert intermediate locations as they change frequently between versions)\n          tuple(\"Sink: this invocation is not safe; a malicious value can be used as argument\", Paths.get(\"src/main/java/foo/DbHelper.java\"), 11, 35, 11, 64,\n            \"d123d615e9ea7cc7e78c784c768f2941\"),\n          tuple(\"Source: a user can craft an HTTP request with malicious content\", Paths.get(\"src/main/java/foo/Endpoint.java\"), 9, 18, 9, 46, \"a2b69949119440a24e900f15c0939c30\"),\n          // flow 2 (don't assert intermediate locations as they change frequently between versions)\n          tuple(\"Sink: this invocation is not safe; a malicious value can be used as argument\", Paths.get(\"src/main/java/foo/DbHelper.java\"), 11, 35, 11, 64,\n            \"d123d615e9ea7cc7e78c784c768f2941\"),\n          tuple(\"Source: a user can craft an HTTP request with malicious content\", Paths.get(\"src/main/java/foo/Endpoint.java\"), 8, 18, 8, 46, \"2ef54227b849e317e7104dc550be8146\"));\n\n      resolveIssueAsWontFix(adminWsClient, issueKey);\n\n      // check IssueChangedEvent is received\n      waitAtMost(1, TimeUnit.MINUTES).until(() -> !didChangeTaintVulnerabilitiesEvents.isEmpty());\n      var secondTaintEvent = didChangeTaintVulnerabilitiesEvents.remove(0);\n      assertThat(secondTaintEvent)\n        .extracting(DidChangeTaintVulnerabilitiesParams::getConfigurationScopeId, DidChangeTaintVulnerabilitiesParams::getClosedTaintVulnerabilityIds,\n          DidChangeTaintVulnerabilitiesParams::getAddedTaintVulnerabilities)\n        .containsExactly(CONFIG_SCOPE_ID, emptySet(), emptyList());\n      assertThat(secondTaintEvent.getUpdatedTaintVulnerabilities())\n        .extracting(TaintVulnerabilityDto::isResolved)\n        .containsExactly(true);\n\n      reopenIssue(adminWsClient, issueKey);\n\n      // check IssueChangedEvent is received\n      waitAtMost(1, TimeUnit.MINUTES).until(() -> !didChangeTaintVulnerabilitiesEvents.isEmpty());\n      var thirdTaintEvent = didChangeTaintVulnerabilitiesEvents.remove(0);\n      assertThat(thirdTaintEvent)\n        .extracting(DidChangeTaintVulnerabilitiesParams::getConfigurationScopeId, DidChangeTaintVulnerabilitiesParams::getClosedTaintVulnerabilityIds,\n          DidChangeTaintVulnerabilitiesParams::getAddedTaintVulnerabilities)\n        .containsExactly(CONFIG_SCOPE_ID, emptySet(), emptyList());\n      assertThat(thirdTaintEvent.getUpdatedTaintVulnerabilities())\n        .extracting(TaintVulnerabilityDto::isResolved)\n        .containsExactly(false);\n\n      // analyze another project under the same project key to close the taint issue\n      analyzeMavenProject(\"sample-java\", PROJECT_KEY_JAVA_TAINT);\n\n      // check TaintVulnerabilityClosed is received\n      waitAtMost(1, TimeUnit.MINUTES).until(() -> !didChangeTaintVulnerabilitiesEvents.isEmpty());\n      var fourthTaintEvent = didChangeTaintVulnerabilitiesEvents.remove(0);\n      assertThat(fourthTaintEvent)\n        .extracting(DidChangeTaintVulnerabilitiesParams::getConfigurationScopeId, DidChangeTaintVulnerabilitiesParams::getUpdatedTaintVulnerabilities,\n          DidChangeTaintVulnerabilitiesParams::getAddedTaintVulnerabilities)\n        .containsExactly(CONFIG_SCOPE_ID, emptyList(), emptyList());\n      assertThat(fourthTaintEvent.getClosedTaintVulnerabilityIds())\n        .containsExactly(raisedIssueId);\n    }\n  }\n\n  @Nested\n  // TODO Can be removed when switching to Java 16+ and changing prepare() to static\n  @TestInstance(TestInstance.Lifecycle.PER_CLASS)\n  class Hotspots {\n\n    private static final String PROJECT_KEY_JAVA_HOTSPOT = \"sample-java-hotspot\";\n\n    @BeforeAll\n    void prepare() {\n      provisionProject(ORCHESTRATOR, PROJECT_KEY_JAVA_HOTSPOT, \"Sample Java Hotspot\");\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/java-sonarlint-with-hotspot.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(PROJECT_KEY_JAVA_HOTSPOT, \"java\", \"SonarLint IT Java Hotspot\");\n\n      // Build project to have bytecode and analyze\n      analyzeMavenProject(\"sample-java-hotspot\", PROJECT_KEY_JAVA_HOTSPOT);\n    }\n\n    @BeforeEach\n    void start() {\n      var globalProps = new HashMap<String, String>();\n      globalProps.put(\"sonar.global.label\", \"It works\");\n    }\n\n    @AfterEach\n    void stop() {\n      adminWsClient.settings().reset(new ResetRequest().setKeys(singletonList(\"sonar.java.file.suffixes\")));\n    }\n\n    @Test\n    // SonarQube should support opening security hotspots\n    @OnlyOnSonarQube(from = \"9.9\")\n    @Disabled\n    void shouldShowHotspotWhenOpenedFromSonarQube() throws InvalidProtocolBufferException {\n      var configScopeId = \"shouldShowHotspotWhenOpenedFromSonarQube\";\n      openBoundConfigurationScope(configScopeId, PROJECT_KEY_JAVA_HOTSPOT, true);\n      waitForAnalysisToBeReady(configScopeId);\n      var hotspotKey = getFirstHotspotKey(PROJECT_KEY_JAVA_HOTSPOT);\n\n      requestOpenHotspotWithParams(PROJECT_KEY_JAVA_HOTSPOT, hotspotKey);\n\n      var captor = ArgumentCaptor.forClass(HotspotDetailsDto.class);\n      verify(client, timeout(1000)).showHotspot(eq(configScopeId), captor.capture());\n\n      var actualHotspot = captor.getValue();\n      assertThat(actualHotspot.getKey()).isEqualTo(hotspotKey);\n      assertThat(actualHotspot.getMessage()).isEqualTo(\"Make sure that this logger's configuration is safe.\");\n      assertThat(actualHotspot.getIdeFilePath()).isEqualTo(Path.of(\"src/main/java/foo/Foo.java\"));\n      assertThat(actualHotspot.getTextRange()).usingRecursiveComparison().isEqualTo(new TextRangeDto(9, 4, 9, 45));\n      assertThat(actualHotspot.getAuthor()).isEmpty();\n      assertThat(actualHotspot.getStatus()).isEqualTo(\"TO_REVIEW\");\n      assertThat(actualHotspot.getResolution()).isNull();\n      assertThat(actualHotspot.getRule().getKey()).isEqualTo(\"java:S4792\");\n    }\n\n    private int requestOpenHotspotWithParams(String projectKey, String hotspotKey) {\n      var request = HttpRequest.newBuilder()\n        .GET()\n        .uri(URI.create(\"http://localhost:\" + serverLauncher.getServer().getEmbeddedServerPort() + \"/sonarlint/api/hotspots/show?server=\"\n          + URLEncoder.encode(ORCHESTRATOR.getServer().getUrl(), StandardCharsets.UTF_8) + \"&project=\"\n          + projectKey + \"&hotspot=\" + hotspotKey))\n        .build();\n      HttpResponse<String> response;\n      try {\n        response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n      } catch (IOException e) {\n        throw new RuntimeException(e);\n      } catch (InterruptedException e) {\n        Thread.currentThread().interrupt();\n        return -1;\n      }\n      return response.statusCode();\n    }\n\n    private String getFirstHotspotKey(String projectKey) throws InvalidProtocolBufferException {\n      var response = ORCHESTRATOR.getServer()\n        .newHttpCall(\"/api/hotspots/search.protobuf\")\n        .setParam(\"projectKey\", projectKey)\n        .setAdminCredentials()\n        .execute();\n      var parser = org.sonarqube.ws.Hotspots.SearchWsResponse.parser();\n      return parser.parseFrom(response.getBody()).getHotspots(0).getKey();\n    }\n\n    @Test\n    void reportHotspots() {\n      var configScopeId = \"reportHotspots\";\n      openBoundConfigurationScope(configScopeId, PROJECT_KEY_JAVA_HOTSPOT, false);\n      waitForAnalysisToBeReady(configScopeId);\n      await().untilAsserted(() -> assertThat(rpcClientLogs.stream().anyMatch(s -> s.getMessage().equals(\"Stored server info\"))).isTrue());\n\n      var rawIssues = analyzeAndAwaitHotspots(backend, client, configScopeId, Path.of(\"projects\", PROJECT_KEY_JAVA_HOTSPOT), \"src/main/java/foo/Foo.java\",\n        \"sonar.java.binaries\", new File(\"projects/sample-java-hotspot/target/classes\").getAbsolutePath());\n\n      assertThat(rawIssues)\n        .extracting(RaisedHotspotDto::getRuleKey, h -> h.getSeverityMode().getLeft().getType())\n        .containsExactly(tuple(javaRuleKey(\"S4792\"), RuleType.SECURITY_HOTSPOT));\n    }\n\n    @Test\n    @OnlyOnSonarQube(from = \"9.9\")\n    void loadHotspotRuleDescription() throws Exception {\n      var configScopeId = \"loadHotspotRuleDescription\";\n      openBoundConfigurationScope(configScopeId, PROJECT_KEY_JAVA_HOTSPOT, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var ruleDetails = backend.getRulesService().getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams(configScopeId, \"java:S4792\", null)).get();\n      assertThat(ruleDetails.details().getName()).isEqualTo(\"Configuring loggers is security-sensitive\");\n      assertThat(ruleDetails.details().getDescription().getRight().getTabs().get(2).getContent().getLeft().getHtmlContent())\n        .contains(\"Check that your production deployment doesn’t have its loggers in \\\"debug\\\" mode\");\n    }\n\n    @Test\n    void shouldMatchServerSecurityHotspots() throws InvalidProtocolBufferException {\n      var configScopeId = \"shouldMatchServerSecurityHotspots\";\n      openBoundConfigurationScope(configScopeId, PROJECT_KEY_JAVA_HOTSPOT, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var hotspotKey = getFirstHotspotKey(PROJECT_KEY_JAVA_HOTSPOT);\n      resolveHotspotAsSafe(adminWsClient, hotspotKey);\n\n      waitAtMost(1, TimeUnit.MINUTES).untilAsserted(() -> {\n        // wait server event\n        var fooIssues = analyzeAndAwaitHotspots(backend, client, configScopeId, Path.of(\"projects\", \"sample-java-hotspot\"), \"src/main/java/foo/Foo.java\");\n\n        assertThat(fooIssues)\n          .extracting(RaisedFindingDto::getRuleKey, RaisedHotspotDto::getStatus)\n          .contains(tuple(\"java:S4792\", HotspotStatus.SAFE));\n      });\n    }\n  }\n\n  @Nested\n  class RuleDescription {\n\n    @Test\n    void shouldFailIfNotAuthenticated() {\n      var configScopeId = \"shouldFailIfNotAuthenticated\";\n      var projectKey = \"noAuth\";\n      var projectName = \"Sample Javascript\";\n      provisionProject(ORCHESTRATOR, projectKey, projectName);\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/java-sonarlint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"java\", \"SonarLint IT Java\");\n\n      backend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(\n        List.of(new ConfigurationScopeDto(configScopeId, null, true, projectName, new BindingConfigurationDto(CONNECTION_ID_WRONG_CREDENTIALS, projectKey, true)))));\n\n      adminWsClient.settings().set(new SetRequest().setKey(\"sonar.forceAuthentication\").setValue(\"true\"));\n      try {\n        var ex = assertThrows(ExecutionException.class,\n          () -> backend.getRulesService().getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams(configScopeId, javaRuleKey(\"S106\"), null)).get());\n        assertThat(ex.getCause()).hasMessage(\"Could not find rule '\" + javaRuleKey(\"S106\") + \"' in plugins loaded from '\" + CONNECTION_ID_WRONG_CREDENTIALS + \"'\");\n      } finally {\n        adminWsClient.settings().reset(new ResetRequest().setKeys(List.of(\"sonar.forceAuthentication\")));\n      }\n    }\n\n    @Test\n    void shouldContainExtendedDescription() throws Exception {\n      var configScopeId = \"shouldContainExtendedDescription\";\n      var projectKey = \"project-with-extended-description\";\n      var projectName = \"Project With Extended Description\";\n      provisionProject(ORCHESTRATOR, projectKey, projectName);\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/java-sonarlint.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"java\", \"SonarLint IT Java\");\n\n      var extendedDescription = \" = Title\\n*my dummy extended description*\";\n\n      WsRequest request = new PostRequest(\"/api/rules/update\")\n        .setParam(\"key\", javaRuleKey(\"S106\"))\n        .setParam(\"markdown_note\", extendedDescription);\n      try (var response = adminWsClient.wsConnector().call(request)) {\n        assertThat(response.code()).isEqualTo(200);\n      }\n\n      String expected;\n      if (ORCHESTRATOR.getServer().version().isGreaterThan(7, 9)) {\n        expected = \"<h1>Title</h1><strong>my dummy extended description</strong>\";\n      } else {\n        // For some reason, there is an extra line break in the generated HTML\n        expected = \"<h1>Title\\n</h1><strong>my dummy extended description</strong>\";\n      }\n\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var ruleDetailsResponse = backend.getRulesService().getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams(configScopeId,\n        javaRuleKey(\"S106\"), null)).get();\n      var ruleDescription = ruleDetailsResponse.details().getDescription();\n      if (ORCHESTRATOR.getServer().version().isGreaterThan(10, 1)) {\n        var ruleTabs = ruleDescription.getRight().getTabs();\n        assertThat(ruleTabs.get(ruleTabs.size() - 1).getContent().getLeft().getHtmlContent()).contains(expected);\n      } else {\n        // no description sections at that time\n        assertThat(ruleDescription.isRight()).isFalse();\n      }\n    }\n\n    @Test\n    void shouldSupportsMarkdownDescription() throws Exception {\n      var configScopeId = \"shouldSupportsMarkdownDescription\";\n      var projectKey = \"project-with-markdown-description\";\n      var projectName = \"Project With Markdown Description\";\n      provisionProject(ORCHESTRATOR, projectKey, projectName);\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/java-sonarlint-with-markdown.xml\"));\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, \"java\", \"SonarLint IT Java Markdown\");\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var ruleDetailsResponse = backend.getRulesService().getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams(configScopeId, \"mycompany-java:markdown\",\n        null)).get();\n\n      assertThat(ruleDetailsResponse.details().getDescription().getLeft().getHtmlContent())\n        .isEqualTo(\"<h1>Title</h1><ul><li>one</li>\\n\"\n          + \"<li>two</li></ul>\");\n    }\n\n    @Test\n    void shouldReturnAllContextsWithOthersSelectedIfNoContextProvided() throws ExecutionException, InterruptedException {\n      var configScopeId = \"shouldReturnAllContextsWithOthersSelectedIfNoContextProvided\";\n      var projectKey = \"sample-java-taint-new-backend\";\n\n      var projectName = \"Java With Taint Vulnerabilities\";\n      provisionProject(ORCHESTRATOR, projectKey, projectName);\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var activeRuleDetailsResponse = backend.getRulesService().getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams(configScopeId, \"javasecurity:S2083\", null)).get();\n\n      var description = activeRuleDetailsResponse.details().getDescription();\n      var serverVersion = ORCHESTRATOR.getServer().version();\n      var extendedDescription = description.getRight();\n      assertThat(extendedDescription.getIntroductionHtmlContent()).isNull();\n      var framework = serverVersion.isGreaterThanOrEquals(2025, 3) ? \"Java I/O API (java_i_o_api)\" : \"Java SE (java_se)\";\n\n      var link = serverVersion.isGreaterThanOrEquals(2026, 2) ? \"OWASP - <a href=\\\"...\"\n        : serverVersion.isGreaterThanOrEquals(10, 4) ? \" OWASP - <a href=...\" : \" <a href=\\\"https:/...\";\n\n      assertThat(extendedDescription.getTabs())\n        .flatExtracting(this::extractTabContent)\n        .containsExactly(\n          \"Why is this an issue?\",\n          \"<p>Path injections occur when an application us...\",\n          \"How can I fix it?\",\n          \"--> \" + framework,\n          \"    <p>The following code is vulnerable to path inj...\",\n          \"--> Others (others)\",\n          \"    <h4>How can I fix it in another component or fr...\",\n          \"More Info\",\n          \"<h3>Standards</h3>\\n\"\n            + \"<ul>\\n\"\n            + \"  <li>\" + link);\n\n      var howToFixTab = extendedDescription.getTabs().get(1);\n      assertThat(howToFixTab.getContent().getRight().getDefaultContextKey()).isEqualTo(\"others\");\n    }\n\n    @Test\n    void shouldReturnAllContextsWithTheMatchingOneSelectedIfContextProvided() throws ExecutionException, InterruptedException {\n      var configScopeId = \"shouldReturnAllContextsWithTheMatchingOneSelectedIfContextProvided\";\n      var projectKey = \"sample-java-taint-rule-context-new-backend\";\n      var projectName = \"Java With Taint Vulnerabilities And Multiple Contexts\";\n      provisionProject(ORCHESTRATOR, projectKey, projectName);\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var activeRuleDetailsResponse = backend.getRulesService().getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams(configScopeId, \"javasecurity:S5131\", \"spring\")).get();\n\n      var description = activeRuleDetailsResponse.details().getDescription();\n      var serverVersion = ORCHESTRATOR.getServer().version();\n\n      var extendedDescription = description.getRight();\n      assertThat(extendedDescription.getIntroductionHtmlContent())\n        .isEqualTo(\"<p>This vulnerability makes it possible to temporarily execute JavaScript code in the context of the application, granting access to the session of\\n\"\n          + \"the victim. This is possible because user-provided data, such as URL parameters, are copied into the HTML body of the HTTP response that is sent back\\n\"\n          + \"to the user.</p>\");\n      var iterator = extendedDescription.getTabs().iterator();\n      iterator.next();\n\n      var documentation = serverVersion.isGreaterThanOrEquals(2026, 2) ? \"OWASP - <a hr...\"\n        : serverVersion.isGreaterThanOrEquals(2025, 3) ? \" OWASP - <a h...\" : \" <a href=\\\"htt...\";\n\n      assertThat(extendedDescription.getTabs())\n        .flatExtracting(this::extractTabContent)\n        .contains(\n          \"Why is this an issue?\",\n          \"<p>Reflected cross-site scripting (XSS) occurs ...\",\n          \"How can I fix it?\",\n          \"--> JSP (jsp)\",\n          \"    <p>The following code is vulnerable to cross-si...\",\n          \"--> Servlet (servlet)\",\n          \"    <p>The following code is vulnerable to cross-si...\",\n          \"--> Spring (spring)\",\n          \"    <p>The following code is vulnerable to cross-si...\",\n          \"--> Thymeleaf (thymeleaf)\",\n          \"    <p>The following code is vulnerable to cross-si...\",\n          \"--> Others (others)\",\n          \"    <h4>How can I fix it in another component or fr...\",\n          \"More Info\",\n          \"<h3>Documentation</h3>\\n\"\n            + \"<ul>\\n\"\n            + \"  <li>\" + documentation);\n\n      var howToFixTab = extendedDescription.getTabs().get(1);\n      assertThat(howToFixTab.getContent().getRight().getDefaultContextKey()).isEqualTo(\"spring\");\n    }\n\n    @Test\n    @OnlyOnSonarQube(from = \"9.9\")\n    void shouldEmulateDescriptionSectionsForHotspotRules() throws ExecutionException, InterruptedException {\n      var configScopeId = \"shouldEmulateDescriptionSectionsForHotspotRules\";\n      var projectKey = \"sample-java-hotspot-new-backend\";\n      var projectName = \"Java With Security Hotspots\";\n      provisionProject(ORCHESTRATOR, projectKey, projectName);\n      openBoundConfigurationScope(configScopeId, projectKey, true);\n      waitForAnalysisToBeReady(configScopeId);\n\n      var activeRuleDetailsResponse = backend.getRulesService()\n        .getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams(configScopeId, \"java:S4792\", null))\n        .get();\n\n      var extendedDescription = activeRuleDetailsResponse.details().getDescription().getRight();\n      assertThat(extendedDescription.getIntroductionHtmlContent()).isNull();\n      var riskTabContentPrefix = ORCHESTRATOR.getServer().version().isGreaterThanOrEquals(10, 4)\n        // SONARJAVA-4739 Rule S4792 is deprecated\n        ? \"<p>This rule is deprecated, and will eventually\"\n        : \"<p>Configuring loggers is security-sensitive.\";\n      assertThat(extendedDescription.getTabs())\n        .hasSize(3)\n        .satisfiesExactlyInAnyOrder(\n          tab -> {\n            assertThat(tab.getTitle()).isEqualTo(\"What's the risk?\");\n            assertThat(tab.getContent().getLeft().getHtmlContent()).startsWith(riskTabContentPrefix);\n          },\n          tab -> {\n            assertThat(tab.getTitle()).isEqualTo(\"Assess the risk\");\n            assertThat(tab.getContent().getLeft().getHtmlContent()).startsWith(\"<h2>Ask Yourself Whether</h2>\\n<ul>\\n  <li>\");\n          },\n          tab -> {\n            assertThat(tab.getTitle()).isEqualTo(\"How can I fix it?\");\n            assertThat(tab.getContent().getLeft().getHtmlContent()).startsWith(\"<h2>Recommended Secure Coding Practices</h2>\");\n          });\n    }\n\n    private List<String> extractTabContent(RuleDescriptionTabDto tab) {\n      List<String> result = new ArrayList<>();\n      result.add(tab.getTitle());\n      if (tab.getContent().isLeft()) {\n        result.add(abbreviate(tab.getContent().getLeft().getHtmlContent(), 50));\n      } else {\n        tab.getContent().getRight().getContextualSections().forEach(s -> {\n          result.add(\"--> \" + s.getDisplayName() + \" (\" + s.getContextKey() + \")\");\n          result.add(\"    \" + abbreviate(s.getHtmlContent(), 50));\n        });\n      }\n      return result;\n    }\n\n  }\n\n  @Nested\n  class SonarProjectService {\n\n    @Test\n    void getProject() throws ExecutionException, InterruptedException {\n      var projectKey = \"sample-project\";\n      provisionProject(ORCHESTRATOR, projectKey, \"Sample Project\");\n\n      var allProjects = backend.getConnectionService()\n        .getAllProjects(\n          new GetAllProjectsParams(new TransientSonarQubeConnectionDto(ORCHESTRATOR.getServer().getUrl(), Either.forRight(new UsernamePasswordDto(SONARLINT_USER, SONARLINT_PWD)))))\n        .get().getSonarProjects();\n\n      assertThat(allProjects).extracting(SonarProjectDto::getKey).doesNotContain(\"non-existing\");\n      assertThat(allProjects).extracting(SonarProjectDto::getKey).contains(projectKey);\n    }\n\n    @Test\n    void downloadAllProjects() throws ExecutionException, InterruptedException {\n      provisionProject(ORCHESTRATOR, \"foo-bar1\", \"Foo1\");\n      provisionProject(ORCHESTRATOR, \"foo-bar2\", \"Foo2\");\n      provisionProject(ORCHESTRATOR, \"foo-bar3\", \"Foo3\");\n      var getAllProjectsParams = new GetAllProjectsParams(new TransientSonarQubeConnectionDto(ORCHESTRATOR.getServer().getUrl(),\n        Either.forRight(new UsernamePasswordDto(SONARLINT_USER, SONARLINT_PWD))));\n      var allProjectsResponse = backend.getConnectionService().getAllProjects(getAllProjectsParams).get();\n      assertThat(allProjectsResponse.getSonarProjects()).extracting(SonarProjectDto::getKey).contains(\"foo-bar1\", \"foo-bar2\", \"foo-bar3\");\n    }\n\n  }\n\n  private String javaRuleKey(String key) {\n    return \"java:\" + key;\n  }\n\n  private void setSettingsMultiValue(@Nullable String moduleKey, String key, String value) {\n    adminWsClient.settings().set(new SetRequest()\n      .setKey(key)\n      .setValues(singletonList(value))\n      .setComponent(moduleKey));\n  }\n\n  private static void analyzeMavenProject(String projectDirName, String projectKey) {\n    analyzeMavenProject(ORCHESTRATOR, projectDirName, Map.of(\"sonar.projectKey\", projectKey));\n  }\n\n  private static void openBoundConfigurationScope(String configScopeId, String projectKey, boolean bindingSuggestionDisabled) {\n    backend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(\n      List.of(new ConfigurationScopeDto(configScopeId, null, true, \"My \" + configScopeId, new BindingConfigurationDto(CONNECTION_ID, projectKey, bindingSuggestionDisabled)))));\n  }\n\n  private static void waitForSync(String configScopeId) {\n    await().atMost(1, TimeUnit.MINUTES).untilAsserted(() -> assertThat(didSynchronizeConfigurationScopes).contains(configScopeId));\n  }\n\n  private static void waitForAnalysisToBeReady(String configScopeId) {\n    await().atMost(1, TimeUnit.MINUTES).untilAsserted(() -> assertThat(analysisReadinessByConfigScopeId).containsEntry(configScopeId, true));\n  }\n\n  private void analyzeProject(String projectDirName, String projectKey, String... properties) {\n    var projectDir = Paths.get(\"projects/\" + projectDirName).toAbsolutePath();\n    var scanner = SonarScanner.create(projectDir.toFile())\n      .setProjectKey(projectKey)\n      .setSourceDirs(\"src\")\n      .setProperties(properties);\n\n    if (ORCHESTRATOR.getServer().version().isGreaterThanOrEquals(10, 2)) {\n      scanner.setProperty(\"sonar.token\", ORCHESTRATOR.getDefaultAdminToken());\n    } else {\n      scanner.setProperty(\"sonar.login\", com.sonar.orchestrator.container.Server.ADMIN_LOGIN)\n        .setProperty(\"sonar.password\", com.sonar.orchestrator.container.Server.ADMIN_PASSWORD);\n    }\n    ORCHESTRATOR.executeBuild(scanner);\n  }\n\n  private static MockSonarLintRpcClientDelegate newDummySonarLintClient() {\n    return new MockSonarLintRpcClientDelegate() {\n\n      @Override\n      public Either<TokenDto, UsernamePasswordDto> getCredentials(String connectionId) throws ConnectionNotFoundException {\n        if (connectionId.equals(CONNECTION_ID)) {\n          return Either.forRight(new UsernamePasswordDto(SONARLINT_USER, SONARLINT_PWD));\n        } else if (connectionId.equals(CONNECTION_ID_WRONG_CREDENTIALS)) {\n          return Either.forRight(new UsernamePasswordDto(\"foo\", \"bar\"));\n        } else {\n          return super.getCredentials(connectionId);\n        }\n      }\n\n      @Override\n      public void didChangeTaintVulnerabilities(String configurationScopeId, Set<UUID> closedTaintVulnerabilityIds, List<TaintVulnerabilityDto> addedTaintVulnerabilities,\n        List<TaintVulnerabilityDto> updatedTaintVulnerabilities) {\n        didChangeTaintVulnerabilitiesEvents\n          .add(new DidChangeTaintVulnerabilitiesParams(configurationScopeId, closedTaintVulnerabilityIds, addedTaintVulnerabilities, updatedTaintVulnerabilities));\n      }\n\n      @Override\n      public void didSynchronizeConfigurationScopes(Set<String> configurationScopeIds) {\n        didSynchronizeConfigurationScopes.addAll(configurationScopeIds);\n      }\n\n      @Override\n      public void didChangeAnalysisReadiness(Set<String> configurationScopeIds, boolean areReadyForAnalysis) {\n        analysisReadinessByConfigScopeId.putAll(configurationScopeIds.stream().collect(Collectors.toMap(Function.identity(), k -> areReadyForAnalysis)));\n      }\n\n      @Override\n      public String matchSonarProjectBranch(String configurationScopeId, String mainBranchName, Set<String> allBranchesNames, SonarLintCancelChecker cancelChecker)\n        throws ConfigScopeNotFoundException {\n        allBranchNamesForProject.addAll(allBranchesNames);\n        return matchedBranchNameForProject == null ? super.matchSonarProjectBranch(configurationScopeId, mainBranchName, allBranchesNames, cancelChecker)\n          : matchedBranchNameForProject;\n      }\n\n      @Override\n      public void log(LogParams params) {\n        System.out.println(params);\n        rpcClientLogs.add(params);\n      }\n    };\n  }\n}\n"
  },
  {
    "path": "its/tests/src/test/java/its/SonarQubeEnterpriseEditionTests.java",
    "content": "/*\n * SonarLint Core - ITs - Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage its;\n\nimport com.sonar.orchestrator.container.Edition;\nimport com.sonar.orchestrator.junit5.OnlyOnSonarQube;\nimport com.sonar.orchestrator.junit5.OrchestratorExtension;\nimport com.sonar.orchestrator.locator.FileLocation;\nimport com.sonar.orchestrator.version.Version;\nimport its.utils.OrchestratorUtils;\nimport its.utils.PluginLocator;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.PipedInputStream;\nimport java.io.PipedOutputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ExecutionException;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport org.apache.commons.io.FileUtils;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestInstance;\nimport org.junit.jupiter.api.condition.DisabledOnOs;\nimport org.junit.jupiter.api.condition.OS;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarqube.ws.client.WsClient;\nimport org.sonarqube.ws.client.permissions.RemoveGroupRequest;\nimport org.sonarqube.ws.client.settings.SetRequest;\nimport org.sonarqube.ws.client.users.CreateRequest;\nimport org.sonarsource.sonarlint.core.rpc.client.ClientJsonRpcLauncher;\nimport org.sonarsource.sonarlint.core.rpc.client.ConnectionNotFoundException;\nimport org.sonarsource.sonarlint.core.rpc.impl.BackendJsonRpcLauncher;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidRemoveConfigurationScopeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarQubeConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.HttpConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.CheckDependencyRiskSupportedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\n\nimport static its.utils.AnalysisUtils.analyzeAndAwaitIssues;\nimport static java.util.Collections.emptyList;\nimport static java.util.Collections.emptySet;\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.APEX;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.C;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.COBOL;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.CPP;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JAVA;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JCL;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.SECRETS;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.TSQL;\n\nclass SonarQubeEnterpriseEditionTests extends AbstractConnectedTests {\n  public static final String CONNECTION_ID = \"orchestrator\";\n  private static final String PROJECT_KEY_COBOL = \"sample-cobol\";\n  private static final String PROJECT_KEY_JCL = \"sample-jcl\";\n  private static final String PROJECT_KEY_C = \"sample-c\";\n  private static final String PROJECT_KEY_TSQL = \"sample-tsql\";\n  private static final String PROJECT_KEY_APEX = \"sample-apex\";\n  private static final String PROJECT_KEY_CUSTOM_SECRETS = \"sample-custom-secrets\";\n  private static final String PROJECT_KEY_MISRA = \"sample-misra\";\n  private static final String PROJECT_KEY_SCA = \"sample-sca\";\n  public static final String SONAR_EARLY_ACCESS_MISRA_ENABLED_PROPERTY_KEY = \"sonar.earlyAccess.misra.enabled\";\n  public static final String SONAR_LEGACY_SCA_FEATURE_ENABLED_PROPERTY_KEY = \"sonar.sca.enabled\";\n  public static final String SONAR_SCA_FEATURE_ENABLED_PROPERTY_KEY = \"sonar.sca.featureEnabled\";\n\n  @RegisterExtension\n  static OrchestratorExtension ORCHESTRATOR = OrchestratorUtils.defaultEnvBuilder()\n    // for versions up to 2025.4\n    .setServerProperty(SONAR_LEGACY_SCA_FEATURE_ENABLED_PROPERTY_KEY, \"true\")\n    .setServerProperty(SONAR_SCA_FEATURE_ENABLED_PROPERTY_KEY, \"true\")\n    .setServerProperty(SONAR_EARLY_ACCESS_MISRA_ENABLED_PROPERTY_KEY, \"true\")\n    .setEdition(Edition.ENTERPRISE)\n    .activateLicense()\n    .restoreProfileAtStartup(FileLocation.ofClasspath(\"/c-sonarlint.xml\"))\n    .restoreProfileAtStartup(FileLocation.ofClasspath(\"/cobol-sonarlint.xml\"))\n    .restoreProfileAtStartup(FileLocation.ofClasspath(\"/jcl-sonarlint.xml\"))\n    .restoreProfileAtStartup(FileLocation.ofClasspath(\"/tsql-sonarlint.xml\"))\n    .restoreProfileAtStartup(FileLocation.ofClasspath(\"/apex-sonarlint.xml\"))\n    .build();\n\n  private static WsClient adminWsClient;\n\n  @TempDir\n  private static Path sonarUserHome;\n\n  private static SonarLintRpcServer backend;\n  private static MockSonarLintRpcClientDelegate client;\n\n  private static final Map<String, Boolean> analysisReadinessByConfigScopeId = new ConcurrentHashMap<>();\n\n  @AfterAll\n  static void stopBackend() throws ExecutionException, InterruptedException {\n    if (backend != null) {\n      backend.shutdown().get();\n    }\n  }\n\n  private static String singlePointOfExitRuleKey;\n\n  @BeforeAll\n  static void prepare() {\n    adminWsClient = newAdminWsClient(ORCHESTRATOR);\n    adminWsClient.settings().set(new SetRequest().setKey(\"sonar.forceAuthentication\").setValue(\"true\"));\n    // we have to set it again via API because the server property value is not returned by api/settings/values\n    adminWsClient.settings().set(new SetRequest().setKey(SONAR_LEGACY_SCA_FEATURE_ENABLED_PROPERTY_KEY).setValue(\"true\"));\n    adminWsClient.settings().set(new SetRequest().setKey(SONAR_EARLY_ACCESS_MISRA_ENABLED_PROPERTY_KEY).setValue(\"true\"));\n\n    removeGroupPermission(\"anyone\", \"scan\");\n\n    adminWsClient.users().create(new CreateRequest().setLogin(SONARLINT_USER).setPassword(SONARLINT_PWD).setName(\"SonarLint\"));\n\n    provisionProject(ORCHESTRATOR, PROJECT_KEY_C, \"Sample C\");\n    provisionProject(ORCHESTRATOR, PROJECT_KEY_COBOL, \"Sample Cobol\");\n    provisionProject(ORCHESTRATOR, PROJECT_KEY_TSQL, \"Sample TSQL\");\n    provisionProject(ORCHESTRATOR, PROJECT_KEY_APEX, \"Sample APEX\");\n    ORCHESTRATOR.getServer().associateProjectToQualityProfile(PROJECT_KEY_C, \"c\", \"SonarLint IT C\");\n    ORCHESTRATOR.getServer().associateProjectToQualityProfile(PROJECT_KEY_COBOL, \"cobol\", \"SonarLint IT Cobol\");\n    ORCHESTRATOR.getServer().associateProjectToQualityProfile(PROJECT_KEY_TSQL, \"tsql\", \"SonarLint IT TSQL\");\n    ORCHESTRATOR.getServer().associateProjectToQualityProfile(PROJECT_KEY_APEX, \"apex\", \"SonarLint IT APEX\");\n    if (ORCHESTRATOR.getServer().version().isGreaterThanOrEquals(10, 4)) {\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/custom-secrets-sonarlint.xml\"));\n      provisionProject(ORCHESTRATOR, PROJECT_KEY_CUSTOM_SECRETS, \"Sample Custom Secrets\");\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(PROJECT_KEY_CUSTOM_SECRETS, \"secrets\", \"SonarLint IT Custom Secrets\");\n    }\n    if (ORCHESTRATOR.getServer().version().isGreaterThanOrEquals(10, 5)) {\n      provisionProject(ORCHESTRATOR, PROJECT_KEY_JCL, \"Sample JCL\");\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(PROJECT_KEY_JCL, \"jcl\", \"SonarLint IT JCL\");\n    }\n\n    if (ORCHESTRATOR.getServer().version().isGreaterThanOrEquals(9, 4)) {\n      singlePointOfExitRuleKey = \"c:S1005\";\n    } else {\n      singlePointOfExitRuleKey = \"c:FunctionSinglePointOfExit\";\n    }\n    if (ORCHESTRATOR.getServer().version().isGreaterThanOrEquals(2025, 4)) {\n      ORCHESTRATOR.getServer().restoreProfile(FileLocation.ofClasspath(\"/cpp-misra-sonarlint.xml\"));\n      provisionProject(ORCHESTRATOR, PROJECT_KEY_MISRA, \"Sample MISRA\");\n      ORCHESTRATOR.getServer().associateProjectToQualityProfile(PROJECT_KEY_MISRA, \"cpp\", \"SonarLint IT MISRA\");\n    }\n  }\n\n  @AfterEach\n  void stop() {\n    analysisReadinessByConfigScopeId.forEach((scopeId, readiness) -> backend.getConfigurationService().didRemoveConfigurationScope(new DidRemoveConfigurationScopeParams(scopeId)));\n    analysisReadinessByConfigScopeId.clear();\n    rpcClientLogs.clear();\n    client.clear();\n  }\n\n  @Nested\n  // TODO Can be removed when switching to Java 16+ and changing prepare() to static\n  @TestInstance(TestInstance.Lifecycle.PER_CLASS)\n  class CommercialAnalyzers {\n\n    @BeforeAll\n    void prepare() throws IOException {\n      startBackend(Map.of());\n    }\n\n    void start(String configScopeId, String projectKey) {\n      bindProject(configScopeId, \"project-\" + projectKey, projectKey);\n    }\n\n    @Test\n    void analysisC_old_build_wrapper_prop(@TempDir File buildWrapperOutput) throws Exception {\n      String configScopeId = \"analysisC_old_build_wrapper_prop\";\n      start(configScopeId, PROJECT_KEY_C);\n\n      var buildWrapperContent = \"{\\\"version\\\":0,\\\"captures\\\":[\" +\n        \"{\" +\n        \"\\\"compiler\\\": \\\"clang\\\",\" +\n        \"\\\"executable\\\": \\\"compiler\\\",\" +\n        \"\\\"stdout\\\": \\\"#define __STDC_VERSION__ 201112L\\n\\\",\" +\n        \"\\\"stderr\\\": \\\"\\\"\" +\n        \"},\" +\n        \"{\" +\n        \"\\\"compiler\\\": \\\"clang\\\",\" +\n        \"\\\"executable\\\": \\\"compiler\\\",\" +\n        \"\\\"stdout\\\": \\\"#define __cplusplus 201703L\\n\\\",\" +\n        \"\\\"stderr\\\": \\\"\\\"\" +\n        \"},\" +\n        \"{\\\"compiler\\\":\\\"clang\\\",\\\"cwd\\\":\\\"\" +\n        Paths.get(\"projects/\" + PROJECT_KEY_C).toAbsolutePath().toString().replace(\"\\\\\", \"\\\\\\\\\") +\n        \"\\\",\\\"executable\\\":\\\"compiler\\\",\\\"cmd\\\":[\\\"cc\\\",\\\"src/file.c\\\"]}]}\";\n\n      FileUtils.write(new File(buildWrapperOutput, \"build-wrapper-dump.json\"), buildWrapperContent, StandardCharsets.UTF_8);\n\n      var issues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", PROJECT_KEY_C), \"src/file.c\", \"sonar.cfamily.build-wrapper-output\", buildWrapperOutput.getAbsolutePath());\n\n      assertThat(issues)\n        .extracting(RaisedIssueDto::getRuleKey)\n        .containsOnly(\"c:S3805\", singlePointOfExitRuleKey);\n    }\n\n    @Test\n    // New property was introduced in SonarCFamily 6.18 part of SQ 8.8\n    @OnlyOnSonarQube(from = \"8.8\")\n    void analysisC_new_prop() {\n      String configScopeId = \"analysisC_new_prop\";\n      start(configScopeId, PROJECT_KEY_C);\n\n      var buildWrapperContent = \"{\\\"version\\\":0,\\\"captures\\\":[\" +\n        \"{\" +\n        \"\\\"compiler\\\": \\\"clang\\\",\" +\n        \"\\\"executable\\\": \\\"compiler\\\",\" +\n        \"\\\"stdout\\\": \\\"#define __STDC_VERSION__ 201112L\\n\\\",\" +\n        \"\\\"stderr\\\": \\\"\\\"\" +\n        \"},\" +\n        \"{\" +\n        \"\\\"compiler\\\": \\\"clang\\\",\" +\n        \"\\\"executable\\\": \\\"compiler\\\",\" +\n        \"\\\"stdout\\\": \\\"#define __cplusplus 201703L\\n\\\",\" +\n        \"\\\"stderr\\\": \\\"\\\"\" +\n        \"},\" +\n        \"{\\\"compiler\\\":\\\"clang\\\",\\\"cwd\\\":\\\"\" +\n        Paths.get(\"projects/\" + PROJECT_KEY_C).toAbsolutePath().toString().replace(\"\\\\\", \"\\\\\\\\\") +\n        \"\\\",\\\"executable\\\":\\\"compiler\\\",\\\"cmd\\\":[\\\"cc\\\",\\\"src/file.c\\\"]}]}\";\n\n      var issues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", PROJECT_KEY_C), \"src/file.c\", \"sonar.cfamily.build-wrapper-content\", buildWrapperContent);\n\n      assertThat(issues)\n        .extracting(RaisedIssueDto::getRuleKey)\n        .containsOnly(\"c:S3805\", singlePointOfExitRuleKey);\n    }\n\n    @Test\n    // the compile commands file is specific to Unix-like platforms, so skip on Windows\n    @DisabledOnOs(OS.WINDOWS)\n    @OnlyOnSonarQube(from = \"2025.4\")\n    void analysisMisraRules(@TempDir Path tmpDir) throws IOException {\n      // early-access flag will be removed in 2025.6, MISRA rules will be available out of the box\n      assumeTrue(ORCHESTRATOR.getServer().version().compareTo(Version.create(\"2025.6\")) < 0);\n      var configScopeId = \"analysisMisraRules\";\n      start(configScopeId, PROJECT_KEY_MISRA);\n      var projectDir = Path.of(\"projects\").resolve(PROJECT_KEY_MISRA);\n      var filePath = projectDir.resolve(\"foo.cpp\").toAbsolutePath().toString();\n      var compileCommandsFilePath = tmpDir.resolve(\"compile_commands.json\");\n      Files.writeString(compileCommandsFilePath, \"\"\"\n        [\n        {\n          \"directory\": \"%s\",\n          \"command\": \"/usr/bin/c++ -g -std=gnu++20 -fdiagnostics-color=always -o CMakeFiles/untitled.dir/foo.cpp.o -c %s\",\n          \"file\": \"%s\",\n          \"output\": \"CMakeFiles/untitled.dir/foo.cpp.o\"\n        }\n        ]\"\"\".formatted(projectDir.toAbsolutePath().toString(), filePath, filePath));\n\n      var issues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", PROJECT_KEY_MISRA), \"foo.cpp\", \"sonar.cfamily.compile-commands\", compileCommandsFilePath.toAbsolutePath().toString());\n\n      assertThat(issues)\n        .extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getPrimaryMessage)\n        .containsOnly(tuple(\"cpp:M23_151\", \"Either add a parameter list or the \\\"&\\\" operator to this use of \\\"f\\\".\"));\n    }\n\n    @Test\n    void analysisCobol() {\n      String configScopeId = \"analysisCobol\";\n      start(configScopeId, PROJECT_KEY_COBOL);\n\n      var issues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", PROJECT_KEY_COBOL), \"src/Custmnt2.cbl\", \"sonar.cobol.file.suffixes\", \"cbl\");\n      assertThat(issues).hasSize(1);\n    }\n\n    @Test\n    @OnlyOnSonarQube(from = \"10.5\")\n    void analysisJCL() {\n      String configScopeId = \"analysisJCL\";\n      start(configScopeId, PROJECT_KEY_JCL);\n\n      var issues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", PROJECT_KEY_JCL), \"GAM0VCDB.jcl\");\n\n      assertThat(issues).hasSize(6);\n    }\n\n    @Test\n    void analysisTsql() {\n      String configScopeId = \"analysisTsql\";\n      start(configScopeId, PROJECT_KEY_TSQL);\n\n      var issues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", PROJECT_KEY_TSQL), \"src/file.tsql\");\n\n      assertThat(issues).hasSize(1);\n    }\n\n    @Test\n    void analysisApex() {\n      String configScopeId = \"analysisApex\";\n      start(configScopeId, PROJECT_KEY_APEX);\n\n      var issues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", PROJECT_KEY_APEX), \"src/file.cls\");\n\n      assertThat(issues).hasSize(1);\n    }\n\n    @Test\n    @OnlyOnSonarQube(from = \"10.4\")\n    void analysisCustomSecrets() {\n      var configScopeId = \"analysisCustomSecrets\";\n      start(configScopeId, PROJECT_KEY_CUSTOM_SECRETS);\n\n      var issues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", PROJECT_KEY_CUSTOM_SECRETS), \"src/file.md\");\n\n      assertThat(issues)\n        .extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getPrimaryMessage)\n        .containsOnly(tuple(\"secrets:custom_secret_rule\", \"User-specified secrets should not be disclosed.\"));\n    }\n  }\n\n  @Nested\n  // TODO Can be removed when switching to Java 16+ and changing prepare() to static\n  @TestInstance(TestInstance.Lifecycle.PER_CLASS)\n  class WithEmbeddedAnalyzer {\n\n    @BeforeAll\n    void setup() throws IOException {\n      startBackend(Map.of(\"cpp\", PluginLocator.getCppPluginPath()));\n    }\n\n    /**\n     * SLCORE-365 c:FunctionSinglePointOfExit has been deprecated in SonarCFamily 6.32.0.44918 (SQ 9.4) so older versions of SQ will\n     * return a QP with rule c:FunctionSinglePointOfExit,\n     * while embedded analyzer contains the new rule key. So SLCORE should do the translation.\n     */\n    @Test\n    void analysisWithDeprecatedRuleKey() {\n      var configScopeId = \"analysisWithDeprecatedRuleKey\";\n      bindProject(configScopeId, \"project-\" + PROJECT_KEY_C, PROJECT_KEY_C);\n      var buildWrapperContent = \"{\\\"version\\\":0,\\\"captures\\\":[\" +\n        \"{\" +\n        \"\\\"compiler\\\": \\\"clang\\\",\" +\n        \"\\\"executable\\\": \\\"compiler\\\",\" +\n        \"\\\"stdout\\\": \\\"#define __STDC_VERSION__ 201112L\\n\\\",\" +\n        \"\\\"stderr\\\": \\\"\\\"\" +\n        \"},\" +\n        \"{\" +\n        \"\\\"compiler\\\": \\\"clang\\\",\" +\n        \"\\\"executable\\\": \\\"compiler\\\",\" +\n        \"\\\"stdout\\\": \\\"#define __cplusplus 201703L\\n\\\",\" +\n        \"\\\"stderr\\\": \\\"\\\"\" +\n        \"},\" +\n        \"{\\\"compiler\\\":\\\"clang\\\",\\\"cwd\\\":\\\"\" +\n        Paths.get(\"projects/\" + PROJECT_KEY_C).toAbsolutePath().toString().replace(\"\\\\\", \"\\\\\\\\\") +\n        \"\\\",\\\"executable\\\":\\\"compiler\\\",\\\"cmd\\\":[\\\"cc\\\",\\\"src/file.c\\\"]}]}\";\n\n      var issues = analyzeAndAwaitIssues(backend, client, configScopeId, Path.of(\"projects\", PROJECT_KEY_C), \"src/file.c\", \"sonar.cfamily.build-wrapper-content\", buildWrapperContent);\n      assertThat(issues)\n        .extracting(RaisedIssueDto::getRuleKey)\n        .containsOnly(\"c:S3805\", \"c:S1005\");\n    }\n  }\n\n  @Nested\n  // TODO Can be removed when switching to Java 16+ and changing prepare() to static\n  @TestInstance(TestInstance.Lifecycle.PER_CLASS)\n  @OnlyOnSonarQube(from = \"2025.4\")\n  class Sca {\n    @BeforeAll\n    void prepare() throws IOException {\n      startBackend(Map.of());\n      provisionProject(ORCHESTRATOR, PROJECT_KEY_SCA, \"Sample SCA\");\n    }\n\n    @Test\n    void sca_feature_should_be_enabled() {\n      var configScopeId = \"sca_check_enabled\";\n      analyzeMavenProject(ORCHESTRATOR, \"sample-sca\", Map.of(\"sonar.projectKey\", PROJECT_KEY_SCA));\n      bindProject(configScopeId, PROJECT_KEY_SCA, PROJECT_KEY_SCA);\n\n      var supportedResponse = backend.getDependencyRiskService().checkSupported(new CheckDependencyRiskSupportedParams(configScopeId)).join();\n\n      assertThat(supportedResponse.isSupported()).isTrue();\n      assertThat(supportedResponse.getReason()).isNull();\n    }\n  }\n\n  private static void bindProject(String configScopeId, String projectName, String projectKey) {\n    backend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(\n      List.of(new ConfigurationScopeDto(configScopeId, null, true, projectName,\n        new BindingConfigurationDto(CONNECTION_ID, projectKey, true)))));\n    await().atMost(30, SECONDS).untilAsserted(() -> assertThat(analysisReadinessByConfigScopeId).containsEntry(configScopeId, true));\n  }\n\n  private static void removeGroupPermission(String groupName, String permission) {\n    adminWsClient.permissions().removeGroup(new RemoveGroupRequest()\n      .setGroupName(groupName)\n      .setPermission(permission));\n  }\n\n  private static MockSonarLintRpcClientDelegate newDummySonarLintClient() {\n    return new MockSonarLintRpcClientDelegate() {\n\n      @Override\n      public Either<TokenDto, UsernamePasswordDto> getCredentials(String connectionId) throws ConnectionNotFoundException {\n        if (connectionId.equals(CONNECTION_ID)) {\n          return Either.forRight(new UsernamePasswordDto(SONARLINT_USER, SONARLINT_PWD));\n        }\n        return super.getCredentials(connectionId);\n      }\n\n      @Override\n      public void didChangeAnalysisReadiness(Set<String> configurationScopeIds, boolean areReadyForAnalysis) {\n        analysisReadinessByConfigScopeId.putAll(configurationScopeIds.stream().collect(Collectors.toMap(Function.identity(), k -> areReadyForAnalysis)));\n      }\n\n      @Override\n      public void log(LogParams params) {\n        System.out.println(params);\n        rpcClientLogs.add(params);\n      }\n    };\n  }\n\n  static void startBackend(Map<String, Path> connectedModeEmbeddedPluginPathsByKey) throws IOException {\n    if (backend != null) {\n      try {\n        backend.shutdown().get();\n      } catch (Exception e) {\n        // ignore\n      }\n    }\n    var clientToServerOutputStream = new PipedOutputStream();\n    var clientToServerInputStream = new PipedInputStream(clientToServerOutputStream);\n\n    var serverToClientOutputStream = new PipedOutputStream();\n    var serverToClientInputStream = new PipedInputStream(serverToClientOutputStream);\n\n    new BackendJsonRpcLauncher(clientToServerInputStream, serverToClientOutputStream);\n    client = newDummySonarLintClient();\n    var clientLauncher = new ClientJsonRpcLauncher(serverToClientInputStream, clientToServerOutputStream, client);\n\n    backend = clientLauncher.getServerProxy();\n    try {\n      var languages = Set.of(JAVA, COBOL, C, CPP, TSQL, APEX, SECRETS, JCL);\n      backend.initialize(\n        new InitializeParams(IT_CLIENT_INFO, IT_TELEMETRY_ATTRIBUTES, HttpConfigurationDto.defaultConfig(), null,\n          Set.of(BackendCapability.FULL_SYNCHRONIZATION, BackendCapability.PROJECT_SYNCHRONIZATION, BackendCapability.SECURITY_HOTSPOTS),\n          sonarUserHome.resolve(\"storage\"),\n          sonarUserHome.resolve(\"work\"),\n          emptySet(),\n          connectedModeEmbeddedPluginPathsByKey, languages, emptySet(), emptySet(),\n          List.of(new SonarQubeConnectionConfigurationDto(CONNECTION_ID, ORCHESTRATOR.getServer().getUrl(), true)), emptyList(),\n          sonarUserHome.toString(),\n          Map.of(), false, null, false, null))\n        .get();\n    } catch (Exception e) {\n      throw new IllegalStateException(\"Cannot initialize the backend\", e);\n    }\n  }\n}\n"
  },
  {
    "path": "its/tests/src/test/java/its/StandaloneTests.java",
    "content": "/*\n * SonarLint Core - ITs - Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage its;\n\nimport its.utils.TestClientInputFile;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.PipedInputStream;\nimport java.io.PipedOutputStream;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ExecutionException;\nimport org.apache.commons.io.FileUtils;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.rpc.client.ClientJsonRpcLauncher;\nimport org.sonarsource.sonarlint.core.rpc.impl.BackendJsonRpcLauncher;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.ClientConstantInfoDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.HttpConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.TelemetryClientConstantAttributesDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleDefinitionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleParamDefinitionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleParamType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.StandaloneRuleConfigDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.UpdateStandaloneRulesConfigurationParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\n\nimport static its.utils.AnalysisUtils.analyzeAndAwaitIssues;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.COBOL;\n\nclass StandaloneTests {\n  public static final TelemetryClientConstantAttributesDto IT_TELEMETRY_ATTRIBUTES = new TelemetryClientConstantAttributesDto(\"SonarLint ITs\", \"SonarLint ITs\",\n    \"1.2.3\", \"4.5.6\", Collections.emptyMap());\n  public static final ClientConstantInfoDto IT_CLIENT_INFO = new ClientConstantInfoDto(\"clientName\", \"integrationTests\");\n  private static final String CONFIG_SCOPE_ID = \"my-ide-project-name\";\n  @TempDir\n  private File baseDir;\n  @TempDir\n  private static Path sonarUserHome;\n\n  private static SonarLintRpcServer backend;\n  private static MockSonarLintRpcClientDelegate client;\n\n  @BeforeAll\n  static void prepare() throws Exception {\n    var clientToServerOutputStream = new PipedOutputStream();\n    var clientToServerInputStream = new PipedInputStream(clientToServerOutputStream);\n\n    var serverToClientOutputStream = new PipedOutputStream();\n    var serverToClientInputStream = new PipedInputStream(serverToClientOutputStream);\n    client = newDummySonarLintClient();\n    new BackendJsonRpcLauncher(clientToServerInputStream, serverToClientOutputStream);\n    var clientLauncher = new ClientJsonRpcLauncher(serverToClientInputStream, clientToServerOutputStream, client);\n    backend = clientLauncher.getServerProxy();\n    try {\n      // The global-extension-plugin reuses the cobol plugin key to be whitelisted\n      var languages = Set.of(COBOL);\n      System.out.println(\"Before backend initialize\");\n      backend.initialize(\n        new InitializeParams(IT_CLIENT_INFO, IT_TELEMETRY_ATTRIBUTES, HttpConfigurationDto.defaultConfig(), null, Set.of(),\n          sonarUserHome.resolve(\"storage\"),\n          sonarUserHome.resolve(\"work\"),\n          Set.of(Paths.get(\"../plugins/global-extension-plugin/target/global-extension-plugin.jar\")), Collections.emptyMap(),\n          languages, Collections.emptySet(), Collections.emptySet(), Collections.emptyList(), Collections.emptyList(), sonarUserHome.toString(), Map.of(),\n          false, null, false, null))\n        .get();\n      backend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, false, CONFIG_SCOPE_ID, null))));\n      System.out.println(\"After backend initialize\");\n    } catch (Exception e) {\n      throw new IllegalStateException(\"Cannot initialize the backend\", e);\n    }\n    Map<String, String> globalProps = new HashMap<>();\n    globalProps.put(\"sonar.global.label\", \"It works\");\n  }\n\n  @AfterEach\n  void cleanup() {\n    client.clear();\n  }\n\n  @Test\n  void checkRuleParameterDeclarations() throws ExecutionException, InterruptedException {\n    var ruleDetails = backend.getRulesService().listAllStandaloneRulesDefinitions().get().getRulesByKey();\n    assertThat(ruleDetails).hasSize(1);\n    var incRule = ruleDetails.entrySet().iterator().next().getValue();\n    assertThat(incRule.getParamsByKey()).hasSize(8);\n    assertRuleHasParam(incRule, \"stringParam\", RuleParamType.STRING);\n    assertRuleHasParam(incRule, \"textParam\", RuleParamType.TEXT);\n    assertRuleHasParam(incRule, \"boolParam\", RuleParamType.BOOLEAN);\n    assertRuleHasParam(incRule, \"intParam\", RuleParamType.INTEGER);\n    assertRuleHasParam(incRule, \"floatParam\", RuleParamType.FLOAT);\n    assertRuleHasParam(incRule, \"enumParam\", RuleParamType.STRING, \"enum1\", \"enum2\", \"enum3\");\n    assertRuleHasParam(incRule, \"enumListParam\", RuleParamType.STRING, \"list1\", \"list2\", \"list3\");\n    assertRuleHasParam(incRule, \"multipleIntegersParam\", RuleParamType.INTEGER, \"80\", \"120\", \"160\");\n  }\n\n  private static void assertRuleHasParam(RuleDefinitionDto rule, String paramKey, RuleParamType expectedType, String... possibleValues) {\n    var param = rule.getParamsByKey().entrySet().stream().filter(p -> p.getKey().equals(paramKey)).findFirst();\n    assertThat(param).isNotEmpty();\n    assertThat(param.get().getValue())\n      .extracting(RuleParamDefinitionDto::getType, RuleParamDefinitionDto::getPossibleValues)\n      .containsExactly(expectedType, Arrays.asList(possibleValues));\n  }\n\n  @Test\n  void globalExtension() throws Exception {\n    prepareInputFile(\"foo.glob\", \"foo\", false);\n\n    var raisedIssues = analyzeAndAwaitIssues(backend, client, CONFIG_SCOPE_ID, baseDir.toPath(), \"foo.glob\", \"sonar.cobol.file.suffixes\", \"glob\");\n    assertThat(raisedIssues).extracting(\"ruleKey\", \"primaryMessage\").containsOnly(\n      tuple(\"global:inc\", \"Issue number 0\"));\n\n    backend.getRulesService().updateStandaloneRulesConfiguration(new UpdateStandaloneRulesConfigurationParams(\n      Map.of(\"global:inc\", new StandaloneRuleConfigDto(true, Map.of(\"stringParam\", \"polop\", \"textParam\", \"\", \"multipleIntegersParam\", \"80,160\", \"unknown\", \"parameter\")))));\n\n    raisedIssues = analyzeAndAwaitIssues(backend, client, CONFIG_SCOPE_ID, baseDir.toPath(), \"foo.glob\", \"sonar.cobol.file.suffixes\", \"glob\");\n    assertThat(raisedIssues).extracting(\"ruleKey\", \"primaryMessage\").containsOnly(\n      tuple(\"global:inc\", \"Issue number 1\"));\n  }\n\n  private ClientInputFile prepareInputFile(String relativePath, String content, final boolean isTest, Charset encoding) throws IOException {\n    final var file = new File(baseDir, relativePath);\n    FileUtils.write(file, content, encoding);\n    return new TestClientInputFile(baseDir.toPath(), file.toPath(), isTest, encoding);\n  }\n\n  private ClientInputFile prepareInputFile(String relativePath, String content, final boolean isTest) throws IOException {\n    return prepareInputFile(relativePath, content, isTest, StandardCharsets.UTF_8);\n  }\n\n  private static MockSonarLintRpcClientDelegate newDummySonarLintClient() {\n    return new MockSonarLintRpcClientDelegate() {\n      @Override\n      public void log(LogParams params) {\n        System.out.println(params);\n      }\n    };\n  }\n\n}\n"
  },
  {
    "path": "its/tests/src/test/java/its/utils/AnalysisUtils.java",
    "content": "/*\n * SonarLint Core - ITs - Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage its.utils;\n\nimport its.MockSonarLintRpcClientDelegate;\nimport java.net.URI;\nimport java.nio.file.Path;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.UUID;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidUpdateFileSystemParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.RaisedHotspotDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\n\npublic class AnalysisUtils {\n\n  private AnalysisUtils() {\n    // utility class\n  }\n\n  public static List<RaisedIssueDto> analyzeAndAwaitIssues(SonarLintRpcServer backend, MockSonarLintRpcClientDelegate client, String configScopeId, Path baseDir,\n    String filePathStr, String... properties) {\n    var filePath = baseDir.resolve(filePathStr);\n    var fileUri = filePath.toAbsolutePath().toUri();\n    backend.getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(\n      List.of(new ClientFileDto(fileUri, Path.of(filePathStr), configScopeId, false, null, filePath.toAbsolutePath(), null, null, true)),\n      List.of(), List.of()));\n    return analyzeAndAwaitIssues(backend, client, configScopeId, List.of(fileUri), toMap(properties));\n  }\n\n  public static List<RaisedIssueDto> analyzeAndAwaitIssues(SonarLintRpcServer backend, MockSonarLintRpcClientDelegate client, String configScopeId, List<URI> fileUris,\n    Map<String, String> extraProperties) {\n    var response = backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(configScopeId, UUID.randomUUID(), fileUris, extraProperties, true)).join();\n    assertThat(response.getFailedAnalysisFiles()).isEmpty();\n    var issues = await().until(() -> client.takeRaisedIssues(configScopeId), Objects::nonNull);\n    return issues.values().stream().flatMap(List::stream).toList();\n  }\n\n  public static List<RaisedHotspotDto> analyzeAndAwaitHotspots(SonarLintRpcServer backend, MockSonarLintRpcClientDelegate client, String configScopeId, Path baseDir,\n    String filePathStr, String... properties) {\n    var filePath = baseDir.resolve(filePathStr);\n    var fileUri = filePath.toAbsolutePath().toUri();\n    backend.getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(\n      List.of(),\n      List.of(new ClientFileDto(fileUri, Path.of(filePathStr), configScopeId, false, null, filePath.toAbsolutePath(), null, null, true)),\n      List.of()));\n    return analyzeAndAwaitHotspots(backend, client, configScopeId, List.of(fileUri), toMap(properties));\n  }\n\n  public static List<RaisedHotspotDto> analyzeAndAwaitHotspots(SonarLintRpcServer backend, MockSonarLintRpcClientDelegate client, String configScopeId, List<URI> fileUris,\n    Map<String, String> extraProperties) {\n    var response = backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(configScopeId, UUID.randomUUID(), fileUris, extraProperties, true)).join();\n    assertThat(response.getFailedAnalysisFiles()).isEmpty();\n    var hotspots = await().until(() -> client.takeRaisedHotspots(configScopeId), Objects::nonNull);\n    return hotspots.values().stream().flatMap(List::stream).toList();\n  }\n\n  private static Map<String, String> toMap(String[] keyValues) {\n    var map = new HashMap<String, String>();\n    for (int i = 0; i < keyValues.length; i += 2) {\n      map.put(keyValues[i], keyValues[i + 1]);\n    }\n    return map;\n  }\n}\n"
  },
  {
    "path": "its/tests/src/test/java/its/utils/ItUtils.java",
    "content": "/*\n * SonarLint Core - ITs - Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage its.utils;\n\npublic class ItUtils {\n\n  public static final String LATEST_RELEASE = \"LATEST_RELEASE\";\n  public static final String SONAR_VERSION = getSonarVersion();\n\n  private ItUtils() {\n    // utility class, forbidden constructor\n  }\n\n  private static String getSonarVersion() {\n    var versionProperty = System.getProperty(\"sonar.runtimeVersion\");\n    return versionProperty != null ? versionProperty : LATEST_RELEASE;\n  }\n\n}\n"
  },
  {
    "path": "its/tests/src/test/java/its/utils/LogOnTestFailure.java",
    "content": "/*\n * SonarLint Core - ITs - Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage its.utils;\n\nimport java.util.Queue;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.api.extension.TestWatcher;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\n\npublic class LogOnTestFailure implements TestWatcher {\n\n  private final Queue<LogParams> logs;\n\n  public LogOnTestFailure(Queue<LogParams> logs) {\n    this.logs = logs;\n  }\n\n  @Override\n  public void testFailed(ExtensionContext context, Throwable cause) {\n    System.out.println(\"Test failed: \" + context.getDisplayName());\n    System.out.println(\"Client RPC logs: \");\n    logs.forEach(l -> System.out.println(\"  \" + l));\n  }\n}\n"
  },
  {
    "path": "its/tests/src/test/java/its/utils/OrchestratorUtils.java",
    "content": "/*\n * SonarLint Core - ITs - Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage its.utils;\n\nimport com.sonar.orchestrator.config.Configuration;\nimport com.sonar.orchestrator.junit5.OrchestratorExtension;\nimport com.sonar.orchestrator.junit5.OrchestratorExtensionBuilder;\n\nimport static its.utils.ItUtils.SONAR_VERSION;\n\npublic class OrchestratorUtils {\n  public static OrchestratorExtensionBuilder defaultEnvBuilder() {\n    var config = Configuration.builder().addEnvVariables().addSystemProperties();\n    var orchestratorJavaHome = System.getenv().get(\"ORCHESTRATOR_JAVA_HOME\");\n    if (orchestratorJavaHome != null) {\n      config.setProperty(\"java.home\", orchestratorJavaHome);\n    }\n    return OrchestratorExtension.builder(config.build())\n      .defaultForceAuthentication()\n      .keepBundledPlugins()\n      .useDefaultAdminCredentialsForBuilds(true)\n      .setSonarVersion(SONAR_VERSION);\n  }\n}\n"
  },
  {
    "path": "its/tests/src/test/java/its/utils/PluginLocator.java",
    "content": "/*\n * SonarLint Core - ITs - Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage its.utils;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Map;\n\npublic class PluginLocator {\n\n  public static Path getCppPluginPath() {\n    return getPluginPath(\"sonar-cfamily-plugin-6.75.1.93101.jar\");\n  }\n\n  public static Path getGoPluginPath() {\n    return getPluginPath(\"sonar-go-plugin-1.31.0.4938.jar\");\n  }\n\n  public static Path getIacPluginPath() {\n    return getPluginPath(\"sonar-iac-plugin-2.2.0.18377.jar\");\n  }\n\n  public static Path getJavascriptPluginPath() {\n    return getPluginPath(\"sonar-javascript-plugin-11.7.1.36988.jar\");\n  }\n\n  public static Map<String, Path> getEmbeddedPluginsByKeyForTests() {\n    return Map.of(\n      \"javascript\", getJavascriptPluginPath(),\n      \"go\", PluginLocator.getGoPluginPath(),\n      \"iac\", PluginLocator.getIacPluginPath());\n  }\n\n  private static Path getPluginPath(String file) {\n    return Paths.get(\"target/plugins/\").resolve(file);\n  }\n\n}\n"
  },
  {
    "path": "its/tests/src/test/java/its/utils/TestClientInputFile.java",
    "content": "/*\n * SonarLint Core - ITs - Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage its.utils;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.nio.charset.Charset;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\n\npublic class TestClientInputFile implements ClientInputFile {\n  private final Path path;\n  private final boolean isTest;\n  private final Charset encoding;\n  private final Path baseDir;\n\n  public TestClientInputFile(final Path baseDir, final Path path, final boolean isTest, final Charset encoding) {\n    this.baseDir = baseDir;\n    this.path = path;\n    this.isTest = isTest;\n    this.encoding = encoding;\n  }\n\n  @Override\n  public String getPath() {\n    return path.toString();\n  }\n\n  @Override\n  public String relativePath() {\n    return baseDir.relativize(path).toString();\n  }\n\n  @Override\n  public boolean isTest() {\n    return isTest;\n  }\n\n  @Override\n  public Charset getCharset() {\n    return encoding;\n  }\n\n  @Override\n  public <G> G getClientObject() {\n    return null;\n  }\n\n  @Override\n  public InputStream inputStream() throws IOException {\n    return Files.newInputStream(path);\n  }\n\n  @Override\n  public String contents() throws IOException {\n    return new String(Files.readAllBytes(path), encoding);\n  }\n\n  @Override\n  public URI uri() {\n    return path.toUri();\n  }\n}\n"
  },
  {
    "path": "its/tests/src/test/resources/apex-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT APEX</name>\n  <language>apex</language>\n  <rules>\n    <rule>\n      <repositoryKey>apex</repositoryKey>\n      <key>S2757</key>\n      <priority>MAJOR</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/c-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT C</name>\n  <language>c</language>\n  <rules>\n    <rule>\n      <repositoryKey>c</repositoryKey>\n      <key>S3805</key>\n      <priority>MAJOR</priority>\n    </rule>\n    <!-- single point of exit -->\n    <rule>\n      <repositoryKey>c</repositoryKey>\n      <key>S1005</key>\n      <priority>MAJOR</priority>\n    </rule>\n    <!-- Keep for pre 6.32.0.44918 sonar-cfamily version -->\n    <!-- single point of exit -->\n    <rule>\n      <repositoryKey>c</repositoryKey>\n      <key>FunctionSinglePointOfExit</key>\n      <priority>MAJOR</priority>\n    </rule>\n    \n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/cloudformation-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT CloudFormation</name>\n  <language>cloudformation</language>\n  <rules>\n    <rule>\n      <repositoryKey>cloudformation</repositoryKey>\n      <key>S6273</key>\n      <priority>MINOR</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/cobol-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT Cobol</name>\n  <language>cobol</language>\n  <rules>\n    <rule>\n      <repositoryKey>cobol</repositoryKey>\n      <key>COBOL.AlterStatementUsageCheck</key>\n      <priority>MAJOR</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/cpp-misra-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT MISRA</name>\n  <language>cpp</language>\n  <rules>\n    <rule>\n      <repositoryKey>cpp</repositoryKey>\n      <key>M23_151</key>\n      <priority>MAJOR</priority>\n    </rule>\n\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/custom-secrets-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT Custom Secrets</name>\n  <language>secrets</language>\n  <rules>\n    <rule>\n      <repositoryKey>secrets</repositoryKey>\n      <key>custom_secret_rule</key>\n      <type>VULNERABILITY</type>\n      <priority>CRITICAL</priority>\n      <name>Crazy secrets should not be used</name>\n      <templateKey>S6784</templateKey>\n      <description>Yay!!</description>\n      <parameters>\n        <parameter>\n          <key>detectionSpecification</key>\n          <value>matching:\n            pattern: \"YaYYaYYaY\"</value>\n        </parameter>\n      </parameters>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/custom-sensor.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT Custom Sensor</name>\n  <language>java</language>\n  <rules>\n    <rule>\n      <!-- One issue per line -->\n      <repositoryKey>foolint</repositoryKey>\n      <key>ExampleRule1</key>\n      <priority>MAJOR</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/dbd-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT DBD</name>\n  <language>py</language>\n  <rules>\n    <rule>\n      <repositoryKey>pythonbugs</repositoryKey>\n      <key>S6466</key>\n      <priority>MAJOR</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/docker-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT Docker</name>\n  <language>docker</language>\n  <rules>\n    <rule>\n      <repositoryKey>docker</repositoryKey>\n      <key>S6476</key>\n      <priority>MAJOR</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/global-extension.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT Global Extension</name>\n  <language>cobol</language>\n  <rules>\n    <rule>\n      <repositoryKey>global</repositoryKey>\n      <key>inc</key>\n      <priority>MAJOR</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/go-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT Go</name>\n  <language>go</language>\n  <rules>\n    <rule>\n      <repositoryKey>go</repositoryKey>\n      <key>S1763</key>\n      <priority>MAJOR</priority>\n    </rule>\n    <rule>\n      <repositoryKey>go</repositoryKey>\n      <key>S5542</key>\n      <priority>CRITICAL</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/java-custom.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT Java Custom</name>\n  <language>java</language>\n  <rules>\n    <rule>\n      <repositoryKey>mycompany-java</repositoryKey>\n      <key>AvoidAnnotation</key>\n      <priority>MAJOR</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/java-sonarlint-with-hotspot.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT Java Hotspot</name>\n  <language>java</language>\n  <rules>\n    <!-- Using hardcoded IP addresses is security-sensitive. Hotspot that should not be visible in SonarLint -->\n    <rule>\n      <repositoryKey>java</repositoryKey>\n      <key>S4792</key>\n      <priority>BLOCKER</priority>\n    </rule>\n\n    <!-- Keep for pre 6.0 sonar-java version -->\n    <rule>\n      <repositoryKey>squid</repositoryKey>\n      <key>S4792</key>\n      <priority>BLOCKER</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/java-sonarlint-with-markdown.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT Java Markdown</name>\n  <language>java</language>\n  <rules>\n    <rule>\n      <repositoryKey>mycompany-java</repositoryKey>\n      <key>markdown</key>\n      <priority>MAJOR</priority>\n    </rule>\n\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/java-sonarlint-with-taint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint Taint Java</name>\n  <language>java</language>\n  <rules>\n    <!-- Database queries should not be vulnerable to injection attacks -->\n    <rule>\n      <repositoryKey>javasecurity</repositoryKey>\n      <key>S3649</key>\n      <priority>MAJOR</priority>\n    </rule>\n\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/java-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT Java</name>\n  <language>java</language>\n  <rules>\n    <rule>\n      <!-- System.out -->\n      <repositoryKey>java</repositoryKey>\n      <key>S106</key>\n      <priority>MAJOR</priority>\n    </rule>\n    <!-- Static private method -->\n    <rule>\n      <repositoryKey>java</repositoryKey>\n      <key>S2325</key>\n      <priority>MAJOR</priority>\n    </rule>\n\n    <!-- Keep for pre 6.0 sonar-java version -->\n    <rule>\n      <!-- System.out -->\n      <repositoryKey>squid</repositoryKey>\n      <key>S106</key>\n      <priority>MAJOR</priority>\n    </rule>\n    <!-- Static private method -->\n    <rule>\n      <repositoryKey>squid</repositoryKey>\n      <key>S2325</key>\n      <priority>MAJOR</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/javascript-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT Javascript</name>\n  <language>js</language>\n  <rules>\n    <rule>\n      <!-- Multiple statements per line, deprecated key -->\n      <repositoryKey>javascript</repositoryKey>\n      <key>OneStatementPerLine</key>\n      <priority>MAJOR</priority>\n    </rule>\n     <rule>\n      <!-- Multiple statements per line -->\n      <repositoryKey>javascript</repositoryKey>\n      <key>S122</key>\n      <priority>MAJOR</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/jcl-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT JCL</name>\n  <language>jcl</language>\n  <rules>\n    <rule>\n      <repositoryKey>jcl</repositoryKey>\n      <key>S6935</key>\n      <priority>MAJOR</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/kotlin-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT Kotlin</name>\n  <language>kotlin</language>\n  <rules>\n    <rule>\n      <repositoryKey>kotlin</repositoryKey>\n      <key>S1656</key>\n      <priority>MAJOR</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/kubernetes-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT Kubernetes</name>\n  <language>kubernetes</language>\n  <rules>\n    <rule>\n      <repositoryKey>kubernetes</repositoryKey>\n      <key>S6433</key>\n      <priority>MAJOR</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/php-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT PHP</name>\n  <language>php</language>\n  <rules>\n    <rule>\n      <!-- TODO -->\n      <repositoryKey>php</repositoryKey>\n      <key>S1135</key>\n      <priority>MAJOR</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/python-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT Python</name>\n  <language>py</language>\n  <rules>\n    <rule>\n      <repositoryKey>python</repositoryKey>\n      <key>PrintStatementUsage</key>\n      <priority>MAJOR</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/ruby-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT Ruby</name>\n  <language>ruby</language>\n  <rules>\n    <rule>\n      <repositoryKey>ruby</repositoryKey>\n      <key>S1656</key>\n      <priority>MAJOR</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/scala-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT Scala</name>\n  <language>scala</language>\n  <rules>\n    <rule>\n      <repositoryKey>scala</repositoryKey>\n      <key>S1656</key>\n      <priority>MAJOR</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/terraform-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT Terraform</name>\n  <language>terraform</language>\n  <rules>\n    <rule>\n      <repositoryKey>terraform</repositoryKey>\n      <key>S6273</key>\n      <priority>MINOR</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/tsql-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT TSQL</name>\n  <language>tsql</language>\n  <rules>\n    <rule>\n      <repositoryKey>tsql</repositoryKey>\n      <!-- \"NULL\" should not be compared directly -->\n      <key>S2527</key>\n      <priority>MAJOR</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/typescript-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT Typescript</name>\n  <language>ts</language>\n  <rules>\n    <rule>\n      <!-- \"===\" and \"!==\" should be used instead of \"==\" and \"!=\" -->\n      <repositoryKey>typescript</repositoryKey>\n      <key>S1440</key>\n      <priority>MAJOR</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "its/tests/src/test/resources/web-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT Web</name>\n  <language>web</language>\n  <rules>\n    <rule>\n      <repositoryKey>Web</repositoryKey>\n      <key>DoctypePresenceCheck</key>\n      <priority>MAJOR</priority>\n    </rule>\n  </rules>\n</profile>"
  },
  {
    "path": "its/tests/src/test/resources/xml-sonarlint.xml",
    "content": "<?xml version='1.0' encoding='UTF-8'?>\n<profile>\n  <name>SonarLint IT XML</name>\n  <language>xml</language>\n  <rules>\n    <rule>\n      <repositoryKey>xml</repositoryKey>\n      <!-- Lines should not be too long (120 char) -->\n      <key>S103</key>\n      <priority>MAJOR</priority>\n    </rule>\n  </rules>\n</profile>\n"
  },
  {
    "path": "medium-tests/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-core-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n  </parent>\n  <artifactId>sonarlint-medium-tests</artifactId>\n  <name>SonarLint Core - Medium Tests</name>\n  <description>Medium tests for SonarLint RPC</description>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.code.findbugs</groupId>\n      <artifactId>jsr305</artifactId>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-rpc-java-client</artifactId>\n      <version>${project.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-rpc-impl</artifactId>\n      <version>${project.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>uk.org.webcompere</groupId>\n      <artifactId>system-stubs-jupiter</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-java-client-utils</artifactId>\n      <version>${project.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-core-test-utils</artifactId>\n      <version>${project.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>ch.qos.logback</groupId>\n      <artifactId>logback-classic</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-engine</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-params</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.assertj</groupId>\n      <artifactId>assertj-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.awaitility</groupId>\n      <artifactId>awaitility</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.codehaus.plexus</groupId>\n      <artifactId>plexus-archiver</artifactId>\n      <version>4.11.0</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.eclipse.jgit</groupId>\n      <artifactId>org.eclipse.jgit</artifactId>\n      <version>${jgit7.version}</version>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-dependency-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>copy-sonar-plugins</id>\n            <phase>generate-test-resources</phase>\n            <goals>\n              <goal>copy</goal>\n            </goals>\n            <configuration>\n              <artifactItems>\n                <artifactItem>\n                  <groupId>org.sonarsource.java</groupId>\n                  <artifactId>sonar-java-plugin</artifactId>\n                  <version>8.25.0.42802</version>\n                  <type>jar</type>\n                </artifactItem>\n                <artifactItem>\n                  <groupId>org.sonarsource.javascript</groupId>\n                  <artifactId>sonar-javascript-plugin</artifactId>\n                  <version>11.8.0.37897</version>\n                  <type>jar</type>\n                </artifactItem>\n                <artifactItem>\n                  <groupId>org.sonarsource.php</groupId>\n                  <artifactId>sonar-php-plugin</artifactId>\n                  <version>3.55.0.15704</version>\n                  <type>jar</type>\n                </artifactItem>\n                <artifactItem>\n                  <groupId>org.sonarsource.python</groupId>\n                  <artifactId>sonar-python-plugin</artifactId>\n                  <version>5.18.0.31561</version>\n                  <type>jar</type>\n                </artifactItem>\n                <artifactItem>\n                  <groupId>org.sonarsource.xml</groupId>\n                  <artifactId>sonar-xml-plugin</artifactId>\n                  <version>2.16.0.7616</version>\n                  <type>jar</type>\n                </artifactItem>\n                <artifactItem>\n                  <groupId>org.sonarsource.text</groupId>\n                  <artifactId>sonar-text-plugin</artifactId>\n                  <version>2.41.0.10709</version>\n                  <type>jar</type>\n                </artifactItem>\n                <artifactItem>\n                  <groupId>org.sonarsource.kotlin</groupId>\n                  <artifactId>sonar-kotlin-plugin</artifactId>\n                  <version>3.4.0.8957</version>\n                  <type>jar</type>\n                </artifactItem>\n              </artifactItems>\n              <outputDirectory>${project.build.directory}/plugins</outputDirectory>\n              <overWriteReleases>false</overWriteReleases>\n              <overWriteSnapshots>true</overWriteSnapshots>\n              <stripVersion>false</stripVersion>\n            </configuration>\n          </execution>\n          <execution>\n            <id>copy-lib-with-comma</id>\n            <phase>generate-test-resources</phase>\n            <goals>\n              <goal>copy</goal>\n            </goals>\n            <configuration>\n              <artifactItems>\n                <artifactItem>\n                  <groupId>com.google.guava</groupId>\n                  <artifactId>guava</artifactId>\n                  <version>10.0.1</version>\n                  <outputDirectory>${project.build.directory}/lib</outputDirectory>\n                  <destFileName>guava,with,comma.jar</destFileName>\n                </artifactItem>\n              </artifactItems>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>com.googlecode.maven-download-plugin</groupId>\n        <artifactId>download-maven-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>download-cfamily-plugin-jar</id>\n            <phase>generate-test-resources</phase>\n            <goals>\n              <goal>wget</goal>\n            </goals>\n            <configuration>\n              <url>https://binaries.sonarsource.com/CommercialDistribution/sonar-cfamily-plugin/sonar-cfamily-plugin-${cfamily.version}.jar</url>\n              <outputDirectory>${project.build.testOutputDirectory}/ondemand</outputDirectory>\n              <outputFileName>sonar-cpp-plugin-test.jar</outputFileName>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n    </plugins>\n  </build>\n  <profiles>\n    <profile>\n      <id>commercial</id>\n      <activation>\n        <property>\n          <name>commercial</name>\n        </property>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-dependency-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>copy-commercial-plugins</id>\n                <phase>validate</phase>\n                <goals>\n                  <goal>copy</goal>\n                </goals>\n                <configuration>\n                  <artifactItems>\n                    <artifactItem>\n                      <groupId>com.sonarsource.cpp</groupId>\n                      <artifactId>sonar-cfamily-plugin</artifactId>\n                      <version>6.78.0.96395</version>\n                      <type>jar</type>\n                    </artifactItem>\n                    <artifactItem>\n                      <groupId>org.sonarsource.java</groupId>\n                      <artifactId>sonar-java-symbolic-execution-plugin</artifactId>\n                      <version>8.19.0.1586</version>\n                      <type>jar</type>\n                    </artifactItem>\n                    <artifactItem>\n                      <groupId>com.sonarsource.dbd</groupId>\n                      <artifactId>sonar-dbd-plugin</artifactId>\n                      <version>2.7.0.20531</version>\n                      <type>jar</type>\n                    </artifactItem>\n                    <artifactItem>\n                      <groupId>com.sonarsource.dbd</groupId>\n                      <artifactId>sonar-dbd-java-frontend-plugin</artifactId>\n                      <version>2.7.0.20531</version>\n                      <type>jar</type>\n                    </artifactItem>\n                  </artifactItems>\n                  <outputDirectory>${project.build.directory}/plugins</outputDirectory>\n                  <overWriteReleases>false</overWriteReleases>\n                  <overWriteSnapshots>true</overWriteSnapshots>\n                  <stripVersion>false</stripVersion>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <!-- Workaround for https://issues.apache.org/jira/projects/MJAR/issues/MJAR-138 -->\n    <profile>\n      <id>conditionally-add-commons-tests-if-tests-not-skipped</id>\n      <activation>\n        <property>\n          <name>maven.test.skip</name>\n          <value>!true</value>\n        </property>\n      </activation>\n      <dependencies>\n        <dependency>\n          <groupId>${project.groupId}</groupId>\n          <artifactId>sonarlint-commons</artifactId>\n          <version>${project.version}</version>\n          <classifier>tests</classifier>\n          <type>test-jar</type>\n          <scope>test</scope>\n        </dependency>\n      </dependencies>\n    </profile>\n    <profile>\n      <activation>\n        <os>\n          <family>Windows</family>\n        </os>\n      </activation>\n      <id>its-windows</id>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>com.googlecode.maven-download-plugin</groupId>\n            <artifactId>download-maven-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>unpack-windows-jre</id>\n                <phase>generate-test-resources</phase>\n                <goals>\n                  <goal>wget</goal>\n                </goals>\n                <configuration>\n                  <skip>${maven.test.skip}</skip>\n                  <url>https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.10%2B7/OpenJDK21U-jre_x64_windows_hotspot_21.0.10_7.zip</url>\n                  <unpack>true</unpack>\n                  <outputDirectory>${project.build.directory}/jre-windows</outputDirectory>\n                  <sha256>a6ac6789e51a2c245f41430c42e72b39ec706a449812fc5e4cbfc55ceed1e5ae</sha256>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-dependency-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>fetch-sloop-win-distribution</id>\n                <phase>generate-test-resources</phase>\n                <goals>\n                  <goal>copy</goal>\n                </goals>\n                <configuration>\n                  <skip>${maven.test.skip}</skip>\n                  <artifactItems>\n                    <artifactItem>\n                      <groupId>org.sonarsource.sonarlint.core</groupId>\n                      <artifactId>sonarlint-backend-cli</artifactId>\n                      <version>${project.version}</version>\n                      <classifier>windows_x64</classifier>\n                      <type>zip</type>\n                    </artifactItem>\n                  </artifactItems>\n                  <overWriteReleases>false</overWriteReleases>\n                  <overWriteSnapshots>true</overWriteSnapshots>\n                  <stripVersion>false</stripVersion>\n                  <outputDirectory>${project.build.directory}</outputDirectory>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <profile>\n      <activation>\n        <os>\n          <family>unix</family>\n        </os>\n      </activation>\n      <id>its-linux</id>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>com.googlecode.maven-download-plugin</groupId>\n            <artifactId>download-maven-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>unpack-linux-jre</id>\n                <phase>generate-test-resources</phase>\n                <goals>\n                  <goal>wget</goal>\n                </goals>\n                <configuration>\n                  <skip>${maven.test.skip}</skip>\n                  <url>https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.10%2B7/OpenJDK21U-jre_x64_linux_hotspot_21.0.10_7.tar.gz</url>\n                  <unpack>true</unpack>\n                  <outputDirectory>${project.build.directory}/jre-linux</outputDirectory>\n                  <sha256>991be6ac6725e76109ecbd131d658f992dcbeacba3a8b4b6650302c8012b52fb</sha256>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-dependency-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>fetch-sloop-linux-distribution</id>\n                <phase>generate-test-resources</phase>\n                <goals>\n                  <goal>copy</goal>\n                </goals>\n                <configuration>\n                  <skip>${maven.test.skip}</skip>\n                  <artifactItems>\n                    <artifactItem>\n                      <groupId>org.sonarsource.sonarlint.core</groupId>\n                      <artifactId>sonarlint-backend-cli</artifactId>\n                      <version>${project.version}</version>\n                      <classifier>linux_x64</classifier>\n                      <type>tar.gz</type>\n                    </artifactItem>\n                  </artifactItems>\n                  <overWriteReleases>false</overWriteReleases>\n                  <overWriteSnapshots>true</overWriteSnapshots>\n                  <stripVersion>false</stripVersion>\n                  <outputDirectory>${project.build.directory}</outputDirectory>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n  </profiles>\n\n</project>\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/BindingSuggestionsMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest;\n\nimport com.github.tomakehurst.wiremock.junit5.WireMockExtension;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.UUID;\nimport java.util.Map;\nimport java.util.concurrent.ExecutionException;\nimport org.eclipse.jgit.api.errors.GitAPIException;\nimport org.eclipse.jgit.transport.URIish;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.mockito.ArgumentCaptor;\nimport org.sonarsource.sonarlint.core.commons.testutils.GitUtils;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.binding.GetBindingSuggestionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidUpdateConnectionsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarQubeConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidUpdateFileSystemParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Components;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.get;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.verify;\nimport static org.sonarsource.sonarlint.core.test.utils.ProtobufUtils.protobufBody;\n\nclass BindingSuggestionsMediumTests {\n\n  public static final String MYSONAR = \"mysonar\";\n  public static final String CONFIG_SCOPE_ID = \"myProject1\";\n  public static final String SLCORE_PROJECT_KEY = \"org.sonarsource.sonarlint:sonarlint-core-parent\";\n  public static final String SLCORE_PROJECT_NAME = \"SonarLint Core\";\n  public static final String PROJECT_ID = \"123e4567-e89b-12d3-a456-426614174000\";\n  public static final String REMOTE_URL = \"git@github.com:myorg/myproject.git\";\n\n  @RegisterExtension\n  static WireMockExtension sonarqubeMock = WireMockExtension.newInstance()\n    .options(wireMockConfig().dynamicPort())\n    .build();\n\n  @BeforeEach\n  void init() {\n    sonarqubeMock.stubFor(get(\"/api/system/status\")\n      .willReturn(aResponse().withStatus(200).withBody(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"10.8\\\",\\\"status\\\": \" +\n        \"\\\"UP\\\"}\")));\n  }\n\n  @SonarLintTest\n  void test_connection_added_should_suggest_binding_with_no_matches(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID, \"My Project 1\")\n      .start(fakeClient);\n    await().untilAsserted(() -> assertThat(fakeClient.getLogMessages()).contains(\"No connections configured, skipping binding suggestions.\"));\n\n    backend.getConnectionService()\n      .didUpdateConnections(new DidUpdateConnectionsParams(List.of(new SonarQubeConnectionConfigurationDto(MYSONAR, sonarqubeMock.baseUrl(), true)), List.of()));\n\n    await().untilAsserted(() -> assertThat(fakeClient.getLogMessages()).contains(\"Found 0 suggestions for configuration scope '\" + CONFIG_SCOPE_ID + \"'\"));\n    verify(fakeClient, never()).suggestBinding(any());\n  }\n\n  @SonarLintTest\n  void test_connection_added_should_suggest_binding_with_matches(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID, \"sonarlint-core\")\n      .start(fakeClient);\n    await().untilAsserted(() -> assertThat(fakeClient.getLogMessages()).contains(\"No connections configured, skipping binding suggestions.\"));\n\n    sonarqubeMock.stubFor(get(\"/api/components/search.protobuf?qualifiers=TRK&ps=500&p=1\")\n      .willReturn(aResponse().withResponseBody(protobufBody(Components.SearchWsResponse.newBuilder()\n        .addComponents(Components.Component.newBuilder()\n          .setKey(SLCORE_PROJECT_KEY)\n          .setName(SLCORE_PROJECT_NAME)\n          .build())\n        .setPaging(Common.Paging.newBuilder().setTotal(1).build())\n        .build()))));\n\n    backend.getConnectionService()\n      .didUpdateConnections(new DidUpdateConnectionsParams(List.of(new SonarQubeConnectionConfigurationDto(MYSONAR, sonarqubeMock.baseUrl(), true)), List.of()));\n\n    ArgumentCaptor<Map<String, List<BindingSuggestionDto>>> suggestionCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(fakeClient, timeout(5000)).suggestBinding(suggestionCaptor.capture());\n\n    var bindingSuggestions = suggestionCaptor.getValue();\n    assertThat(bindingSuggestions).containsOnlyKeys(CONFIG_SCOPE_ID);\n    assertThat(bindingSuggestions.get(CONFIG_SCOPE_ID))\n      .extracting(BindingSuggestionDto::getConnectionId, BindingSuggestionDto::getSonarProjectKey, BindingSuggestionDto::getSonarProjectName,\n        BindingSuggestionDto::isFromSharedConfiguration, BindingSuggestionDto::getOrigin)\n      .containsExactly(tuple(MYSONAR, SLCORE_PROJECT_KEY, SLCORE_PROJECT_NAME, false, BindingSuggestionOrigin.PROJECT_NAME));\n  }\n\n  @SonarLintTest\n  void test_project_added_should_suggest_binding_with_matches(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(MYSONAR, sonarqubeMock.baseUrl())\n      .start(fakeClient);\n\n    sonarqubeMock.stubFor(get(\"/api/components/search.protobuf?qualifiers=TRK&ps=500&p=1\")\n      .willReturn(aResponse().withResponseBody(protobufBody(Components.SearchWsResponse.newBuilder()\n        .addComponents(Components.Component.newBuilder()\n          .setKey(SLCORE_PROJECT_KEY)\n          .setName(SLCORE_PROJECT_NAME)\n          .build())\n        .setPaging(Common.Paging.newBuilder().setTotal(1).build())\n        .build()))));\n\n    backend.getConfigurationService()\n      .didAddConfigurationScopes(\n        new DidAddConfigurationScopesParams(List.of(\n          new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, \"sonarlint-core\",\n            new BindingConfigurationDto(null, null, false)))));\n\n    ArgumentCaptor<Map<String, List<BindingSuggestionDto>>> suggestionCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(fakeClient, timeout(5000)).suggestBinding(suggestionCaptor.capture());\n\n    var bindingSuggestions = suggestionCaptor.getValue();\n    assertThat(bindingSuggestions).containsOnlyKeys(CONFIG_SCOPE_ID);\n    assertThat(bindingSuggestions.get(CONFIG_SCOPE_ID))\n      .extracting(BindingSuggestionDto::getConnectionId, BindingSuggestionDto::getSonarProjectKey, BindingSuggestionDto::getSonarProjectName,\n        BindingSuggestionDto::isFromSharedConfiguration, BindingSuggestionDto::getOrigin)\n      .containsExactly(tuple(MYSONAR, SLCORE_PROJECT_KEY, SLCORE_PROJECT_NAME, false, BindingSuggestionOrigin.PROJECT_NAME));\n  }\n\n  @SonarLintTest\n  void test_uses_binding_clues_when_initializing_fs(SonarLintTestHarness harness, @TempDir Path tmp) throws IOException {\n    var clue = tmp.resolve(\"sonar-project.properties\");\n    Files.writeString(clue, \"sonar.projectKey=\" + SLCORE_PROJECT_KEY + \"\\nsonar.projectName=\" + SLCORE_PROJECT_NAME, StandardCharsets.UTF_8);\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID,\n        List.of(new ClientFileDto(clue.toUri(), Paths.get(\"sonar-project.properties\"), CONFIG_SCOPE_ID, null, StandardCharsets.UTF_8.name(), clue, null, null, true)))\n      .build();\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(MYSONAR, sonarqubeMock.baseUrl())\n      .withSonarQubeConnection(\"another\")\n      .start(fakeClient);\n\n    sonarqubeMock.stubFor(get(\"/api/components/show.protobuf?component=org.sonarsource.sonarlint%3Asonarlint-core-parent\")\n      .willReturn(aResponse().withResponseBody(protobufBody(Components.ShowWsResponse.newBuilder()\n        .setComponent(Components.Component.newBuilder()\n          .setKey(SLCORE_PROJECT_KEY)\n          .setName(SLCORE_PROJECT_NAME)\n          .build())\n        .build()))));\n\n    backend.getConfigurationService()\n      .didAddConfigurationScopes(\n        new DidAddConfigurationScopesParams(List.of(\n          new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, \"sonarlint-core\",\n            new BindingConfigurationDto(null, null, false)))));\n\n    ArgumentCaptor<Map<String, List<BindingSuggestionDto>>> suggestionCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(fakeClient, timeout(5000)).suggestBinding(suggestionCaptor.capture());\n\n    var bindingSuggestions = suggestionCaptor.getValue();\n    assertThat(bindingSuggestions).containsOnlyKeys(CONFIG_SCOPE_ID);\n    assertThat(bindingSuggestions.get(CONFIG_SCOPE_ID))\n      .extracting(BindingSuggestionDto::getConnectionId, BindingSuggestionDto::getSonarProjectKey, BindingSuggestionDto::getSonarProjectName, BindingSuggestionDto::isFromSharedConfiguration)\n      .containsExactly(tuple(MYSONAR, SLCORE_PROJECT_KEY, SLCORE_PROJECT_NAME, false));\n  }\n\n  @SonarLintTest\n  void test_uses_binding_clues_when_updating_fs(SonarLintTestHarness harness, @TempDir Path tmp) throws IOException {\n    var fakeClient = harness.newFakeClient()\n      .build();\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(MYSONAR, sonarqubeMock.baseUrl())\n      .withSonarQubeConnection(\"another\")\n      .start(fakeClient);\n\n    sonarqubeMock.stubFor(get(\"/api/components/show.protobuf?component=org.sonarsource.sonarlint%3Asonarlint-core-parent\")\n      .willReturn(aResponse().withResponseBody(protobufBody(Components.ShowWsResponse.newBuilder()\n        .setComponent(Components.Component.newBuilder()\n          .setKey(SLCORE_PROJECT_KEY)\n          .setName(SLCORE_PROJECT_NAME)\n          .build())\n        .build()))));\n\n    backend.getConfigurationService()\n      .didAddConfigurationScopes(\n        new DidAddConfigurationScopesParams(List.of(\n          new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, \"sonarlint-core\",\n            new BindingConfigurationDto(null, null, false)))));\n\n    // Without binding clue, there is no matching connection/project, so the list of suggestion is empty\n    await().untilAsserted(() -> assertThat(fakeClient.getLogMessages()).contains(\"Found 0 suggestions for configuration scope '\" + CONFIG_SCOPE_ID + \"'\"));\n    verify(fakeClient, never()).suggestBinding(any());\n\n    // Now add a binding clue to the FS\n    var clue = tmp.resolve(\"sonar-project.properties\");\n    Files.writeString(clue, \"sonar.projectKey=\" + SLCORE_PROJECT_KEY + \"\\nsonar.projectName=\" + SLCORE_PROJECT_NAME, StandardCharsets.UTF_8);\n\n    backend.getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(\n      List.of(new ClientFileDto(clue.toUri(), Paths.get(\"sonar-project.properties\"), CONFIG_SCOPE_ID, null, StandardCharsets.UTF_8.name()\n        , clue, null, null, true)),\n      List.of(),\n      List.of()\n    ));\n\n    ArgumentCaptor<Map<String, List<BindingSuggestionDto>>> suggestionCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(fakeClient, timeout(5000)).suggestBinding(suggestionCaptor.capture());\n\n    var bindingSuggestions = suggestionCaptor.getAllValues().get(0);\n    assertThat(bindingSuggestions).containsOnlyKeys(CONFIG_SCOPE_ID);\n    assertThat(bindingSuggestions.get(CONFIG_SCOPE_ID))\n      .extracting(BindingSuggestionDto::getConnectionId, BindingSuggestionDto::getSonarProjectKey, BindingSuggestionDto::getSonarProjectName,\n        BindingSuggestionDto::isFromSharedConfiguration, BindingSuggestionDto::getOrigin)\n      .containsExactly(tuple(MYSONAR, SLCORE_PROJECT_KEY, SLCORE_PROJECT_NAME, false, BindingSuggestionOrigin.PROPERTIES_FILE));\n  }\n\n  @SonarLintTest\n  void test_binding_suggestion_via_service(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(MYSONAR, sonarqubeMock.baseUrl())\n      .start(fakeClient);\n\n    sonarqubeMock.stubFor(get(\"/api/components/search.protobuf?qualifiers=TRK&ps=500&p=1\")\n      .willReturn(aResponse().withResponseBody(protobufBody(Components.SearchWsResponse.newBuilder()\n        .addComponents(Components.Component.newBuilder()\n          .setKey(SLCORE_PROJECT_KEY)\n          .setName(SLCORE_PROJECT_NAME)\n          .build())\n        .setPaging(Common.Paging.newBuilder().setTotal(1).build())\n        .build()))));\n\n    backend.getConfigurationService()\n      .didAddConfigurationScopes(\n        new DidAddConfigurationScopesParams(List.of(\n          new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, \"sonarlint-core\",\n            new BindingConfigurationDto(null, null, false)))));\n\n    // Ignore the automatic binding suggestions\n    verify(fakeClient, timeout(5000)).suggestBinding(any());\n\n    var bindingParamsCompletableFuture = backend.getBindingService().getBindingSuggestions(new GetBindingSuggestionParams(CONFIG_SCOPE_ID, MYSONAR));\n    var bindingSuggestions = bindingParamsCompletableFuture.get().getSuggestions();\n    assertThat(bindingSuggestions).containsOnlyKeys(CONFIG_SCOPE_ID);\n    assertThat(bindingSuggestions.get(CONFIG_SCOPE_ID))\n      .extracting(BindingSuggestionDto::getConnectionId, BindingSuggestionDto::getSonarProjectKey, BindingSuggestionDto::getSonarProjectName, BindingSuggestionDto::isFromSharedConfiguration)\n      .containsExactly(tuple(MYSONAR, SLCORE_PROJECT_KEY, SLCORE_PROJECT_NAME, false));\n  }\n\n  @SonarLintTest\n  void test_uses_binding_clues_from_shared_configuration_when_updating_fs(SonarLintTestHarness harness, @TempDir Path tmp) throws IOException {\n    var fakeClient = harness.newFakeClient()\n      .build();\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(MYSONAR, sonarqubeMock.baseUrl())\n      .withSonarQubeConnection(\"another\")\n      .start(fakeClient);\n\n    sonarqubeMock.stubFor(get(\"/api/components/show.protobuf?component=org.sonarsource.sonarlint%3Asonarlint-core-parent\")\n      .willReturn(aResponse().withResponseBody(protobufBody(Components.ShowWsResponse.newBuilder()\n        .setComponent(Components.Component.newBuilder()\n          .setKey(SLCORE_PROJECT_KEY)\n          .setName(SLCORE_PROJECT_NAME)\n          .build())\n        .build()))));\n\n    backend.getConfigurationService()\n      .didAddConfigurationScopes(\n        new DidAddConfigurationScopesParams(List.of(\n          new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, \"sonarlint-core\",\n            new BindingConfigurationDto(null, null, false)))));\n\n    // Without binding clue, there is no matching connection/project, so the list of suggestion is empty\n    await().untilAsserted(() -> assertThat(fakeClient.getLogMessages()).contains(\"Found 0 suggestions for configuration scope '\" + CONFIG_SCOPE_ID + \"'\"));\n    verify(fakeClient, never()).suggestBinding(any());\n\n    // Now add a binding clue to the FS\n    var sonarlintDir = tmp.resolve(\".sonarlint/\");\n    Files.createDirectory(sonarlintDir);\n    var clue = tmp.resolve(\".sonarlint/connectedMode.json\");\n    Files.writeString(clue, \"{\\\"projectKey\\\": \\\"\" + SLCORE_PROJECT_KEY + \"\\\",\\\"sonarQubeUri\\\": \\\"\" + sonarqubeMock.baseUrl() + \"\\\"}\", StandardCharsets.UTF_8);\n\n    backend.getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(\n      List.of(new ClientFileDto(clue.toUri(), Paths.get(\".sonarlint/connectedMode.json\"), CONFIG_SCOPE_ID, null, StandardCharsets.UTF_8.name(), clue, null, null, true)),\n      List.of(),\n      List.of())\n    );\n\n    ArgumentCaptor<Map<String, List<BindingSuggestionDto>>> suggestionCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(fakeClient, timeout(5000)).suggestBinding(suggestionCaptor.capture());\n\n    var bindingSuggestions = suggestionCaptor.getAllValues().get(0);\n    assertThat(bindingSuggestions).containsOnlyKeys(CONFIG_SCOPE_ID);\n    assertThat(bindingSuggestions.get(CONFIG_SCOPE_ID))\n      .extracting(BindingSuggestionDto::getConnectionId, BindingSuggestionDto::getSonarProjectKey, BindingSuggestionDto::getSonarProjectName,\n        BindingSuggestionDto::isFromSharedConfiguration, BindingSuggestionDto::getOrigin)\n      .containsExactly(tuple(MYSONAR, SLCORE_PROJECT_KEY, SLCORE_PROJECT_NAME, true, BindingSuggestionOrigin.SHARED_CONFIGURATION));\n  }\n\n  @SonarLintTest\n  void should_suggest_binding_by_remote_url_when_no_other_suggestions_found(SonarLintTestHarness harness, @TempDir Path tmp) throws IOException {\n    var gitRepo = tmp.resolve(\"git-repo\");\n    Files.createDirectory(gitRepo);\n\n    try (var git = GitUtils.createRepository(gitRepo)) {\n      git.remoteAdd()\n        .setName(\"origin\")\n        .setUri(new URIish(REMOTE_URL))\n        .call();\n    } catch (Exception e) {\n      throw new RuntimeException(\"Failed to setup Git repository\", e);\n    }\n\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, gitRepo, List.of())\n      .build();\n\n    var scServer = harness.newFakeSonarCloudServer()\n      .withOrganization(\"orgKey\", organization ->\n        organization.withProject(SLCORE_PROJECT_KEY, project -> project\n          .withBranch(\"main\")\n          .withName(SLCORE_PROJECT_NAME)\n          .withId(UUID.fromString(PROJECT_ID))\n          .withBinding(REMOTE_URL)))\n      .start();\n\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(scServer.baseUrl())\n      .withSonarQubeCloudEuRegionApiUri(scServer.baseUrl())\n      .withSonarCloudConnection(MYSONAR, \"orgKey\")\n      .withUnboundConfigScope(CONFIG_SCOPE_ID, \"unmatched-project-name\")\n      .withTelemetryEnabled()\n      .start(fakeClient);\n\n    ArgumentCaptor<Map<String, List<BindingSuggestionDto>>> suggestionCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(fakeClient, timeout(5000)).suggestBinding(suggestionCaptor.capture());\n\n    var bindingSuggestions = suggestionCaptor.getValue();\n    assertThat(backend.telemetryFileContent().getSuggestedRemoteBindingsCount()).isEqualTo(1);\n    assertThat(bindingSuggestions).containsOnlyKeys(CONFIG_SCOPE_ID);\n    assertThat(bindingSuggestions.get(CONFIG_SCOPE_ID))\n      .extracting(BindingSuggestionDto::getConnectionId, BindingSuggestionDto::getSonarProjectKey,\n        BindingSuggestionDto::getSonarProjectName, BindingSuggestionDto::getOrigin)\n      .containsExactly(tuple(MYSONAR, SLCORE_PROJECT_KEY, SLCORE_PROJECT_NAME, BindingSuggestionOrigin.REMOTE_URL));\n  }\n\n  @SonarLintTest\n  void should_suggest_binding_by_remote_url_when_no_other_suggestions_found_for_sonarqube_server(SonarLintTestHarness harness, @TempDir Path tmp) throws IOException {\n    var gitRepo = tmp.resolve(\"git-repo\");\n    Files.createDirectory(gitRepo);\n\n    try (var git = GitUtils.createRepository(gitRepo)) {\n      git.remoteAdd()\n        .setName(\"origin\")\n        .setUri(new URIish(REMOTE_URL))\n        .call();\n    } catch (Exception e) {\n      throw new RuntimeException(\"Failed to setup Git repository\", e);\n    }\n\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, gitRepo, List.of())\n      .build();\n\n    var sqServer = harness.newFakeSonarQubeServer()\n      .withProject(SLCORE_PROJECT_KEY, project -> project\n        .withBranch(\"main\")\n        .withProjectName(SLCORE_PROJECT_NAME)\n        .withId(UUID.fromString(PROJECT_ID))\n        .withBinding(REMOTE_URL))\n      .start();\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(MYSONAR, sqServer.baseUrl())\n      .withUnboundConfigScope(CONFIG_SCOPE_ID, \"unmatched-project-name\")\n      .withTelemetryEnabled()\n      .start(fakeClient);\n\n    ArgumentCaptor<Map<String, List<BindingSuggestionDto>>> suggestionCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(fakeClient, timeout(5000)).suggestBinding(suggestionCaptor.capture());\n\n    var bindingSuggestions = suggestionCaptor.getValue();\n    assertThat(backend.telemetryFileContent().getSuggestedRemoteBindingsCount()).isEqualTo(1);\n    assertThat(bindingSuggestions).containsOnlyKeys(CONFIG_SCOPE_ID);\n    assertThat(bindingSuggestions.get(CONFIG_SCOPE_ID))\n      .extracting(BindingSuggestionDto::getConnectionId, BindingSuggestionDto::getSonarProjectKey,\n        BindingSuggestionDto::getSonarProjectName, BindingSuggestionDto::getOrigin)\n      .containsExactly(tuple(MYSONAR, SLCORE_PROJECT_KEY, SLCORE_PROJECT_NAME, BindingSuggestionOrigin.REMOTE_URL));\n  }\n\n  @SonarLintTest\n  void should_return_empty_when_sqc_project_bindings_is_null(SonarLintTestHarness harness, @TempDir Path tmp) throws IOException, GitAPIException, URISyntaxException {\n    var gitRepo = tmp.resolve(\"git-repo\");\n    Files.createDirectory(gitRepo);\n\n    try (var git = GitUtils.createRepository(gitRepo)) {\n      git.remoteAdd()\n        .setName(\"origin\")\n        .setUri(new URIish(REMOTE_URL))\n        .call();\n    }\n\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, gitRepo, List.of())\n      .build();\n\n    var scServer = harness.newFakeSonarCloudServer()\n      .withOrganization(\"orgKey\", organization ->\n        organization.withProject(SLCORE_PROJECT_KEY, project -> project.withBranch(\"main\")))\n      .withResponseCodes(codes -> codes.withStatusCode(500))\n      .start();\n\n    harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(scServer.baseUrl())\n      .withSonarQubeCloudEuRegionApiUri(scServer.baseUrl())\n      .withSonarCloudConnection(MYSONAR, \"orgKey\")\n      .withUnboundConfigScope(CONFIG_SCOPE_ID, \"unmatched-project-name\")\n      .start(fakeClient);\n\n    await().untilAsserted(() -> assertThat(fakeClient.getLogMessages()).contains(\"Found 0 suggestions for configuration scope '\" + CONFIG_SCOPE_ID + \"'\"));\n    verify(fakeClient, never()).suggestBinding(any());\n  }\n\n  @SonarLintTest\n  void should_return_empty_when_sqs_project_bindings_is_null(SonarLintTestHarness harness, @TempDir Path tmp) throws IOException, GitAPIException, URISyntaxException {\n    var gitRepo = tmp.resolve(\"git-repo\");\n    Files.createDirectory(gitRepo);\n\n    try (var git = GitUtils.createRepository(gitRepo)) {\n      git.remoteAdd()\n        .setName(\"origin\")\n        .setUri(new URIish(REMOTE_URL))\n        .call();\n    }\n\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, gitRepo, List.of())\n      .build();\n\n    var sqServer = harness.newFakeSonarQubeServer()\n      .withProject(SLCORE_PROJECT_KEY, project -> project.withBranch(\"main\"))\n      .withResponseCodes(codes -> codes.withStatusCode(500))\n      .start();\n\n    harness.newBackend()\n      .withSonarQubeConnection(MYSONAR, sqServer.baseUrl())\n      .withUnboundConfigScope(CONFIG_SCOPE_ID, \"unmatched-project-name\")\n      .start(fakeClient);\n\n    await().untilAsserted(() -> assertThat(fakeClient.getLogMessages()).contains(\"Found 0 suggestions for configuration scope '\" + CONFIG_SCOPE_ID + \"'\"));\n    verify(fakeClient, never()).suggestBinding(any());\n  }\n\n  @SonarLintTest\n  void should_return_empty_when_sqc_search_response_is_null(SonarLintTestHarness harness, @TempDir Path tmp) throws IOException, GitAPIException, URISyntaxException {\n    var gitRepo = tmp.resolve(\"git-repo\");\n    Files.createDirectory(gitRepo);\n\n    try (var git = GitUtils.createRepository(gitRepo)) {\n      git.remoteAdd()\n        .setName(\"origin\")\n        .setUri(new URIish(REMOTE_URL))\n        .call();\n    }\n\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, gitRepo, List.of())\n      .build();\n\n    var scServer = harness.newFakeSonarCloudServer()\n      .withOrganization(\"orgKey\", organization ->\n        organization.withProject(SLCORE_PROJECT_KEY, project -> project\n          .withBranch(\"main\")\n          .withName(SLCORE_PROJECT_NAME)\n          .withId(UUID.fromString(PROJECT_ID))\n          .withBinding(REMOTE_URL)))\n      .withResponseCodes(codes -> codes.withStatusCode(500))\n      .start();\n\n    harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(scServer.baseUrl())\n      .withSonarQubeCloudEuRegionApiUri(scServer.baseUrl())\n      .withSonarCloudConnection(MYSONAR, \"orgKey\")\n      .withUnboundConfigScope(CONFIG_SCOPE_ID, \"unmatched-project-name\")\n      .start(fakeClient);\n\n    await().untilAsserted(() -> assertThat(fakeClient.getLogMessages()).contains(\"Found 0 suggestions for configuration scope '\" + CONFIG_SCOPE_ID + \"'\"));\n    verify(fakeClient, never()).suggestBinding(any());\n  }\n\n  @SonarLintTest\n  void should_return_empty_when_sqs_server_project_is_not_present(SonarLintTestHarness harness, @TempDir Path tmp) throws IOException, GitAPIException, URISyntaxException {\n    var gitRepo = tmp.resolve(\"git-repo\");\n    Files.createDirectory(gitRepo);\n\n    try (var git = GitUtils.createRepository(gitRepo)) {\n      git.remoteAdd()\n        .setName(\"origin\")\n        .setUri(new URIish(REMOTE_URL))\n        .call();\n    }\n\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, gitRepo, List.of())\n      .build();\n\n    var sqServer = harness.newFakeSonarQubeServer()\n      .withDopTranslation(dop -> dop\n        .withProjectBinding(REMOTE_URL, PROJECT_ID, SLCORE_PROJECT_KEY))\n      .start();\n\n    harness.newBackend()\n      .withSonarQubeConnection(MYSONAR, sqServer.baseUrl())\n      .withUnboundConfigScope(CONFIG_SCOPE_ID, \"unmatched-project-name\")\n      .start(fakeClient);\n\n    await().untilAsserted(() -> assertThat(fakeClient.getLogMessages()).contains(\"Found 0 suggestions for configuration scope '\" + CONFIG_SCOPE_ID + \"'\"));\n    verify(fakeClient, never()).suggestBinding(any());\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/BindingTelemetryMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest;\n\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingMode;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AcceptedBindingSuggestionParams;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\n\nclass BindingTelemetryMediumTests {\n\n  private static final String CONNECTION_ID = \"connectionId\";\n  private static final String CONFIG_SCOPE_ID = \"scopeId\";\n  private static final String PROJECT_KEY = \"projectKey\";\n\n  @SonarLintTest\n  void should_count_new_binding_from_suggestion_remote_url(SonarLintTestHarness harness) {\n    var backend = setupBackendUnboundWithTelemetry(harness);\n\n    backend.getTelemetryService().acceptedBindingSuggestion(new AcceptedBindingSuggestionParams(\n      BindingSuggestionOrigin.REMOTE_URL));\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getNewBindingsRemoteUrlCount()).isEqualTo(1));\n  }\n\n  @SonarLintTest\n  void should_count_new_binding_from_suggestion_project_name(SonarLintTestHarness harness) {\n    var backend = setupBackendUnboundWithTelemetry(harness);\n\n    backend.getTelemetryService().acceptedBindingSuggestion(new AcceptedBindingSuggestionParams(\n      BindingSuggestionOrigin.PROJECT_NAME));\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getNewBindingsProjectNameCount()).isEqualTo(1));\n  }\n\n  @SonarLintTest\n  void should_count_new_binding_from_suggestion_shared_configuration(SonarLintTestHarness harness) {\n    var backend = setupBackendUnboundWithTelemetry(harness);\n\n    backend.getTelemetryService().acceptedBindingSuggestion(new AcceptedBindingSuggestionParams(\n      BindingSuggestionOrigin.SHARED_CONFIGURATION));\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getNewBindingsSharedConfigurationCount()).isEqualTo(1));\n  }\n\n  @SonarLintTest\n  void should_count_new_binding_from_suggestion_properties_file(SonarLintTestHarness harness) {\n    var backend = setupBackendUnboundWithTelemetry(harness);\n\n    backend.getTelemetryService().acceptedBindingSuggestion(new AcceptedBindingSuggestionParams(\n      BindingSuggestionOrigin.PROPERTIES_FILE));\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getNewBindingsPropertiesFileCount()).isEqualTo(1));\n  }\n\n  @SonarLintTest\n  void should_not_count_when_suggestion_origin_is_missing(SonarLintTestHarness harness) {\n    var backend = setupBackendUnboundWithTelemetry(harness);\n\n    backend.getConfigurationService().didUpdateBinding(new DidUpdateBindingParams(\n      CONFIG_SCOPE_ID,\n      new BindingConfigurationDto(CONNECTION_ID, PROJECT_KEY, true),\n      BindingMode.FROM_SUGGESTION,\n      null\n    ));\n\n    await().untilAsserted(() -> {\n      assertThat(backend.telemetryFileContent().getNewBindingsRemoteUrlCount()).isZero();\n      assertThat(backend.telemetryFileContent().getNewBindingsProjectNameCount()).isZero();\n      assertThat(backend.telemetryFileContent().getNewBindingsSharedConfigurationCount()).isZero();\n      assertThat(backend.telemetryFileContent().getNewBindingsPropertiesFileCount()).isZero();\n    });\n  }\n\n  private SonarLintTestRpcServer setupBackendUnboundWithTelemetry(SonarLintTestHarness harness) {\n    return harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID)\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withTelemetryEnabled()\n      .start();\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/ConnectedHotspotMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest;\n\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.TimeUnit;\nimport org.apache.commons.lang3.StringUtils;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SECURITY_HOTSPOTS;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JAVA;\nimport static utils.AnalysisUtils.createFile;\n\nclass ConnectedHotspotMediumTests {\n\n  @SonarLintTest\n  void should_locally_detect_hotspots_when_connected_to_sonarqube(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, \"Foo.java\", \"\"\"\n      public class Foo {\n        void foo() throws Exception {\n          java.security.MessageDigest md = java.security.MessageDigest.getInstance(\"MD5\");\n        }\n      }\n      \"\"\");\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIG_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var projectKey = \"projectKey\";\n    var branchName = \"main\";\n    var server = harness.newFakeSonarQubeServer(\"9.9\")\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile\n        .withLanguage(\"java\")\n        .withActiveRule(\"java:S4790\", activeRule -> activeRule.withSeverity(org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity.BLOCKER))\n      )\n      .withProject(projectKey,\n        project -> project\n          .withQualityProfile(\"qpKey\")\n          .withBranch(branchName, branch -> branch.withHotspot(\"key\", hotspot -> hotspot.withFilePath(\"Foo.java\"))))\n      .withPlugin(TestPlugin.JAVA)\n      .start();\n    var backend = harness.newBackend()\n      .withBackendCapability(FULL_SYNCHRONIZATION, SECURITY_HOTSPOTS)\n      .withSonarQubeConnection(CONNECTION_ID, server)\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, projectKey)\n      .withExtraEnabledLanguagesInConnectedMode(JAVA)\n      .start(client);\n    client.waitForSynchronization();\n\n    var analysisId = UUID.randomUUID();\n    var analysisResult = backend.getAnalysisService().analyzeFilesAndTrack(\n      new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(inputFile.toUri()), Map.of(), true))\n      .join();\n    assertThat(analysisResult.getFailedAnalysisFiles()).isEmpty();\n    await().atMost(20, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedHotspotsForScopeIdAsList(CONFIG_SCOPE_ID)).isNotEmpty());\n\n    var hotspot = client.getRaisedHotspotsForScopeIdAsList(CONFIG_SCOPE_ID).get(0);\n    assertThat(hotspot.getRuleKey()).isEqualTo(\"java:S4790\");\n    assertThat(hotspot.getSeverityMode().isLeft()).isTrue();\n    assertThat(hotspot.getSeverityMode().getLeft().getSeverity()).isEqualTo(IssueSeverity.BLOCKER);\n  }\n\n  private static final String CONNECTION_ID = StringUtils.repeat(\"very-long-id\", 20);\n  private static final String CONFIG_SCOPE_ID = \"configScopeId\";\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/ConnectedIssueExclusionsMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;\nimport org.sonarsource.sonarlint.core.serverconnection.proto.Sonarlint;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ProtobufFileUtil;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintBackendFixture;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ProjectStoragePaths.encodeForFs;\nimport static utils.AnalysisUtils.analyzeFilesAndGetIssuesAsMap;\nimport static utils.AnalysisUtils.analyzeFilesAndVerifyNoIssues;\nimport static utils.AnalysisUtils.createFile;\n\nclass ConnectedIssueExclusionsMediumTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  @RegisterExtension\n  private static final SonarLintTestHarness harness = new SonarLintTestHarness();\n\n  private static final String FILE1_PATH = \"Foo.java\";\n  private static final String FILE2_PATH = \"Foo2.java\";\n  private static Path inputFile1;\n  private static Path inputFile2;\n  private static final String CONNECTION_ID = \"local\";\n  private static final String JAVA_MODULE_KEY = \"test-project-2\";\n  private static SonarLintTestRpcServer backend;\n  private static SonarLintBackendFixture.FakeSonarLintRpcClient client;\n\n  @BeforeAll\n  static void prepare(@TempDir Path baseDir) {\n    inputFile1 = prepareJavaInputFile1(baseDir, FILE1_PATH);\n    inputFile2 = prepareJavaInputFile2(baseDir, FILE2_PATH);\n\n    client = harness.newFakeClient()\n      .withInitialFs(JAVA_MODULE_KEY, List.of(\n        new ClientFileDto(inputFile1.toUri(), baseDir.relativize(inputFile1), JAVA_MODULE_KEY, false, null, inputFile1, null, null, true),\n        new ClientFileDto(inputFile2.toUri(), baseDir.relativize(inputFile2), JAVA_MODULE_KEY, false, null, inputFile2, null, null, true)\n      ))\n      .build();\n    var server = harness.newFakeSonarQubeServer()\n      .withPlugin(TestPlugin.JAVA)\n      .start();\n    backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server, storage -> storage\n        .withPlugin(TestPlugin.JAVA)\n        .withProject(\"test-project\")\n        .withProject(JAVA_MODULE_KEY, project -> project\n          .withMainBranch(\"main\")\n          .withRuleSet(\"java\", ruleSet -> ruleSet\n            .withActiveRule(\"java:S106\", \"MAJOR\")\n            .withActiveRule(\"java:S1220\", \"MINOR\")\n            .withActiveRule(\"java:S1481\", \"BLOCKER\"))))\n      .withBoundConfigScope(JAVA_MODULE_KEY, CONNECTION_ID, JAVA_MODULE_KEY)\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .start(client);\n  }\n\n  @BeforeEach\n  void restoreConfig() {\n    updateIssueExclusionsSettings(Map.of());\n  }\n\n  @Test\n  void issueExclusions() {\n    var issues = analyzeFilesAndGetIssuesAsMap(List.of(inputFile1.toUri(), inputFile2.toUri()), client, backend, JAVA_MODULE_KEY);\n    var issuesFile1 = issues.get(inputFile1.toUri());\n    var issuesFile2 = issues.get(inputFile2.toUri());\n    assertThat(issuesFile1).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S106\", new TextRangeDto(5, 4, 5, 14)),\n        tuple(\"java:S1220\", null),\n        tuple(\"java:S1481\", new TextRangeDto(3, 8, 3, 9)));\n    assertThat(issuesFile2).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S106\", new TextRangeDto(4, 4, 4, 14)),\n        tuple(\"java:S1220\", null),\n        tuple(\"java:S1481\", new TextRangeDto(3, 8, 3, 9)));\n    client.cleanRaisedIssues();\n\n    updateIssueExclusionsSettings(Map.of(\"sonar.issue.ignore.multicriteria\", \"1\",\n      \"sonar.issue.ignore.multicriteria.1.resourceKey\", \"*\",\n      \"sonar.issue.ignore.multicriteria.1.ruleKey\", \"*\"));\n    analyzeFilesAndVerifyNoIssues(List.of(inputFile1.toUri(), inputFile2.toUri()), client, backend, JAVA_MODULE_KEY);\n\n    updateIssueExclusionsSettings(Map.of(\"sonar.issue.ignore.multicriteria\", \"1\",\n      \"sonar.issue.ignore.multicriteria.1.resourceKey\", \"*\",\n      \"sonar.issue.ignore.multicriteria.1.ruleKey\", \"*S1481\"));\n    issues = analyzeFilesAndGetIssuesAsMap(List.of(inputFile1.toUri(), inputFile2.toUri()), client, backend, JAVA_MODULE_KEY);\n    issuesFile1 = issues.get(inputFile1.toUri());\n    issuesFile2 = issues.get(inputFile2.toUri());\n    assertThat(issuesFile1).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S106\", new TextRangeDto(5, 4, 5, 14)),\n        tuple(\"java:S1220\", null));\n    assertThat(issuesFile2).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S106\", new TextRangeDto(4, 4, 4, 14)),\n        tuple(\"java:S1220\", null));\n    client.cleanRaisedIssues();\n\n    updateIssueExclusionsSettings(Map.of(\"sonar.issue.ignore.multicriteria\", \"1\",\n      \"sonar.issue.ignore.multicriteria.1.resourceKey\", FILE2_PATH,\n      \"sonar.issue.ignore.multicriteria.1.ruleKey\", \"*\"));\n    issues = analyzeFilesAndGetIssuesAsMap(List.of(inputFile1.toUri(), inputFile2.toUri()), client, backend, JAVA_MODULE_KEY);\n    issuesFile1 = issues.get(inputFile1.toUri());\n    issuesFile2 = issues.get(inputFile2.toUri());\n    assertThat(issuesFile1).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S106\", new TextRangeDto(5, 4, 5, 14)),\n        tuple(\"java:S1220\", null),\n        tuple(\"java:S1481\", new TextRangeDto(3, 8, 3, 9)));\n    assertThat(issuesFile2).isNullOrEmpty();\n    client.cleanRaisedIssues();\n\n    updateIssueExclusionsSettings(Map.of(\"sonar.issue.ignore.multicriteria\", \"1,2\",\n      \"sonar.issue.ignore.multicriteria.1.resourceKey\", FILE2_PATH,\n      \"sonar.issue.ignore.multicriteria.1.ruleKey\", \"java:S1481\",\n      \"sonar.issue.ignore.multicriteria.2.resourceKey\", FILE1_PATH,\n      \"sonar.issue.ignore.multicriteria.2.ruleKey\", \"java:S106\"));\n    issues = analyzeFilesAndGetIssuesAsMap(List.of(inputFile1.toUri(), inputFile2.toUri()), client, backend, JAVA_MODULE_KEY);\n    issuesFile1 = issues.get(inputFile1.toUri());\n    issuesFile2 = issues.get(inputFile2.toUri());\n    assertThat(issuesFile1).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S1220\", null),\n        tuple(\"java:S1481\", new TextRangeDto(3, 8, 3, 9)));\n    assertThat(issuesFile2).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S106\", new TextRangeDto(4, 4, 4, 14)),\n        tuple(\"java:S1220\", null));\n  }\n\n  @Test\n  void issueExclusionsByRegexp() {\n    var issues = analyzeFilesAndGetIssuesAsMap(List.of(inputFile1.toUri(), inputFile2.toUri()), client, backend, JAVA_MODULE_KEY);\n    var issuesFile1 = issues.get(inputFile1.toUri());\n    var issuesFile2 = issues.get(inputFile2.toUri());\n    assertThat(issuesFile1).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S106\", new TextRangeDto(5, 4, 5, 14)),\n        tuple(\"java:S1220\", null),\n        tuple(\"java:S1481\", new TextRangeDto(3, 8, 3, 9)));\n    assertThat(issuesFile2).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S106\", new TextRangeDto(4, 4, 4, 14)),\n        tuple(\"java:S1220\", null),\n        tuple(\"java:S1481\", new TextRangeDto(3, 8, 3, 9)));\n    client.cleanRaisedIssues();\n\n    updateIssueExclusionsSettings(Map.of(\"sonar.issue.ignore.allfile\", \"1\",\n      \"sonar.issue.ignore.allfile.1.fileRegexp\", \"NOSL1\"));\n    issues = analyzeFilesAndGetIssuesAsMap(List.of(inputFile1.toUri(), inputFile2.toUri()), client, backend, JAVA_MODULE_KEY);\n    issuesFile1 = issues.get(inputFile1.toUri());\n    issuesFile2 = issues.get(inputFile2.toUri());\n    assertThat(issuesFile2).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S106\", new TextRangeDto(4, 4, 4, 14)),\n        tuple(\"java:S1220\", null),\n        tuple(\"java:S1481\", new TextRangeDto(3, 8, 3, 9)));\n    assertThat(issuesFile1).isNullOrEmpty();\n    client.cleanRaisedIssues();\n\n    updateIssueExclusionsSettings(Map.of(\"sonar.issue.ignore.allfile\", \"1\",\n      \"sonar.issue.ignore.allfile.1.fileRegexp\", \"NOSL(1|2)\"));\n    analyzeFilesAndVerifyNoIssues(List.of(inputFile1.toUri(), inputFile2.toUri()), client, backend, JAVA_MODULE_KEY);\n  }\n\n  @Test\n  void issueExclusionsByBlock() {\n    var issues = analyzeFilesAndGetIssuesAsMap(List.of(inputFile1.toUri(), inputFile2.toUri()), client, backend, JAVA_MODULE_KEY);\n    var issuesFile1 = issues.get(inputFile1.toUri());\n    var issuesFile2 = issues.get(inputFile2.toUri());\n    assertThat(issuesFile1).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S106\", new TextRangeDto(5, 4, 5, 14)),\n        tuple(\"java:S1220\", null),\n        tuple(\"java:S1481\", new TextRangeDto(3, 8, 3, 9)));\n    assertThat(issuesFile2).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S106\", new TextRangeDto(4, 4, 4, 14)),\n        tuple(\"java:S1220\", null),\n        tuple(\"java:S1481\", new TextRangeDto(3, 8, 3, 9)));\n\n    updateIssueExclusionsSettings(Map.of(\"sonar.issue.ignore.block\", \"1\",\n      \"sonar.issue.ignore.block.1.beginBlockRegexp\", \"SON.*-OFF\",\n      \"sonar.issue.ignore.block.1.endBlockRegexp\", \"SON.*-ON\"));\n    issues = analyzeFilesAndGetIssuesAsMap(List.of(inputFile1.toUri(), inputFile2.toUri()), client, backend, JAVA_MODULE_KEY);\n    issuesFile1 = issues.get(inputFile1.toUri());\n    issuesFile2 = issues.get(inputFile2.toUri());\n    assertThat(issuesFile1).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S1220\", null),\n        tuple(\"java:S1481\", new TextRangeDto(3, 8, 3, 9)));\n    assertThat(issuesFile2).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S106\", new TextRangeDto(4, 4, 4, 14)),\n        tuple(\"java:S1220\", null),\n        tuple(\"java:S1481\", new TextRangeDto(3, 8, 3, 9)));\n  }\n\n  @Test\n  void issueInclusions() {\n    updateIssueExclusionsSettings(Map.of(\"sonar.issue.enforce.multicriteria\", \"1\",\n      \"sonar.issue.enforce.multicriteria.1.resourceKey\", \"Foo*.java\",\n      \"sonar.issue.enforce.multicriteria.1.ruleKey\", \"*\"));\n    var issues = analyzeFilesAndGetIssuesAsMap(List.of(inputFile1.toUri(), inputFile2.toUri()), client, backend, JAVA_MODULE_KEY);\n    var issuesFile1 = issues.get(inputFile1.toUri());\n    var issuesFile2 = issues.get(inputFile2.toUri());\n    assertThat(issuesFile1).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S106\", new TextRangeDto(5, 4, 5, 14)),\n        tuple(\"java:S1220\", null),\n        tuple(\"java:S1481\", new TextRangeDto(3, 8, 3, 9)));\n    assertThat(issuesFile2).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S106\", new TextRangeDto(4, 4, 4, 14)),\n        tuple(\"java:S1220\", null),\n        tuple(\"java:S1481\", new TextRangeDto(3, 8, 3, 9)));\n\n    updateIssueExclusionsSettings(Map.of(\"sonar.issue.enforce.multicriteria\", \"1\",\n      \"sonar.issue.enforce.multicriteria.1.resourceKey\", FILE2_PATH,\n      \"sonar.issue.enforce.multicriteria.1.ruleKey\", \"*S1481\"));\n    issues = analyzeFilesAndGetIssuesAsMap(List.of(inputFile1.toUri(), inputFile2.toUri()), client, backend, JAVA_MODULE_KEY);\n    issuesFile1 = issues.get(inputFile1.toUri());\n    issuesFile2 = issues.get(inputFile2.toUri());\n    assertThat(issuesFile1).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S106\", new TextRangeDto(5, 4, 5, 14)),\n        tuple(\"java:S1220\", null));\n    assertThat(issuesFile2).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S106\", new TextRangeDto(4, 4, 4, 14)),\n        tuple(\"java:S1220\", null),\n        tuple(\"java:S1481\", new TextRangeDto(3, 8, 3, 9)));\n\n    updateIssueExclusionsSettings(Map.of(\"sonar.issue.enforce.multicriteria\", \"1\",\n      \"sonar.issue.enforce.multicriteria.1.resourceKey\", FILE2_PATH,\n      \"sonar.issue.enforce.multicriteria.1.ruleKey\", \"*\"));\n    issues = analyzeFilesAndGetIssuesAsMap(List.of(inputFile1.toUri(), inputFile2.toUri()), client, backend, JAVA_MODULE_KEY);\n    issuesFile1 = issues.get(inputFile1.toUri());\n    issuesFile2 = issues.get(inputFile2.toUri());\n    assertThat(issuesFile1).isEmpty();\n    assertThat(issuesFile2).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S106\", new TextRangeDto(4, 4, 4, 14)),\n        tuple(\"java:S1220\", null),\n        tuple(\"java:S1481\", new TextRangeDto(3, 8, 3, 9)));\n\n    updateIssueExclusionsSettings(Map.of(\"sonar.issue.enforce.multicriteria\", \"1,2\",\n      \"sonar.issue.enforce.multicriteria.1.resourceKey\", FILE2_PATH,\n      \"sonar.issue.enforce.multicriteria.1.ruleKey\", \"java:S1481\",\n      \"sonar.issue.enforce.multicriteria.2.resourceKey\", FILE1_PATH,\n      \"sonar.issue.enforce.multicriteria.2.ruleKey\", \"java:S106\"));\n    issues = analyzeFilesAndGetIssuesAsMap(List.of(inputFile1.toUri(), inputFile2.toUri()), client, backend, JAVA_MODULE_KEY);\n    issuesFile1 = issues.get(inputFile1.toUri());\n    issuesFile2 = issues.get(inputFile2.toUri());\n    assertThat(issuesFile1).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S106\", new TextRangeDto(5, 4, 5, 14)),\n        tuple(\"java:S1220\", null));\n    assertThat(issuesFile2).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S1220\", null),\n        tuple(\"java:S1481\", new TextRangeDto(3, 8, 3, 9)));\n  }\n\n  private void updateIssueExclusionsSettings(Map<String, String> settings) {\n    var analyzerConfigPath = backend.getStorageRoot().resolve(encodeForFs(CONNECTION_ID)).resolve(\"projects\").resolve(encodeForFs(JAVA_MODULE_KEY)).resolve(\"analyzer_config.pb\");\n    Sonarlint.AnalyzerConfiguration.Builder analyzerConfigurationBuilder;\n    if (Files.exists(analyzerConfigPath)) {\n      analyzerConfigurationBuilder = Sonarlint.AnalyzerConfiguration.newBuilder(ProtobufFileUtil.readFile(analyzerConfigPath, Sonarlint.AnalyzerConfiguration.parser()));\n      analyzerConfigurationBuilder.clearSettings();\n    } else {\n      analyzerConfigurationBuilder = Sonarlint.AnalyzerConfiguration.newBuilder();\n    }\n    analyzerConfigurationBuilder.putAllSettings(settings);\n    ProtobufFileUtil.writeToFile(analyzerConfigurationBuilder.build(), analyzerConfigPath);\n  }\n\n  private static Path prepareJavaInputFile1(Path baseDir, String filePath) {\n    return createFile(baseDir, filePath, \"\"\"\n      /*NOSL1*/ public class Foo {\n        public void foo() {\n          int x;\n          // SONAR-OFF\n          System.out.println(\"Foo\");\n          // SONAR-ON\n        }\n      }\"\"\");\n  }\n\n  private static Path prepareJavaInputFile2(Path baseDir, String filePath) {\n    return createFile(baseDir, filePath, \"\"\"\n      /*NOSL2*/ public class Foo2 {\n        public void foo() {\n          int x;\n          System.out.println(\"Foo\");\n        }\n      }\"\"\");\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/ConnectedIssueMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest;\n\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.TimeUnit;\nimport org.assertj.core.api.Assertions;\nimport org.assertj.core.api.AssertionsForInterfaceTypes;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.GetEffectiveIssueDetailsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.DATAFLOW_BUG_DETECTION;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SECURITY_HOTSPOTS;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JAVA;\nimport static utils.AnalysisUtils.analyzeFileAndGetIssues;\nimport static utils.AnalysisUtils.createFile;\n\nclass ConnectedIssueMediumTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  private static final String CONFIG_SCOPE_ID = \"configScopeId\";\n  private static final String CONNECTION_ID = \"connectionId\";\n  // commercial plugins might not be available\n  // (if you pass -Dcommercial to maven, a profile will be activated that downloads the commercial plugins)\n  private static final boolean COMMERCIAL_ENABLED = System.getProperty(\"commercial\") != null;\n\n  @SonarLintTest\n  void simpleJavaBound(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, \"Foo.java\", \"\"\"\n      public class Foo {\n        public void foo() {\n          int x;\n          System.out.println(\"Foo\");\n          System.out.println(\"Foo\"); //NOSONAR\n        }\n      }\"\"\");\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIG_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var projectKey = \"projectKey\";\n    var branchName = \"main\";\n    var server = harness.newFakeSonarQubeServer()\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile\n        .withLanguage(\"java\")\n        .withActiveRule(\"java:S106\", activeRule -> activeRule\n          .withSeverity(IssueSeverity.MAJOR))\n        .withActiveRule(\"java:S1220\", activeRule -> activeRule\n          .withSeverity(IssueSeverity.MINOR))\n        .withActiveRule(\"java:S1481\", activeRule -> activeRule\n          .withSeverity(IssueSeverity.BLOCKER)))\n      .withProject(projectKey,\n        project -> project\n          .withQualityProfile(\"qpKey\")\n          .withBranch(branchName))\n      .withPlugin(TestPlugin.JAVA)\n      .start();\n    var backend = harness.newBackend()\n      .withBackendCapability(FULL_SYNCHRONIZATION, SECURITY_HOTSPOTS)\n      .withSonarQubeConnection(CONNECTION_ID, server)\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, projectKey)\n      .withExtraEnabledLanguagesInConnectedMode(JAVA)\n      .start(client);\n    client.waitForSynchronization();\n\n    var analysisId = UUID.randomUUID();\n    var analysisResult = backend.getAnalysisService().analyzeFilesAndTrack(\n      new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(inputFile.toUri()), Map.of(), true))\n      .join();\n    assertThat(analysisResult.getFailedAnalysisFiles()).isEmpty();\n    await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID)).isNotEmpty());\n\n    var issues = client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID);\n    assertThat(issues).extracting(\"ruleKey\", \"severityMode.right.cleanCodeAttribute\")\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S106\", CleanCodeAttribute.MODULAR),\n        tuple(\"java:S1220\", CleanCodeAttribute.MODULAR),\n        tuple(\"java:S1481\", CleanCodeAttribute.CLEAR));\n  }\n\n  @SonarLintTest\n  void simpleJavaSymbolicEngineBound(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    assumeTrue(COMMERCIAL_ENABLED);\n    var inputFile = createFile(baseDir, \"Foo.java\",\n      \"\"\"\n        public class Foo {\n          public void foo() {\n            boolean a = true;\n            if (a) {\n              System.out.println( \"Hello World!\" );\n            }\n          }\n        }\"\"\");\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIG_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var server = harness.newFakeSonarQubeServer()\n      .withPlugin(TestPlugin.JAVA)\n      .withPlugin(TestPlugin.JAVA_SE)\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile.withLanguage(\"java\"))\n      .withProject(\"projectKey\")\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server, storage -> storage\n        .withPlugins(TestPlugin.JAVA, TestPlugin.JAVA_SE)\n        .withProject(\"projectKey\", project -> project.withRuleSet(\"java\", ruleSet -> ruleSet\n          .withActiveRule(\"java:S2589\", \"BLOCKER\")\n          .withActiveRule(\"java:S106\", \"BLOCKER\"))\n          .withMainBranch(\"main\")))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .start(client);\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIG_SCOPE_ID);\n\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .contains(\n        tuple(\"java:S2589\", new TextRangeDto(4, 8, 4, 9)),\n        tuple(\"java:S106\", new TextRangeDto(5, 6, 5, 16)));\n  }\n\n  @SonarLintTest\n  void simpleJavaSymbolicEngineBoundWithDbd(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    assumeTrue(COMMERCIAL_ENABLED);\n    var inputFile = createFile(baseDir, \"Foo.java\",\n      \"\"\"\n        public class Foo {\n          public void foo() {\n            boolean a = true;\n            if (a) {\n              System.out.println( \"Hello World!\" );\n            }\n          }\n        }\"\"\");\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIG_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var server = harness.newFakeSonarQubeServer()\n      .withPlugin(TestPlugin.JAVA)\n      .withPlugin(TestPlugin.JAVA_SE)\n      .withPlugin(TestPlugin.PYTHON)\n      .withPlugin(TestPlugin.DBD)\n      .withPlugin(TestPlugin.DBD_JAVA)\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile.withLanguage(\"java\"))\n      .withProject(\"projectKey\")\n      .start();\n    var backend = harness.newBackend()\n      .withBackendCapability(DATAFLOW_BUG_DETECTION)\n      .withSonarQubeConnection(\"connectionId\", server, storage -> storage\n        .withPlugins(TestPlugin.PYTHON, TestPlugin.JAVA, TestPlugin.JAVA_SE, TestPlugin.DBD, TestPlugin.DBD_JAVA)\n        .withProject(\"projectKey\", project -> project.withRuleSet(\"java\", ruleSet -> ruleSet\n          .withActiveRule(\"java:S2589\", \"BLOCKER\")\n          .withActiveRule(\"java:S106\", \"BLOCKER\"))\n          .withMainBranch(\"main\")))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withEnabledLanguageInStandaloneMode(Language.PYTHON)\n      .start(client);\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIG_SCOPE_ID);\n\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .contains(\n        tuple(\"java:S2589\", new TextRangeDto(4, 8, 4, 9)),\n        tuple(\"java:S106\", new TextRangeDto(5, 6, 5, 16)));\n  }\n\n  @SonarLintTest\n  void emptyQPJava(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, \"Foo.java\", \"\"\"\n      public class Foo {\n        public void foo() {\n          int x;\n          System.out.println(\"Foo\");\n          System.out.println(\"Foo\"); //NOSONAR\n        }\n      }\"\"\");\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIG_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var projectKey = \"projectKey\";\n    var branchName = \"main\";\n    var server = harness.newFakeSonarQubeServer()\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile.withLanguage(\"java\"))\n      .withProject(projectKey,\n        project -> project\n          .withQualityProfile(\"qpKey\")\n          .withBranch(branchName))\n      .withPlugin(TestPlugin.JAVA)\n      .start();\n    var backend = harness.newBackend()\n      .withBackendCapability(FULL_SYNCHRONIZATION, SECURITY_HOTSPOTS)\n      .withSonarQubeConnection(CONNECTION_ID, server)\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, projectKey)\n      .withExtraEnabledLanguagesInConnectedMode(JAVA)\n      .start(client);\n    client.waitForSynchronization();\n\n    var analysisId = UUID.randomUUID();\n    var analysisResult = backend.getAnalysisService().analyzeFilesAndTrack(\n      new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(inputFile.toUri()), Map.of(), true))\n      .join();\n    assertThat(analysisResult.getFailedAnalysisFiles()).isEmpty();\n    await().during(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID)).isEmpty());\n  }\n\n  @SonarLintTest\n  void it_should_get_hotspot_details(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var fileFoo = createFile(baseDir, \"Foo.java\", \"\"\"\n      public class Foo {\n        void foo() throws Exception {\n          java.security.MessageDigest md = java.security.MessageDigest.getInstance(\"MD5\");\n        }\n      }\"\"\");\n    var fileFooUri = fileFoo.toUri();\n\n    var connectionId = \"connectionId\";\n    var branchName = \"branchName\";\n    var projectKey = \"projectKey\";\n    var serverWithHotspots = harness.newFakeSonarQubeServer(\"10.4\")\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile.withLanguage(\"java\")\n        .withActiveRule(\"java:S4790\", activeRule -> activeRule.withSeverity(IssueSeverity.BLOCKER)))\n      .withProject(projectKey,\n        project -> project\n          .withQualityProfile(\"qpKey\")\n          .withBranch(branchName))\n      .start();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(\n        new ClientFileDto(fileFooUri, baseDir.relativize(fileFoo), CONFIG_SCOPE_ID, false, null, fileFoo, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withBackendCapability(FULL_SYNCHRONIZATION, SECURITY_HOTSPOTS)\n      .withSonarQubeConnection(connectionId, serverWithHotspots,\n        storage -> storage.withServerVersion(\"10.4\").withProject(projectKey,\n          project -> project.withRuleSet(\"java\", ruleSet -> ruleSet.withActiveRule(\"java:S4790\", \"BLOCKER\"))))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, connectionId, projectKey)\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n    await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> Assertions.assertThat(client.getSynchronizedConfigScopeIds()).contains(CONFIG_SCOPE_ID));\n\n    var analysisId = UUID.randomUUID();\n\n    backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(fileFooUri), Map.of(), true))\n      .join();\n    await().atMost(20, TimeUnit.SECONDS).untilAsserted(() -> AssertionsForInterfaceTypes.assertThat(client.getRaisedHotspotsForScopeIdAsList(CONFIG_SCOPE_ID)).hasSize(1));\n\n    var raisedIssuesForFoo = client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID).get(fileFooUri);\n    var raisedHotspotsForFoo = client.getRaisedHotspotsForScopeId(CONFIG_SCOPE_ID).get(fileFooUri);\n    AssertionsForInterfaceTypes.assertThat(raisedIssuesForFoo).isEmpty();\n    AssertionsForInterfaceTypes.assertThat(raisedHotspotsForFoo).hasSize(1);\n\n    var result = backend.getIssueService().getEffectiveIssueDetails(new GetEffectiveIssueDetailsParams(CONFIG_SCOPE_ID, raisedHotspotsForFoo.get(0).getId())).join();\n    assertThat(result.getDetails()).isNotNull();\n\n    serverWithHotspots.shutdown();\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/ConnectedStorageProblemsMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static utils.AnalysisUtils.createFile;\n\nclass ConnectedStorageProblemsMediumTests {\n  private static final String CONNECTION_ID = \"localhost\";\n  private static final String CONFIG_SCOPE_ID = \"myProject\";\n\n  @SonarLintTest\n  void corrupted_plugin_should_not_prevent_startup(SonarLintTestHarness harness, @TempDir Path baseDir) throws Exception {\n    var inputFile = createFile(baseDir, \"Foo.java\", \"\"\"\n      public class Foo {\n        public void foo() {\n          int x;\n          System.out.println(\"Foo\");\n          System.out.println(\"Foo\"); //NOSONAR\n        }\n      }\"\"\");\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIG_SCOPE_ID, false, null, inputFile, null, null, true)\n      ))\n      .build();\n    var server = harness.newFakeSonarQubeServer()\n      .withPlugin(TestPlugin.JAVA)\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server, storage -> storage\n        .withPlugin(TestPlugin.JAVA)\n        .withPlugin(SonarPlugin.JS.getKey(), createFakePlugin(), \"hash\")\n        .withProject(CONFIG_SCOPE_ID,\n          project -> project.withMainBranch(\"main\").withRuleSet(SonarLanguage.JS.getSonarLanguageKey(),\n            ruleSet -> ruleSet.withActiveRule(\"java:S106\", \"BLOCKER\"))))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, CONFIG_SCOPE_ID)\n      .withEnabledLanguageInStandaloneMode(org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JAVA)\n      .withEnabledLanguageInStandaloneMode(org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JS).start(client);\n\n    backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(),\n      List.of(inputFile.toUri()), Map.of(), false)).get();\n\n    await().untilAsserted(() -> assertThat(client.getLogMessages()).contains(\"Execute Sensor: JavaSensor\"));\n  }\n\n  private static Path createFakePlugin() {\n    try {\n      return Files.createTempFile(\"fakePlugin\", \"jar\");\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/ConnectionSetupMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest;\n\nimport com.google.gson.JsonArray;\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.net.URI;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\nimport java.util.concurrent.ExecutionException;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;\nimport org.jetbrains.annotations.NotNull;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth.HelpGenerateUserTokenParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth.HelpGenerateUserTokenResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarQubeConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.validate.ValidateConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport org.sonarsource.sonarlint.core.test.utils.server.ServerFixture;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.verify;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.EMBEDDED_SERVER;\n\nclass ConnectionSetupMediumTests {\n\n  public static final String EXPECTED_MESSAGE = \"UTM parameters should match regular expression: [a-z0-9\\\\-]+\";\n\n  @SonarLintTest\n  void it_should_open_the_sonarlint_auth_url_for_sonarcloud(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var fakeClient = harness.newFakeClient().build();\n    ServerFixture.Server scServer = harness.newFakeSonarCloudServer()\n      .start();\n\n    var backend = harness.newBackend().withBackendCapability(EMBEDDED_SERVER).withClientName(\"ClientName\").withSonarCloudConnection(\"connectionId\").start(fakeClient);\n\n    var futureResponse = backend.getConnectionService().helpGenerateUserToken(new HelpGenerateUserTokenParams(scServer.baseUrl(), null));\n\n    verify(fakeClient, timeout(3000)).openUrlInBrowser(URI.create(scServer.url(\"/sonarlint/auth?ideName=ClientName&port=\" + backend.getEmbeddedServerPort())).toURL());\n\n    var request = HttpRequest.newBuilder()\n      .uri(URI.create(\"http://localhost:\" + backend.getEmbeddedServerPort() + \"/sonarlint/api/token\"))\n      .header(\"Content-Type\", \"application/json; charset=utf-8\")\n      .header(\"Origin\", scServer.baseUrl())\n      .POST(HttpRequest.BodyPublishers.ofString(\"{\\\"token\\\": \\\"value\\\"}\")).build();\n    var response = java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n    assertThat(response.statusCode()).isEqualTo(200);\n\n    assertThat(futureResponse)\n      .succeedsWithin(Duration.ofSeconds(3))\n      .extracting(HelpGenerateUserTokenResponse::getToken)\n      .isEqualTo(\"value\");\n  }\n\n  @SonarLintTest\n  void it_should_open_token_generation_url_for_sonarcloud_with_tracking(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var fakeClient = harness.newFakeClient().build();\n    ServerFixture.Server scServer = harness.newFakeSonarCloudServer()\n      .start();\n\n    var backend = harness.newBackend().withBackendCapability(EMBEDDED_SERVER).withClientName(\"ClientName\").withSonarCloudConnection(\"connectionId\").start(fakeClient);\n\n    var futureResponse = backend.getConnectionService().helpGenerateUserToken(\n      new HelpGenerateUserTokenParams(scServer.baseUrl(),\n        new HelpGenerateUserTokenParams.Utm(\"referral\", \"sq-ide-product-name\", \"create-new-sqc-connection\", \"generate-token-2\")));\n\n    verify(fakeClient, timeout(3000)).openUrlInBrowser(\n      URI.create(scServer.url(\"/sonarlint/auth?ideName=ClientName&port=\" + backend.getEmbeddedServerPort() +\n        \"&utm_medium=referral&utm_source=sq-ide-product-name&utm_content=create-new-sqc-connection&utm_term=generate-token-2\")).toURL());\n\n    var request = HttpRequest.newBuilder()\n      .uri(URI.create(\"http://localhost:\" + backend.getEmbeddedServerPort() + \"/sonarlint/api/token\"))\n      .header(\"Content-Type\", \"application/json; charset=utf-8\")\n      .header(\"Origin\", scServer.baseUrl())\n      .POST(HttpRequest.BodyPublishers.ofString(\"{\\\"token\\\": \\\"value\\\"}\")).build();\n    var response = java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n    assertThat(response.statusCode()).isEqualTo(200);\n\n    assertThat(futureResponse)\n      .succeedsWithin(Duration.ofSeconds(3))\n      .extracting(HelpGenerateUserTokenResponse::getToken)\n      .isEqualTo(\"value\");\n  }\n\n  @SonarLintTest\n  void it_should_throw_invalid_parameters_for_invalid_utm_params(SonarLintTestHarness harness) {\n    var backend = harness.newBackend().withBackendCapability(EMBEDDED_SERVER).withClientName(\"ClientName\").withSonarCloudConnection(\"connectionId\").start();\n\n    var futureResponse = backend.getConnectionService().helpGenerateUserToken(\n      new HelpGenerateUserTokenParams(\"irrelevant\",\n        new HelpGenerateUserTokenParams.Utm(\"referral\", \"sq-ide-product-name\", \"create-new-sqc-connection\", \"INVALID\")));\n\n    assertThat(futureResponse)\n      .failsWithin(Duration.ofSeconds(3))\n      .withThrowableOfType(ExecutionException.class)\n      .withCauseInstanceOf(ResponseErrorException.class)\n      .havingCause()\n      .withMessage(EXPECTED_MESSAGE)\n      .extracting(\"responseError.message\", \"responseError.code\", \"responseError.data\")\n      .containsOnly(EXPECTED_MESSAGE, ResponseErrorCode.InvalidParams.getValue(), utmArray());\n  }\n\n  @NotNull\n  private static JsonArray utmArray() {\n    JsonArray arrayOfInvalidParameters = new JsonArray();\n    arrayOfInvalidParameters.add(\"utm_term\");\n    return arrayOfInvalidParameters;\n  }\n\n  @SonarLintTest\n  void it_should_open_the_sonarlint_auth_url_for_sonarqube_9_7_plus(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var fakeClient = harness.newFakeClient().build();\n    var server = harness.newFakeSonarQubeServer(\"9.9\").start();\n    var backend = harness.newBackend().withBackendCapability(EMBEDDED_SERVER).withClientName(\"ClientName\").withSonarQubeConnection(\"connectionId\", server).start(fakeClient);\n\n    var futureResponse = backend.getConnectionService().helpGenerateUserToken(new HelpGenerateUserTokenParams(server.baseUrl(), null));\n\n    verify(fakeClient, timeout(3000)).openUrlInBrowser(URI.create(server.url(\"/sonarlint/auth?ideName=ClientName&port=\" + backend.getEmbeddedServerPort())).toURL());\n\n    var request = HttpRequest.newBuilder()\n      .uri(URI.create(\"http://localhost:\" + backend.getEmbeddedServerPort() + \"/sonarlint/api/token\"))\n      .header(\"Content-Type\", \"application/json; charset=utf-8\")\n      .header(\"Origin\", server.baseUrl())\n      .POST(HttpRequest.BodyPublishers.ofString(\"{\\\"token\\\": \\\"value\\\"}\")).build();\n    var response = java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n    assertThat(response.statusCode()).isEqualTo(200);\n\n    assertThat(futureResponse)\n      .succeedsWithin(Duration.ofSeconds(3))\n      .extracting(HelpGenerateUserTokenResponse::getToken)\n      .isEqualTo(\"value\");\n  }\n\n  @SonarLintTest\n  void it_should_reject_tokens_from_missing_origin(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var fakeClient = harness.newFakeClient().build();\n    var server = harness.newFakeSonarQubeServer(\"9.9\").start();\n    var backend = harness.newBackend().withBackendCapability(EMBEDDED_SERVER).withClientName(\"ClientName\").withSonarQubeConnection(\"connectionId\", server).start(fakeClient);\n\n    backend.getConnectionService().helpGenerateUserToken(new HelpGenerateUserTokenParams(server.baseUrl(), null));\n\n    verify(fakeClient, timeout(3000)).openUrlInBrowser(URI.create(server.url(\"/sonarlint/auth?ideName=ClientName&port=\" + backend.getEmbeddedServerPort())).toURL());\n\n    var request = HttpRequest.newBuilder()\n      .uri(URI.create(\"http://localhost:\" + backend.getEmbeddedServerPort() + \"/sonarlint/api/token\"))\n      .header(\"Content-Type\", \"application/json; charset=utf-8\")\n      .POST(HttpRequest.BodyPublishers.ofString(\"{\\\"token\\\": \\\"value\\\"}\")).build();\n    var response = java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n    assertThat(response.statusCode()).isEqualTo(400);\n  }\n\n  @SonarLintTest\n  void it_should_reject_tokens_from_unexpected_origin(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var fakeClient = harness.newFakeClient().build();\n    var server = harness.newFakeSonarQubeServer(\"9.9\").start();\n    var backend = harness.newBackend().withBackendCapability(EMBEDDED_SERVER).withClientName(\"ClientName\").withSonarQubeConnection(\"connectionId\", server).start(fakeClient);\n\n    backend.getConnectionService().helpGenerateUserToken(new HelpGenerateUserTokenParams(server.baseUrl(), null));\n\n    verify(fakeClient, timeout(3000)).openUrlInBrowser(URI.create(server.url(\"/sonarlint/auth?ideName=ClientName&port=\" + backend.getEmbeddedServerPort())).toURL());\n\n    var request = HttpRequest.newBuilder()\n      .uri(URI.create(\"http://localhost:\" + backend.getEmbeddedServerPort() + \"/sonarlint/api/token\"))\n      .header(\"Content-Type\", \"application/json; charset=utf-8\")\n      .header(\"Origin\", \"https://unexpected.sonar\")\n      .POST(HttpRequest.BodyPublishers.ofString(\"{\\\"token\\\": \\\"value\\\"}\")).build();\n    var response = java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n    assertThat(response.statusCode()).isEqualTo(403);\n  }\n\n  @SonarLintTest\n  void it_should_open_the_sonarlint_auth_url_without_port_for_sonarqube_9_7_plus_when_server_is_not_started(SonarLintTestHarness harness) throws MalformedURLException {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend().withClientName(\"ClientName\").start(fakeClient);\n    var server = harness.newFakeSonarQubeServer(\"9.9\").start();\n\n    var futureResponse = backend.getConnectionService().helpGenerateUserToken(new HelpGenerateUserTokenParams(server.baseUrl(), null));\n\n    assertThat(futureResponse)\n      .succeedsWithin(Duration.ofSeconds(3))\n      .extracting(HelpGenerateUserTokenResponse::getToken)\n      .isNull();\n    verify(fakeClient, timeout(3000)).openUrlInBrowser(URI.create(server.url(\"/sonarlint/auth?ideName=ClientName\")).toURL());\n  }\n\n  @SonarLintTest\n  void it_should_reject_incoming_user_token_with_wrong_http_method(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend().withBackendCapability(EMBEDDED_SERVER).start(fakeClient);\n\n    var request = HttpRequest.newBuilder()\n      .uri(URI.create(\"http://localhost:\" + backend.getEmbeddedServerPort() + \"/sonarlint/api/token\"))\n      .GET().build();\n    var response = java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n\n    assertThat(response.statusCode()).isEqualTo(400);\n  }\n\n  @SonarLintTest\n  void it_should_reject_incoming_user_token_with_wrong_body(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend().withBackendCapability(EMBEDDED_SERVER).start(fakeClient);\n\n    var request = HttpRequest.newBuilder()\n      .uri(URI.create(\"http://localhost:\" + backend.getEmbeddedServerPort() + \"/sonarlint/api/token\"))\n      .header(\"Content-Type\", \"application/json; charset=utf-8\")\n      .POST(HttpRequest.BodyPublishers.ofString(\"{\\\"token\\\":\")).build();\n    var response = java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n\n    assertThat(response.statusCode()).isEqualTo(400);\n  }\n\n  @SonarLintTest\n  void it_should_fail_to_validate_connection_if_host_not_found(SonarLintTestHarness harness) throws InterruptedException, ExecutionException {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend().start(fakeClient);\n\n    var connectionResponse = backend.getConnectionService()\n      .validateConnection(new ValidateConnectionParams(new TransientSonarQubeConnectionDto(\"http://notexists\", Either.forRight(new UsernamePasswordDto(\"foo\", \"bar\"))))).get();\n\n    assertThat(connectionResponse.isSuccess()).isFalse();\n    assertThat(connectionResponse.getMessage()).contains(\"Request failed\");\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/ConnectionSuggestionMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest;\n\nimport com.github.tomakehurst.wiremock.junit5.WireMockExtension;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Stream;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.mockito.ArgumentCaptor;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidUpdateFileSystemParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.ConnectionSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.GetConnectionSuggestionsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.verify;\n\nclass ConnectionSuggestionMediumTests {\n\n  public static final String CONFIG_SCOPE_ID = \"myProject1\";\n  public static final String SLCORE_PROJECT_KEY = \"org.sonarsource.sonarlint:sonarlint-core-parent\";\n  public static final String ORGANIZATION = \"Org\";\n\n  @RegisterExtension\n  static WireMockExtension sonarqubeMock = WireMockExtension.newInstance()\n    .options(wireMockConfig().dynamicPort())\n    .build();\n\n  @SonarLintTest\n  void should_suggest_sonarqube_connection_when_initializing_fs(SonarLintTestHarness harness, @TempDir Path tmp) throws IOException {\n    var sonarlintDir = tmp.resolve(\".sonarlint/\");\n    Files.createDirectory(sonarlintDir);\n    var clue = tmp.resolve(\".sonarlint/connectedMode.json\");\n    Files.writeString(clue, \"{\\\"projectKey\\\": \\\"\" + SLCORE_PROJECT_KEY + \"\\\",\\\"sonarQubeUri\\\": \\\"\" + sonarqubeMock.baseUrl() + \"\\\"}\", StandardCharsets.UTF_8);\n    var fileDto = new ClientFileDto(clue.toUri(), Paths.get(\".sonarlint/connectedMode.json\"), CONFIG_SCOPE_ID, null, StandardCharsets.UTF_8.name(), clue, null, null, true);\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID,\n        List.of(fileDto))\n      .build();\n\n    var backend = harness.newBackend()\n      .start(fakeClient);\n\n    backend.getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(List.of(fileDto), Collections.emptyList(), Collections.emptyList()));\n\n    ArgumentCaptor<Map<String, List<ConnectionSuggestionDto>>> suggestionCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(fakeClient, timeout(5000)).suggestConnection(suggestionCaptor.capture());\n\n    var connectionSuggestion = suggestionCaptor.getValue();\n    assertThat(connectionSuggestion).containsOnlyKeys(CONFIG_SCOPE_ID);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID)).hasSize(1);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getLeft()).isNotNull();\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getLeft().getServerUrl()).isEqualTo(sonarqubeMock.baseUrl());\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getLeft().getProjectKey()).isEqualTo(SLCORE_PROJECT_KEY);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).isFromSharedConfiguration()).isTrue();\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getOrigin()).isEqualTo(BindingSuggestionOrigin.SHARED_CONFIGURATION);\n  }\n\n  @ParameterizedTest(name = \"Should not suggest connection setup for projectKey: {0}, SQ server URL: {1}, and SC organization key: {2}\")\n  @ExtendWith(SonarLintTestHarness.class)\n  @MethodSource(\"emptyBindingSuggestionsTestValueProvider\")\n  void should_not_suggest_connection_for_empty_values(String projectKey, String serverUrl, String organizationKey, SonarLintTestHarness harness, @TempDir Path tmp) throws IOException {\n    var sonarlintDir = tmp.resolve(\".sonarlint/\");\n    Files.createDirectory(sonarlintDir);\n    var projectKeyData = projectKey != null ? \"\\\"projectKey\\\":\\\"\" + projectKey + \"\\\",\" : \"\";\n    var serverData = serverUrl != null ? \"\\\"sonarQubeUri\\\":\\\"\" + serverUrl + \"\\\"\" :\n      organizationKey != null ? \"\\\"sonarCloudOrganization\\\":\\\"\" + organizationKey + \"\\\"\" : \"\";\n    var clue = tmp.resolve(\".sonarlint/connectedMode.json\");\n    var content = \"{\" + projectKeyData + serverData + \"}\";\n    Files.writeString(clue, content, StandardCharsets.UTF_8);\n    var fileDto = new ClientFileDto(clue.toUri(), Paths.get(\".sonarlint/connectedMode.json\"), CONFIG_SCOPE_ID, null, StandardCharsets.UTF_8.name(), clue, null, null, true);\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, List.of(fileDto))\n      .build();\n\n    var backend = harness.newBackend().start(fakeClient);\n\n    backend.getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(List.of(fileDto), Collections.emptyList(), Collections.emptyList()));\n\n    await().pollDelay(Duration.ofMillis(300)).untilAsserted(() -> assertThat(fakeClient.getSuggestionsByConfigScope()).isEmpty());\n  }\n\n  @ParameterizedTest(name = \"Should not suggest connection setup for projectKey: {0}, SQ server URL: {1}, and SC organization key: {2}\")\n  @MethodSource(\"nonEmptyBindingSuggestionsTestValueProvider\")\n  @ExtendWith(SonarLintTestHarness.class)\n  void should_suggest_connection_for_non_empty_values(String projectKey, String serverUrl, String organizationKey, SonarLintTestHarness harness, @TempDir Path tmp) throws IOException {\n    var sonarlintDir = tmp.resolve(\".sonarlint/\");\n    Files.createDirectory(sonarlintDir);\n    var projectKeyData = projectKey != null ? \"\\\"projectKey\\\":\\\"\" + projectKey + \"\\\",\" : \"\";\n    var serverData = serverUrl != null ? \"\\\"sonarQubeUri\\\":\\\"\" + serverUrl + \"\\\"\" :\n      organizationKey != null ? \"\\\"sonarCloudOrganization\\\":\\\"\" + organizationKey + \"\\\"\" : \"\";\n    var clue = tmp.resolve(\".sonarlint/connectedMode.json\");\n    var content = \"{\" + projectKeyData + serverData + \"}\";\n    Files.writeString(clue, content, StandardCharsets.UTF_8);\n    var fileDto = new ClientFileDto(clue.toUri(), Paths.get(\".sonarlint/connectedMode.json\"), CONFIG_SCOPE_ID, null, StandardCharsets.UTF_8.name(), clue, null, null, true);\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID,\n        List.of(fileDto))\n      .build();\n\n    var backend = harness.newBackend().start(fakeClient);\n\n    backend.getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(List.of(fileDto), Collections.emptyList(), Collections.emptyList()));\n\n    await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(fakeClient.getSuggestionsByConfigScope()).hasSize(1));\n  }\n\n  @SonarLintTest\n  void should_suggest_sonarcloud_connection_when_initializing_fs(SonarLintTestHarness harness, @TempDir Path tmp) throws IOException {\n    var sonarlintDir = tmp.resolve(\".sonarlint/\");\n    Files.createDirectory(sonarlintDir);\n    var clue = tmp.resolve(\".sonarlint/connectedMode.json\");\n    Files.writeString(clue, \"{\\\"projectKey\\\": \\\"\" + SLCORE_PROJECT_KEY + \"\\\",\\\"sonarCloudOrganization\\\": \\\"\" + ORGANIZATION + \"\\\"}\", StandardCharsets.UTF_8);\n    var fileDto = new ClientFileDto(clue.toUri(), Paths.get(\".sonarlint/connectedMode.json\"), CONFIG_SCOPE_ID, null, StandardCharsets.UTF_8.name(), clue, null, null, true);\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID,\n        List.of(fileDto))\n      .build();\n\n    var backend = harness.newBackend()\n      .start(fakeClient);\n\n    backend.getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(List.of(fileDto), Collections.emptyList(), Collections.emptyList()));\n\n    ArgumentCaptor<Map<String, List<ConnectionSuggestionDto>>> suggestionCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(fakeClient, timeout(5000)).suggestConnection(suggestionCaptor.capture());\n\n    var connectionSuggestion = suggestionCaptor.getValue();\n    assertThat(connectionSuggestion).containsOnlyKeys(CONFIG_SCOPE_ID);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID)).hasSize(1);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getRight()).isNotNull();\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getRight().getOrganization()).isEqualTo(ORGANIZATION);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getRight().getProjectKey()).isEqualTo(SLCORE_PROJECT_KEY);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).isFromSharedConfiguration()).isTrue();\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getOrigin()).isEqualTo(BindingSuggestionOrigin.SHARED_CONFIGURATION);\n  }\n\n  @SonarLintTest\n  void should_suggest_connection_when_initializing_fs_for_csharp_project(SonarLintTestHarness harness, @TempDir Path tmp) throws IOException {\n    var sonarlintDir = tmp.resolve(\"random\");\n    Files.createDirectory(sonarlintDir);\n    sonarlintDir = tmp.resolve(\"random/path\");\n    Files.createDirectory(sonarlintDir);\n    sonarlintDir = tmp.resolve(\"random/path/.sonarlint\");\n    Files.createDirectory(sonarlintDir);\n    var clue = tmp.resolve(\"random/path/.sonarlint/random_name.json\");\n    Files.writeString(clue, \"{\\\"projectKey\\\": \\\"\" + SLCORE_PROJECT_KEY + \"\\\",\\\"sonarQubeUri\\\": \\\"\" + sonarqubeMock.baseUrl() + \"\\\"}\", StandardCharsets.UTF_8);\n    var fileDto = new ClientFileDto(clue.toUri(), Paths.get(\"random/path/.sonarlint/random_name.json\"), CONFIG_SCOPE_ID, null, StandardCharsets.UTF_8.name(), clue, null, null, true);\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID,\n        List.of(fileDto))\n      .build();\n\n    var backend = harness.newBackend()\n      .start(fakeClient);\n\n    backend.getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(List.of(fileDto), Collections.emptyList(), Collections.emptyList()));\n\n    ArgumentCaptor<Map<String, List<ConnectionSuggestionDto>>> suggestionCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(fakeClient, timeout(5000)).suggestConnection(suggestionCaptor.capture());\n\n    var connectionSuggestion = suggestionCaptor.getValue();\n    assertThat(connectionSuggestion).containsOnlyKeys(CONFIG_SCOPE_ID);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID)).hasSize(1);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getLeft()).isNotNull();\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getLeft().getServerUrl()).isEqualTo(sonarqubeMock.baseUrl());\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getLeft().getProjectKey()).isEqualTo(SLCORE_PROJECT_KEY);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).isFromSharedConfiguration()).isTrue();\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getOrigin()).isEqualTo(BindingSuggestionOrigin.SHARED_CONFIGURATION);\n  }\n\n  @SonarLintTest\n  void should_suggest_connection_when_initializing_fs_with_scanner_file(SonarLintTestHarness harness, @TempDir Path tmp) throws IOException {\n    var clue = tmp.resolve(\"sonar-project.properties\");\n    Files.writeString(clue, \"sonar.host.url=\" + sonarqubeMock.baseUrl() + \"\\nsonar.projectKey=\" + SLCORE_PROJECT_KEY, StandardCharsets.UTF_8);\n    var fileDto = new ClientFileDto(clue.toUri(), Paths.get(\"sonar-project.properties\"), CONFIG_SCOPE_ID, null, StandardCharsets.UTF_8.name(), clue, null, null, true);\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID,\n        List.of(fileDto))\n      .build();\n\n    var backend = harness.newBackend()\n      .start(fakeClient);\n\n    backend.getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(List.of(fileDto), Collections.emptyList(), Collections.emptyList()));\n\n    ArgumentCaptor<Map<String, List<ConnectionSuggestionDto>>> suggestionCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(fakeClient, timeout(5000)).suggestConnection(suggestionCaptor.capture());\n\n    var connectionSuggestion = suggestionCaptor.getValue();\n    assertThat(connectionSuggestion).containsOnlyKeys(CONFIG_SCOPE_ID);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID)).hasSize(1);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getLeft()).isNotNull();\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getLeft().getServerUrl()).isEqualTo(sonarqubeMock.baseUrl());\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getLeft().getProjectKey()).isEqualTo(SLCORE_PROJECT_KEY);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).isFromSharedConfiguration()).isFalse();\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getOrigin()).isEqualTo(BindingSuggestionOrigin.PROPERTIES_FILE);\n  }\n\n  @SonarLintTest\n  void should_suggest_sonarcloud_connection_when_initializing_fs_with_scanner_file(SonarLintTestHarness harness, @TempDir Path tmp) throws IOException {\n    var clue = tmp.resolve(\".sonarcloud.properties\");\n    Files.writeString(clue, \"sonar.organization=\" + ORGANIZATION + \"\\nsonar.projectKey=\" + SLCORE_PROJECT_KEY, StandardCharsets.UTF_8);\n    var fileDto = new ClientFileDto(clue.toUri(), Paths.get(\"sonar-project.properties\"), CONFIG_SCOPE_ID, null, StandardCharsets.UTF_8.name(), clue, null, null, true);\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID,\n        List.of(fileDto))\n      .build();\n\n    var backend = harness.newBackend()\n      .start(fakeClient);\n\n    backend.getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(List.of(fileDto), Collections.emptyList(), Collections.emptyList()));\n\n    ArgumentCaptor<Map<String, List<ConnectionSuggestionDto>>> suggestionCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(fakeClient, timeout(5000)).suggestConnection(suggestionCaptor.capture());\n\n    var connectionSuggestion = suggestionCaptor.getValue();\n    assertThat(connectionSuggestion).containsOnlyKeys(CONFIG_SCOPE_ID);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID)).hasSize(1);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getRight()).isNotNull();\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getRight().getOrganization()).isEqualTo(ORGANIZATION);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getRight().getProjectKey()).isEqualTo(SLCORE_PROJECT_KEY);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).isFromSharedConfiguration()).isFalse();\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getOrigin()).isEqualTo(BindingSuggestionOrigin.PROPERTIES_FILE);\n  }\n\n  @SonarLintTest\n  void should_suggest_connection_when_config_scope_added(SonarLintTestHarness harness, @TempDir Path tmp) throws IOException {\n    var sonarlintDir = tmp.resolve(\".sonarlint/\");\n    Files.createDirectory(sonarlintDir);\n    var clue = tmp.resolve(\".sonarlint/connectedMode.json\");\n    Files.writeString(clue, \"{\\\"projectKey\\\": \\\"\" + SLCORE_PROJECT_KEY + \"\\\",\\\"sonarQubeUri\\\": \\\"\" + sonarqubeMock.baseUrl() + \"\\\"}\", StandardCharsets.UTF_8);\n    var fileDto = new ClientFileDto(clue.toUri(), Paths.get(\".sonarlint/connectedMode.json\"), CONFIG_SCOPE_ID, null, StandardCharsets.UTF_8.name(), clue, null, null, true);\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID,\n        List.of(fileDto))\n      .build();\n\n    var backend = harness.newBackend()\n      .start(fakeClient);\n\n    backend.getConfigurationService()\n      .didAddConfigurationScopes(\n        new DidAddConfigurationScopesParams(List.of(\n          new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, \"sonarlint-core\",\n            new BindingConfigurationDto(null, null, false)))));\n\n    ArgumentCaptor<Map<String, List<ConnectionSuggestionDto>>> suggestionCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(fakeClient, timeout(5000)).suggestConnection(suggestionCaptor.capture());\n\n    var connectionSuggestion = suggestionCaptor.getValue();\n    assertThat(connectionSuggestion).containsOnlyKeys(CONFIG_SCOPE_ID);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID)).hasSize(1);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getLeft()).isNotNull();\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getLeft().getServerUrl()).isEqualTo(sonarqubeMock.baseUrl());\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getLeft().getProjectKey()).isEqualTo(SLCORE_PROJECT_KEY);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).isFromSharedConfiguration()).isTrue();\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getOrigin()).isEqualTo(BindingSuggestionOrigin.SHARED_CONFIGURATION);\n  }\n\n  @SonarLintTest\n  void should_suggest_connection_with_multiple_bindings_when_config_scope_added(SonarLintTestHarness harness, @TempDir Path tmp) throws IOException {\n    var sonarlintDir = tmp.resolve(\".sonarlint/\");\n    Files.createDirectory(sonarlintDir);\n    var sqClue = tmp.resolve(\".sonarlint/connectedMode1.json\");\n    Files.writeString(sqClue, \"{\\\"projectKey\\\": \\\"\" + SLCORE_PROJECT_KEY + \"\\\",\\\"sonarQubeUri\\\": \\\"\" + sonarqubeMock.baseUrl() + \"\\\"}\", StandardCharsets.UTF_8);\n    var scClue = tmp.resolve(\".sonarlint/connectedMode2.json\");\n    Files.writeString(scClue, \"{\\\"projectKey\\\": \\\"\" + SLCORE_PROJECT_KEY + \"\\\",\\\"sonarCloudOrganization\\\": \\\"\" + ORGANIZATION + \"\\\"}\", StandardCharsets.UTF_8);\n    var sqFileDto = new ClientFileDto(sqClue.toUri(), Paths.get(\".sonarlint/connectedMode.json\"), CONFIG_SCOPE_ID, null, StandardCharsets.UTF_8.name(), sqClue, null, null, true);\n    var scFileDto = new ClientFileDto(scClue.toUri(), Paths.get(\".sonarlint/connectedMode.json\"), CONFIG_SCOPE_ID, null, StandardCharsets.UTF_8.name(), scClue, null, null, true);\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID,\n        List.of(sqFileDto, scFileDto))\n      .build();\n\n    var backend = harness.newBackend()\n      .start(fakeClient);\n\n    backend.getConfigurationService()\n      .didAddConfigurationScopes(\n        new DidAddConfigurationScopesParams(List.of(\n          new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, \"sonarlint-core\",\n            new BindingConfigurationDto(null, null, false)))));\n\n    ArgumentCaptor<Map<String, List<ConnectionSuggestionDto>>> suggestionCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(fakeClient, timeout(5000)).suggestConnection(suggestionCaptor.capture());\n\n    var connectionSuggestion = suggestionCaptor.getValue();\n    assertThat(connectionSuggestion).containsOnlyKeys(CONFIG_SCOPE_ID);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID)).hasSize(2);\n    for (var suggestion : connectionSuggestion.get(CONFIG_SCOPE_ID)) {\n      if (suggestion.getConnectionSuggestion().isLeft()) {\n        assertThat(suggestion.getConnectionSuggestion().getLeft().getServerUrl()).isEqualTo(sonarqubeMock.baseUrl());\n        assertThat(suggestion.getConnectionSuggestion().getLeft().getProjectKey()).isEqualTo(SLCORE_PROJECT_KEY);\n      } else {\n        assertThat(suggestion.getConnectionSuggestion().getRight().getOrganization()).isEqualTo(ORGANIZATION);\n        assertThat(suggestion.getConnectionSuggestion().getRight().getProjectKey()).isEqualTo(SLCORE_PROJECT_KEY);\n      }\n      assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).isFromSharedConfiguration()).isTrue();\n      assertThat(suggestion.getOrigin()).isEqualTo(BindingSuggestionOrigin.SHARED_CONFIGURATION);\n    }\n  }\n\n  @SonarLintTest\n  void should_suggest_sonarlint_configuration_in_priority(SonarLintTestHarness harness, @TempDir Path tmp) throws IOException {\n    var sonarlintDir = tmp.resolve(\".sonarlint/\");\n    Files.createDirectory(sonarlintDir);\n    var sqClue = tmp.resolve(\".sonarlint/connectedMode1.json\");\n    Files.writeString(sqClue, \"{\\\"projectKey\\\": \\\"\" + SLCORE_PROJECT_KEY + \"\\\",\\\"sonarQubeUri\\\": \\\"\" + sonarqubeMock.baseUrl() + \"\\\"}\", StandardCharsets.UTF_8);\n    var propertyClue = tmp.resolve(\"sonar-project.properties\");\n    Files.writeString(propertyClue, \"sonar.host.url=https://sonarcloud.io\\nsonar.projectKey=\", StandardCharsets.UTF_8);\n    var sqFileDto = new ClientFileDto(sqClue.toUri(), Paths.get(\".sonarlint/connectedMode.json\"), CONFIG_SCOPE_ID, null, StandardCharsets.UTF_8.name(), sqClue, null, null, true);\n    var scFileDto = new ClientFileDto(propertyClue.toUri(), Paths.get(\"sonar-project.properties\"), CONFIG_SCOPE_ID, null, StandardCharsets.UTF_8.name(), propertyClue, null, null, true);\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID,\n        List.of(sqFileDto, scFileDto))\n      .build();\n\n    var backend = harness.newBackend()\n      .start(fakeClient);\n\n    backend.getConfigurationService()\n      .didAddConfigurationScopes(\n        new DidAddConfigurationScopesParams(List.of(\n          new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, \"sonarlint-core\",\n            new BindingConfigurationDto(null, null, false)))));\n\n    ArgumentCaptor<Map<String, List<ConnectionSuggestionDto>>> suggestionCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(fakeClient, timeout(5000)).suggestConnection(suggestionCaptor.capture());\n\n    var connectionSuggestion = suggestionCaptor.getValue();\n    assertThat(connectionSuggestion).containsOnlyKeys(CONFIG_SCOPE_ID);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID)).hasSize(1);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getLeft()).isNotNull();\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getLeft().getServerUrl()).isEqualTo(sonarqubeMock.baseUrl());\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getLeft().getProjectKey()).isEqualTo(SLCORE_PROJECT_KEY);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).isFromSharedConfiguration()).isTrue();\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getOrigin()).isEqualTo(BindingSuggestionOrigin.SHARED_CONFIGURATION);\n  }\n\n  @SonarLintTest\n  void should_suggest_sonarqube_connection_when_pascal_case(SonarLintTestHarness harness, @TempDir Path tmp) throws IOException {\n    var sonarlintDir = tmp.resolve(\".sonarlint/\");\n    Files.createDirectory(sonarlintDir);\n    var clue = tmp.resolve(\".sonarlint/connectedMode.json\");\n    Files.writeString(clue, \"{\\\"ProjectKey\\\": \\\"\" + SLCORE_PROJECT_KEY + \"\\\",\\\"SonarQubeUri\\\": \\\"\" + sonarqubeMock.baseUrl() + \"\\\"}\", StandardCharsets.UTF_8);\n    var fileDto = new ClientFileDto(clue.toUri(), Paths.get(\".sonarlint/connectedMode.json\"), CONFIG_SCOPE_ID, null, StandardCharsets.UTF_8.name(), clue, null, null, true);\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID,\n        List.of(fileDto))\n      .build();\n\n    var backend = harness.newBackend()\n      .start(fakeClient);\n\n    backend.getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(List.of(fileDto), Collections.emptyList(), Collections.emptyList()));\n\n    ArgumentCaptor<Map<String, List<ConnectionSuggestionDto>>> suggestionCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(fakeClient, timeout(5000)).suggestConnection(suggestionCaptor.capture());\n\n    var connectionSuggestion = suggestionCaptor.getValue();\n    assertThat(connectionSuggestion).containsOnlyKeys(CONFIG_SCOPE_ID);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID)).hasSize(1);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getLeft()).isNotNull();\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getLeft().getServerUrl()).isEqualTo(sonarqubeMock.baseUrl());\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getLeft().getProjectKey()).isEqualTo(SLCORE_PROJECT_KEY);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).isFromSharedConfiguration()).isTrue();\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getOrigin()).isEqualTo(BindingSuggestionOrigin.SHARED_CONFIGURATION);\n  }\n\n  @SonarLintTest\n  void should_suggest_sonarcloud_connection_when_pascal_case(SonarLintTestHarness harness, @TempDir Path tmp) throws IOException {\n    var sonarlintDir = tmp.resolve(\".sonarlint/\");\n    Files.createDirectory(sonarlintDir);\n    var clue = tmp.resolve(\".sonarlint/connectedMode.json\");\n    Files.writeString(clue, \"{\\\"ProjectKey\\\": \\\"\" + SLCORE_PROJECT_KEY + \"\\\",\\\"SonarCloudOrganization\\\": \\\"\" + ORGANIZATION + \"\\\"}\", StandardCharsets.UTF_8);\n    var fileDto = new ClientFileDto(clue.toUri(), Paths.get(\".sonarlint/connectedMode.json\"), CONFIG_SCOPE_ID, null, StandardCharsets.UTF_8.name(), clue, null, null, true);\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID,\n        List.of(fileDto))\n      .build();\n\n    var backend = harness.newBackend()\n      .start(fakeClient);\n\n    backend.getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(List.of(fileDto), Collections.emptyList(), Collections.emptyList()));\n\n    ArgumentCaptor<Map<String, List<ConnectionSuggestionDto>>> suggestionCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(fakeClient, timeout(5000)).suggestConnection(suggestionCaptor.capture());\n\n    var connectionSuggestion = suggestionCaptor.getValue();\n    assertThat(connectionSuggestion).containsOnlyKeys(CONFIG_SCOPE_ID);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID)).hasSize(1);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getRight()).isNotNull();\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getRight().getOrganization()).isEqualTo(ORGANIZATION);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getConnectionSuggestion().getRight().getProjectKey()).isEqualTo(SLCORE_PROJECT_KEY);\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).isFromSharedConfiguration()).isTrue();\n    assertThat(connectionSuggestion.get(CONFIG_SCOPE_ID).get(0).getOrigin()).isEqualTo(BindingSuggestionOrigin.SHARED_CONFIGURATION);\n  }\n\n  @SonarLintTest\n  void should_return_list_of_suggestions_when_requested(SonarLintTestHarness harness, @TempDir Path tmp) throws IOException {\n    var sonarlintDir = tmp.resolve(\".sonarlint/\");\n    Files.createDirectory(sonarlintDir);\n    var clue = tmp.resolve(\".sonarlint/connectedMode.json\");\n    Files.writeString(clue, \"{\\\"ProjectKey\\\": \\\"\" + SLCORE_PROJECT_KEY + \"\\\",\\\"SonarCloudOrganization\\\": \\\"\" + ORGANIZATION + \"\\\"}\", StandardCharsets.UTF_8);\n    var fileDto = new ClientFileDto(clue.toUri(), Paths.get(\".sonarlint/connectedMode.json\"), CONFIG_SCOPE_ID, null, StandardCharsets.UTF_8.name(), clue, null, null, true);\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID,\n        List.of(fileDto))\n      .build();\n\n    var backend = harness.newBackend()\n      .start(fakeClient);\n\n    backend.getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(List.of(fileDto), Collections.emptyList(), Collections.emptyList()));\n\n    var connectionSuggestions = backend.getConnectionService().getConnectionSuggestions(new GetConnectionSuggestionsParams(CONFIG_SCOPE_ID)).join();\n\n    assertThat(connectionSuggestions.getConnectionSuggestions()).hasSize(1);\n    var connectionSuggestion = connectionSuggestions.getConnectionSuggestions().get(0);\n    assertThat(connectionSuggestion.getConnectionSuggestion().getRight()).isNotNull();\n    assertThat(connectionSuggestion.getConnectionSuggestion().getRight().getOrganization()).isEqualTo(ORGANIZATION);\n    assertThat(connectionSuggestion.getConnectionSuggestion().getRight().getProjectKey()).isEqualTo(SLCORE_PROJECT_KEY);\n    assertThat(connectionSuggestion.isFromSharedConfiguration()).isTrue();\n    assertThat(connectionSuggestion.getOrigin()).isEqualTo(BindingSuggestionOrigin.SHARED_CONFIGURATION);\n  }\n\n  @SonarLintTest\n  void should_return_empty_suggestions_list_when_no_clues(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient()\n      .build();\n\n    var backend = harness.newBackend()\n      .start(fakeClient);\n\n    backend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(\n      new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, \"sonarlint-core\",\n        null))));\n\n    var connectionSuggestions = backend.getConnectionService().getConnectionSuggestions(new GetConnectionSuggestionsParams(CONFIG_SCOPE_ID)).join();\n\n    assertThat(connectionSuggestions.getConnectionSuggestions()).isEmpty();\n  }\n\n  private static Stream<Arguments> emptyBindingSuggestionsTestValueProvider() {\n    return Stream.of(\n      Arguments.of(\"\", \"\", \"\"),\n      Arguments.of(\"\", \"\", null),\n      Arguments.of(\"\", \"\", \"foo\"),\n      Arguments.of(\"\", \"\", \"foo\"),\n      Arguments.of(\"\", null, \"\"),\n      Arguments.of(null, \"\", \"\"),\n      Arguments.of(\"\", null, null),\n      Arguments.of(null, \"\", null),\n      Arguments.of(null, null, \"\"),\n      Arguments.of(null, \"foo\", \"bar\")\n    );\n  }\n\n  public static Stream<Arguments> nonEmptyBindingSuggestionsTestValueProvider() {\n    return Stream.of(\n      Arguments.of(\"\", \"foo\", \"\"),\n      Arguments.of(\"\", \"foo\", null),\n      Arguments.of(\"\", null, \"foo\"),\n      Arguments.of(\"key\", \"foo\", \"\"),\n      Arguments.of(\"key\", \"foo\", null),\n      Arguments.of(\"key\", null, \"foo\"),\n      Arguments.of(\"key\", \"\", \"foo\")\n    );\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/EffectiveRulesMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.EffectiveRuleDetailsDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.EffectiveRuleParamDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.GetEffectiveRuleDetailsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleDescriptionTabDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleNonContextualSectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.StandaloneRuleConfigDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.UpdateStandaloneRulesConfigurationParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Rules;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.MockWebServerExtensionWithProtobuf;\nimport utils.TestPlugin;\n\nimport static org.apache.commons.lang3.StringUtils.abbreviate;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SECURITY_HOTSPOTS;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute.CONVENTIONAL;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute.FORMATTED;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute.MODULAR;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity.LOW;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity.MEDIUM;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity.BLOCKER;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity.INFO;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JAVA;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.PYTHON;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.RuleType.BUG;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.RuleType.VULNERABILITY;\n\nclass EffectiveRulesMediumTests {\n\n  @RegisterExtension\n  private final MockWebServerExtensionWithProtobuf mockWebServerExtension = new MockWebServerExtensionWithProtobuf();\n\n  @SonarLintTest\n  void it_should_return_embedded_rule_when_project_is_not_bound(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(\"scopeId\")\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start();\n\n    var details = getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\");\n\n    assertThat(details)\n      .extracting(EffectiveRuleDetailsDto::getKey, EffectiveRuleDetailsDto::getName, EffectiveRuleDetailsDto::getCleanCodeAttribute, EffectiveRuleDetailsDto::getLanguage,\n        r -> r.getDefaultImpacts().get(0).getImpactSeverity(), r -> r.getDescription().getRight().getTabs().get(0).getContent().getLeft().getHtmlContent())\n      .containsExactly(\"python:S139\", \"Comments should not be located at the end of lines of code\", FORMATTED, PYTHON, LOW,\n        PYTHON_S139_DESCRIPTION);\n    assertThat(details.getParams())\n      .extracting(EffectiveRuleParamDto::getName, EffectiveRuleParamDto::getDescription, EffectiveRuleParamDto::getValue, EffectiveRuleParamDto::getDefaultValue)\n      .containsExactly(tuple(\"legalTrailingCommentPattern\",\n        \"Pattern for text of trailing comments that are allowed. By default, Mypy and Black pragma comments as well as comments containing only one word.\",\n        \"^#\\\\s*+([^\\\\s]++|fmt.*|type.*|noqa.*)$\",\n        \"^#\\\\s*+([^\\\\s]++|fmt.*|type.*|noqa.*)$\"));\n  }\n\n  @SonarLintTest\n  void it_should_consider_standalone_rule_config_for_effective_parameter_values(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(\"scopeId\")\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .withStandaloneRuleConfig(\"python:S139\", true, Map.of(\"legalTrailingCommentPattern\", \"initialValue\"))\n      .start();\n\n    var detailsAfterInit = getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\");\n\n    assertThat(detailsAfterInit.getParams())\n      .extracting(EffectiveRuleParamDto::getName, EffectiveRuleParamDto::getDescription, EffectiveRuleParamDto::getValue, EffectiveRuleParamDto::getDefaultValue)\n      .containsExactly(tuple(\"legalTrailingCommentPattern\",\n        \"Pattern for text of trailing comments that are allowed. By default, Mypy and Black pragma comments as well as comments containing only one word.\",\n        \"initialValue\",\n        \"^#\\\\s*+([^\\\\s]++|fmt.*|type.*|noqa.*)$\"));\n\n    backend.getRulesService().updateStandaloneRulesConfiguration(new UpdateStandaloneRulesConfigurationParams(Map.of(\"python:S139\",\n      new StandaloneRuleConfigDto(true, Map.of(\"legalTrailingCommentPattern\", \"updatedValue\")))));\n\n    var detailsAfterUpdate = getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\");\n\n    assertThat(detailsAfterUpdate.getParams())\n      .extracting(EffectiveRuleParamDto::getName, EffectiveRuleParamDto::getDescription, EffectiveRuleParamDto::getValue, EffectiveRuleParamDto::getDefaultValue)\n      .containsExactly(tuple(\"legalTrailingCommentPattern\",\n        \"Pattern for text of trailing comments that are allowed. By default, Mypy and Black pragma comments as well as comments containing only one word.\",\n        \"updatedValue\",\n        \"^#\\\\s*+([^\\\\s]++|fmt.*|type.*|noqa.*)$\"));\n  }\n\n  @SonarLintTest\n  void it_should_fail_when_rule_key_unknown_and_project_is_not_bound(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(\"scopeId\")\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start();\n\n    var futureResponse = backend.getRulesService().getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams(\"scopeId\", \"python:SXXXX\", null));\n\n    assertThat(futureResponse).failsWithin(1, TimeUnit.SECONDS)\n      .withThrowableOfType(ExecutionException.class)\n      .withCauseInstanceOf(ResponseErrorException.class)\n      .withMessageContaining(\"Could not find rule 'python:SXXXX' in embedded rules\");\n  }\n\n  @SonarLintTest\n  void it_should_return_rule_loaded_from_server_plugin_when_project_is_bound_and_project_storage_does_not_exist(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withSonarQubeConnection(\"connectionId\", mockWebServerExtension.endpointParams().getBaseUrl(), storage -> storage.withPlugin(TestPlugin.JAVA))\n      .withEnabledLanguageInStandaloneMode(JAVA)\n      .start();\n\n    var details = getEffectiveRuleDetails(backend, \"scopeId\", \"java:S106\");\n\n    assertThat(details)\n      .extracting(EffectiveRuleDetailsDto::getKey, EffectiveRuleDetailsDto::getName, EffectiveRuleDetailsDto::getCleanCodeAttribute, EffectiveRuleDetailsDto::getLanguage,\n        r -> r.getDefaultImpacts().get(0).getImpactSeverity(), r -> r.getDescription().getRight().getTabs().get(0).getContent().getLeft().getHtmlContent())\n      .containsExactly(\"java:S106\", \"Standard outputs should not be used directly to log anything\", MODULAR, JAVA, MEDIUM,\n        JAVA_S106_DESCRIPTION);\n    assertThat(details.getParams()).isEmpty();\n  }\n\n  @SonarLintTest\n  void it_should_merge_rule_from_storage_and_server_when_project_is_bound(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", mockWebServerExtension.endpointParams().getBaseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(\"python\",\n          ruleSet -> ruleSet.withActiveRule(\"python:S139\", \"INFO\", Map.of(\"legalTrailingCommentPattern\", \"blah\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start();\n    mockWebServerExtension.addProtobufResponse(\"/api/rules/show.protobuf?key=python:S139\", Rules.ShowResponse.newBuilder()\n      .setRule(Rules.Rule.newBuilder().setName(\"newName\").setSeverity(\"INFO\").setType(Common.RuleType.BUG).setLang(\"py\").build())\n      .build());\n\n    var details = getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\");\n\n    assertThat(details)\n      .extracting(EffectiveRuleDetailsDto::getKey, EffectiveRuleDetailsDto::getName, EffectiveRuleDetailsDto::getCleanCodeAttribute, EffectiveRuleDetailsDto::getLanguage,\n        r -> r.getDefaultImpacts().get(0).getImpactSeverity(), r -> r.getDescription().getRight().getTabs().get(0).getContent().getLeft().getHtmlContent())\n      .containsExactly(\"python:S139\", \"Comments should not be located at the end of lines of code\", FORMATTED, PYTHON, LOW,\n        PYTHON_S139_DESCRIPTION);\n    assertThat(details.getParams()).isEmpty();\n  }\n\n  @SonarLintTest\n  void it_should_merge_rule_from_storage_and_server_when_parent_project_is_bound(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", mockWebServerExtension.endpointParams().getBaseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(\"python\",\n          ruleSet -> ruleSet.withActiveRule(\"python:S139\", \"INFO\", Map.of(\"legalTrailingCommentPattern\", \"blah\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withChildConfigScope(\"childScopeId\", \"scopeId\")\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start();\n    mockWebServerExtension.addProtobufResponse(\"/api/rules/show.protobuf?key=python:S139\", Rules.ShowResponse.newBuilder()\n      .setRule(Rules.Rule.newBuilder().setName(\"newName\").setSeverity(\"INFO\").setType(Common.RuleType.BUG).setLang(\"py\").build())\n      .build());\n\n    var details = getEffectiveRuleDetails(backend, \"childScopeId\", \"python:S139\");\n\n    assertThat(details)\n      .extracting(EffectiveRuleDetailsDto::getKey, EffectiveRuleDetailsDto::getName, EffectiveRuleDetailsDto::getCleanCodeAttribute, EffectiveRuleDetailsDto::getLanguage,\n        r -> r.getDefaultImpacts().get(0).getImpactSeverity(), r -> r.getDescription().getRight().getTabs().get(0).getContent().getLeft().getHtmlContent())\n      .containsExactly(\"python:S139\", \"Comments should not be located at the end of lines of code\", FORMATTED, PYTHON, LOW,\n        PYTHON_S139_DESCRIPTION);\n    assertThat(details.getParams()).isEmpty();\n  }\n\n  @SonarLintTest\n  void it_return_single_section_from_server_when_project_is_bound(SonarLintTestHarness harness) {\n    var name = \"name\";\n    var desc = \"desc\";\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", mockWebServerExtension.endpointParams().getBaseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(\"js\",\n          ruleSet -> ruleSet.withActiveRule(\"jssecurity:S5696\", \"BLOCKER\"))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .start();\n    mockWebServerExtension.addProtobufResponse(\"/api/rules/show.protobuf?key=jssecurity:S5696\", Rules.ShowResponse.newBuilder()\n      .setRule(Rules.Rule.newBuilder().setName(name).setSeverity(\"BLOCKER\").setType(Common.RuleType.VULNERABILITY).setLang(\"js\")\n        .setDescriptionSections(Rules.Rule.DescriptionSections.newBuilder()\n          .addDescriptionSections(Rules.Rule.DescriptionSection.newBuilder()\n            .setKey(\"default\")\n            .setContent(desc)\n            .build())\n          .build())\n        .build())\n      .build());\n\n    var details = getEffectiveRuleDetails(backend, \"scopeId\", \"jssecurity:S5696\");\n\n    assertThat(details)\n      .extracting(EffectiveRuleDetailsDto::getKey, EffectiveRuleDetailsDto::getName, EffectiveRuleDetailsDto::getType, EffectiveRuleDetailsDto::getLanguage,\n        EffectiveRuleDetailsDto::getSeverity, r -> r.getDescription().getLeft().getHtmlContent())\n      .containsExactly(\"jssecurity:S5696\", name, VULNERABILITY, Language.JS, BLOCKER, desc);\n    assertThat(details.getParams()).isEmpty();\n  }\n\n  @SonarLintTest\n  void it_should_fail_to_merge_rule_from_storage_and_server_when_connection_is_unknown(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withStorage(\"connectionId\", storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(\"python\",\n          ruleSet -> ruleSet.withActiveRule(\"python:S139\", \"INFO\", Map.of(\"legalTrailingCommentPattern\", \"blah\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .start();\n\n    var futureResponse = backend.getRulesService().getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams(\"scopeId\", \"python:S139\", null));\n\n    assertThat(futureResponse).failsWithin(1, TimeUnit.SECONDS)\n      .withThrowableOfType(ExecutionException.class)\n      .withCauseInstanceOf(ResponseErrorException.class)\n      .withMessageContaining(\"Connection 'connectionId' is not valid\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_to_merge_rule_from_storage_and_server_when_rule_does_not_exist_on_server(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", mockWebServerExtension.endpointParams().getBaseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(\"python\",\n          ruleSet -> ruleSet.withActiveRule(\"python:S139\", \"INFO\", Map.of(\"legalTrailingCommentPattern\", \"blah\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .start();\n\n    var futureResponse = backend.getRulesService().getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams(\"scopeId\", \"python:S139\", null));\n\n    assertThat(futureResponse).failsWithin(3, TimeUnit.SECONDS)\n      .withThrowableOfType(ExecutionException.class)\n      .havingCause()\n      .isInstanceOfSatisfying(ResponseErrorException.class, ex -> {\n        assertThat(ex.getResponseError().getMessage()).contains(\"Could not find rule 'python:S139' on server 'connectionId'\");\n      });\n  }\n\n  @SonarLintTest\n  void it_should_merge_template_rule_from_storage_and_server_when_project_is_bound(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", mockWebServerExtension.endpointParams().getBaseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(\"python\",\n          ruleSet -> ruleSet.withCustomActiveRule(\"python:custom\", \"python:CommentRegularExpression\", \"INFO\", Map.of(\"message\", \"msg\", \"regularExpression\", \"regExp\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start();\n    mockWebServerExtension.addProtobufResponse(\"/api/rules/show.protobuf?key=python:custom\", Rules.ShowResponse.newBuilder()\n      .setRule(Rules.Rule.newBuilder().setName(\"newName\").setSeverity(\"INFO\").setType(Common.RuleType.BUG).setLang(\"py\").setHtmlNote(\"extendedDesc\")\n        .setDescriptionSections(Rules.Rule.DescriptionSections.newBuilder()\n          .addDescriptionSections(Rules.Rule.DescriptionSection.newBuilder()\n            .setKey(\"default\")\n            .setContent(\"desc\")\n            .build())\n          .build())\n        .build())\n      .build());\n\n    var details = getEffectiveRuleDetails(backend, \"scopeId\", \"python:custom\");\n\n    assertThat(details)\n      .extracting(EffectiveRuleDetailsDto::getKey, EffectiveRuleDetailsDto::getName, EffectiveRuleDetailsDto::getCleanCodeAttribute, EffectiveRuleDetailsDto::getLanguage,\n        r -> r.getDefaultImpacts().get(0).getImpactSeverity(), r -> r.getDescription().getLeft().getHtmlContent())\n      .containsExactly(\"python:custom\", \"newName\", CONVENTIONAL, PYTHON, MEDIUM, \"descextendedDesc\");\n    assertThat(details.getParams()).isEmpty();\n  }\n\n  @SonarLintTest\n  void it_should_merge_rule_from_storage_and_server_rule_when_rule_is_unknown_in_loaded_plugins(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", mockWebServerExtension.endpointParams().getBaseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(\"python\",\n          ruleSet -> ruleSet.withActiveRule(\"python:S139\", \"INFO\", Map.of(\"legalTrailingCommentPattern\", \"blah\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withEnabledLanguageInStandaloneMode(PYTHON)\n      .start();\n    mockWebServerExtension.addProtobufResponse(\"/api/rules/show.protobuf?key=python:S139\", Rules.ShowResponse.newBuilder()\n      .setRule(Rules.Rule.newBuilder().setName(\"newName\").setSeverity(\"INFO\").setType(Common.RuleType.BUG).setLang(\"py\").setHtmlNote(\"extendedDesc\")\n        .setDescriptionSections(Rules.Rule.DescriptionSections.newBuilder()\n          .addDescriptionSections(Rules.Rule.DescriptionSection.newBuilder()\n            .setKey(\"default\")\n            .setContent(\"desc\")\n            .build())\n          .build())\n        .build())\n      .build());\n\n    var details = getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\");\n\n    assertThat(details)\n      .extracting(EffectiveRuleDetailsDto::getKey, EffectiveRuleDetailsDto::getName, EffectiveRuleDetailsDto::getType, EffectiveRuleDetailsDto::getLanguage,\n        EffectiveRuleDetailsDto::getSeverity, r -> r.getDescription().getLeft().getHtmlContent())\n      .containsExactly(\"python:S139\", \"newName\", BUG, PYTHON, INFO, \"descextendedDesc\");\n    assertThat(details.getParams()).isEmpty();\n  }\n\n  @SonarLintTest\n  void it_should_merge_rule_from_storage_and_server_with_description_sections_when_project_is_bound_and_none_context(SonarLintTestHarness harness) {\n    var backend = prepareForRuleDescriptionSectionsAndContext(harness);\n\n    var details = getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\");\n\n    assertThat(details)\n      .extracting(EffectiveRuleDetailsDto::getKey, EffectiveRuleDetailsDto::getName, EffectiveRuleDetailsDto::getType, EffectiveRuleDetailsDto::getLanguage,\n        EffectiveRuleDetailsDto::getSeverity)\n      .containsExactly(\"python:S139\", \"newName\", BUG, PYTHON, INFO);\n    assertThat(details.getParams()).isEmpty();\n    assertThat(details.getDescription().getRight().getIntroductionHtmlContent())\n      .isEqualTo(\"intro content\");\n    assertThat(details.getDescription().getRight().getTabs())\n      .flatExtracting(EffectiveRulesMediumTests::flattenTabContent)\n      .containsExactly(\n        \"How can I fix it?\",\n        \"--> Spring (spring)\",\n        \"    fix spring\",\n        \"--> Struts (struts)\",\n        \"    fix struts\",\n        \"--> Others (others)\",\n        \"    <h4>How can I fix it in another component or fr...\",\n        \"More Info\",\n        \"htmlContent3extendedDesc<h3>Clean Code Principl...\");\n  }\n\n  @SonarLintTest\n  void it_should_return_all_contexts_in_alphabetical_order_with_others_as_default_if_context_not_found(SonarLintTestHarness harness) {\n    var backend = prepareForRuleDescriptionSectionsAndContext(harness);\n\n    var details = getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\", \"not_found\");\n\n    assertThat(details)\n      .extracting(EffectiveRuleDetailsDto::getKey, EffectiveRuleDetailsDto::getName, EffectiveRuleDetailsDto::getType, EffectiveRuleDetailsDto::getLanguage,\n        EffectiveRuleDetailsDto::getSeverity)\n      .containsExactly(\"python:S139\", \"newName\", BUG, PYTHON, INFO);\n    assertThat(details.getParams()).isEmpty();\n    assertThat(details.getDescription().getRight().getIntroductionHtmlContent())\n      .isEqualTo(\"intro content\");\n    assertThat(details.getDescription().getRight().getTabs())\n      .flatExtracting(EffectiveRulesMediumTests::flattenTabContent)\n      .containsExactly(\n        \"How can I fix it?\",\n        \"--> Spring (spring)\",\n        \"    fix spring\",\n        \"--> Struts (struts)\",\n        \"    fix struts\",\n        \"--> Others (others)\",\n        \"    <h4>How can I fix it in another component or fr...\",\n        \"More Info\",\n        \"htmlContent3extendedDesc<h3>Clean Code Principl...\");\n\n    assertThat(details.getDescription().getRight().getTabs().iterator().next().getContent().getRight().getDefaultContextKey())\n      .isEqualTo(\"others\");\n  }\n\n  @SonarLintTest\n  void it_should_return_all_contexts_in_alphabetical_order_with_the_provided_context_as_default(SonarLintTestHarness harness) {\n    var backend = prepareForRuleDescriptionSectionsAndContext(harness);\n\n    var details = getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\", \"spring\");\n\n    assertThat(details)\n      .extracting(EffectiveRuleDetailsDto::getKey, EffectiveRuleDetailsDto::getName, EffectiveRuleDetailsDto::getType, EffectiveRuleDetailsDto::getLanguage,\n        EffectiveRuleDetailsDto::getSeverity)\n      .containsExactly(\"python:S139\", \"newName\", BUG, PYTHON, INFO);\n    assertThat(details.getParams()).isEmpty();\n    assertThat(details.getDescription())\n      .extracting(\"right.introductionHtmlContent\")\n      .isEqualTo(\"intro content\");\n    assertThat(details.getDescription().getRight().getTabs())\n      .flatExtracting(EffectiveRulesMediumTests::flattenTabContent)\n      .containsExactly(\n        \"How can I fix it?\",\n        \"--> Spring (spring)\",\n        \"    fix spring\",\n        \"--> Struts (struts)\",\n        \"    fix struts\",\n        \"--> Others (others)\",\n        \"    <h4>How can I fix it in another component or fr...\",\n        \"More Info\",\n        \"htmlContent3extendedDesc<h3>Clean Code Principl...\");\n\n    assertThat(details.getDescription().getRight().getTabs().iterator().next().getContent().getRight().getDefaultContextKey())\n      .isEqualTo(\"spring\");\n  }\n\n  @SonarLintTest\n  void it_should_add_a_more_info_tab_if_no_resource_section_exists_and_extended_description_exists(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", mockWebServerExtension.endpointParams().getBaseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(\"python\",\n          ruleSet -> ruleSet.withActiveRule(\"python:S139\", \"INFO\", Map.of(\"legalTrailingCommentPattern\", \"blah\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withEnabledLanguageInStandaloneMode(PYTHON)\n      .start();\n    mockWebServerExtension.addProtobufResponse(\"/api/rules/show.protobuf?key=python:S139\", Rules.ShowResponse.newBuilder()\n      .setRule(Rules.Rule.newBuilder().setName(\"newName\").setSeverity(\"INFO\").setType(Common.RuleType.BUG).setLang(\"py\").setHtmlNote(\"extendedDesc\")\n        .setEducationPrinciples(Rules.Rule.EducationPrinciples.newBuilder().addEducationPrinciples(\"never_trust_user_input\").build())\n        .setDescriptionSections(Rules.Rule.DescriptionSections.newBuilder()\n          .addDescriptionSections(Rules.Rule.DescriptionSection.newBuilder()\n            .setKey(\"introduction\").setContent(\"htmlContent\")\n            .setContext(Rules.Rule.DescriptionSection.Context.newBuilder().setKey(\"contextKey\").setDisplayName(\"displayName\").build()).build())\n          .addDescriptionSections(Rules.Rule.DescriptionSection.newBuilder()\n            .setKey(\"how_to_fix\").setContent(\"htmlContent2\")\n            .setContext(Rules.Rule.DescriptionSection.Context.newBuilder().setKey(\"contextKey2\").setDisplayName(\"displayName2\").build()).build())\n          .build())\n        .build())\n      .build());\n\n    var details = getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\");\n\n    assertThat(details.getDescription().getRight().getTabs())\n      .filteredOn(RuleDescriptionTabDto::getTitle, \"More Info\")\n      .extracting(RuleDescriptionTabDto::getContent)\n      .extracting(Either::getLeft)\n      .extracting(RuleNonContextualSectionDto::getHtmlContent)\n      .containsExactly(\"\"\"\n        extendedDesc<h3>Clean Code Principles</h3>\n        <h4>Never Trust User Input</h4>\n        <p>\n            Applications must treat all user input and, more generally, all third-party data as\n            attacker-controlled data.\n        </p>\n        <p>\n            The application must determine where the third-party data comes from and treat that data\n            source as an attack vector. Two rules apply:\n        </p>\n\n        <p>\n            First, before using it in the application&apos;s business logic, the application must\n            validate the attacker-controlled data against predefined formats, such as:\n        </p>\n        <ul>\n            <li>Character sets</li>\n            <li>Sizes</li>\n            <li>Types</li>\n            <li>Or any strict schema</li>\n        </ul>\n\n        <p>\n            Second, the application must sanitize string data before inserting it into interpreted\n            contexts (client-side code, file paths, SQL queries). Unsanitized code can corrupt the\n            application&apos;s logic.\n        </p>\"\"\");\n  }\n\n  @SonarLintTest\n  void it_should_split_security_hotspots_rule_description_and_adapt_title(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", mockWebServerExtension.endpointParams().getBaseUrl())\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .withBackendCapability(SECURITY_HOTSPOTS)\n      .start();\n\n    var details = getEffectiveRuleDetails(backend, \"scopeId\", \"python:S4784\");\n\n    assertThat(details.getDescription().isRight()).isTrue();\n    assertThat(details.getDescription().getRight().getTabs())\n      .hasSize(3)\n      .extracting(RuleDescriptionTabDto::getTitle)\n      .contains(\"What's the risk?\");\n  }\n\n  private SonarLintTestRpcServer prepareForRuleDescriptionSectionsAndContext(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", mockWebServerExtension.endpointParams().getBaseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(\"python\",\n          ruleSet -> ruleSet.withActiveRule(\"python:S139\", \"INFO\", Map.of(\"legalTrailingCommentPattern\", \"blah\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withEnabledLanguageInStandaloneMode(PYTHON)\n      .start();\n    mockWebServerExtension.addProtobufResponse(\"/api/rules/show.protobuf?key=python:S139\", Rules.ShowResponse.newBuilder()\n      .setRule(Rules.Rule.newBuilder().setName(\"newName\").setSeverity(\"INFO\").setType(Common.RuleType.BUG).setLang(\"py\").setHtmlNote(\"extendedDesc\")\n        .setEducationPrinciples(Rules.Rule.EducationPrinciples.newBuilder().addEducationPrinciples(\"never_trust_user_input\").build())\n        .setDescriptionSections(Rules.Rule.DescriptionSections.newBuilder()\n          .addDescriptionSections(Rules.Rule.DescriptionSection.newBuilder()\n            .setKey(\"introduction\").setContent(\"intro content\"))\n          .addDescriptionSections(Rules.Rule.DescriptionSection.newBuilder()\n            .setKey(\"how_to_fix\").setContent(\"fix spring\")\n            .setContext(Rules.Rule.DescriptionSection.Context.newBuilder().setKey(\"spring\").setDisplayName(\"Spring\").build()).build())\n          .addDescriptionSections(Rules.Rule.DescriptionSection.newBuilder()\n            .setKey(\"how_to_fix\").setContent(\"fix struts\")\n            .setContext(Rules.Rule.DescriptionSection.Context.newBuilder().setKey(\"struts\").setDisplayName(\"Struts\").build()).build())\n          .addDescriptionSections(Rules.Rule.DescriptionSection.newBuilder()\n            .setKey(\"resources\").setContent(\"htmlContent3\").build()))\n        .build())\n      .build());\n    return backend;\n  }\n\n  private static List<String> flattenTabContent(RuleDescriptionTabDto tab) {\n    List<String> result = new ArrayList<>();\n    result.add(tab.getTitle());\n    if (tab.getContent().isLeft()) {\n      result.add(abbreviate(tab.getContent().getLeft().getHtmlContent(), 50));\n    } else {\n      tab.getContent().getRight().getContextualSections().forEach(s -> {\n        result.add(\"--> \" + s.getDisplayName() + \" (\" + s.getContextKey() + \")\");\n        result.add(\"    \" + abbreviate(s.getHtmlContent(), 50));\n      });\n    }\n    return result;\n  }\n\n  private EffectiveRuleDetailsDto getEffectiveRuleDetails(SonarLintTestRpcServer backend, String configScopeId, String ruleKey) {\n    return getEffectiveRuleDetails(backend, configScopeId, ruleKey, null);\n  }\n\n  private EffectiveRuleDetailsDto getEffectiveRuleDetails(SonarLintTestRpcServer backend, String configScopeId, String ruleKey, String contextKey) {\n    try {\n      return backend.getRulesService().getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams(configScopeId, ruleKey, contextKey)).get().details();\n    } catch (InterruptedException | ExecutionException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  private static final String PYTHON_S139_DESCRIPTION = \"\"\"\n    <p>This rule verifies that single-line comments are not located at the ends of lines of code. The main idea behind this rule is that in order to be\n    really readable, trailing comments would have to be properly written and formatted (correct alignment, no interference with the visual structure of\n    the code, not too long to be visible) but most often, automatic code formatters would not handle this correctly: the code would end up less readable.\n    Comments are far better placed on the previous empty line of code, where they will always be visible and properly formatted.</p>\n    <h3>Noncompliant code example</h3>\n    <pre>\n    a = b + c   # This is a trailing comment that can be very very long\n    </pre>\n    <h3>Compliant solution</h3>\n    <pre>\n    # This very long comment is better placed before the line of code\n    a = b + c\n    </pre>\"\"\";\n  private static final String JAVA_S106_DESCRIPTION = \"\"\"\n    <p>In software development, logs serve as a record of events within an application, providing crucial insights for debugging. When logging, it is\n    essential to ensure that the logs are:</p>\n    <ul>\n      <li>easily accessible</li>\n      <li>uniformly formatted for readability</li>\n      <li>properly recorded</li>\n      <li>securely logged when dealing with sensitive data</li>\n    </ul>\n    <p>Those requirements are not met if a program directly writes to the standard outputs (e.g., System.out, System.err, IO). That is why defining and\n    using a dedicated logger is highly recommended.</p>\n    \n    <p>The following noncompliant code:</p>\n    <pre data-diff-id=\"1\" data-diff-type=\"noncompliant\">\n    class MyClass {\n      public void doSomething() {\n        System.out.println(\"My Message\");  // Noncompliant, output directly to System.out without a logger\n        IO.println(\"Second Message\"); // Noncompliant, same problem, but using Java 25 API.\n      }\n    }\n    </pre>\n    <p>Could be replaced by:</p>\n    <pre data-diff-id=\"1\" data-diff-type=\"compliant\">\n    import java.util.logging.Logger;\n    \n    class MyClass {\n    \n      Logger logger = Logger.getLogger(getClass().getName());\n    \n      public void doSomething() {\n        logger.info(\"My Message\");  // Compliant, output via logger\n        logger.info(\"Second Message\");  // Compliant, output via logger\n      }\n    }\n    </pre>\"\"\";\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/EmbeddedServerMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\nimport java.util.List;\nimport org.eclipse.jetty.http.HttpStatus;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static com.github.tomakehurst.wiremock.common.Strings.randomAlphabetic;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.Mockito.when;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.EMBEDDED_SERVER;\n\nclass EmbeddedServerMediumTests {\n\n  @SonarLintTest\n  void it_should_return_the_ide_name_and_empty_description_if_the_origin_is_not_trusted(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend().withBackendCapability(EMBEDDED_SERVER).withClientName(\"ClientName\").start(fakeClient);\n\n    var embeddedServerPort = backend.getEmbeddedServerPort();\n    var request = HttpRequest.newBuilder()\n      .uri(URI.create(\"http://localhost:\" + embeddedServerPort + \"/sonarlint/api/status\"))\n      .header(\"Origin\", \"https://untrusted\")\n      .GET().build();\n    var response = java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n\n    assertThat(response)\n      .extracting(HttpResponse::statusCode, HttpResponse::body)\n      .containsExactly(HttpStatus.OK_200, \"{\\\"ideName\\\":\\\"ClientName\\\",\\\"description\\\":\\\"\\\",\\\"needsToken\\\":true,\\\"capabilities\\\":{\\\"canOpenFixSuggestion\\\":true}}\");\n    assertCspResponseHeader(response, embeddedServerPort);\n  }\n\n  @SonarLintTest\n  void it_should_not_trust_origin_having_known_connection_prefix(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var fakeClient = harness.newFakeClient().build();\n    when(fakeClient.getClientLiveDescription()).thenReturn(\"WorkspaceTitle\");\n\n    var backend = harness.newBackend().withBackendCapability(EMBEDDED_SERVER).withClientName(\"ClientName\").withSonarQubeConnection(\"connectionId\", \"https://sonar.my\").start(fakeClient);\n\n    var embeddedServerPort = backend.getEmbeddedServerPort();\n    var request = HttpRequest.newBuilder()\n      .uri(URI.create(\"http://localhost:\" + embeddedServerPort + \"/sonarlint/api/status\"))\n      .header(\"Origin\", \"https://sonar\")\n      .GET().build();\n    var response = java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n\n    assertThat(response)\n      .extracting(HttpResponse::statusCode, HttpResponse::body)\n      .containsExactly(HttpStatus.OK_200, \"{\\\"ideName\\\":\\\"ClientName\\\",\\\"description\\\":\\\"\\\",\\\"needsToken\\\":true,\\\"capabilities\\\":{\\\"canOpenFixSuggestion\\\":true}}\");\n    assertCspResponseHeader(response, embeddedServerPort);\n  }\n\n  @SonarLintTest\n  void it_should_return_the_ide_name_and_full_description_if_the_origin_is_trusted(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var fakeClient = harness.newFakeClient().build();\n    when(fakeClient.getClientLiveDescription()).thenReturn(\"WorkspaceTitle\");\n\n    var backend = harness.newBackend().withBackendCapability(EMBEDDED_SERVER).withClientName(\"ClientName\").withSonarQubeConnection(\"connectionId\", \"https://sonar.my\").start(fakeClient);\n\n    var embeddedServerPort = backend.getEmbeddedServerPort();\n    var request = HttpRequest.newBuilder()\n      .uri(URI.create(\"http://localhost:\" + embeddedServerPort + \"/sonarlint/api/status\"))\n      .header(\"Origin\", \"https://sonar.my\")\n      .GET().build();\n    var response = java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n\n    assertThat(response)\n      .extracting(HttpResponse::statusCode, HttpResponse::body)\n      .containsExactly(HttpStatus.OK_200, \"{\\\"ideName\\\":\\\"ClientName\\\",\\\"description\\\":\\\"WorkspaceTitle\\\",\\\"needsToken\\\":false,\\\"capabilities\\\":{\\\"canOpenFixSuggestion\\\":true}}\");\n    assertCspResponseHeader(response, embeddedServerPort);\n  }\n\n  private void assertCspResponseHeader(HttpResponse<String> response, int embeddedServerPort) {\n    assertThat(response.headers().map().get(\"Content-Security-Policy-Report-Only\"))\n      .contains(\"connect-src 'self' http://localhost:\" + embeddedServerPort + \";\");\n  }\n\n  @SonarLintTest\n  void it_should_set_preflight_response_accordingly_when_receiving_preflight_request(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var fakeClient = harness.newFakeClient().build();\n    when(fakeClient.getClientLiveDescription()).thenReturn(\"WorkspaceTitle\");\n\n    var backend = harness.newBackend().withBackendCapability(EMBEDDED_SERVER).withClientName(\"ClientName\").withSonarQubeConnection(\"connectionId\", \"http://sonar.my\").start(fakeClient);\n\n    var request = HttpRequest.newBuilder()\n      .method(\"OPTIONS\", HttpRequest.BodyPublishers.noBody())\n      .uri(URI.create(\"http://localhost:\" + backend.getEmbeddedServerPort() + \"/sonarlint/api/token\"))\n      .header(\"Origin\", \"http://sonar.my\")\n      .build();\n    var response = java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n\n    assertThat(response.headers().map())\n      .extracting(\"access-control-allow-methods\", \"access-control-allow-origin\", \"access-control-allow-private-network\")\n      .containsExactly(List.of(\"GET, POST, OPTIONS\"), List.of(\"http://sonar.my\"), List.of(\"true\"));\n    assertThat(response.statusCode()).isEqualTo(HttpStatus.OK_200);\n    assertThat(response.headers().map()).doesNotContainKey(\"Content-Security-Policy-Report-Only\");\n  }\n\n  @SonarLintTest\n  void it_should_receive_bad_request_response_if_not_right_method(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var fakeClient = harness.newFakeClient().build();\n    when(fakeClient.getClientLiveDescription()).thenReturn(\"WorkspaceTitle\");\n\n    var backend = harness.newBackend().withBackendCapability(EMBEDDED_SERVER).withClientName(\"ClientName\").withSonarQubeConnection(\"connectionId\", \"https://sonar.my\").start(fakeClient);\n\n    var embeddedServerPort = backend.getEmbeddedServerPort();\n    var requestToken = HttpRequest.newBuilder()\n      .uri(URI.create(\"http://localhost:\" + embeddedServerPort + \"/sonarlint/api/token\"))\n      .header(\"Origin\", \"https://sonar.my\")\n      .GET().build();\n    var requestStatus = HttpRequest.newBuilder()\n      .uri(URI.create(\"http://localhost:\" + embeddedServerPort + \"/sonarlint/api/status\"))\n      .header(\"Origin\", \"https://sonar.my\")\n      .DELETE().build();\n    var responseToken = java.net.http.HttpClient.newHttpClient().send(requestToken, HttpResponse.BodyHandlers.ofString());\n    var responseStatus = java.net.http.HttpClient.newHttpClient().send(requestStatus, HttpResponse.BodyHandlers.ofString());\n\n    assertThat(responseToken.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST_400);\n    assertThat(responseStatus.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST_400);\n    assertThat(responseToken.headers().map()).doesNotContainKey(\"Content-Security-Policy-Report-Only\");\n    assertThat(responseStatus.headers().map()).doesNotContainKey(\"Content-Security-Policy-Report-Only\");\n  }\n\n  @SonarLintTest\n  void it_should_rate_limit_origin_if_too_many_requests(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend().withBackendCapability(EMBEDDED_SERVER).withClientName(\"ClientName\").start(fakeClient);\n\n    var embeddedServerPort = backend.getEmbeddedServerPort();\n    var request = HttpRequest.newBuilder()\n      .uri(URI.create(\"http://localhost:\" + embeddedServerPort + \"/sonarlint/api/status\"))\n      .header(\"Origin\", randomAlphabetic(10))\n      .GET().build();\n    for (int i = 0; i < 15; i++) {\n      java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n    }\n    var response = java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n\n    assertThat(response)\n      .extracting(HttpResponse::statusCode, HttpResponse::body)\n      .containsExactly(HttpStatus.TOO_MANY_REQUESTS_429, \"\");\n  }\n\n  @SonarLintTest\n  void it_should_not_allow_request_if_origin_is_missing(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend().withBackendCapability(EMBEDDED_SERVER).withClientName(\"ClientName\").start(fakeClient);\n\n    var embeddedServerPort = backend.getEmbeddedServerPort();\n    var request = HttpRequest.newBuilder()\n      .uri(URI.create(\"http://localhost:\" + embeddedServerPort + \"/sonarlint/api/status\"))\n      .GET().build();\n    var response = java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n\n    assertThat(response)\n      .extracting(HttpResponse::statusCode, HttpResponse::body)\n      .containsExactly(HttpStatus.BAD_REQUEST_400, \"\");\n  }\n\n  @SonarLintTest\n  void it_should_not_rate_limit_over_time(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend().withBackendCapability(EMBEDDED_SERVER).withClientName(\"ClientName\").start(fakeClient);\n\n    var embeddedServerPort = backend.getEmbeddedServerPort();\n    var request = HttpRequest.newBuilder()\n      .uri(URI.create(\"http://localhost:\" + embeddedServerPort + \"/sonarlint/api/status\"))\n      .header(\"Origin\", randomAlphabetic(10))\n      .GET().build();\n    for (int i = 0; i < 15; i++) {\n      java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n    }\n    await().atMost(Duration.ofSeconds(15)).untilAsserted(() -> {\n      var response = java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n      assertThat(response.statusCode()).isEqualTo(HttpStatus.OK_200);\n    });\n  }\n\n  @SonarLintTest\n  void it_should_notify_client_when_started(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend().withBackendCapability(EMBEDDED_SERVER).withClientName(\"ClientName\").start(fakeClient);\n\n    await().atMost(Duration.ofSeconds(5)).untilAsserted(() -> {\n      var startedPort = fakeClient.getEmbeddedServerPort();\n      assertThat(startedPort).isGreaterThan(0);\n      assertThat(startedPort).isEqualTo(backend.getEmbeddedServerPort());\n    });\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/InitializationMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest;\n\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.util.Set;\nimport java.util.concurrent.ExecutionException;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.ClientConstantInfoDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.HttpConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.TelemetryClientConstantAttributesDto;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static java.util.Collections.emptyList;\nimport static java.util.Collections.emptyMap;\nimport static java.util.Collections.emptySet;\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass InitializationMediumTests {\n\n  @SonarLintTest\n  void it_should_fail_to_initialize_the_backend_twice(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .start();\n    var telemetryInitDto = new TelemetryClientConstantAttributesDto(\"mediumTests\", \"mediumTests\", \"1.2.3\", \"4.5.6\", emptyMap());\n    var future = backend\n      .initialize(new InitializeParams(new ClientConstantInfoDto(\"name\", \"productKey\"), telemetryInitDto,\n        HttpConfigurationDto.defaultConfig(), null, Set.of(),\n        Path.of(\"unused\"), Path.of(\"unused\"),\n        emptySet(), emptyMap(), emptySet(), emptySet(), emptySet(),\n        emptyList(), emptyList(), \"home\", emptyMap(), false, null, false, null));\n\n    assertThat(future)\n      .failsWithin(Duration.ofSeconds(1))\n      .withThrowableOfType(ExecutionException.class)\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class)\n      .withMessage(\"Backend already initialized\");\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/MCPServerConfigurationProviderMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest;\n\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.TimeUnit;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.GetMCPServerConfigurationParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.GetMCPServerConfigurationResponse;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThat;\n\nclass MCPServerConfigurationProviderMediumTests {\n\n  @SonarLintTest\n  void should_throw_when_connection_does_not_exist(SonarLintTestHarness harness) {\n    var connectionId = \"nonExistingConnection\";\n    var token = \"nonExistingToken\";\n    var backend = harness.newBackend()\n      .start();\n\n    var fileContents = getSettings(backend, connectionId, token);\n\n    assertThat(fileContents).failsWithin(1, TimeUnit.SECONDS);\n  }\n\n  @SonarLintTest\n  void should_return_sonarcloud_config_for_sonarcloud_eu_connection(SonarLintTestHarness harness) throws Exception {\n    var connectionId = \"scConnection\";\n    var organizationKey = \"myOrg\";\n    var token = \"token123\";\n    var embeddedServerPort = 0;\n\n    var server = harness.newFakeSonarCloudServer().start();\n    var cloudUrl = server.baseUrl().replaceAll(\"/$\", \"\");\n\n    var expectedSettings = String.format(\"\"\"\n      {\n        \"command\": \"docker\",\n        \"args\": [\n          \"run\",\n          \"-i\",\n          \"--rm\",\n          \"-e\",\n          \"SONARQUBE_TOKEN\",\n          \"-e\",\n          \"SONARQUBE_ORG\",\n          \"-e\",\n          \"SONARQUBE_CLOUD_URL\",\n          \"-e\",\n          \"SONARQUBE_IDE_PORT\",\n          \"mcp/sonarqube\"\n        ],\n        \"env\": {\n          \"SONARQUBE_ORG\": \"%s\",\n          \"SONARQUBE_CLOUD_URL\": \"%s\",\n          \"SONARQUBE_TOKEN\": \"%s\",\n          \"SONARQUBE_IDE_PORT\": \"%s\"\n        }\n      }\n      \"\"\", organizationKey, cloudUrl, token, embeddedServerPort);\n\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarCloudConnection(connectionId, organizationKey)\n      .withTelemetryEnabled()\n      .start();\n\n    var result = getSettings(backend, connectionId, token);\n\n    assertThat(result).succeedsWithin(3, TimeUnit.SECONDS);\n    assertThat(result.get().getJsonConfiguration()).isEqualTo(expectedSettings);\n    assertThat(backend.telemetryFileContent().getMcpServerConfigurationRequestedCount()).isEqualTo(1);\n  }\n\n  @SonarLintTest\n  void should_return_sonarcloud_config_for_sonarcloud_us_connection(SonarLintTestHarness harness) throws Exception {\n    var connectionId = \"scUsConnection\";\n    var organizationKey = \"myOrg\";\n    var token = \"token123\";\n    var embeddedServerPort = 0;\n\n    var server = harness.newFakeSonarCloudServer().start();\n    var cloudUrl = server.baseUrl().replaceAll(\"/$\", \"\");\n\n    var expectedSettings = String.format(\"\"\"\n      {\n        \"command\": \"docker\",\n        \"args\": [\n          \"run\",\n          \"-i\",\n          \"--rm\",\n          \"-e\",\n          \"SONARQUBE_TOKEN\",\n          \"-e\",\n          \"SONARQUBE_ORG\",\n          \"-e\",\n          \"SONARQUBE_CLOUD_URL\",\n          \"-e\",\n          \"SONARQUBE_IDE_PORT\",\n          \"mcp/sonarqube\"\n        ],\n        \"env\": {\n          \"SONARQUBE_ORG\": \"%s\",\n          \"SONARQUBE_CLOUD_URL\": \"%s\",\n          \"SONARQUBE_TOKEN\": \"%s\",\n          \"SONARQUBE_IDE_PORT\": \"%s\"\n        }\n      }\n      \"\"\", organizationKey, cloudUrl, token, embeddedServerPort);\n\n    var backend = harness.newBackend()\n      .withSonarQubeCloudUsRegionUri(server.baseUrl())\n      .withSonarCloudConnection(connectionId, organizationKey, \"US\")\n      .withTelemetryEnabled()\n      .start();\n\n    var result = getSettings(backend, connectionId, token);\n\n    assertThat(result).succeedsWithin(3, TimeUnit.SECONDS);\n    assertThat(result.get().getJsonConfiguration()).isEqualTo(expectedSettings);\n    assertThat(backend.telemetryFileContent().getMcpServerConfigurationRequestedCount()).isEqualTo(1);\n  }\n\n  @SonarLintTest\n  void should_return_sonarqube_config_for_sonarqube_connection(SonarLintTestHarness harness) throws Exception {\n    var connectionId = \"scConnection\";\n    var organizationKey = \"myOrg\";\n    var connectionId2 = \"sqConnection\";\n    var serverUrl = \"http://my-sonarqube\";\n    var token = \"token123\";\n    var embeddedServerPort = 0;\n\n    var expectedSettings = String.format(\"\"\"\n      {\n        \"command\": \"docker\",\n        \"args\": [\n          \"run\",\n          \"-i\",\n          \"--rm\",\n          \"-e\",\n          \"SONARQUBE_TOKEN\",\n          \"-e\",\n          \"SONARQUBE_URL\",\n          \"-e\",\n          \"SONARQUBE_IDE_PORT\",\n          \"mcp/sonarqube\"\n        ],\n        \"env\": {\n          \"SONARQUBE_URL\": \"%s\",\n          \"SONARQUBE_TOKEN\": \"%s\",\n          \"SONARQUBE_IDE_PORT\": \"%s\"\n        }\n      }\n      \"\"\", serverUrl, token, embeddedServerPort);\n\n    var server = harness.newFakeSonarCloudServer().start();\n\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarCloudConnection(connectionId, organizationKey)\n      .withSonarQubeConnection(connectionId2, serverUrl)\n      .withTelemetryEnabled()\n      .start();\n\n    var result = getSettings(backend, connectionId2, token);\n\n    assertThat(result).succeedsWithin(3, TimeUnit.SECONDS);\n    assertThat(result.get().getJsonConfiguration()).isEqualTo(expectedSettings);\n    assertThat(backend.telemetryFileContent().getMcpServerConfigurationRequestedCount()).isEqualTo(1);\n  }\n\n  private CompletableFuture<GetMCPServerConfigurationResponse> getSettings(SonarLintTestRpcServer backend, String connectionId, String token) {\n    return backend.getConnectionService().getMCPServerConfiguration(new GetMCPServerConfigurationParams(connectionId, token));\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/NotebookLanguageMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidChangeCredentialsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.test.utils.server.ServerFixture.newSonarQubeServer;\n\nclass NotebookLanguageMediumTests {\n\n  private static final String CONNECTION_ID = StringUtils.repeat(\"very-long-id\", 30);\n  private static final String JAVA_MODULE_KEY = \"test-project-2\";\n  public static final String SCOPE_ID = \"scopeId\";\n\n  @SonarLintTest\n  void should_not_enable_sync_for_notebook_python_language(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient()\n      .build();\n    var server = newSonarQubeServer()\n      .withProject(JAVA_MODULE_KEY, project -> project.withBranch(\"main\"))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server, storage -> storage\n        .withPlugin(TestPlugin.JAVA)\n        .withPlugin(TestPlugin.JAVASCRIPT)\n        .withPlugin(TestPlugin.PYTHON)\n        .withProject(JAVA_MODULE_KEY))\n      .withBoundConfigScope(SCOPE_ID, CONNECTION_ID, JAVA_MODULE_KEY)\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withEnabledLanguageInStandaloneMode(Language.JS)\n      .withEnabledLanguageInStandaloneMode(Language.IPYTHON)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(fakeClient);\n\n    backend.getConnectionService().didChangeCredentials(new DidChangeCredentialsParams(CONNECTION_ID));\n\n    await().untilAsserted(() -> assertThat(fakeClient.getLogMessages()).contains(\"[SYNC] Languages enabled for synchronization: [java, js]\"));\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/SharedConnectedModeSettingsMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest;\n\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport org.apache.commons.lang3.Strings;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.binding.GetSharedConnectedModeConfigFileParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.binding.GetSharedConnectedModeConfigFileResponse;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SharedConnectedModeSettingsMediumTests {\n\n  @SonarLintTest\n  void should_throw_when_not_bound(SonarLintTestHarness harness) {\n    var configScopeId = \"file:///my/folder\";\n    var backend = harness.newBackend()\n      .start();\n\n    var fileContents = getFileContents(backend, configScopeId);\n\n    assertThat(fileContents).failsWithin(1, TimeUnit.SECONDS);\n  }\n\n  @SonarLintTest\n  void should_return_sc_config_when_bound_to_sonarcloud(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    var configScopeId = \"file:///my/workspace/folder\";\n    var connectionId = \"scConnection\";\n    var organizationKey = \"myOrg\";\n    var projectKey = \"projectKey\";\n\n    var expectedFileContent = String.format(\"\"\"\n      {\n          \"sonarCloudOrganization\": \"%s\",\n          \"projectKey\": \"%s\",\n          \"region\": \"EU\"\n      }\"\"\", organizationKey, projectKey);\n\n    var server = harness.newFakeSonarCloudServer().start();\n\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarCloudConnection(connectionId, organizationKey)\n      .withBoundConfigScope(configScopeId, connectionId, projectKey)\n      .withTelemetryEnabled()\n      .start();\n\n    var result = getFileContents(backend, configScopeId);\n\n    assertThat(result).succeedsWithin(3, TimeUnit.SECONDS);\n    assertThat(result.get().getJsonFileContent()).isEqualTo(expectedFileContent);\n    assertThat(backend.telemetryFileContent().getExportedConnectedModeCount()).isEqualTo(1);\n  }\n\n  @SonarLintTest\n  void should_return_wrong_sc_config_when_bound_to_sonarcloud_us(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    var configScopeId = \"file:///my/workspace/folder\";\n    var connectionId = \"scConnection\";\n    var organizationKey = \"myOrg\";\n    var projectKey = \"projectKey\";\n\n    var expectedFileContent = String.format(\"\"\"\n      {\n          \"sonarCloudOrganization\": \"%s\",\n          \"projectKey\": \"%s\",\n          \"region\": \"US\"\n      }\"\"\", organizationKey, projectKey);\n\n    var server = harness.newFakeSonarCloudServer().start();\n\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarCloudConnection(connectionId, organizationKey, \"US\")\n      .withBoundConfigScope(configScopeId, connectionId, projectKey)\n      .withTelemetryEnabled()\n      .start();\n\n    var result = getFileContents(backend, configScopeId);\n\n    assertThat(result).succeedsWithin(3, TimeUnit.SECONDS);\n    assertThat(result.get().getJsonFileContent()).isEqualTo(expectedFileContent);\n    assertThat(backend.telemetryFileContent().getExportedConnectedModeCount()).isEqualTo(1);\n  }\n\n  @SonarLintTest\n  void should_return_sq_config_when_bound_to_sonarqube(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    var configScopeId = \"file:///my/workspace/folder\";\n    var connectionId = \"scConnection\";\n    var projectKey = \"projectKey\";\n\n    var server = harness.newFakeSonarQubeServer().start();\n\n    var expectedFileContent = String.format(\"\"\"\n      {\n          \"sonarQubeUri\": \"%s\",\n          \"projectKey\": \"%s\"\n      }\"\"\", Strings.CS.removeEnd(server.baseUrl(), \"/\"), projectKey);\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(connectionId, server)\n      .withBoundConfigScope(configScopeId, connectionId, projectKey)\n      .withTelemetryEnabled()\n      .start();\n\n    var result = getFileContents(backend, configScopeId);\n\n    assertThat(result).succeedsWithin(3, TimeUnit.SECONDS);\n    assertThat(result.get().getJsonFileContent()).isEqualTo(expectedFileContent);\n    assertThat(backend.telemetryFileContent().getExportedConnectedModeCount()).isEqualTo(1);\n  }\n\n  private CompletableFuture<GetSharedConnectedModeConfigFileResponse> getFileContents(SonarLintTestRpcServer backend, String configScopeId) {\n    return backend.getBindingService()\n      .getSharedConnectedModeConfigFileContents(\n        new GetSharedConnectedModeConfigFileParams(configScopeId));\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/SonarLintTestHarnessMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.CompletionException;\nimport java.util.logging.Handler;\nimport java.util.logging.Level;\nimport java.util.logging.LogRecord;\nimport java.util.logging.Logger;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.sonarsource.sonarlint.core.rpc.client.SonarLintRpcClientDelegate;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport org.sonarsource.sonarlint.core.test.utils.server.ServerFixture;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass SonarLintTestHarnessTest {\n\n  private SonarLintTestHarness harness;\n  private TestLogHandler logHandler;\n\n  @BeforeEach\n  void setUp() {\n    harness = new SonarLintTestHarness();\n\n    var testLogger = Logger.getLogger(SonarLintTestHarness.class.getName());\n    logHandler = new TestLogHandler();\n    testLogger.addHandler(logHandler);\n    testLogger.setLevel(Level.ALL);\n  }\n\n  @Test\n  void should_shutdown_normally() throws IOException {\n    SonarLintTestRpcServer backend = new TestBackend(mock(SonarLintRpcClientDelegate.class), CompletableFuture.completedFuture(null));\n    harness.addBackend(backend);\n    TestServer server = new TestServer();\n    harness.addServer(server);\n\n    harness.afterEach(emptyContext());\n\n    assertThat(harness.getBackends()).isEmpty();\n    assertThat(harness.getServers()).isEmpty();\n    assertThat(server.isShutdownCalled()).isTrue();\n  }\n\n  @Test\n  void should_handle_exceptionally_callback() throws IOException {\n    CompletableFuture<Void> failingFuture = new CompletableFuture<>();\n    failingFuture.completeExceptionally(new RuntimeException(\"Simulated exception\"));\n    SonarLintTestRpcServer backend = new TestBackend(mock(SonarLintRpcClientDelegate.class), failingFuture);\n    harness.addBackend(backend);\n    TestServer server = new TestServer();\n    harness.addServer(server);\n\n    harness.afterEach(emptyContext());\n\n    assertThat(harness.getBackends()).isEmpty();\n    assertThat(harness.getServers()).isEmpty();\n    assertThat(server.isShutdownCalled()).isTrue();\n    assertThat(logHandler.getRecords()).anySatisfy(logRecord -> {\n      assertThat(logRecord.getLevel()).isEqualTo(Level.WARNING);\n      assertThat(logRecord.getMessage()).contains(\"Error shutting down backend\");\n      assertThat(logRecord.getThrown()).isNotNull();\n    });\n  }\n\n  @Test\n  void should_handle_catch_block_exceptions() throws IOException {\n    SonarLintTestRpcServer backend1 = new ThrowingBackend(mock(SonarLintRpcClientDelegate.class),\n      new CompletionException(\"Simulated completion exception\", new RuntimeException()));\n    SonarLintTestRpcServer backend2 = new ThrowingBackend(mock(SonarLintRpcClientDelegate.class), new IllegalStateException(\"Simulated illegal state exception\"));\n    harness.addBackend(backend1);\n    harness.addBackend(backend2);\n    TestServer server = new TestServer();\n    harness.addServer(server);\n\n    harness.afterEach(emptyContext());\n\n    assertThat(harness.getBackends()).isEmpty();\n    assertThat(harness.getServers()).isEmpty();\n    assertThat(server.isShutdownCalled()).isTrue();\n    assertThat(logHandler.getRecords()).anySatisfy(logRecord -> {\n      assertThat(logRecord.getLevel()).isEqualTo(Level.WARNING);\n      assertThat(logRecord.getMessage()).contains(\"Failed to shutdown backend\");\n      assertThat(logRecord.getThrown()).isInstanceOf(CompletionException.class);\n    });\n    assertThat(logHandler.getRecords()).anySatisfy(logRecord -> {\n      assertThat(logRecord.getLevel()).isEqualTo(Level.WARNING);\n      assertThat(logRecord.getMessage()).contains(\"Failed to shutdown backend\");\n      assertThat(logRecord.getThrown()).isInstanceOf(IllegalStateException.class);\n    });\n  }\n\n  @Test\n  void should_handle_server_exceptions() throws IOException {\n    SonarLintTestRpcServer testBackend = new TestBackend(mock(SonarLintRpcClientDelegate.class), CompletableFuture.completedFuture(null));\n    harness.addBackend(testBackend);\n    ServerFixture.Server throwingServer1 = new ThrowingTestServer(new RuntimeException(\"Server 1 shutdown error\"));\n    ServerFixture.Server throwingServer2 = new ThrowingTestServer(new RuntimeException(\"Server 2 shutdown error\"));\n    harness.addServer(throwingServer1);\n    harness.addServer(throwingServer2);\n\n    harness.afterEach(emptyContext());\n\n    assertThat(harness.getBackends()).isEmpty();\n    assertThat(harness.getServers()).isEmpty();\n    assertThat(logHandler.getRecords()).anySatisfy(logRecord -> {\n      assertThat(logRecord.getLevel()).isEqualTo(Level.WARNING);\n      assertThat(logRecord.getMessage()).contains(\"Failed to shutdown server\");\n      assertThat(logRecord.getThrown()).isInstanceOf(RuntimeException.class);\n      assertThat(logRecord.getThrown().getMessage()).contains(\"Server 1 shutdown error\");\n    });\n    assertThat(logHandler.getRecords()).anySatisfy(logRecord -> {\n      assertThat(logRecord.getLevel()).isEqualTo(Level.WARNING);\n      assertThat(logRecord.getMessage()).contains(\"Failed to shutdown server\");\n      assertThat(logRecord.getThrown()).isInstanceOf(RuntimeException.class);\n      assertThat(logRecord.getThrown().getMessage()).contains(\"Server 2 shutdown error\");\n    });\n  }\n\n  @Test\n  void should_handle_multiple_backends_and_servers() throws IOException {\n    SonarLintTestRpcServer backend1 = new TestBackend(mock(SonarLintRpcClientDelegate.class), CompletableFuture.completedFuture(null));\n    CompletableFuture<Void> failingFuture = new CompletableFuture<>();\n    failingFuture.completeExceptionally(new RuntimeException(\"Backend 2 error\"));\n    SonarLintTestRpcServer backend2 = new TestBackend(mock(SonarLintRpcClientDelegate.class), failingFuture);\n    SonarLintTestRpcServer backend3 = new ThrowingBackend(mock(SonarLintRpcClientDelegate.class), new IllegalStateException(\"Backend 3 error\"));\n    harness.addBackend(backend1);\n    harness.addBackend(backend2);\n    harness.addBackend(backend3);\n    TestServer server1 = new TestServer();\n    ServerFixture.Server server2 = new ThrowingTestServer(new RuntimeException(\"Server 2 error\"));\n    harness.addServer(server1);\n    harness.addServer(server2);\n\n    harness.afterEach(emptyContext());\n\n    assertThat(harness.getBackends()).isEmpty();\n    assertThat(harness.getServers()).isEmpty();\n    assertThat(server1.isShutdownCalled()).isTrue();\n    assertThat(logHandler.getRecords()).anySatisfy(logRecord -> assertThat(logRecord.getMessage()).contains(\"Error shutting down backend\"));\n    assertThat(logHandler.getRecords()).anySatisfy(logRecord -> assertThat(logRecord.getMessage()).contains(\"Failed to shutdown backend\"));\n    assertThat(logHandler.getRecords()).anySatisfy(logRecord -> assertThat(logRecord.getMessage()).contains(\"Failed to shutdown server\"));\n  }\n\n  private static ExtensionContext emptyContext() {\n    var context = mock(ExtensionContext.class);\n    when(context.getTestMethod()).thenReturn(Optional.empty());\n    return context;\n  }\n\n  static class TestLogHandler extends Handler {\n    private final List<LogRecord> logRecords = new java.util.ArrayList<>();\n\n    @Override\n    public void publish(LogRecord logRecord) {\n      logRecords.add(logRecord);\n    }\n\n    @Override\n    public void flush() {\n    }\n\n    @Override\n    public void close() throws SecurityException {\n    }\n\n    public List<LogRecord> getRecords() {\n      return logRecords;\n    }\n  }\n\n  private static class TestBackend extends SonarLintTestRpcServer {\n    private final CompletableFuture<Void> shutdownFuture;\n\n    TestBackend(SonarLintRpcClientDelegate client, CompletableFuture<Void> shutdownFuture) throws IOException {\n      super(client);\n      this.shutdownFuture = shutdownFuture;\n    }\n\n    @Override\n    public CompletableFuture<Void> shutdown() {\n      return shutdownFuture;\n    }\n  }\n\n  private static class ThrowingBackend extends SonarLintTestRpcServer {\n    private final RuntimeException exceptionToThrow;\n\n    ThrowingBackend(SonarLintRpcClientDelegate client, RuntimeException exceptionToThrow) throws IOException {\n      super(client);\n      this.exceptionToThrow = exceptionToThrow;\n    }\n\n    @Override\n    public CompletableFuture<Void> shutdown() {\n      throw exceptionToThrow;\n    }\n  }\n\n  static class TestServer extends ServerFixture.Server {\n    private boolean shutdownCalled = false;\n\n    public TestServer() {\n      super(null, null, null, null, null, null, null, null, null, false, null, null, null, null);\n    }\n\n    @Override\n    public void shutdown() {\n      shutdownCalled = true;\n    }\n\n    public boolean isShutdownCalled() {\n      return shutdownCalled;\n    }\n  }\n\n  static class ThrowingTestServer extends ServerFixture.Server {\n    private final RuntimeException exceptionToThrow;\n\n    ThrowingTestServer(RuntimeException exceptionToThrow) {\n      super(null, null, null, null, null, null, null, null, null, false, null, null, null, null);\n      this.exceptionToThrow = exceptionToThrow;\n    }\n\n    @Override\n    public void shutdown() {\n      throw exceptionToThrow;\n    }\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/StandaloneIssueMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.EnumSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.CompletionException;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport org.apache.commons.io.FileUtils;\nimport org.assertj.core.groups.Tuple;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.GetEffectiveIssueDetailsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleDefinitionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.StandaloneRuleConfigDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.UpdateStandaloneRulesConfigurationParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.FileEditDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.QuickFixDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.TextEditDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static java.util.Collections.emptyMap;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.C;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JAVA;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JS;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.KOTLIN;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.PHP;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.PYTHON;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.TS;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.XML;\nimport static utils.AnalysisUtils.analyzeFileAndGetIssues;\nimport static utils.AnalysisUtils.analyzeFilesAndVerifyNoIssues;\nimport static utils.AnalysisUtils.createFile;\n\nclass StandaloneIssueMediumTests {\n  private static final String A_JAVA_FILE_PATH = \"Foo.java\";\n  private static final String CONFIGURATION_SCOPE_ID = \"configScopeId\";\n  // commercial plugins might not be available\n  // (if you pass -Dcommercial to maven, a profile will be activated that downloads the commercial plugins)\n  private static final boolean COMMERCIAL_ENABLED = System.getProperty(\"commercial\") != null;\n\n  @SonarLintTest\n  void simpleJavaScript(SonarLintTestHarness harness, @TempDir Path baseDir) throws Exception {\n    var content = \"\"\"\n      function foo() {\n        let x;\n        let y; //NOSONAR\n      }\"\"\";\n    var inputFile = createFile(baseDir, \"foo.js\", content);\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVASCRIPT)\n      .start(client);\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    assertThat(issues)\n      .extracting(RaisedIssueDto::getRuleKey, i -> i.getTextRange().getStartLine(), RaisedIssueDto::getRuleDescriptionContextKey, StandaloneIssueMediumTests::extractMqrDetails)\n      .containsOnly(tuple(\"javascript:S1481\", 2, null, tuple(CleanCodeAttribute.CLEAR, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW)))));\n    client.cleanRaisedIssues();\n\n    // SLCORE-160\n    var nodeModulesDir = Files.createDirectory(baseDir.resolve(\"node_modules\"));\n\n    inputFile = createFile(nodeModulesDir, \"foo.js\", content);\n\n    analyzeFilesAndVerifyNoIssues(List.of(inputFile.toUri()), client, backend, CONFIGURATION_SCOPE_ID);\n  }\n\n  // looks like we don't pass global settings to init params, only exclusion is omnisharp params\n  // to be checked if we need this functionality back, it will require to modify init params\n  @SonarLintTest\n  void sonarjs_should_honor_global_and_analysis_level_properties(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var content = \"\"\"\n      function foo() {\n        console.log(LOCAL1); // Noncompliant\n        console.log(GLOBAL1); // GLOBAL1 defined as global variable in global settings\n      }\"\"\";\n    var inputFile = createFile(baseDir, \"foo.js\", content);\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVASCRIPT)\n\n      .start(client);\n    backend.getRulesService()\n      .updateStandaloneRulesConfiguration(new UpdateStandaloneRulesConfigurationParams(Map.of(\"javascript:S3827\", new StandaloneRuleConfigDto(true, emptyMap()))));\n\n    var analysisId = UUID.randomUUID();\n    var analysisResult = backend.getAnalysisService().analyzeFilesAndTrack(\n      new AnalyzeFilesAndTrackParams(CONFIGURATION_SCOPE_ID, analysisId, List.of(inputFile.toUri()), Map.of(\"sonar.javascript.globals\", \"LOCAL1\"), true,\n        System.currentTimeMillis()))\n      .join();\n    assertThat(analysisResult.getFailedAnalysisFiles()).isEmpty();\n    await().atMost(20, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIGURATION_SCOPE_ID)).isNotEmpty());\n    var issues = client.getRaisedIssuesForScopeIdAsList(CONFIGURATION_SCOPE_ID);\n    assertThat(issues).hasSize(1);\n    var issue = issues.get(0);\n    assertThat(issue.getTextRange().getStartLine()).isEqualTo(3);\n  }\n\n  @SonarLintTest\n  void simpleTypeScript(SonarLintTestHarness harness, @TempDir Path baseDir) throws Exception {\n    final var tsConfig = new File(baseDir.toFile(), \"tsconfig.json\");\n    FileUtils.write(tsConfig, \"{}\", StandardCharsets.UTF_8);\n    var tsConfigPath = tsConfig.toPath();\n    var content = \"\"\"\n      function foo() {\n        if(bar() && bar()) { return 42; }\n      }\"\"\";\n    var inputFile = createFile(baseDir, \"foo.ts\", content);\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true),\n        new ClientFileDto(tsConfigPath.toUri(), baseDir.relativize(tsConfigPath), CONFIGURATION_SCOPE_ID, false, null, tsConfigPath, null, null, true)))\n      .build();\n\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVASCRIPT)\n      .start(client);\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, i -> i.getTextRange().getStartLine()).containsOnly(\n      tuple(\"typescript:S1764\", 2));\n  }\n\n  @Disabled(\"https://sonarsource.atlassian.net/browse/SLCORE-873 - plug test YAML plugin\")\n  @SonarLintTest\n  void simpleJavaScriptInYamlFile(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    String content = \"\"\"\n      Resources:\n        LambdaFunction:\n          Type: 'AWS::Lambda::Function'\n          Properties:\n            Code:\n              ZipFile: >\n                exports.handler = function(event, context) {\n                  let x;\n                };\n            Runtime: nodejs8.10\"\"\";\n\n    var inputFile = createFile(baseDir, \"foo.yaml\", content);\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVASCRIPT)\n      .start(client);\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, i -> i.getTextRange().getStartLine()).containsOnly(\n      tuple(\"javascript:S1481\", 8));\n  }\n\n  @SonarLintTest\n  void simpleC(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    assumeTrue(COMMERCIAL_ENABLED);\n    var inputFile = createFile(baseDir, \"foo.c\", \"\"\"\n      #import \"foo.h\"\n      #import \"foo2.h\" //NOSONAR\n      \"\"\");\n    var buildWrapperContent = \"{\\\"version\\\":0,\\\"captures\\\":[\" +\n      \"{\" +\n      \"\\\"compiler\\\": \\\"clang\\\",\" +\n      \"\\\"executable\\\": \\\"compiler\\\",\" +\n      \"\\\"stdout\\\": \\\"#define __STDC_VERSION__ 201112L\\n\\\",\" +\n      \"\\\"stderr\\\": \\\"\\\"\" +\n      \"},\" +\n      \"{\" +\n      \"\\\"compiler\\\": \\\"clang\\\",\" +\n      \"\\\"executable\\\": \\\"compiler\\\",\" +\n      \"\\\"stdout\\\": \\\"#define __cplusplus 201703L\\n\\\",\" +\n      \"\\\"stderr\\\": \\\"\\\"\" +\n      \"},\" +\n      \"{\\\"compiler\\\":\\\"clang\\\",\\\"cwd\\\":\\\"\" +\n      baseDir.toString().replace(\"\\\\\", \"\\\\\\\\\") +\n      \"\\\",\\\"executable\\\":\\\"compiler\\\",\\\"cmd\\\":[\\\"cc\\\",\\\"foo.c\\\"]}]}\";\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.CFAMILY)\n      .start(client);\n\n    var analysisId = UUID.randomUUID();\n    var analysisResult = backend.getAnalysisService().analyzeFilesAndTrack(\n      new AnalyzeFilesAndTrackParams(CONFIGURATION_SCOPE_ID, analysisId, List.of(inputFile.toUri()), Map.of(\"sonar.cfamily.build-wrapper-content\", buildWrapperContent), true,\n        System.currentTimeMillis()))\n      .join();\n    assertThat(analysisResult.getFailedAnalysisFiles()).isEmpty();\n    await().atMost(20, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIGURATION_SCOPE_ID)).isNotEmpty());\n    var issues = client.getRaisedIssuesForScopeIdAsList(CONFIGURATION_SCOPE_ID);\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, i -> i.getTextRange().getStartLine(), i -> i.getTextRange().getStartLineOffset())\n      .containsOnly(\n        tuple(\"c:S3805\", 1, 0));\n  }\n\n  @SonarLintTest\n  void simplePhp(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, \"foo.php\", \"\"\"\n      <?php\n      function writeMsg($fname) {\n          $i = 0; // NOSONAR\n          echo \"Hello world!\";\n      }\n      ?>\n      \"\"\");\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PHP)\n      .start(client);\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    assertThat(issues)\n      .extracting(RaisedIssueDto::getRuleKey, i -> i.getTextRange().getStartLine())\n      .containsOnly(\n        tuple(\"php:S1172\", 2),\n        tuple(\"php:S1780\", 6));\n  }\n\n  @SonarLintTest\n  void fileEncoding(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var content = \"\"\"\n      <?php\n      function writeMsg($fname) {\n          $i = 0; // NOSONAR\n          echo \"Hello world!\";\n      }\n      ?>\n      \"\"\";\n    var inputFile = baseDir.resolve(\"foo.php\");\n    try {\n      Files.writeString(inputFile, content, StandardCharsets.UTF_16);\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, StandardCharsets.UTF_16.name(), inputFile, null, null, true)))\n      .build();\n\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PHP)\n      .start(client);\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, i -> i.getTextRange().getStartLine()).contains(tuple(\"php:S1172\", 2));\n  }\n\n  @SonarLintTest\n  void analysisErrors(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var content = \"\"\"\n      <?php\n      function writeMsg($fname) {\n          echo \"Hello world!;\n      }\n      ?>\"\"\";\n    var inputFile = createFile(baseDir, \"foo.php\", content);\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PHP)\n      .start(client);\n\n    var analysisId = UUID.randomUUID();\n    var analysisResult = backend.getAnalysisService().analyzeFilesAndTrack(\n      new AnalyzeFilesAndTrackParams(CONFIGURATION_SCOPE_ID, analysisId, List.of(inputFile.toUri()), Map.of(), true, System.currentTimeMillis()))\n      .join();\n    assertThat(analysisResult.getFailedAnalysisFiles()).containsExactly(inputFile.toUri());\n    await().during(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIGURATION_SCOPE_ID)).isEmpty());\n  }\n\n  @SonarLintTest\n  void simplePython(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, \"foo.py\", \"\"\"\n      def my_function(name):\n          print \"Hello\"\n          print \"world!\" # NOSONAR\n      \n      \"\"\");\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(client);\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, i -> i.getTextRange().getStartLine()).containsOnly(\n      tuple(\"python:S1172\", 1),\n      tuple(\"python:PrintStatementUsage\", 2));\n  }\n\n  @SonarLintTest\n  void simpleKotlinKts(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, \"settings.gradle.kts\", \"description = \\\"SonarLint for IntelliJ IDEA\\\"\");\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.KOTLIN)\n      .start(client);\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange).containsOnly(\n      tuple(\"kotlin:S6625\", null));\n  }\n\n  // SLCORE-162\n  @SonarLintTest\n  void useRelativePathToEvaluatePathPatterns(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, \"foo.tmp\", \"\"\"\n      def my_function(name):\n          print \"Hello\"\n          print \"world!\" # NOSONAR\n      \n      \"\"\");\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), Path.of(\"foo.py\"), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(client);\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, i -> i.getTextRange().getStartLine()).containsOnly(\n      tuple(\"python:S1172\", 1),\n      tuple(\"python:PrintStatementUsage\", 2));\n  }\n\n  @SonarLintTest\n  void simpleJava(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, A_JAVA_FILE_PATH,\n      \"\"\"\n        public class Foo {\n          public void foo() {\n            int x;\n            System.out.println(\"Foo\");\n            // TODO full line issue\n          }\n        }\"\"\");\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange, StandaloneIssueMediumTests::extractMqrDetails)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S1220\", null, tuple(CleanCodeAttribute.MODULAR, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW)))),\n        tuple(\"java:S1481\", new TextRangeDto(3, 8, 3, 9), tuple(CleanCodeAttribute.CLEAR, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW)))),\n        tuple(\"java:S106\", new TextRangeDto(4, 4, 4, 14), tuple(CleanCodeAttribute.MODULAR, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.MEDIUM)))),\n        tuple(\"java:S1135\", new TextRangeDto(5, 0, 5, 27), tuple(CleanCodeAttribute.COMPLETE, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.INFO)))));\n  }\n\n  @SonarLintTest\n  void simpleJavaSymbolicEngine(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    assumeTrue(COMMERCIAL_ENABLED);\n    var inputFile = createFile(baseDir, A_JAVA_FILE_PATH,\n      \"\"\"\n        public class Foo {\n          public void foo() {\n            boolean a = true;\n            if (a) {\n               System.out.println( \"Hello World!\" );\n            }\n          }\n        }\"\"\");\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)\n      ))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .withStandaloneEmbeddedPlugin(TestPlugin.JAVA_SE)\n      .start(client);\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .contains(\n        tuple(\"java:S2589\", new TextRangeDto(4, 8, 4, 9)));\n  }\n\n  @SonarLintTest\n  void simpleJavaWithQuickFix(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, A_JAVA_FILE_PATH,\n      \"\"\"\n        public class Foo {\n          public void foo() {\n            \\s\n          }\n        }\"\"\");\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange, StandaloneIssueMediumTests::extractMqrDetails)\n      .usingRecursiveFieldByFieldElementComparator()\n      .contains(\n        tuple(\"java:S1186\", new TextRangeDto(2, 14, 2, 17), tuple(CleanCodeAttribute.COMPLETE, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.HIGH)))));\n\n    assertThat(issues)\n      .flatExtracting(RaisedIssueDto::getQuickFixes)\n      .extracting(QuickFixDto::message)\n      .containsOnly(\"Insert placeholder comment\");\n    assertThat(issues)\n      .flatExtracting(RaisedIssueDto::getQuickFixes)\n      .flatExtracting(QuickFixDto::fileEdits)\n      .extracting(FileEditDto::target)\n      .containsOnly(inputFile.toUri());\n    assertThat(issues)\n      .usingRecursiveFieldByFieldElementComparator()\n      .flatExtracting(RaisedIssueDto::getQuickFixes)\n      .flatExtracting(QuickFixDto::fileEdits)\n      .flatExtracting(FileEditDto::textEdits)\n      .extracting(TextEditDto::range, TextEditDto::newText)\n      .containsOnly(\n        tuple(new TextRangeDto(2, 21, 4, 2), \"\\n    // TODO document why this method is empty\\n  \"));\n  }\n\n  @SonarLintTest\n  void simpleJavaWithCommaInClasspath(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, A_JAVA_FILE_PATH,\n      \"\"\"\n        public class Foo {\n          public void foo() {\n            int x;\n          }\n        }\"\"\");\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    var analysisId = UUID.randomUUID();\n    var analysisResult = backend.getAnalysisService().analyzeFilesAndTrack(\n      new AnalyzeFilesAndTrackParams(CONFIGURATION_SCOPE_ID, analysisId, List.of(inputFile.toUri()),\n        Map.of(\"sonar.java.libraries\", \"\\\"\" + Paths.get(\"target/lib/guava,with,comma.jar\").toAbsolutePath() + \"\\\"\"), true, System.currentTimeMillis()))\n      .join();\n    assertThat(analysisResult.getFailedAnalysisFiles()).isEmpty();\n    await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIGURATION_SCOPE_ID)).isNotEmpty());\n    var issues = client.getRaisedIssuesForScopeIdAsList(CONFIGURATION_SCOPE_ID);\n\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange, StandaloneIssueMediumTests::extractMqrDetails)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S1220\", null, tuple(CleanCodeAttribute.MODULAR, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW)))),\n        tuple(\"java:S1481\", new TextRangeDto(3, 8, 3, 9), tuple(CleanCodeAttribute.CLEAR, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW)))));\n  }\n\n  @SonarLintTest\n  void it_should_get_issue_details_for_standalone_issue(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"secret.py\",\n      \"aws_secret_access_key=kHeUAwnSUizTWpSbyGAz4f+As5LshPIjvtpswqGb\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, baseDir,\n        List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIGURATION_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.TEXT)\n      .start(client);\n    var analysisId = UUID.randomUUID();\n\n    backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIGURATION_SCOPE_ID, analysisId, List.of(fileUri), Map.of(), false, System.currentTimeMillis())).join();\n    await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(CONFIGURATION_SCOPE_ID).get(fileUri)).isNotEmpty());\n\n    var issueId = client.getRaisedIssuesForScopeId(CONFIGURATION_SCOPE_ID).get(fileUri).get(0).getId();\n    var result = backend.getIssueService().getEffectiveIssueDetails(new GetEffectiveIssueDetailsParams(CONFIGURATION_SCOPE_ID, issueId)).join();\n\n    assertThat(result.getDetails()).isNotNull();\n    // standalone mode should have Clean Code attribute\n    assertThat(result.getDetails().getSeverityDetails().isRight()).isTrue();\n    assertThat(result.getDetails().getSeverityDetails().getRight().getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.TRUSTWORTHY);\n    assertThat(result.getDetails().getRuleKey()).isEqualTo(\"secrets:S6290\");\n    assertThat(result.getDetails().getName()).isEqualTo(\"Amazon Web Services credentials should not be disclosed\");\n    assertThat(result.getDetails().getRuleDescriptionContextKey()).isNull();\n    assertThat(result.getDetails().getVulnerabilityProbability()).isNull();\n    assertThat(result.getDetails().getDescription().isRight()).isTrue();\n    assertThat(result.getDetails().getDescription().getRight().getIntroductionHtmlContent()).contains(\"Secret leaks often occur\");\n    assertThat(result.getDetails().getLanguage().name()).isEqualTo(\"SECRETS\");\n  }\n\n  @SonarLintTest\n  void it_should_not_get_issue_details_for_non_existent_issue(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"secret.py\",\n      \"KEY = \\\"AKIAIGKECZXA7AEIJLMQ\\\"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, baseDir,\n        List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIGURATION_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.TEXT)\n      .start(client);\n\n    var issueId = UUID.randomUUID();\n    var params = new GetEffectiveIssueDetailsParams(CONFIGURATION_SCOPE_ID, issueId);\n    var issueService = backend.getIssueService();\n    var detailsFuture = issueService.getEffectiveIssueDetails(params);\n    assertThrows(CompletionException.class, detailsFuture::join);\n  }\n\n  // SLCORE-251\n  @SonarLintTest\n  void noRuleTemplates(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(client);\n\n    var response = backend.getRulesService().listAllStandaloneRulesDefinitions().get();\n    assertThat(response.getRulesByKey()).doesNotContainKey(\"python:XPath\");\n  }\n\n  @SonarLintTest\n  void onlyLoadRulesOfEnabledLanguages(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    var client = harness.newFakeClient().build();\n    var backendBuilder = harness.newBackend()\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVASCRIPT)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PHP)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.KOTLIN);\n\n    if (COMMERCIAL_ENABLED) {\n      backendBuilder = backendBuilder.withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.CFAMILY);\n    }\n    var backend = backendBuilder.start(client);\n\n    var enabledLanguages = EnumSet.of(JAVA, JS, PHP, PYTHON, TS, XML, KOTLIN);\n\n    if (COMMERCIAL_ENABLED) {\n      enabledLanguages.add(C);\n    }\n    var response = backend.getRulesService().listAllStandaloneRulesDefinitions().get();\n    assertThat(response.getRulesByKey().values())\n      .flatExtracting(RuleDefinitionDto::getLanguage)\n      .containsAll(enabledLanguages);\n  }\n\n  @SonarLintTest\n  void simpleJavaNoHotspots(SonarLintTestHarness harness, @TempDir Path baseDir) throws Exception {\n    var fooDir = Files.createDirectory(baseDir.resolve(\"foo\"));\n    var inputFile = createFile(fooDir, \"Foo.java\",\n      \"\"\"\n        package foo;\n        public class Foo {\n          String ip = \"192.168.12.42\"; // Hotspots should not be reported in SonarLint\n        }\"\"\");\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n    backend.getRulesService()\n      .updateStandaloneRulesConfiguration(new UpdateStandaloneRulesConfigurationParams(Map.of(\"java:S1313\", new StandaloneRuleConfigDto(true, emptyMap()))));\n\n    analyzeFilesAndVerifyNoIssues(List.of(inputFile.toUri()), client, backend, CONFIGURATION_SCOPE_ID);\n  }\n\n  @SonarLintTest\n  void simpleJavaPomXml(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, i -> i.getTextRange().getStartLine(), StandaloneIssueMediumTests::extractMqrDetails).containsOnly(\n      tuple(\"xml:S3421\", 6, tuple(CleanCodeAttribute.CONVENTIONAL, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW)))));\n  }\n\n  @SonarLintTest\n  void supportJavaSuppressWarning(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, A_JAVA_FILE_PATH,\n      \"\"\"\n        public class Foo {\n          @SuppressWarnings(\"java:S106\")\n          public void foo() {\n            int x;\n            System.out.println(\"Foo\");\n            System.out.println(\"Foo\"); //NOSONAR\n          }\n        }\"\"\");\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange, StandaloneIssueMediumTests::extractMqrDetails)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S1220\", null, tuple(CleanCodeAttribute.MODULAR, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW)))),\n        tuple(\"java:S1481\", new TextRangeDto(4, 8, 4, 9), tuple(CleanCodeAttribute.CLEAR, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW)))));\n  }\n\n  @SonarLintTest\n  void simpleJavaWithBytecode(SonarLintTestHarness harness) {\n    var projectWithByteCode = new File(\"src/test/projects/java-with-bytecode\").getAbsoluteFile().toPath();\n    var inputFile = projectWithByteCode.resolve(\"src/Foo.java\");\n    var binFile = projectWithByteCode.resolve(\"bin/Foo.class\");\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), projectWithByteCode.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true),\n        new ClientFileDto(binFile.toUri(), projectWithByteCode.relativize(binFile), CONFIGURATION_SCOPE_ID, false, null, binFile, null, null, false)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n    var analysisId = UUID.randomUUID();\n    var analysisResult = backend.getAnalysisService().analyzeFilesAndTrack(\n      new AnalyzeFilesAndTrackParams(CONFIGURATION_SCOPE_ID, analysisId, List.of(inputFile.toUri()), Map.of(\"sonar.java.binaries\", projectWithByteCode.resolve(\"bin\").toString()),\n        true, System.currentTimeMillis()))\n      .join();\n    assertThat(analysisResult.getFailedAnalysisFiles()).isEmpty();\n    await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIGURATION_SCOPE_ID)).isNotEmpty());\n    var issues = client.getRaisedIssuesForScopeIdAsList(CONFIGURATION_SCOPE_ID);\n\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S106\", new TextRangeDto(5, 2, 5, 12)),\n        tuple(\"java:S1220\", null),\n        tuple(\"java:S1144\", new TextRangeDto(8, 14, 8, 17)),\n        tuple(\"java:S1186\", new TextRangeDto(8, 14, 8, 17)));\n  }\n\n  @SonarLintTest\n  void simpleJavaWithExcludedRules(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, A_JAVA_FILE_PATH,\n      \"\"\"\n        public class Foo {\n          public void foo() {\n            int x;\n            System.out.println(\"Foo\");\n          }\n        }\"\"\");\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n    backend.getRulesService()\n      .updateStandaloneRulesConfiguration(new UpdateStandaloneRulesConfigurationParams(Map.of(\"java:S106\", new StandaloneRuleConfigDto(false, emptyMap()))));\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange, StandaloneIssueMediumTests::extractMqrDetails)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S1220\", null, tuple(CleanCodeAttribute.MODULAR, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW)))),\n        tuple(\"java:S1481\", new TextRangeDto(3, 8, 3, 9), tuple(CleanCodeAttribute.CLEAR, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW)))));\n  }\n\n  @SonarLintTest\n  void simpleJavaWithExcludedRulesUsingDeprecatedKey(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, A_JAVA_FILE_PATH,\n      \"\"\"\n        public class Foo {\n          public void foo() {\n            int x;\n            System.out.println(\"Foo\");\n          }\n        }\"\"\");\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n    backend.getRulesService()\n      .updateStandaloneRulesConfiguration(new UpdateStandaloneRulesConfigurationParams(Map.of(\"squid:S106\", new StandaloneRuleConfigDto(false, emptyMap()))));\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange, StandaloneIssueMediumTests::extractMqrDetails)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S1220\", null, tuple(CleanCodeAttribute.MODULAR, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW)))),\n        tuple(\"java:S1481\", new TextRangeDto(3, 8, 3, 9), tuple(CleanCodeAttribute.CLEAR, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW)))));\n\n    assertThat(client.getLogMessages()).contains(\"Rule 'java:S106' was excluded using its deprecated key 'squid:S106'. Please fix your configuration.\");\n  }\n\n  @SonarLintTest\n  void simpleJavaWithIncludedRules(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, A_JAVA_FILE_PATH,\n      \"\"\"\n        import java.util.Optional;\n        public class Foo {\n          public void foo(Optional<String> name) {  // for squid:3553, not in Sonar Way\n            int x;\n            System.out.println(\"Foo\" + name.isPresent());\n          }\n        }\"\"\");\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n    backend.getRulesService()\n      .updateStandaloneRulesConfiguration(new UpdateStandaloneRulesConfigurationParams(Map.of(\"java:S3553\", new StandaloneRuleConfigDto(true, emptyMap()))));\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange, StandaloneIssueMediumTests::extractMqrDetails)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S3553\", new TextRangeDto(3, 18, 3, 34), tuple(CleanCodeAttribute.CLEAR, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.MEDIUM)))),\n        tuple(\"java:S106\", new TextRangeDto(5, 4, 5, 14), tuple(CleanCodeAttribute.MODULAR, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.MEDIUM)))),\n        tuple(\"java:S1220\", null, tuple(CleanCodeAttribute.MODULAR, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW)))),\n        tuple(\"java:S1481\", new TextRangeDto(4, 8, 4, 9), tuple(CleanCodeAttribute.CLEAR, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW)))));\n  }\n\n  @SonarLintTest\n  void simpleJavaWithIncludedRulesUsingDeprecatedKey(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, A_JAVA_FILE_PATH,\n      \"\"\"\n        import java.util.Optional;\n        public class Foo {\n          public void foo(Optional<String> name) {  // for squid:3553, not in Sonar Way\n            int x;\n            System.out.println(\"Foo\" + name.isPresent());\n          }\n        }\"\"\");\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n    backend.getRulesService()\n      .updateStandaloneRulesConfiguration(new UpdateStandaloneRulesConfigurationParams(Map.of(\"squid:S3553\", new StandaloneRuleConfigDto(true, emptyMap()))));\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange, StandaloneIssueMediumTests::extractMqrDetails)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S3553\", new TextRangeDto(3, 18, 3, 34), tuple(CleanCodeAttribute.CLEAR, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.MEDIUM)))),\n        tuple(\"java:S106\", new TextRangeDto(5, 4, 5, 14), tuple(CleanCodeAttribute.MODULAR, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.MEDIUM)))),\n        tuple(\"java:S1220\", null, tuple(CleanCodeAttribute.MODULAR, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW)))),\n        tuple(\"java:S1481\", new TextRangeDto(4, 8, 4, 9), tuple(CleanCodeAttribute.CLEAR, List.of(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW)))));\n\n    assertThat(client.getLogMessages()).contains(\"Rule 'java:S3553' was included using its deprecated key 'squid:S3553'. Please fix your configuration.\");\n  }\n\n  @Disabled(\"Rule java:S1228 is not reported: Add a 'package-info.java' file to document the 'foo' package\")\n  @SonarLintTest\n  void simpleJavaWithIssueOnDir(SonarLintTestHarness harness, @TempDir Path baseDir) throws Exception {\n    var fooDir = Files.createDirectory(baseDir.resolve(\"foo\"));\n    var inputFile = createFile(fooDir, \"Foo.java\",\n      \"\"\"\n        package foo;\n        public class Foo {\n        }\"\"\");\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n    backend.getRulesService()\n      .updateStandaloneRulesConfiguration(new UpdateStandaloneRulesConfigurationParams(Map.of(\"java:S1228\", new StandaloneRuleConfigDto(true, emptyMap()))));\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    assertThat(issues)\n      .extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange, StandaloneIssueMediumTests::extractMqrDetails)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S2094\", new TextRangeDto(2, 13, 2, 16), IssueSeverity.MINOR),\n        tuple(\"java:S1228\", null, IssueSeverity.MINOR));\n  }\n\n  @SonarLintTest\n  void simpleJavaWithSecondaryLocations(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, \"Foo.java\",\n      \"\"\"\n        package foo;\n        public class Foo {\n          public void method() {\n            String S1 = \"duplicated\";\n            String S2 = \"duplicated\";\n            String S3 = \"duplicated\";\n          }\\\n        }\"\"\");\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    assertThat(issues)\n      .extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .contains(tuple(\"java:S1192\", new TextRangeDto(4, 16, 4, 28)));\n    assertThat(issues)\n      .filteredOn(issue -> issue.getRuleKey().equals(\"java:S1192\"))\n      .flatExtracting(RaisedIssueDto::getFlows)\n      .hasSize(3);\n  }\n\n  @SonarLintTest\n  void testJavaSurefireDontCrashAnalysis(SonarLintTestHarness harness, @TempDir Path baseDir) throws Exception {\n    var surefireReport = new File(baseDir.toFile(), \"reports/TEST-FooTest.xml\");\n    FileUtils.write(surefireReport, \"\"\"\n      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <testsuite name=\"FooTest\" time=\"0.121\" tests=\"1\" errors=\"0\" skipped=\"0\" failures=\"0\">\n      <testcase name=\"errorAnalysis\" classname=\"FooTest\" time=\"0.031\"/>\n      </testsuite>\"\"\", StandardCharsets.UTF_8);\n\n    var inputFile = createFile(baseDir, A_JAVA_FILE_PATH,\n      \"\"\"\n        public class Foo {\n          public void foo() {\n            int x;\n            System.out.println(\"Foo\");\n            System.out.println(\"Foo\"); //NOSONAR\n          }\n        }\"\"\");\n\n    var inputFileTest = createFile(baseDir, \"FooTest.java\",\n      \"\"\"\n        public class FooTest {\n          public void testFoo() {\n          }\n        }\"\"\");\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true),\n        new ClientFileDto(inputFileTest.toUri(), baseDir.relativize(inputFileTest), CONFIGURATION_SCOPE_ID, true, null, inputFileTest, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    var analysisId = UUID.randomUUID();\n    var analysisResult = backend.getAnalysisService().analyzeFilesAndTrack(\n      new AnalyzeFilesAndTrackParams(CONFIGURATION_SCOPE_ID, analysisId, List.of(inputFile.toUri(), inputFileTest.toUri()), Map.of(\"sonar.junit.reportsPath\", \"reports/\"), true,\n        System.currentTimeMillis()))\n      .join();\n    assertThat(analysisResult.getFailedAnalysisFiles()).isEmpty();\n    await().atMost(20, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIGURATION_SCOPE_ID)).isNotEmpty());\n\n    var issues = client.getRaisedIssuesForScopeIdAsList(CONFIGURATION_SCOPE_ID);\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S106\", new TextRangeDto(4, 4, 4, 14)),\n        tuple(\"java:S1220\", null),\n        tuple(\"java:S1220\", null),\n        tuple(\"java:S1186\", new TextRangeDto(2, 14, 2, 21)),\n        tuple(\"java:S1481\", new TextRangeDto(3, 8, 3, 9)),\n        tuple(\"java:S2187\", new TextRangeDto(1, 13, 1, 20)));\n  }\n\n  @SonarLintTest\n  void lazy_init_file_metadata(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    final var inputFile = createFile(baseDir, A_JAVA_FILE_PATH,\n      \"\"\"\n        public class Foo {\n          public void foo() {\n            int x;\n            System.out.println(\"Foo\");\n            System.out.println(\"Foo\"); //NOSONAR\n          }\n        }\"\"\");\n    var unexistingFile = new File(baseDir.toFile(), \"missing.bin\");\n    var unexistingFilePath = unexistingFile.toPath();\n    assertThat(unexistingFile).doesNotExist();\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true),\n        new ClientFileDto(unexistingFilePath.toUri(), baseDir.relativize(unexistingFilePath), CONFIGURATION_SCOPE_ID, false, null, unexistingFilePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    var analysisId = UUID.randomUUID();\n    var analysisResult = backend.getAnalysisService().analyzeFilesAndTrack(\n      new AnalyzeFilesAndTrackParams(CONFIGURATION_SCOPE_ID, analysisId, List.of(inputFile.toUri(), unexistingFilePath.toUri()), Map.of(\"sonar.junit.reportsPath\", \"reports/\"),\n        true, System.currentTimeMillis()))\n      .join();\n\n    assertThat(analysisResult.getFailedAnalysisFiles()).isEmpty();\n    assertThat(client.getLogMessages())\n      .contains(\"Initializing metadata of file \" + inputFile.toUri())\n      .doesNotContain(\"Initializing metadata of file \" + unexistingFilePath.toFile());\n  }\n\n  private static Tuple extractMqrDetails(RaisedIssueDto raisedIssueDto) {\n    assertThat(raisedIssueDto.getSeverityMode().isRight()).isTrue();\n    var mqrModeDetails = raisedIssueDto.getSeverityMode().getRight();\n    return tuple(mqrModeDetails.getCleanCodeAttribute(),\n      mqrModeDetails.getImpacts().stream().map(i -> tuple(i.getSoftwareQuality(), i.getImpactSeverity())).toList());\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/StandaloneNoPluginMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest;\n\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SECURITY_HOTSPOTS;\nimport static utils.AnalysisUtils.createFile;\n\nclass StandaloneNoPluginMediumTests {\n\n  private static final String CONFIG_SCOPE_ID = \"configScopeId\";\n\n  @SonarLintTest\n  void dont_fail_and_detect_language_even_if_no_plugin(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, \"Foo.java\", \"\"\"\n      public class Foo {\n      \n        void foo() {\n          String password = \"blue\";\n        }\n      }\n      \"\"\");\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIG_SCOPE_ID, false, null, inputFile, null, null, true)\n      ))\n      .build();\n    var backend = harness.newBackend()\n      .withBackendCapability(SECURITY_HOTSPOTS)\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .start(client);\n\n    var analysisId = UUID.randomUUID();\n    var analysisResult = backend.getAnalysisService().analyzeFilesAndTrack(\n        new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(inputFile.toUri()), Map.of(), true))\n      .join();\n    assertThat(analysisResult.getFailedAnalysisFiles()).isEmpty();\n    await().during(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID)).isEmpty());\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/StandaloneRulesMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest;\n\nimport java.util.List;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.GetStandaloneRuleDescriptionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.ImpactDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.ListAllStandaloneRulesDefinitionsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleDefinitionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleParamDefinitionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleParamType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\n\nclass StandaloneRulesMediumTests {\n\n  @SonarLintTest\n  void it_should_return_only_embedded_rules_of_enabled_languages(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .withStandaloneEmbeddedPlugin(TestPlugin.PHP)\n      .start();\n\n    var allRules = listAllStandaloneRulesDefinitions(backend).getRulesByKey().values();\n\n    assertThat(allRules).extracting(RuleDefinitionDto::getLanguage).containsOnly(Language.PYTHON);\n  }\n\n  @SonarLintTest\n  void it_should_return_param_definition(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start();\n\n    var javaS1176 = listAllStandaloneRulesDefinitions(backend).getRulesByKey().get(\"java:S1176\");\n\n    assertThat(javaS1176.getParamsByKey()).containsOnlyKeys(\"exclusion\", \"forClasses\");\n    assertThat(javaS1176.getParamsByKey().get(\"forClasses\"))\n      .extracting(RuleParamDefinitionDto::getKey, RuleParamDefinitionDto::getName, RuleParamDefinitionDto::getDescription,\n        RuleParamDefinitionDto::getType, RuleParamDefinitionDto::isMultiple, RuleParamDefinitionDto::getPossibleValues, RuleParamDefinitionDto::getDefaultValue)\n      .containsExactly(\"forClasses\",\n        \"forClasses\",\n        \"Pattern of classes which should adhere to this constraint. Ex : **.api.**\",\n        RuleParamType.STRING,\n        false,\n        List.of(),\n        \"**.api.**\");\n  }\n\n  @SonarLintTest\n  void it_should_return_rule_details_with_definition_and_description(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    var backend = harness.newBackend()\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start();\n\n    var ruleDetails = backend.getRulesService().getStandaloneRuleDetails(new GetStandaloneRuleDescriptionParams(\"java:S1176\")).get();\n\n    assertThat(ruleDetails.getRuleDefinition().getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CLEAR);\n    assertThat(ruleDetails.getRuleDefinition().getSoftwareImpacts())\n      .extracting(ImpactDto::getSoftwareQuality, ImpactDto::getImpactSeverity)\n      .containsExactly(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.MEDIUM));\n    assertThat(ruleDetails.getRuleDefinition().getName()).isEqualTo(\"Public types, methods and fields (API) should be documented with Javadoc\");\n    assertThat(ruleDetails.getDescription().isRight()).isTrue();\n    assertThat(ruleDetails.getDescription().getRight().getIntroductionHtmlContent())\n      .startsWith(\"<p>A good API documentation is a key factor in the usability and success of a software API\");\n  }\n\n  @SonarLintTest\n  void it_should_not_contain_rule_templates(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start();\n\n    var allRules = listAllStandaloneRulesDefinitions(backend).getRulesByKey().values();\n\n    assertThat(allRules).extracting(RuleDefinitionDto::getKey).isNotEmpty().doesNotContain(\"python:XPath\");\n    assertThat(backend.getRulesService().getStandaloneRuleDetails(new GetStandaloneRuleDescriptionParams(\"python:XPath\"))).failsWithin(1, TimeUnit.MINUTES);\n  }\n\n  @SonarLintTest\n  void it_should_not_contain_hotspots_by_default(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start();\n\n    var allRules = listAllStandaloneRulesDefinitions(backend).getRulesByKey().values();\n\n    assertThat(allRules).extracting(RuleDefinitionDto::getKey).isNotEmpty().doesNotContain(\"java:S1313\");\n    assertThat(backend.getRulesService().getStandaloneRuleDetails(new GetStandaloneRuleDescriptionParams(\"java:S1313\"))).failsWithin(1, TimeUnit.MINUTES);\n  }\n\n  private ListAllStandaloneRulesDefinitionsResponse listAllStandaloneRulesDefinitions(SonarLintTestRpcServer backend) {\n    try {\n      return backend.getRulesService().listAllStandaloneRulesDefinitions().get();\n    } catch (InterruptedException | ExecutionException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/TelemetryMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest;\n\nimport com.github.tomakehurst.wiremock.junit5.WireMockExtension;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.OffsetDateTime;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ExecutionException;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidUpdateFileSystemParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.OpenHotspotInBrowserParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.TelemetryMigrationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AcceptedBindingSuggestionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AddQuickFixAppliedForRuleParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AddReportedRulesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AnalysisDoneOnSingleLanguageParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AnalysisReportingTriggeredParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AnalysisReportingType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.DevNotificationsClickedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.FindingsFilteredParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.FixSuggestionResolvedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.FixSuggestionStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.HelpAndFeedbackClickedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.IdeLabsExternalLinkClickedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.IdeLabsFeedbackLinkClickedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.McpTransportMode;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.McpTransportModeUsedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.TelemetryClientLiveAttributesResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.ToolCalledParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.telemetry.InternalDebug;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryLocalStorage;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryLocalStorageManager;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;\nimport static com.github.tomakehurst.wiremock.client.WireMock.post;\nimport static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static java.util.Collections.emptyMap;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\nimport static org.sonarsource.sonarlint.core.telemetry.TelemetrySpringConfig.PROPERTY_TELEMETRY_ENDPOINT;\nimport static utils.AnalysisUtils.analyzeFileAndGetIssue;\nimport static utils.AnalysisUtils.analyzeFileAndGetIssues;\n\nclass TelemetryMediumTests {\n\n  @RegisterExtension\n  static WireMockExtension telemetryEndpointMock = WireMockExtension.newInstance()\n    .options(wireMockConfig().dynamicPort())\n    .build();\n\n  private boolean oldDebugValue;\n\n  @BeforeAll\n  static void mockTelemetryEndpoint() {\n    System.setProperty(\"sonarlint.internal.nodejs.forcedPath\", \"/path/to/nodeJS\");\n    System.setProperty(\"sonarlint.internal.nodejs.forcedVersion\", \"v3.1.4\");\n    telemetryEndpointMock.stubFor(post(\"/sonarlint-telemetry\").willReturn(aResponse().withStatus(200)));\n  }\n\n  @AfterAll\n  static void clearTelemetryEndpoint() {\n    System.clearProperty(PROPERTY_TELEMETRY_ENDPOINT);\n    System.clearProperty(\"sonarlint.internal.telemetry.initialDelay\");\n    System.clearProperty(\"sonarlint.internal.nodejs.forcedPath\");\n    System.clearProperty(\"sonarlint.internal.nodejs.forcedVersion\");\n  }\n\n  @BeforeEach\n  void saveInternalDebugFlag() {\n    this.oldDebugValue = InternalDebug.isEnabled();\n    InternalDebug.setEnabled(true);\n  }\n\n  @AfterEach\n  void tearDown() {\n    InternalDebug.setEnabled(oldDebugValue);\n  }\n\n  @SonarLintTest\n  void it_should_not_create_telemetry_file_if_telemetry_disabled_by_system_property(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", \"http://localhost:12345\", storage -> storage.withProject(\"projectKey\", project -> project.withMainBranch(\"master\")))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .start();\n\n    assertThat(backend.getTelemetryService().getStatus().get().isEnabled()).isFalse();\n\n    backend.getHotspotService().openHotspotInBrowser(new OpenHotspotInBrowserParams(\"scopeId\", \"ab12ef45\"));\n    assertThat(backend.telemetryFilePath()).doesNotExist();\n  }\n\n  @SonarLintTest\n  void it_should_not_enable_telemetry_if_disabled_by_system_property(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    System.setProperty(\"sonarlint.internal.telemetry.initialDelay\", \"0\");\n\n    var fakeClient = harness.newFakeClient()\n      .build();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", \"http://localhost:12345\", storage -> storage.withProject(\"projectKey\",\n        project -> project.withMainBranch(\"master\")))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .start(fakeClient);\n\n    assertThat(backend.getTelemetryService().getStatus().get().isEnabled()).isFalse();\n    backend.getTelemetryService().enableTelemetry();\n\n    await().untilAsserted(() -> assertThat(fakeClient.getLogs()).extracting(LogParams::getMessage).contains((\"Telemetry was disabled on \" +\n      \"server startup. Ignoring client request.\")));\n    assertThat(backend.getTelemetryService().getStatus().get().isEnabled()).isFalse();\n  }\n\n  @SonarLintTest\n  void it_should_create_telemetry_file_if_telemetry_enabled(SonarLintTestHarness harness) {\n    System.setProperty(\"sonarlint.internal.telemetry.initialDelay\", \"0\");\n\n    var fakeClient = harness.newFakeClient()\n      .build();\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", \"http://localhost:12345\", storage -> storage.withProject(\"projectKey\", project -> project.withMainBranch(\"master\")))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withTelemetryEnabled(telemetryEndpointMock.baseUrl() + \"/sonarlint-telemetry\")\n      .withEnabledLanguageInStandaloneMode(Language.JS)\n      .start(fakeClient);\n\n    backend.getHotspotService().openHotspotInBrowser(new OpenHotspotInBrowserParams(\"scopeId\", \"ab12ef45\"));\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().openHotspotInBrowserCount()).isEqualTo(1));\n\n    await().untilAsserted(() -> telemetryEndpointMock.verify(postRequestedFor(urlEqualTo(\"/sonarlint-telemetry\"))\n      .withRequestBody(equalToJson(\"{\\n\" +\n        \"  \\\"sonarlint_version\\\" : \\\"1.2.3\\\",\\n\" +\n        \"  \\\"sonarlint_product\\\" : \\\"mediumTests\\\",\\n\" +\n        \"  \\\"ide_version\\\" : \\\"4.5.6\\\",\\n\" +\n        \"  \\\"platform\\\" : \\\"\" + SystemUtils.OS_NAME + \"\\\",\\n\" +\n        \"  \\\"architecture\\\" : \\\"\" + SystemUtils.OS_ARCH + \"\\\",\\n\" +\n        \"  \\\"nodejs\\\" : \\\"3.1.4\\\",\\n\" +\n        \"  \\\"connected_mode_used\\\" : false,\\n\" +\n        \"  \\\"connected_mode_sonarcloud\\\" : false\\n\" +\n        \"}\", true, true))));\n\n    System.clearProperty(\"sonarlint.internal.telemetry.initialDelay\");\n  }\n\n  @SonarLintTest\n  void it_should_consider_telemetry_status_in_file(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\")\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withTelemetryEnabled()\n      .start();\n\n    assertThat(backend.getTelemetryService().getStatus().get().isEnabled()).isTrue();\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().enabled()).isTrue());\n\n    // Emulate another process has disabled telemetry\n    var telemetryLocalStorageManager = new TelemetryLocalStorageManager(backend.telemetryFilePath(), mock(InitializeParams.class));\n    telemetryLocalStorageManager.tryUpdateAtomically(data -> data.setEnabled(false));\n\n    assertThat(backend.getTelemetryService().getStatus().get().isEnabled()).isFalse();\n    assertThat(backend.telemetryFileContent().enabled()).isFalse();\n  }\n\n  @SonarLintTest\n  void it_should_ping_telemetry_endpoint(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    System.setProperty(\"sonarlint.internal.telemetry.initialDelay\", \"0\");\n    var fakeClient = harness.newFakeClient().build();\n    when(fakeClient.getTelemetryLiveAttributes()).thenReturn(new TelemetryClientLiveAttributesResponse(emptyMap()));\n\n    var backend = harness.newBackend()\n      .withTelemetryEnabled(telemetryEndpointMock.baseUrl() + \"/sonarlint-telemetry\")\n      .withEnabledLanguageInStandaloneMode(Language.JS)\n      .start(fakeClient);\n\n    assertThat(backend.getTelemetryService().getStatus().get().isEnabled()).isTrue();\n\n    await().untilAsserted(() -> telemetryEndpointMock.verify(postRequestedFor(urlEqualTo(\"/sonarlint-telemetry\"))\n      .withRequestBody(equalToJson(\"{\\n\" +\n        \"  \\\"sonarlint_version\\\" : \\\"1.2.3\\\",\\n\" +\n        \"  \\\"sonarlint_product\\\" : \\\"mediumTests\\\",\\n\" +\n        \"  \\\"ide_version\\\" : \\\"4.5.6\\\",\\n\" +\n        \"  \\\"platform\\\" : \\\"\" + SystemUtils.OS_NAME + \"\\\",\\n\" +\n        \"  \\\"architecture\\\" : \\\"\" + SystemUtils.OS_ARCH + \"\\\",\\n\" +\n        \"  \\\"nodejs\\\" : \\\"3.1.4\\\",\\n\" +\n        \"  \\\"connected_mode_used\\\" : false,\\n\" +\n        \"  \\\"connected_mode_sonarcloud\\\" : false\\n\" +\n        \"}\", true, true))));\n    System.clearProperty(\"sonarlint.internal.telemetry.initialDelay\");\n  }\n\n  @SonarLintTest\n  void it_should_disable_telemetry(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    System.setProperty(\"sonarlint.internal.telemetry.initialDelay\", \"0\");\n\n    var fakeClient = harness.newFakeClient().build();\n    when(fakeClient.getTelemetryLiveAttributes()).thenReturn(new TelemetryClientLiveAttributesResponse(emptyMap()));\n\n    var backend = harness.newBackend()\n      .withTelemetryEnabled(telemetryEndpointMock.baseUrl() + \"/sonarlint-telemetry\")\n      .withEnabledLanguageInStandaloneMode(Language.JS)\n      .start(fakeClient);\n\n    assertThat(backend.getTelemetryService().getStatus().get().isEnabled()).isTrue();\n\n    await().untilAsserted(() -> telemetryEndpointMock.verify(postRequestedFor(urlEqualTo(\"/sonarlint-telemetry\"))\n      .withRequestBody(equalToJson(\"{\\n\" +\n        \"  \\\"sonarlint_version\\\" : \\\"1.2.3\\\",\\n\" +\n        \"  \\\"sonarlint_product\\\" : \\\"mediumTests\\\",\\n\" +\n        \"  \\\"ide_version\\\" : \\\"4.5.6\\\",\\n\" +\n        \"  \\\"platform\\\" : \\\"\" + SystemUtils.OS_NAME + \"\\\",\\n\" +\n        \"  \\\"architecture\\\" : \\\"\" + SystemUtils.OS_ARCH + \"\\\",\\n\" +\n        \"  \\\"nodejs\\\" : \\\"3.1.4\\\",\\n\" +\n        \"  \\\"connected_mode_used\\\" : false,\\n\" +\n        \"  \\\"connected_mode_sonarcloud\\\" : false\\n\" +\n        \"}\", true, true))));\n\n    backend.getTelemetryService().disableTelemetry();\n\n    assertThat(backend.getTelemetryService().getStatus().get().isEnabled()).isFalse();\n    System.clearProperty(\"sonarlint.internal.telemetry.initialDelay\");\n  }\n\n  @SonarLintTest\n  void it_should_enable_disabled_telemetry(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    System.setProperty(\"sonarlint.internal.telemetry.initialDelay\", \"0\");\n\n    var fakeClient = harness.newFakeClient().build();\n    when(fakeClient.getTelemetryLiveAttributes()).thenReturn(new TelemetryClientLiveAttributesResponse(emptyMap()));\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\")\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withTelemetryEnabled()\n      .start(fakeClient);\n\n    backend.getTelemetryService().disableTelemetry();\n    assertThat(backend.getTelemetryService().getStatus().get().isEnabled()).isFalse();\n\n    backend.getTelemetryService().enableTelemetry();\n    assertThat(backend.getTelemetryService().getStatus().get().isEnabled()).isTrue();\n    System.clearProperty(\"sonarlint.internal.telemetry.initialDelay\");\n  }\n\n  @SonarLintTest\n  void it_should_not_crash_when_cannot_build_payload(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    when(fakeClient.getTelemetryLiveAttributes()).thenThrow(new IllegalStateException(\"Unexpected error\"));\n    harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\")\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .start(fakeClient);\n    assertThat(telemetryEndpointMock.getAllServeEvents()).isEmpty();\n  }\n\n  @SonarLintTest\n  void failed_upload_payload_should_log_if_debug(SonarLintTestHarness harness) {\n    System.setProperty(\"sonarlint.internal.telemetry.initialDelay\", \"0\");\n    var originalValue = InternalDebug.isEnabled();\n    InternalDebug.setEnabled(true);\n\n    var fakeClient = harness.newFakeClient().build();\n    when(fakeClient.getTelemetryLiveAttributes()).thenThrow(new IllegalStateException(\"Unexpected error\"));\n    harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\")\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withTelemetryEnabled()\n      .start(fakeClient);\n\n    assertThat(telemetryEndpointMock.getAllServeEvents()).isEmpty();\n    await().untilAsserted(() -> assertThat(fakeClient.getLogs()).extracting(LogParams::getMessage).contains((\"Failed to fetch telemetry payload\")));\n    InternalDebug.setEnabled(originalValue);\n    System.clearProperty(\"sonarlint.internal.telemetry.initialDelay\");\n  }\n\n  @SonarLintTest\n  void should_increment_numDays_on_analysis_once_per_day(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().analysisDoneOnMultipleFiles();\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().numUseDays()).isEqualTo(1));\n\n    backend.getTelemetryService().analysisDoneOnMultipleFiles();\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().numUseDays()).isEqualTo(1));\n  }\n\n  @SonarLintTest\n  void it_should_accumulate_investigated_taint_vulnerabilities(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().taintVulnerabilitiesInvestigatedLocally();\n    backend.getTelemetryService().taintVulnerabilitiesInvestigatedRemotely();\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent())\n      .extracting(TelemetryLocalStorage::taintVulnerabilitiesInvestigatedLocallyCount, TelemetryLocalStorage::taintVulnerabilitiesInvestigatedRemotelyCount)\n      .containsExactly(1, 1));\n\n    backend.getTelemetryService().taintVulnerabilitiesInvestigatedLocally();\n    backend.getTelemetryService().taintVulnerabilitiesInvestigatedLocally();\n    backend.getTelemetryService().taintVulnerabilitiesInvestigatedRemotely();\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent())\n      .extracting(TelemetryLocalStorage::taintVulnerabilitiesInvestigatedLocallyCount, TelemetryLocalStorage::taintVulnerabilitiesInvestigatedRemotelyCount)\n      .containsExactly(3, 2));\n  }\n\n  @SonarLintTest\n  void it_should_accumulate_investigated_dependency_risks(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().dependencyRiskInvestigatedLocally();\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getDependencyRiskInvestigatedLocallyCount()).isEqualTo(1));\n\n    backend.getTelemetryService().dependencyRiskInvestigatedLocally();\n    backend.getTelemetryService().dependencyRiskInvestigatedLocally();\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getDependencyRiskInvestigatedLocallyCount()).isEqualTo(3));\n  }\n\n  @SonarLintTest\n  void it_should_accumulate_clicked_dev_notifications(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n    var notificationEvent = \"myNotification\";\n\n    backend.getTelemetryService().devNotificationsClicked(new DevNotificationsClickedParams(notificationEvent));\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().notifications())\n      .extractingFromEntries(Map.Entry::getKey, e -> tuple(e.getValue().getDevNotificationsCount(), e.getValue().getDevNotificationsClicked()))\n      .containsOnly(tuple(\"myNotification\", tuple(0, 1))));\n\n    backend.getTelemetryService().devNotificationsClicked(new DevNotificationsClickedParams(notificationEvent));\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().notifications())\n      .extractingFromEntries(Map.Entry::getKey, e -> tuple(e.getValue().getDevNotificationsCount(), e.getValue().getDevNotificationsClicked()))\n      .containsOnly(tuple(\"myNotification\", tuple(0, 2))));\n  }\n\n  @SonarLintTest\n  void it_should_record_helpAndFeedbackLinkClicked(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().helpAndFeedbackLinkClicked(new HelpAndFeedbackClickedParams(\"itemId\"));\n\n    await()\n      .untilAsserted(() -> assertThat(backend.telemetryFileContent().getHelpAndFeedbackLinkClickedCounter())\n        .extractingFromEntries(Map.Entry::getKey, e -> e.getValue().getHelpAndFeedbackLinkClickedCount())\n        .containsOnly(tuple(\"itemId\", 1)));\n  }\n\n  @SonarLintTest\n  void it_should_record_analysisReportingTriggered(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().analysisReportingTriggered(new AnalysisReportingTriggeredParams(AnalysisReportingType.ALL_FILES_ANALYSIS_TYPE));\n\n    await().untilAsserted(\n      () -> assertThat(backend.telemetryFileContent().getAnalysisReportingCountersByType())\n        .extractingFromEntries(Map.Entry::getKey, e -> e.getValue().getAnalysisReportingCount())\n        .containsOnly(tuple(AnalysisReportingType.ALL_FILES_ANALYSIS_TYPE, 1)));\n  }\n\n  @SonarLintTest\n  void it_should_record_fixSuggestionResolved(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().fixSuggestionResolved(new FixSuggestionResolvedParams(\"suggestionId\", FixSuggestionStatus.ACCEPTED, 0));\n    backend.getTelemetryService().fixSuggestionResolved(new FixSuggestionResolvedParams(\"suggestionId2\", FixSuggestionStatus.DECLINED, null));\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getFixSuggestionResolved())\n      .extractingFromEntries(Map.Entry::getKey,\n        e -> e.getValue().stream().map(status -> tuple(status.getFixSuggestionResolvedStatus(), status.getFixSuggestionResolvedSnippetIndex())).toList())\n      .containsOnly(\n        tuple(\"suggestionId\", List.of(tuple(FixSuggestionStatus.ACCEPTED, 0))),\n        tuple(\"suggestionId2\", List.of(tuple(FixSuggestionStatus.DECLINED, null)))));\n  }\n\n  @SonarLintTest\n  void it_should_record_addQuickFixAppliedForRule(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().addQuickFixAppliedForRule(new AddQuickFixAppliedForRuleParams(\"ruleKey\"));\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getQuickFixesApplied()).isEqualTo(Set.of(\"ruleKey\")));\n  }\n\n  @SonarLintTest\n  void it_should_record_addReportedRules(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().addReportedRules(new AddReportedRulesParams(Set.of(\"ruleA\")));\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getRaisedIssuesRules()).isEqualTo(Set.of(\"ruleA\")));\n  }\n\n  @SonarLintTest\n  void it_should_record_taintVulnerabilitiesInvestigatedRemotely(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().taintVulnerabilitiesInvestigatedRemotely();\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().taintVulnerabilitiesInvestigatedRemotelyCount()).isEqualTo(1));\n  }\n\n  @SonarLintTest\n  void it_should_record_mcpIntegrationEnabled(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().mcpIntegrationEnabled();\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().isMcpIntegrationEnabled()).isTrue());\n  }\n\n  @SonarLintTest\n  void it_should_record_mcpTransportModeUsed(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().mcpTransportModeUsed(new McpTransportModeUsedParams(McpTransportMode.STDIO));\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getMcpTransportModeUsed()).isEqualTo(McpTransportMode.STDIO));\n  }\n\n  @SonarLintTest\n  void mcp_transport_mode_should_be_null_if_not_recorded(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getMcpTransportModeUsed()).isNull());\n  }\n\n  @SonarLintTest\n  void it_should_record_toolCalled(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().toolCalled(new ToolCalledParams(\"tool_name\", true));\n    backend.getTelemetryService().toolCalled(new ToolCalledParams(\"tool_name\", true));\n    backend.getTelemetryService().toolCalled(new ToolCalledParams(\"tool_name\", false));\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getCalledToolsByName())\n      .extractingFromEntries(Map.Entry::getKey, e -> tuple(e.getValue().getSuccess(), e.getValue().getError()))\n      .containsOnly(tuple(\"tool_name\", tuple(2, 1))));\n  }\n\n  @SonarLintTest\n  void it_should_record_taintVulnerabilitiesInvestigatedLocally(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().taintVulnerabilitiesInvestigatedLocally();\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().taintVulnerabilitiesInvestigatedLocallyCount()).isEqualTo(1));\n  }\n\n  @SonarLintTest\n  void it_should_record_analysisDoneOnSingleLanguage(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().analysisDoneOnSingleLanguage(new AnalysisDoneOnSingleLanguageParams(Language.JAVA, 1000));\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().analyzers())\n      .extractingFromEntries(Map.Entry::getKey, e -> tuple(e.getValue().analysisCount(), e.getValue().frequencies()))\n      .containsOnly(tuple(\"java\", tuple(1, Map.of(\n        \"0-300\", 0,\n        \"300-500\", 0,\n        \"500-1000\", 0,\n        \"1000-2000\", 1,\n        \"2000-4000\", 0,\n        \"4000+\", 0)))));\n  }\n\n  @SonarLintTest\n  void it_should_record_analysisDoneOnMultipleFiles(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().analysisDoneOnMultipleFiles();\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().numUseDays()).isEqualTo(1));\n  }\n\n  @SonarLintTest\n  void it_should_record_addedManualBindings(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().addedManualBindings();\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getManualAddedBindingsCount()).isEqualTo(1));\n  }\n\n  @SonarLintTest\n  void it_should_record_acceptedBindingSuggestion_remoteUrl(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().acceptedBindingSuggestion(new AcceptedBindingSuggestionParams(BindingSuggestionOrigin.REMOTE_URL));\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getNewBindingsRemoteUrlCount()).isEqualTo(1));\n  }\n\n  @SonarLintTest\n  void it_should_record_acceptedBindingSuggestion_sharedConfiguration(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().acceptedBindingSuggestion(new AcceptedBindingSuggestionParams(BindingSuggestionOrigin.SHARED_CONFIGURATION));\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getNewBindingsSharedConfigurationCount()).isEqualTo(1));\n  }\n\n  @SonarLintTest\n  void it_should_record_acceptedBindingSuggestion_propertiesFile(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().acceptedBindingSuggestion(new AcceptedBindingSuggestionParams(BindingSuggestionOrigin.PROPERTIES_FILE));\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getNewBindingsPropertiesFileCount()).isEqualTo(1));\n  }\n\n  @SonarLintTest\n  void it_should_record_acceptedBindingSuggestion_projectName(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().acceptedBindingSuggestion(new AcceptedBindingSuggestionParams(BindingSuggestionOrigin.PROJECT_NAME));\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getNewBindingsProjectNameCount()).isEqualTo(1));\n  }\n\n  @SonarLintTest\n  void it_should_record_addedImportedBindings(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().addedImportedBindings();\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getImportedAddedBindingsCount()).isEqualTo(1));\n  }\n\n  @SonarLintTest\n  void it_should_record_addedAutomaticBindings(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().addedAutomaticBindings();\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getAutoAddedBindingsCount()).isEqualTo(1));\n  }\n\n  @SonarLintTest\n  void it_should_accumulate_investigated_findings_count(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().issueInvestigatedLocally();\n    backend.getTelemetryService().taintInvestigatedLocally();\n    backend.getTelemetryService().taintInvestigatedRemotely();\n    backend.getTelemetryService().hotspotInvestigatedLocally();\n    backend.getTelemetryService().hotspotInvestigatedRemotely();\n\n    backend.getTelemetryService().issueInvestigatedLocally();\n    backend.getTelemetryService().taintInvestigatedRemotely();\n    backend.getTelemetryService().hotspotInvestigatedLocally();\n    backend.getTelemetryService().hotspotInvestigatedRemotely();\n\n    backend.getTelemetryService().issueInvestigatedLocally();\n    backend.getTelemetryService().hotspotInvestigatedLocally();\n    backend.getTelemetryService().hotspotInvestigatedRemotely();\n\n    backend.getTelemetryService().issueInvestigatedLocally();\n    backend.getTelemetryService().hotspotInvestigatedRemotely();\n\n    backend.getTelemetryService().issueInvestigatedLocally();\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent())\n      .extracting(\n        TelemetryLocalStorage::getIssueInvestigatedLocallyCount,\n        TelemetryLocalStorage::getHotspotInvestigatedRemotelyCount,\n        TelemetryLocalStorage::getHotspotInvestigatedLocallyCount,\n        TelemetryLocalStorage::getTaintInvestigatedRemotelyCount,\n        TelemetryLocalStorage::getTaintInvestigatedLocallyCount\n      )\n      .containsExactly(5, 4, 3, 2, 1));\n  }\n\n  @SonarLintTest\n  void it_should_add_issue_uuid_when_ai_fixable(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", \"\"\"\n      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <project>\n        <modelVersion>4.0.0</modelVersion>\n        <groupId>com.foo</groupId>\n        <artifactId>bar</artifactId>\n        <version>${pom.version}</version>\n      </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"organizationKey\", organization -> organization\n        .withProject(\"projectKey\"))\n      .start();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"organizationKey\", true, storage -> storage\n        .withProject(\"projectKey\", project -> project.withMainBranch(\"main\").withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MAJOR\")))\n        .withAiCodeFixSettings(aiCodeFix -> aiCodeFix\n          .withSupportedRules(Set.of(\"xml:S3421\"))\n          .organizationEligible(true)\n          .enabledForProjects(\"projectKey\")))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .withTelemetryEnabled()\n      .start(fakeClient);\n\n    analyzeFileAndGetIssue(fileUri, fakeClient, backend, \"configScope\");\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getCountIssuesWithPossibleAiFixFromIde()).isEqualTo(1));\n  }\n\n  @SonarLintTest\n  void it_should_apply_telemetry_migration(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    var backend = harness.newBackend()\n      .withTelemetryMigration(new TelemetryMigrationDto(OffsetDateTime.now(), 42, false))\n      .withTelemetryEnabled()\n      .start();\n\n    assertThat(backend.getTelemetryService().getStatus().get().isEnabled()).isFalse();\n  }\n\n  @SonarLintTest\n  void it_should_record_findingsFiltered(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().findingsFiltered(new FindingsFilteredParams(\"severity\"));\n    backend.getTelemetryService().findingsFiltered(new FindingsFilteredParams(\"severity\"));\n    backend.getTelemetryService().findingsFiltered(new FindingsFilteredParams(\"location\"));\n    backend.getTelemetryService().findingsFiltered(new FindingsFilteredParams(\"fix_availability\"));\n    backend.getTelemetryService().findingsFiltered(new FindingsFilteredParams(\"fix_availability\"));\n    backend.getTelemetryService().findingsFiltered(new FindingsFilteredParams(\"fix_availability\"));\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFilePath()).content().asBase64Decoded().asString()\n      .contains(\"\\\"findingsFilteredCountersByType\\\":{\\\"severity\\\":{\\\"findingsFilteredCount\\\":2},\\\"location\\\":{\\\"findingsFilteredCount\\\":1},\\\"fix_availability\\\":{\\\"findingsFilteredCount\\\":3}}\"));\n  }\n\n  @SonarLintTest\n  void it_should_count_new_and_fixed_issues(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"sample.py\", \"\"\"\n      def empty_function1():\n        pass\n\n      def empty_function2():\n        pass\n      \"\"\");\n\n    var fileUri = filePath.toUri();\n    var configScope = \"configScope\";\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(configScope, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), configScope, false, null, filePath, null, Language.PYTHON, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .withUnboundConfigScope(configScope)\n      .withTelemetryEnabled()\n      .start(fakeClient);\n\n    var issuesBefore = analyzeFileAndGetIssues(fileUri, fakeClient, backend, configScope);\n    assertThat(issuesBefore).hasSize(2);\n\n    var newContent = \"\"\"\n      def empty_function2():\n        pass\n      \"\"\";\n    var updatedFile = new ClientFileDto(fileUri, baseDir.relativize(filePath), configScope, false, null, filePath, newContent, Language.PYTHON, true);\n    backend.getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(List.of(), List.of(updatedFile), List.of()));\n\n    var issuesAfter = analyzeFileAndGetIssues(fileUri, fakeClient, backend, configScope);\n    assertThat(issuesAfter).hasSize(1);\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent())\n      .extracting(TelemetryLocalStorage::getNewIssuesFoundCount, TelemetryLocalStorage::getIssuesFixedCount)\n      .containsExactly(2L, 1L));\n  }\n\n  @SonarLintTest\n  void it_should_record_each_ide_labs_link_click_separately(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().ideLabsExternalLinkClicked(new IdeLabsExternalLinkClickedParams(\"item1\"));\n    backend.getTelemetryService().ideLabsExternalLinkClicked(new IdeLabsExternalLinkClickedParams(\"item2\"));\n    backend.getTelemetryService().ideLabsExternalLinkClicked(new IdeLabsExternalLinkClickedParams(\"item2\"));\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getLabsLinkClickedCount())\n      .isEqualTo(Map.of(\n        \"item1\", 1,\n        \"item2\", 2\n      )));\n  }\n\n  @SonarLintTest\n  void it_should_record_each_ide_labs_feedback_link_click_separately(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().ideLabsFeedbackLinkClicked(new IdeLabsFeedbackLinkClickedParams(\"feature1\"));\n    backend.getTelemetryService().ideLabsFeedbackLinkClicked(new IdeLabsFeedbackLinkClickedParams(\"feature2\"));\n    backend.getTelemetryService().ideLabsFeedbackLinkClicked(new IdeLabsFeedbackLinkClickedParams(\"feature2\"));\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getLabsFeedbackLinkClickedCount())\n      .isEqualTo(Map.of(\n        \"feature1\", 1,\n        \"feature2\", 2\n      )));\n  }\n\n  @SonarLintTest\n  void it_should_record_supported_languages_panel_opened(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().supportedLanguagesPanelOpened();\n    backend.getTelemetryService().supportedLanguagesPanelOpened();\n    backend.getTelemetryService().supportedLanguagesPanelOpened();\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getSupportedLanguagesPanelOpenedCount()).isEqualTo(3));\n  }\n\n  @SonarLintTest\n  void it_should_record_supported_languages_panel_cta_clicked(SonarLintTestHarness harness) {\n    var backend = setupClientAndBackend(harness);\n\n    backend.getTelemetryService().supportedLanguagesPanelCtaClicked();\n    backend.getTelemetryService().supportedLanguagesPanelCtaClicked();\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getSupportedLanguagesPanelCtaClickedCount()).isEqualTo(2));\n  }\n\n  private SonarLintTestRpcServer setupClientAndBackend(SonarLintTestHarness harness) {\n    return harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\")\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withTelemetryEnabled()\n      .start();\n  }\n\n  private static Path createFile(Path folderPath, String fileName, String content) {\n    var filePath = folderPath.resolve(fileName);\n    try {\n      Files.writeString(filePath, content);\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n    return filePath;\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/ai/ide/AiAgentMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.ai.ide;\n\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.AiAgent;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.GetRuleFileContentParams;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass AiAgentMediumTests {\n\n  @SonarLintTest\n  void it_should_return_the_rule_file_content_for_cursor(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .start();\n\n    var response = backend.getAiAgentService().getRuleFileContent(new GetRuleFileContentParams(AiAgent.CURSOR)).join();\n\n    assertThat(response.getContent()).contains(\"alwaysApply: true\");\n    assertThat(response.getContent()).contains(\"IMPORTANT\");\n    assertThat(response.getContent()).contains(\"analyze_file_list\");\n    assertThat(response.getContent()).contains(\"Important Tool Guidelines\");\n  }\n\n  @SonarLintTest\n  void it_should_return_the_rule_file_content_for_github_copilot(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .start();\n\n    var response = backend.getAiAgentService().getRuleFileContent(new GetRuleFileContentParams(AiAgent.GITHUB_COPILOT)).join();\n\n    assertThat(response.getContent()).doesNotContain(\"alwaysApply: true\");\n    assertThat(response.getContent()).contains(\"applyTo: \\\"**/*\\\"\");\n    assertThat(response.getContent()).contains(\"IMPORTANT\");\n    assertThat(response.getContent()).contains(\"analyze_file_list\");\n    assertThat(response.getContent()).contains(\"Important Tool Guidelines\");\n  }\n\n  @SonarLintTest\n  void it_should_return_the_rule_file_content_for_windsurf(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .start();\n\n    var response = backend.getAiAgentService().getRuleFileContent(new GetRuleFileContentParams(AiAgent.WINDSURF)).join();\n\n    assertThat(response.getContent()).contains(\"alwaysApply: true\");\n    assertThat(response.getContent()).contains(\"IMPORTANT\");\n    assertThat(response.getContent()).contains(\"analyze_file_list\");\n    assertThat(response.getContent()).contains(\"Important Tool Guidelines\");\n  }\n\n  @SonarLintTest\n  void it_should_return_the_rule_file_content_for_kiro(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .start();\n\n    var response = backend.getAiAgentService().getRuleFileContent(new GetRuleFileContentParams(AiAgent.KIRO)).join();\n\n    assertThat(response.getContent()).contains(\"inclusion: always\");\n    assertThat(response.getContent()).contains(\"IMPORTANT\");\n    assertThat(response.getContent()).contains(\"analyze_file_list\");\n    assertThat(response.getContent()).contains(\"Important Tool Guidelines\");\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/ai/ide/AiHookMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.ai.ide;\n\nimport java.time.Duration;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.AiAgent;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.GetHookScriptContentParams;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.EMBEDDED_SERVER;\n\nclass AiHookMediumTests {\n\n  @SonarLintTest\n  void it_should_return_hook_script_content_for_windsurf_with_embedded_port(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .withClientName(\"ClientName\")\n      .start(fakeClient);\n\n    // Wait for embedded server to start\n    await().atMost(Duration.ofSeconds(5)).untilAsserted(() -> assertThat(backend.getEmbeddedServerPort()).isGreaterThan(0));\n\n    var response = backend.getAiAgentService()\n      .getHookScriptContent(new GetHookScriptContentParams(AiAgent.WINDSURF))\n      .join();\n\n    // Check script content\n    assertThat(response.getScriptFileName()).matches(\"sonarqube_analysis_hook\\\\.(js|py|sh)\");\n    assertThat(response.getScriptContent())\n      .contains(\"SonarQube for IDE Windsurf Hook\")\n      .contains(\"sonarqube_analysis_hook\")\n      .contains(\"/sonarlint/api/analysis/files\")\n      .contains(\"/sonarlint/api/status\")\n      .contains(\"STARTING_PORT\")\n      .contains(\"ENDING_PORT\")\n      .containsAnyOf(\n        \"EXPECTED_IDE_NAME = 'Windsurf'\",  // JS/Python\n        \"EXPECTED_IDE_NAME=\\\"Windsurf\\\"\"   // Bash\n      );\n\n    // Check config content\n    assertThat(response.getConfigFileName()).isEqualTo(\"hooks.json\");\n    assertThat(response.getConfigContent()).contains(\"\\\"post_write_code\\\"\");\n    assertThat(response.getConfigContent()).contains(\"{{SCRIPT_PATH}}\");\n    assertThat(response.getConfigContent()).contains(\"\\\"show_output\\\": true\");\n  }\n\n  @SonarLintTest\n  void it_should_throw_exception_for_cursor_not_yet_implemented(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .withClientName(\"ClientName\")\n      .start(fakeClient);\n\n    // Wait for embedded server to start\n    await().atMost(Duration.ofSeconds(5)).untilAsserted(() -> assertThat(backend.getEmbeddedServerPort()).isGreaterThan(0));\n\n    var futureResponse = backend.getAiAgentService()\n      .getHookScriptContent(new GetHookScriptContentParams(AiAgent.CURSOR));\n\n    assertThat(futureResponse)\n      .failsWithin(Duration.ofSeconds(2))\n      .withThrowableThat()\n      .withCauseInstanceOf(UnsupportedOperationException.class)\n      .withMessageContaining(\"hook configuration not yet implemented\");\n  }\n\n}\n\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/analysis/ActiveRulesDumpingSensor.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.analysis;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.util.Collection;\nimport java.util.TreeMap;\nimport org.sonar.api.batch.rule.ActiveRule;\nimport org.sonar.api.batch.sensor.Sensor;\nimport org.sonar.api.batch.sensor.SensorContext;\nimport org.sonar.api.batch.sensor.SensorDescriptor;\n\npublic class ActiveRulesDumpingSensor implements Sensor {\n\n  @Override\n  public void describe(SensorDescriptor sensorDescriptor) {\n    sensorDescriptor.name(\"Active rules dumping sensor\");\n  }\n\n  @Override\n  public void execute(SensorContext sensorContext) {\n    var activeRules = sensorContext.activeRules().findAll();\n    dump(activeRules, sensorContext.fileSystem().baseDir());\n  }\n\n  private void dump(Collection<ActiveRule> activeRules, File baseDir) {\n    try {\n      var activeRuleStringRepresentations = activeRules.stream().map(a -> a.ruleKey() + \";\" + a.language() + \";\" + a.templateRuleKey() + \";\" + new TreeMap<>(a.params())).toList();\n      Files.writeString(baseDir.toPath().resolve(\"activerules.dump\"), String.join(\"\\n\", activeRuleStringRepresentations));\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/analysis/AnalysisCharsetMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.analysis;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.UUID;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.ImpactDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static utils.AnalysisUtils.awaitRaisedIssuesNotification;\n\nclass AnalysisCharsetMediumTests {\n\n  private static final String CONFIG_SCOPE_ID = \"CONFIG_SCOPE_ID\";\n\n  @SonarLintTest\n  void it_should_skip_utf8_bom_when_reading_from_disk(SonarLintTestHarness harness, @TempDir Path baseDir) throws IOException {\n    var filePath = baseDir.resolve(\"file.js\");\n    Files.copy(Objects.requireNonNull(getClass().getResourceAsStream(\"/file-with-utf8-bom.js\")), filePath);\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVASCRIPT)\n      .start(client);\n    var analysisId = UUID.randomUUID();\n\n    var result = backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(fileUri), Map.of(), false, System.currentTimeMillis())).join();\n\n    assertThat(result.getFailedAnalysisFiles()).isEmpty();\n    var raisedIssueDto = awaitRaisedIssuesNotification(client, CONFIG_SCOPE_ID).get(0);\n    assertThat(raisedIssueDto.getSeverityMode().isRight()).isTrue();\n    assertThat(raisedIssueDto.getSeverityMode().getRight().getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.COMPLETE);\n    assertThat(raisedIssueDto.getSeverityMode().getRight().getImpacts())\n      .extracting(ImpactDto::getSoftwareQuality, ImpactDto::getImpactSeverity)\n      .containsExactly(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.INFO));\n    assertThat(raisedIssueDto.getRuleKey()).isEqualTo(\"javascript:S1135\");\n    assertThat(raisedIssueDto.getPrimaryMessage()).isEqualTo(\"Complete the task associated to this \\\"TODO\\\" comment.\");\n    assertThat(raisedIssueDto.getFlows()).isEmpty();\n    assertThat(raisedIssueDto.getQuickFixes()).isEmpty();\n    assertThat(raisedIssueDto.getTextRange()).usingRecursiveComparison().isEqualTo(new TextRangeDto(1, 3, 1, 7));\n    assertThat(raisedIssueDto.getRuleDescriptionContextKey()).isNull();\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/analysis/AnalysisForcedByClientMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.analysis;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\nimport org.eclipse.jgit.api.errors.GitAPIException;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFileListParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFullProjectParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeOpenFilesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeVCSChangedFilesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidOpenFileParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.sonarsource.sonarlint.core.commons.testutils.GitUtils.commit;\nimport static org.sonarsource.sonarlint.core.commons.testutils.GitUtils.createRepository;\nimport static org.sonarsource.sonarlint.core.commons.testutils.GitUtils.modifyFile;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SECURITY_HOTSPOTS;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JAVA;\nimport static utils.AnalysisUtils.createFile;\n\nclass AnalysisForcedByClientMediumTests {\n\n  private static final String CONFIG_SCOPE_ID = \"CONFIG_SCOPE_ID\";\n\n  @SonarLintTest\n  void should_run_forced_analysis_for_list_of_files(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath1 = createFile(baseDir, \"Foo.java\",\n      \"public interface Foo {}\");\n    var filePath2 = createFile(baseDir, \"Bar.java\",\n      \"public interface Bar {}\");\n    var fileUri1 = filePath1.toUri();\n    var fileUri2 = filePath2.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri1, baseDir.relativize(filePath1), CONFIG_SCOPE_ID, false, null, filePath1, null, null, true),\n        new ClientFileDto(fileUri2, baseDir.relativize(filePath2), CONFIG_SCOPE_ID, false, null, filePath2, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    backend.getAnalysisService().analyzeFileList(\n      new AnalyzeFileListParams(CONFIG_SCOPE_ID, List.of(fileUri1, fileUri2)));\n    await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).hasSize(2));\n\n    var raisedIssues = client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID);\n    assertThat(raisedIssues).hasSize(2);\n  }\n\n  @SonarLintTest\n  void should_run_forced_analysis_for_open_files(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath1 = createFile(baseDir, \"Foo.java\", \"public interface Foo {}\");\n    var filePath2 = createFile(baseDir, \"Bar.java\", \"public interface Bar {}\");\n    var filePath3 = createFile(baseDir, \"Baz.java\", \"public interface Baz {}\");\n    var fileUri1 = filePath1.toUri();\n    var fileUri2 = filePath2.toUri();\n    var fileUri3 = filePath3.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(\n        new ClientFileDto(fileUri1, baseDir.relativize(filePath1), CONFIG_SCOPE_ID, false, null, filePath1, null, null, true),\n        new ClientFileDto(fileUri2, baseDir.relativize(filePath2), CONFIG_SCOPE_ID, false, null, filePath2, null, null, true),\n        new ClientFileDto(fileUri3, baseDir.relativize(filePath3), CONFIG_SCOPE_ID, false, null, filePath3, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .withAutomaticAnalysisEnabled(false)\n      .start(client);\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri1));\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri2));\n\n    backend.getAnalysisService().analyzeOpenFiles(new AnalyzeOpenFilesParams(CONFIG_SCOPE_ID));\n    await().during(500, TimeUnit.MILLISECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).hasSize(2));\n\n    var raisedIssues = client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID);\n    assertThat(raisedIssues).hasSize(2);\n  }\n\n  @SonarLintTest\n  void should_run_forced_analysis_vcs_changed_files(SonarLintTestHarness harness, @TempDir Path baseDir) throws IOException, GitAPIException {\n    var git = createRepository(baseDir);\n\n    var fileFoo = createFile(baseDir, \"Foo.java\", \"public interface Foo {}\");\n    var fileBar = createFile(baseDir, \"Bar.java\", \"\");\n    git.add().addFilepattern(\"Foo.java\").call();\n    git.add().addFilepattern(\"Bar.java\").call();\n    commit(git, \"Foo.java\");\n    commit(git, \"Bar.java\");\n    modifyFile(fileBar, \"public interface Bar {}\");\n    var fileBaz = createFile(baseDir, \"Baz.java\", \"public interface Baz {}\");\n    git.add().addFilepattern(\"Baz.java\").call();\n    var fileQux = createFile(baseDir, \"Qux.java\", \"public interface Qux {}\");\n    var fileFooUri = fileFoo.toUri();\n    var fileBarUri = fileBar.toUri();\n    var fileBazUri = fileBaz.toUri();\n    var fileQuxUri = fileQux.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(\n        new ClientFileDto(fileFooUri, baseDir.relativize(fileFoo), CONFIG_SCOPE_ID, false, null, fileFoo, null, null, true),\n        new ClientFileDto(fileBarUri, baseDir.relativize(fileBar), CONFIG_SCOPE_ID, false, null, fileBar, null, null, true),\n        new ClientFileDto(fileBazUri, baseDir.relativize(fileBaz), CONFIG_SCOPE_ID, false, null, fileBaz, null, null, true),\n        new ClientFileDto(fileQuxUri, baseDir.relativize(fileQux), CONFIG_SCOPE_ID, false, null, fileQux, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    backend.getAnalysisService().analyzeVCSChangedFiles(new AnalyzeVCSChangedFilesParams(CONFIG_SCOPE_ID));\n    await().during(500, TimeUnit.MILLISECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).hasSize(3));\n\n    var raisedIssues = client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID);\n    assertThat(raisedIssues).hasSize(3);\n  }\n\n  @SonarLintTest\n  void should_run_forced_full_project_analysis_only_for_hotspots(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var fileFoo = createFile(baseDir, \"Foo.java\", \"\"\"\n      public class Foo {\n        void foo() throws Exception {\n          java.security.MessageDigest md = java.security.MessageDigest.getInstance(\"MD5\");\n        }\n      }\n      \"\"\");\n    var fileBar = createFile(baseDir, \"Bar.java\", \"\");\n    var fileFooUri = fileFoo.toUri();\n    var fileBarUri = fileBar.toUri();\n\n    var connectionId = \"connectionId\";\n    var branchName = \"branchName\";\n    var projectKey = \"projectKey\";\n    var serverWithHotspots = harness.newFakeSonarQubeServer(\"10.4\")\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile.withLanguage(\"java\")\n        .withActiveRule(\"java:S4790\", activeRule -> activeRule.withSeverity(IssueSeverity.MAJOR)))\n      .withProject(projectKey,\n        project -> project\n          .withQualityProfile(\"qpKey\")\n          .withBranch(branchName))\n      .withPlugin(TestPlugin.JAVA)\n      .start();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(\n        new ClientFileDto(fileFooUri, baseDir.relativize(fileFoo), CONFIG_SCOPE_ID, false, null, fileFoo, null, null, true),\n        new ClientFileDto(fileBarUri, baseDir.relativize(fileBar), CONFIG_SCOPE_ID, false, null, fileBar, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withBackendCapability(FULL_SYNCHRONIZATION, SECURITY_HOTSPOTS)\n      .withSonarQubeConnection(connectionId, serverWithHotspots)\n      .withBoundConfigScope(CONFIG_SCOPE_ID, connectionId, projectKey)\n      .withExtraEnabledLanguagesInConnectedMode(JAVA)\n      .start(client);\n    await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(client.getSynchronizedConfigScopeIds()).contains(CONFIG_SCOPE_ID));\n\n    backend.getAnalysisService().analyzeFullProject(new AnalyzeFullProjectParams(CONFIG_SCOPE_ID, true));\n    await().atMost(40, TimeUnit.SECONDS).untilAsserted(() ->\n      assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID)).isEmpty());\n    await().atMost(40, TimeUnit.SECONDS).untilAsserted(() ->\n      assertThat(client.getRaisedHotspotsForScopeIdAsList(CONFIG_SCOPE_ID)).hasSize(1));\n\n    var raisedIssuesForFoo = client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID).get(fileFooUri);\n    var raisedIssuesForBar = client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID).get(fileBarUri);\n    var raisedHotspotsForFoo = client.getRaisedHotspotsForScopeId(CONFIG_SCOPE_ID).get(fileFooUri);\n    var raisedHotspotsForBar = client.getRaisedHotspotsForScopeId(CONFIG_SCOPE_ID).get(fileBarUri);\n    assertThat(raisedIssuesForFoo).isEmpty();\n    assertThat(raisedIssuesForBar).isEmpty();\n    assertThat(raisedHotspotsForFoo).hasSize(1);\n    assertThat(raisedHotspotsForBar).isEmpty();\n  }\n\n  @SonarLintTest\n  void should_run_forced_full_project_analysis_for_all_findings(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var fileFoo = createFile(baseDir, \"Foo.java\", \"\"\"\n      public class Foo {\n        void foo() throws Exception {\n          java.security.MessageDigest md = java.security.MessageDigest.getInstance(\"MD5\");\n        }\n      }\n      \"\"\");\n    var fileBar = createFile(baseDir, \"Bar.java\", \"\");\n    var fileFooUri = fileFoo.toUri();\n    var fileBarUri = fileBar.toUri();\n\n    var connectionId = \"connectionId\";\n    var branchName = \"branchName\";\n    var projectKey = \"projectKey\";\n    var serverWithHotspots = harness.newFakeSonarQubeServer(\"10.4\")\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile.withLanguage(\"java\")\n        .withActiveRule(\"java:S4790\", activeRule -> activeRule.withSeverity(IssueSeverity.MAJOR))\n        .withActiveRule(\"java:S1220\", activeRule -> activeRule.withSeverity(IssueSeverity.MAJOR)))\n      .withProject(projectKey,\n        project -> project\n          .withQualityProfile(\"qpKey\")\n          .withBranch(branchName))\n      .withPlugin(TestPlugin.JAVA)\n      .start();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(\n        new ClientFileDto(fileFooUri, baseDir.relativize(fileFoo), CONFIG_SCOPE_ID, false, null, fileFoo, null, null, true),\n        new ClientFileDto(fileBarUri, baseDir.relativize(fileBar), CONFIG_SCOPE_ID, false, null, fileBar, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .withBackendCapability(SECURITY_HOTSPOTS)\n      .withSonarQubeConnection(connectionId, serverWithHotspots)\n      .withBoundConfigScope(CONFIG_SCOPE_ID, connectionId, projectKey)\n      .withExtraEnabledLanguagesInConnectedMode(JAVA)\n      .start(client);\n    await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(client.getSynchronizedConfigScopeIds()).contains(CONFIG_SCOPE_ID));\n\n    backend.getAnalysisService().analyzeFullProject(new AnalyzeFullProjectParams(CONFIG_SCOPE_ID, false));\n    await().atMost(40, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID)).hasSize(2));\n    await().atMost(40, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedHotspotsForScopeIdAsList(CONFIG_SCOPE_ID)).hasSize(1));\n\n    var raisedIssuesForFoo = client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID).get(fileFooUri);\n    var raisedIssuesForBar = client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID).get(fileBarUri);\n    var raisedHotspotsForFoo = client.getRaisedHotspotsForScopeId(CONFIG_SCOPE_ID).get(fileFooUri);\n    var raisedHotspotsForBar = client.getRaisedHotspotsForScopeId(CONFIG_SCOPE_ID).get(fileBarUri);\n    assertThat(raisedIssuesForFoo).hasSize(1);\n    assertThat(raisedIssuesForBar).hasSize(1);\n    assertThat(raisedHotspotsForFoo).hasSize(1);\n    assertThat(raisedHotspotsForBar).isEmpty();\n  }\n\n  @SonarLintTest\n  void should_apply_client_file_exclusions_for_forced_open_files_analysis(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .withFileExclusions(CONFIG_SCOPE_ID, Set.of(\"**/*.xml\"))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withAutomaticAnalysisEnabled(false)\n      .start(client);\n\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n\n    backend.getAnalysisService().analyzeOpenFiles(new AnalyzeOpenFilesParams(CONFIG_SCOPE_ID));\n\n    await().pollDelay(500, TimeUnit.MILLISECONDS).atMost(2, TimeUnit.SECONDS)\n      .untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).isEmpty());\n  }\n\n  @SonarLintTest\n  void should_apply_client_file_exclusions_for_forced_full_project_analysis(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var fooPath = createFile(baseDir, \"Foo.java\", \"public interface Foo {}\");\n    var barPath = createFile(baseDir, \"Bar.java\", \"public interface Bar {}\");\n    var fooUri = fooPath.toUri();\n    var barUri = barPath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(\n        new ClientFileDto(fooUri, baseDir.relativize(fooPath), CONFIG_SCOPE_ID, false, null, fooPath, null, null, true),\n        new ClientFileDto(barUri, baseDir.relativize(barPath), CONFIG_SCOPE_ID, false, null, barPath, null, null, true)))\n      .withFileExclusions(CONFIG_SCOPE_ID, Set.of(\"**/Bar.java\"))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    backend.getAnalysisService().analyzeFullProject(new AnalyzeFullProjectParams(CONFIG_SCOPE_ID, false));\n    await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).containsKey(fooUri));\n    assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).doesNotContainKey(barUri);\n  }\n\n  @SonarLintTest\n  void should_apply_client_file_exclusions_for_forced_vcs_changed_files_analysis(SonarLintTestHarness harness, @TempDir Path baseDir)\n    throws IOException, GitAPIException {\n    var git = createRepository(baseDir);\n\n    var fooPath = createFile(baseDir, \"Foo.java\", \"public interface Foo {}\");\n    var barPath = createFile(baseDir, \"Bar.java\", \"public interface Bar {}\");\n    commit(git, \"Foo.java\", \"Bar.java\");\n    modifyFile(fooPath, \"public class Foo { }\");\n    modifyFile(barPath, \"public class Bar { }\");\n\n    var fooUri = fooPath.toUri();\n    var barUri = barPath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(\n        new ClientFileDto(fooUri, baseDir.relativize(fooPath), CONFIG_SCOPE_ID, false, null, fooPath, null, null, true),\n        new ClientFileDto(barUri, baseDir.relativize(barPath), CONFIG_SCOPE_ID, false, null, barPath, null, null, true)))\n      .withFileExclusions(CONFIG_SCOPE_ID, Set.of(\"**/Bar.java\"))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    backend.getAnalysisService().analyzeVCSChangedFiles(new AnalyzeVCSChangedFilesParams(CONFIG_SCOPE_ID));\n    await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).containsKey(fooUri));\n    assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).doesNotContainKey(barUri);\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/analysis/AnalysisMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.analysis;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.URLDecoder;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport javax.annotation.Nullable;\nimport mediumtest.analysis.sensor.ThrowingSensorConstructor;\nimport org.apache.commons.io.FileUtils;\nimport org.eclipse.jgit.api.errors.GitAPIException;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.mockito.ArgumentCaptor;\nimport org.sonar.api.utils.System2;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.commons.plugins.SonarPlugin;\nimport org.sonarsource.sonarlint.core.commons.testutils.GitUtils;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.DidChangeAnalysisPropertiesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.DidChangePathToCompileCommandsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.ShouldUseEnterpriseCSharpAnalyzerParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidRemoveConfigurationScopeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidOpenFileParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidUpdateFileSystemParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.GetEffectiveRuleDetailsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.ImpactDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.plugin.DidSkipLoadingPluginParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.progress.ProgressEndNotification;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.progress.ReportProgressParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.progress.StartProgressParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.OnDiskTestClientInputFile;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\nimport static org.mockito.ArgumentMatchers.refEq;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.verify;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.test.utils.plugins.SonarPluginBuilder.newSonarPlugin;\nimport static utils.AnalysisUtils.awaitRaisedIssuesNotification;\nimport static utils.AnalysisUtils.createFile;\nimport static utils.AnalysisUtils.editFile;\nimport static utils.AnalysisUtils.removeFile;\n\nclass AnalysisMediumTests {\n\n  private static final String CONFIG_SCOPE_ID = \"CONFIG_SCOPE_ID\";\n  private String javaVersion;\n  private static final boolean COMMERCIAL_ENABLED = System.getProperty(\"commercial\") != null;\n\n  @BeforeEach\n  void setUp() {\n    javaVersion = System2.INSTANCE.property(\"java.specification.version\");\n  }\n\n  @AfterEach\n  void stop() {\n    System2.INSTANCE.setProperty(\"java.specification.version\", javaVersion);\n  }\n\n  @SonarLintTest\n  void it_should_skip_analysis_if_no_file_provided(SonarLintTestHarness harness, @TempDir Path tempDir) {\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .start(client);\n    var analysisId = UUID.randomUUID();\n\n    var result = backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(tempDir.resolve(\"File.java\").toUri()), Map.of(), false, System.currentTimeMillis()))\n      .join();\n\n    assertThat(result.getFailedAnalysisFiles()).isEmpty();\n    await().during(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID)).isEmpty());\n  }\n\n  @SonarLintTest\n  void should_not_raise_issues_for_previously_analysed_files_if_they_were_not_submitted_for_analysis(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var fileFooPath = createFile(baseDir, \"Foo.java\",\n      \"\"\"\n        public class Foo {\n          public void foo() {\n            int x;\n            System.out.println(\"Foo\");\n          }\n        }\"\"\");\n    var fileBarPath = createFile(baseDir, \"Bar.java\",\n      \"\"\"\n        public class Bar {\n          public void foo() {\n            int x;\n            System.out.println(\"Foo\");\n          }\n        }\"\"\");\n    var fileFooUri = fileFooPath.toUri();\n    var fileBarUri = fileBarPath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(\n        new ClientFileDto(fileFooUri, baseDir.relativize(fileFooPath), CONFIG_SCOPE_ID, false, null, fileFooPath, null, null, true),\n        new ClientFileDto(fileBarUri, baseDir.relativize(fileBarPath), CONFIG_SCOPE_ID, false, null, fileBarPath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .withDisabledPluginsForAnalysis(SonarPlugin.JAVA.getKey())\n      .start(client);\n    var analysisId = UUID.randomUUID();\n\n    var result = backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(fileFooUri), Map.of(), true, System.currentTimeMillis())).join();\n    await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).isNotEmpty());\n    assertThat(result.getFailedAnalysisFiles()).isEmpty();\n    var issues = client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID);\n    assertThat(issues).containsOnlyKeys(fileFooUri);\n\n    client.cleanRaisedIssues();\n\n    result = backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(fileBarUri), Map.of(), true, System.currentTimeMillis())).join();\n    await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).isNotEmpty());\n\n    assertThat(result.getFailedAnalysisFiles()).isEmpty();\n    issues = client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID);\n    assertThat(issues).containsOnlyKeys(fileBarUri);\n  }\n\n  @SonarLintTest\n  void it_should_analyze_xml_file_in_standalone_mode(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n    var analysisId = UUID.randomUUID();\n\n    var result = backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(fileUri), Map.of(), false, System.currentTimeMillis())).join();\n\n    assertThat(result.getFailedAnalysisFiles()).isEmpty();\n    var raisedIssueDto = awaitRaisedIssuesNotification(client, CONFIG_SCOPE_ID).get(0);\n    assertThat(raisedIssueDto.getSeverityMode().isRight()).isTrue();\n    assertThat(raisedIssueDto.getSeverityMode().getRight().getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CONVENTIONAL);\n    assertThat(raisedIssueDto.getSeverityMode().getRight().getImpacts())\n      .extracting(ImpactDto::getSoftwareQuality, ImpactDto::getImpactSeverity)\n      .containsExactly(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW));\n    assertThat(raisedIssueDto.getRuleKey()).isEqualTo(\"xml:S3421\");\n    assertThat(raisedIssueDto.getPrimaryMessage()).isEqualTo(\"Replace \\\"pom.version\\\" with \\\"project.version\\\".\");\n    assertThat(raisedIssueDto.getFlows()).isEmpty();\n    assertThat(raisedIssueDto.getQuickFixes()).isEmpty();\n    assertThat(raisedIssueDto.getTextRange()).usingRecursiveComparison().isEqualTo(new TextRangeDto(6, 11, 6, 25));\n    assertThat(raisedIssueDto.getRuleDescriptionContextKey()).isNull();\n  }\n\n  @SonarLintTest\n  void it_should_analyze_xml_file_in_connected_mode(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var server = harness.newFakeSonarQubeServer().withPlugin(TestPlugin.XML).start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server,\n        storage -> storage.withPlugin(TestPlugin.XML).withProject(\"projectKey\",\n          project -> project.withMainBranch(\"main\").withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"BLOCKER\"))))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, \"connectionId\", \"projectKey\")\n      .withExtraEnabledLanguagesInConnectedMode(Language.XML)\n      .start(client);\n    var analysisId = UUID.randomUUID();\n\n    var result = backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(fileUri), Map.of(), false)).join();\n\n    assertThat(result.getFailedAnalysisFiles()).isEmpty();\n    await().untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).isNotEmpty());\n    var raisedIssueDto = client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID).get(fileUri).get(0);\n    assertThat(raisedIssueDto.getSeverityMode().isRight()).isTrue();\n    assertThat(raisedIssueDto.getSeverityMode().getRight().getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CONVENTIONAL);\n    assertThat(raisedIssueDto.getSeverityMode().getRight().getImpacts())\n      .extracting(ImpactDto::getSoftwareQuality, ImpactDto::getImpactSeverity)\n      .containsExactly(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW));\n    assertThat(raisedIssueDto.getRuleKey()).isEqualTo(\"xml:S3421\");\n  }\n\n  @SonarLintTest\n  void it_should_analyze_xml_file_after_binding(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var xmlFileContent = \"\"\"\n      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <project>\n        <modelVersion>4.0.0</modelVersion>\n        <groupId>com.foo</groupId>\n        <artifactId>bar</artifactId>\n        <version>${pom.version}</version>\n      </project>\"\"\";\n    var firstFilePath = createFile(baseDir, \"pom.xml\", xmlFileContent);\n    var firstFileUri = firstFilePath.toUri();\n    var secondFilePath = createFile(baseDir, \"pom.xml\", xmlFileContent);\n    var secondFileUri = secondFilePath.toUri();\n    final var OTHER_CONFIG_SCOPE_ID = CONFIG_SCOPE_ID + \"2\";\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(firstFileUri, baseDir.relativize(firstFilePath), CONFIG_SCOPE_ID, false, null, firstFilePath, null, null, true)))\n      .withInitialFs(OTHER_CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(secondFileUri, baseDir.relativize(secondFilePath), OTHER_CONFIG_SCOPE_ID, false, null, secondFilePath, null, null, true)))\n      .build();\n    var server = harness.newFakeSonarQubeServer()\n      .withPlugin(TestPlugin.XML)\n      .withProject(\"projectKey\")\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server,\n        storage -> storage.withPlugin(TestPlugin.XML).withProject(\"projectKey\",\n          project -> project.withMainBranch(\"main\").withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"BLOCKER\"))))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, \"connectionId\", \"projectKey\")\n      .withExtraEnabledLanguagesInConnectedMode(Language.XML)\n      .start(client);\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, firstFileUri));\n    await().untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID)).isNotEmpty());\n    client.cleanRaisedIssues();\n\n    backend.getConfigurationService()\n      .didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(new ConfigurationScopeDto(OTHER_CONFIG_SCOPE_ID, null, true, \"Name\", null))));\n    backend.getConfigurationService().didUpdateBinding(new DidUpdateBindingParams(OTHER_CONFIG_SCOPE_ID, new BindingConfigurationDto(\"connectionId\", \"projectKey\", true)));\n\n    var result = backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(OTHER_CONFIG_SCOPE_ID, UUID.randomUUID(), List.of(secondFileUri), Map.of(), false)).join();\n\n    assertThat(result.getFailedAnalysisFiles()).isEmpty();\n    await().untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(OTHER_CONFIG_SCOPE_ID)).isNotEmpty());\n    var raisedIssueDto = client.getRaisedIssuesForScopeId(OTHER_CONFIG_SCOPE_ID).get(secondFileUri).get(0);\n    assertThat(raisedIssueDto.getSeverityMode().isRight()).isTrue();\n    assertThat(raisedIssueDto.getSeverityMode().getRight().getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CONVENTIONAL);\n    assertThat(raisedIssueDto.getSeverityMode().getRight().getImpacts())\n      .extracting(ImpactDto::getSoftwareQuality, ImpactDto::getImpactSeverity)\n      .containsExactly(tuple(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW));\n    assertThat(raisedIssueDto.getRuleKey()).isEqualTo(\"xml:S3421\");\n  }\n\n  @SonarLintTest\n  void it_should_notify_client_on_plugin_skip(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    System2.INSTANCE.setProperty(\"java.specification.version\", \"10\");\n    var filePath = createFile(baseDir, \"Main.java\",\n      \"public class Main {}\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n    var analysisId = UUID.randomUUID();\n\n    var result = backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(fileUri), Map.of(), false)).join();\n\n    assertThat(result.getFailedAnalysisFiles()).isEmpty();\n    verify(client, timeout(200)).didSkipLoadingPlugin(CONFIG_SCOPE_ID, Language.JAVA, DidSkipLoadingPluginParams.SkipReason.UNSATISFIED_JRE, \"17\", \"10\");\n  }\n\n  @SonarLintTest\n  void it_should_notify_client_on_secret_detection(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"secret.py\",\n      \"aws_secret_access_key=kHeUAwnSUizTWpSbyGAz4f+As5LshPIjvtpswqGb\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.TEXT)\n      .start(client);\n    var analysisId = UUID.randomUUID();\n\n    var result = backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(fileUri), Map.of(), false)).join();\n\n    assertThat(result.getFailedAnalysisFiles()).isEmpty();\n    verify(client, timeout(1000)).didDetectSecret(CONFIG_SCOPE_ID);\n  }\n\n  @SonarLintTest\n  void it_should_notify_client_on_analysis_progress(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"secret.py\",\n      \"KEY = \\\"AKIAIGKECZXA7AEIJLMQ\\\"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.TEXT)\n      .start(client);\n    var analysisId = UUID.randomUUID();\n\n    backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(fileUri), Map.of(), false)).join();\n\n    verify(client).startProgress(refEq(new StartProgressParams(analysisId.toString(), CONFIG_SCOPE_ID, \"Analyzing 1 file\", null, true, true)));\n    var reportProgressCaptor = ArgumentCaptor.forClass(ReportProgressParams.class);\n    verify(client, timeout(500)).reportProgress(reportProgressCaptor.capture());\n    assertThat(reportProgressCaptor.getValue())\n      .usingRecursiveComparison()\n      .isEqualTo(new ReportProgressParams(analysisId.toString(), new ProgressEndNotification()));\n  }\n\n  @SonarLintTest\n  void it_should_print_a_log_when_the_analysis_fails(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"secret.py\",\n      \"KEY = \\\"AKIAIGKECZXA7AEIJLMQ\\\"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var throwingPluginPath = newSonarPlugin(\"python\")\n      .withSensor(ThrowingSensorConstructor.class)\n      .generate(baseDir);\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPlugin(throwingPluginPath)\n      .withEnabledLanguageInStandaloneMode(Language.PYTHON)\n      .start(client);\n    var analysisId = UUID.randomUUID();\n\n    var future = backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(fileUri), Map.of(), false));\n\n    assertThat(future)\n      .failsWithin(Duration.ofSeconds(2))\n      .withThrowableOfType(ExecutionException.class)\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class);\n\n    assertThat(client.getLogs()).extracting(LogParams::getMessage).contains(\"Error during analysis\");\n  }\n\n  @SonarLintTest\n  void analysis_response_should_contain_raw_issues(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"secret.py\",\n      \"aws_secret_access_key=kHeUAwnSUizTWpSbyGAz4f+As5LshPIjvtpswqGb\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.TEXT)\n      .start(client);\n    var analysisId = UUID.randomUUID();\n\n    var result = backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(fileUri), Map.of(), false, System.currentTimeMillis())).join();\n\n    assertThat(result.getFailedAnalysisFiles()).isEmpty();\n    assertThat(result.getRawIssues()).hasSize(1);\n    assertThat(result.getRawIssues().get(0).getRuleKey()).isEqualTo(\"secrets:S6290\");\n  }\n\n  @SonarLintTest\n  void it_should_report_issues_for_multi_file_analysis_taking_data_from_module_filesystem(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var fileIssue = createFile(baseDir, \"fileIssue.py\",\n      \"\"\"\n        from fileFuncDef import foo\n        foo(1,2,3)\n        \"\"\");\n    var fileFuncDef = createFile(baseDir, \"fileFuncDef.py\",\n      \"\"\"\n        def foo(a):\n            print(a)\n        \"\"\");\n    var fileIssueUri = fileIssue.toUri();\n    var fileFuncDefUri = fileFuncDef.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileIssueUri, baseDir.relativize(fileIssue), CONFIG_SCOPE_ID, false, null, fileIssue, null, null, true),\n        new ClientFileDto(fileFuncDefUri, baseDir.relativize(fileFuncDef), CONFIG_SCOPE_ID, false, null, fileFuncDef, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(client);\n    var analysisId = UUID.randomUUID();\n\n    var result = backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId,\n        List.of(fileIssueUri), Map.of(), false, System.currentTimeMillis()))\n      .join();\n\n    assertThat(result.getFailedAnalysisFiles()).isEmpty();\n    var raisedIssueDto = awaitRaisedIssuesNotification(client, CONFIG_SCOPE_ID).get(0);\n    assertThat(raisedIssueDto.getRuleKey()).isEqualTo(\"python:S930\");\n  }\n\n  @SonarLintTest\n  void it_should_report_multi_file_issues_for_files_added_after_initialization(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var fileIssue = createFile(baseDir, \"fileIssue.py\",\n      \"\"\"\n        from fileFuncDef import foo\n        foo(1,2,3)\n        \"\"\");\n    var fileFuncDef = createFile(baseDir, \"fileFuncDef.py\",\n      \"\"\"\n        def foo(a):\n            print(a)\n        \"\"\");\n    var fileIssueUri = fileIssue.toUri();\n    var fileFuncDefUri = fileFuncDef.toUri();\n    var client = harness.newFakeClient()\n      .build();\n    var backend = harness.newBackend()\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .start(client);\n    var analysisId = UUID.randomUUID();\n\n    var result = backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId,\n      List.of(fileIssueUri), Map.of(), false, System.currentTimeMillis())).join();\n\n    assertThat(result.getFailedAnalysisFiles()).isEmpty();\n    await().during(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID)).isEmpty());\n\n    backend.getFileService().didUpdateFileSystem(\n      new DidUpdateFileSystemParams(\n        List.of(new ClientFileDto(fileIssueUri, baseDir.relativize(fileIssue), CONFIG_SCOPE_ID, false, null, fileIssue, null, null, true),\n          new ClientFileDto(fileFuncDefUri, baseDir.relativize(fileFuncDef), CONFIG_SCOPE_ID, false, null, fileFuncDef, null, null, true)),\n        List.of(),\n        List.of()));\n\n    result = backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId,\n      List.of(fileIssueUri), Map.of(), false, System.currentTimeMillis())).join();\n\n    assertThat(result.getFailedAnalysisFiles()).isEmpty();\n    var raisedIssueDto = awaitRaisedIssuesNotification(client, CONFIG_SCOPE_ID).get(0);\n    assertThat(raisedIssueDto.getRuleKey()).isEqualTo(\"python:S930\");\n  }\n\n  @SonarLintTest\n  void it_should_report_issues_for_multi_file_analysis_on_child_and_parent_config_scopes(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var file1Issue = createFile(baseDir, \"file1Issue.py\",\n      \"\"\"\n        from file1FuncDef import foo\n        foo(1,2,3)\n        \"\"\");\n    var file1FuncDef = createFile(baseDir, \"file1FuncDef.py\",\n      \"\"\"\n        def foo(a):\n            print(a)\n        \"\"\");\n    var file2Issue = createFile(baseDir, \"file2Issue.py\",\n      \"\"\"\n        from file2FuncDef import foo\n        foo(1,2,3)\n        \"\"\");\n    var file2FuncDef = createFile(baseDir, \"file2FuncDef.py\",\n      \"\"\"\n        def foo(a):\n            print(a)\n        \"\"\");\n    var file1IssueUri = file1Issue.toUri();\n    var file1FuncDefUri = file1FuncDef.toUri();\n    var file2IssueUri = file2Issue.toUri();\n    var file2FuncDefUri = file2FuncDef.toUri();\n    var parentConfigScope = \"parentConfigScope\";\n    var leafConfigScope = \"leafConfigScope\";\n    var client = harness.newFakeClient()\n      .withInitialFs(parentConfigScope, baseDir,\n        List.of(new ClientFileDto(file1IssueUri, baseDir.relativize(file1Issue), parentConfigScope, false, null, file1Issue, null, null, true),\n          new ClientFileDto(file1FuncDefUri, baseDir.relativize(file1FuncDef), parentConfigScope, false, null, file1FuncDef, null, null, true)))\n      .withInitialFs(leafConfigScope, baseDir, List.of(new ClientFileDto(file2IssueUri, baseDir.relativize(file2Issue), leafConfigScope, false, null, file2Issue, null, null, true),\n        new ClientFileDto(file2FuncDefUri, baseDir.relativize(file2FuncDef), leafConfigScope, false, null, file2FuncDef, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(parentConfigScope)\n      .withUnboundConfigScope(leafConfigScope, leafConfigScope, parentConfigScope)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(client);\n    var analysisId = UUID.randomUUID();\n\n    var leafConfigScopeResult = backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(leafConfigScope, analysisId,\n      List.of(file2IssueUri), Map.of(), false, System.currentTimeMillis())).join();\n\n    assertThat(leafConfigScopeResult.getFailedAnalysisFiles()).isEmpty();\n    var raisedIssueDto = awaitRaisedIssuesNotification(client, leafConfigScope).get(0);\n    assertThat(raisedIssueDto.getRuleKey()).isEqualTo(\"python:S930\");\n\n    var parentConfigScopeResult = backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(parentConfigScope,\n      analysisId, List.of(file1IssueUri), Map.of(), false, System.currentTimeMillis())).join();\n\n    assertThat(parentConfigScopeResult.getFailedAnalysisFiles()).isEmpty();\n    raisedIssueDto = awaitRaisedIssuesNotification(client, parentConfigScope).get(0);\n    assertThat(raisedIssueDto.getRuleKey()).isEqualTo(\"python:S930\");\n  }\n\n  @SonarLintTest\n  void it_should_update_module_file_system_on_file_events_creating_file(SonarLintTestHarness harness, @TempDir Path tempDir) throws IOException {\n    var baseDir = Files.createDirectory(tempDir.resolve(\"it_should_update_module_file_system_on_file_events_creating_file\"));\n    var fileIssue = createFile(baseDir, \"fileIssue.py\",\n      \"\"\"\n        from fileFuncDef import foo\n        foo(1,2,3)\n        \"\"\");\n    var fileIssueUri = fileIssue.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileIssueUri, baseDir.relativize(fileIssue),\n        CONFIG_SCOPE_ID, false, null, fileIssue, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(client);\n    var analysisId = UUID.randomUUID();\n\n    var parentConfigScopeResult = backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId,\n      List.of(fileIssueUri), Map.of(), false, System.currentTimeMillis())).join();\n\n    assertThat(parentConfigScopeResult.getFailedAnalysisFiles()).isEmpty();\n    await().during(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID)).isEmpty());\n\n    var fileFuncDef = createFile(baseDir, \"fileFuncDef.py\",\n      \"\"\"\n        def foo(a):\n            print(a)\n        \"\"\");\n    var fileFuncDefUri = fileFuncDef.toUri();\n    backend.getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(\n      List.of(new ClientFileDto(fileFuncDefUri, baseDir.relativize(fileFuncDef), CONFIG_SCOPE_ID, false, null, fileFuncDef, null, null,\n        true)),\n      List.of(),\n      List.of()));\n\n    parentConfigScopeResult = backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID,\n      analysisId, List.of(fileIssueUri), Map.of(), false, System.currentTimeMillis())).join();\n\n    assertThat(parentConfigScopeResult.getFailedAnalysisFiles()).isEmpty();\n    var raisedIssueDto = awaitRaisedIssuesNotification(client, CONFIG_SCOPE_ID).get(0);\n    assertThat(raisedIssueDto.getRuleKey()).isEqualTo(\"python:S930\");\n  }\n\n  @Disabled(\"SLCORE-1113\")\n  @SonarLintTest\n  void it_should_update_module_file_system_on_file_events_deleting_file(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var fileIssue = createFile(baseDir, \"fileIssue.py\",\n      \"\"\"\n        from fileFuncDef import foo\n        foo(1,2,3)\n        \"\"\");\n    var fileFuncDef = createFile(baseDir, \"fileFuncDef.py\",\n      \"\"\"\n        def foo(a):\n            print(a)\n        \"\"\");\n    var fileIssueUri = fileIssue.toUri();\n    var fileFuncDefUri = fileFuncDef.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileIssueUri, baseDir.relativize(fileIssue),\n        CONFIG_SCOPE_ID, false, null, fileIssue, null, null, true),\n        new ClientFileDto(fileFuncDefUri, baseDir.relativize(fileFuncDef),\n          CONFIG_SCOPE_ID, false, null, fileFuncDef, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(client);\n    var analysisId = UUID.randomUUID();\n\n    var result = backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId,\n      List.of(fileIssueUri), Map.of(), false, System.currentTimeMillis())).join();\n\n    assertThat(result.getFailedAnalysisFiles()).isEmpty();\n    var raisedIssueDto = awaitRaisedIssuesNotification(client, CONFIG_SCOPE_ID).get(0);\n    assertThat(raisedIssueDto.getSeverityMode().isLeft()).isTrue();\n    assertThat(raisedIssueDto.getSeverityMode().getLeft().getSeverity()).isEqualTo(IssueSeverity.BLOCKER);\n    assertThat(raisedIssueDto.getRuleKey()).isEqualTo(\"python:S930\");\n\n    removeFile(baseDir, \"fileFuncDef.py\");\n    backend.getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(List.of(), List.of(), List.of(fileFuncDefUri)));\n\n    result = backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId,\n      List.of(fileIssueUri), Map.of(), false, System.currentTimeMillis())).join();\n\n    assertThat(result.getFailedAnalysisFiles()).isEmpty();\n    assertThat(client.getRaisedHotspotsForScopeIdAsList(CONFIG_SCOPE_ID)).isEmpty();\n  }\n\n  @Disabled(\"SLCORE-1113\")\n  @SonarLintTest\n  void it_should_update_module_file_system_on_file_events_editing_file(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var fileIssue = createFile(baseDir, \"fileIssue.py\",\n      \"\"\"\n        from fileFuncDef import foo\n        foo(1,2,3)\n        \"\"\");\n    var fileFuncDef = createFile(baseDir, \"fileFuncDef.py\",\n      \"\"\"\n        def foo(a):\n            print(a)\n        \"\"\");\n    var fileIssueUri = fileIssue.toUri();\n    var fileFuncDefUri = fileFuncDef.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileIssueUri, baseDir.relativize(fileIssue),\n        CONFIG_SCOPE_ID, false, null, fileIssue, null, null, true),\n        new ClientFileDto(fileFuncDefUri, baseDir.relativize(fileFuncDef),\n          CONFIG_SCOPE_ID, false, null, fileFuncDef, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(client);\n    var analysisId = UUID.randomUUID();\n\n    var result = backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId,\n      List.of(fileIssueUri), Map.of(), false, System.currentTimeMillis())).join();\n\n    assertThat(result.getFailedAnalysisFiles()).isEmpty();\n    var raisedIssueDto = awaitRaisedIssuesNotification(client, CONFIG_SCOPE_ID).get(0);\n    assertThat(raisedIssueDto.getSeverityMode().isLeft()).isTrue();\n    assertThat(raisedIssueDto.getSeverityMode().getLeft().getSeverity()).isEqualTo(IssueSeverity.BLOCKER);\n    assertThat(raisedIssueDto.getRuleKey()).isEqualTo(\"python:S930\");\n\n    editFile(baseDir, \"fileFuncDef.py\", \"\");\n    backend.getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(\n      List.of(new ClientFileDto(fileFuncDefUri, baseDir.relativize(fileFuncDef),\n        CONFIG_SCOPE_ID, false, null, fileFuncDef, \"\", null, true)),\n      List.of(),\n      List.of()));\n\n    result = backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId,\n      List.of(fileIssueUri), Map.of(), false, System.currentTimeMillis())).join();\n\n    assertThat(result.getFailedAnalysisFiles()).isEmpty();\n    assertThat(client.getRaisedHotspotsForScopeIdAsList(CONFIG_SCOPE_ID)).isEmpty();\n  }\n\n  @SonarLintTest\n  void should_pass_user_properties_to_sensors(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"file.php\", \"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false,\n        null, filePath, null, null, true)))\n      .build();\n    var propertyDumpingPlugin = newSonarPlugin(\"php\")\n      .withSensor(PropertyDumpingSensor.class)\n      .generate(baseDir);\n    var backend = harness.newBackend()\n      .withStandaloneEmbeddedPlugin(propertyDumpingPlugin)\n      .withEnabledLanguageInStandaloneMode(Language.PHP)\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .start(client);\n    backend.getAnalysisService().didSetUserAnalysisProperties(\n      new DidChangeAnalysisPropertiesParams(CONFIG_SCOPE_ID, Map.of(\"key1\", \"user-value1\")));\n\n    backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(), List.of(fileUri),\n        Map.of(PropertyDumpingSensor.PROPERTY_NAME_TO_DUMP, \"key1\"), false, System.currentTimeMillis()));\n\n    await().atMost(1, TimeUnit.SECONDS)\n      .untilAsserted(() -> assertThat(baseDir.resolve(\"property.dump\")).hasContent(\"user-value1\"));\n  }\n\n  @SonarLintTest\n  void should_pass_eslint_bridge_path_to_sensors_if_provided(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"file.php\", \"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false,\n        null, filePath, null, null, true)))\n      .build();\n    var propertyDumpingPlugin = newSonarPlugin(\"php\")\n      .withSensor(PropertyDumpingSensor.class)\n      .generate(baseDir);\n    var backend = harness.newBackend()\n      .withEslintBridgeServerBundlePath(baseDir.resolve(\"eslint-bridge\"))\n      .withStandaloneEmbeddedPlugin(propertyDumpingPlugin)\n      .withEnabledLanguageInStandaloneMode(Language.PHP)\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .start(client);\n\n    backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(), List.of(fileUri),\n        Map.of(PropertyDumpingSensor.PROPERTY_NAME_TO_DUMP, \"sonar.js.internal.bundlePath\"), false, System.currentTimeMillis()));\n\n    await().atMost(1, TimeUnit.SECONDS)\n      .untilAsserted(() -> assertThat(baseDir.resolve(\"property.dump\")).hasContent(baseDir.resolve(\"eslint-bridge\").toString()));\n  }\n\n  @SonarLintTest\n  void should_not_set_js_internal_bundlePath_when_no_language_specific_requirements(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", \"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false,\n        null, filePath, null, null, true)))\n      .build();\n    var propertyDumpingPlugin = newSonarPlugin(\"php\")\n      .withSensor(PropertyDumpingSensor.class)\n      .generate(baseDir);\n    var backend = harness.newBackend()\n      .withNoLanguageSpecificRequirements()\n      .withStandaloneEmbeddedPlugin(propertyDumpingPlugin)\n      .start(client);\n\n    backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(), List.of(fileUri),\n        Map.of(PropertyDumpingSensor.PROPERTY_NAME_TO_DUMP, \"sonar.js.internal.bundlePath\"), false, System.currentTimeMillis()));\n\n    await().atMost(1, TimeUnit.SECONDS)\n      .untilAsserted(() -> assertThat(baseDir.resolve(\"property.dump\")).doesNotExist());\n  }\n\n  @SonarLintTest\n  void it_should_skip_analysis_and_keep_rules_if_disabled_language_for_analysis(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withDisabledPluginsForAnalysis(SonarPlugin.XML.getKey())\n      .start(client);\n    var analysisId = UUID.randomUUID();\n\n    var result = backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(fileUri), Map.of(), false, System.currentTimeMillis())).join();\n\n    assertThat(result.getFailedAnalysisFiles()).isEmpty();\n    assertThat(result.getRawIssues()).isEmpty();\n    await().during(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID)).isEmpty());\n\n    var allRules = backend.getRulesService().listAllStandaloneRulesDefinitions().join();\n    assertThat(allRules.getRulesByKey().keySet())\n      .isNotEmpty()\n      .allMatch(key -> key.startsWith(\"xml:\"));\n    var ruleDetails = backend.getRulesService().getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams(CONFIG_SCOPE_ID, \"xml:S103\", null)).join();\n    assertThat(ruleDetails.details().getName()).isEqualTo(\"Lines should not be too long\");\n  }\n\n  @SonarLintTest\n  void it_should_skip_analysis_only_for_disabled_language(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var xmlFilePath = createFile(baseDir, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n    var xmlFileUri = xmlFilePath.toUri();\n    var javaFilePath = createFile(baseDir, \"Main.java\",\n      \"public class Main {}\");\n    var javaFileUri = javaFilePath.toUri();\n\n    var xmlClientFile = new ClientFileDto(xmlFileUri, baseDir.relativize(xmlFilePath), CONFIG_SCOPE_ID, false, null, xmlFilePath, null, null, true);\n    var javaClientFile = new ClientFileDto(javaFileUri, baseDir.relativize(javaFilePath), CONFIG_SCOPE_ID, false, null, javaFilePath, null, null, true);\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(xmlClientFile, javaClientFile))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .withDisabledPluginsForAnalysis(SonarPlugin.XML.getKey())\n      .start(client);\n    var analysisId = UUID.randomUUID();\n\n    var result = backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(xmlFileUri, javaFileUri), Map.of(), false, System.currentTimeMillis())).join();\n\n    assertThat(result.getFailedAnalysisFiles()).isEmpty();\n    assertThat(result.getRawIssues())\n      .hasSize(2)\n      .allMatch(rawIssueDto -> rawIssueDto.getRuleKey().startsWith(\"java:\"));\n  }\n\n  @SonarLintTest\n  void should_trigger_analysis_on_path_to_compile_command_change(SonarLintTestHarness harness, @TempDir Path baseDir) throws IOException {\n    assumeTrue(COMMERCIAL_ENABLED);\n    var cFile = prepareInputFile(\"foo.c\", \"#import \\\"foo.h\\\"\\n\", false, StandardCharsets.UTF_8, SonarLanguage.C, baseDir);\n    var cFilePath = baseDir.resolve(\"foo.c\");\n    var buildWrapperContent = \"{\\\"version\\\":0,\\\"captures\\\":[\" +\n      \"{\" +\n      \"\\\"compiler\\\": \\\"clang\\\",\" +\n      \"\\\"executable\\\": \\\"compiler\\\",\" +\n      \"\\\"stdout\\\": \\\"#define __STDC_VERSION__ 201112L\\n\\\",\" +\n      \"\\\"stderr\\\": \\\"\\\"\" +\n      \"},\" +\n      \"{\" +\n      \"\\\"compiler\\\": \\\"clang\\\",\" +\n      \"\\\"executable\\\": \\\"compiler\\\",\" +\n      \"\\\"stdout\\\": \\\"#define __cplusplus 201703L\\n\\\",\" +\n      \"\\\"stderr\\\": \\\"\\\"\" +\n      \"},\" +\n      \"{\\\"compiler\\\":\\\"clang\\\",\\\"cwd\\\":\\\"\" +\n      baseDir.toString().replace(\"\\\\\", \"\\\\\\\\\") +\n      \"\\\",\\\"executable\\\":\\\"compiler\\\",\\\"cmd\\\":[\\\"cc\\\",\\\"foo.c\\\"]}]}\";\n    var cFileUri = cFile.uri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(cFileUri, baseDir.relativize(cFilePath), CONFIG_SCOPE_ID, false, null, cFilePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.CFAMILY)\n      .start(client);\n    backend.getAnalysisService()\n      .didSetUserAnalysisProperties(new DidChangeAnalysisPropertiesParams(CONFIG_SCOPE_ID, Map.of(\"sonar.cfamily.build-wrapper-content\", buildWrapperContent)));\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, cFileUri));\n    await().atMost(20, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID).get(cFileUri)).isNotEmpty());\n    client.cleanRaisedIssues();\n\n    backend.getAnalysisService().didChangePathToCompileCommands(new DidChangePathToCompileCommandsParams(CONFIG_SCOPE_ID, \"/path\"));\n\n    await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).isNotEmpty());\n    assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).containsOnlyKeys(cFileUri);\n    assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID).get(cFileUri)).hasSize(1);\n  }\n\n  @SonarLintTest\n  void should_allow_removing_compile_commands_path(SonarLintTestHarness harness, @TempDir Path baseDir) throws IOException {\n    assumeTrue(COMMERCIAL_ENABLED);\n    var cFile = prepareInputFile(\"foo.c\", \"#import \\\"foo.h\\\"\\n\", false, StandardCharsets.UTF_8, SonarLanguage.C, baseDir);\n    var cFilePath = baseDir.resolve(\"foo.c\");\n    var cFileUri = cFile.uri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(cFileUri, baseDir.relativize(cFilePath), CONFIG_SCOPE_ID, false, null, cFilePath, null, null, true)))\n      .build();\n    var propertyDumpingPlugin = newSonarPlugin(\"php\")\n      .withSensor(PropertyDumpingSensor.class)\n      .generate(baseDir);\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.CFAMILY)\n      .withStandaloneEmbeddedPlugin(propertyDumpingPlugin)\n      .withEnabledLanguageInStandaloneMode(Language.PHP)\n      .start(client);\n    backend.getAnalysisService().didSetUserAnalysisProperties(new DidChangeAnalysisPropertiesParams(CONFIG_SCOPE_ID, Map.of(PropertyDumpingSensor.PROPERTY_NAME_TO_DUMP, \"sonar.cfamily.compile-commands\")));\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, cFileUri));\n\n    backend.getAnalysisService().didChangePathToCompileCommands(new DidChangePathToCompileCommandsParams(CONFIG_SCOPE_ID, null));\n\n    await().atMost(3, TimeUnit.SECONDS)\n      .untilAsserted(() -> assertThat(baseDir.resolve(\"property.dump\")).isEmptyFile());\n  }\n\n  @SonarLintTest\n  void it_should_unload_rules_cache_on_config_scope_closed(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n    var filePath2 = createFile(baseDir, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var fileUri2 = filePath2.toUri();\n    var configScope2 = \"configScope2\";\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .withInitialFs(configScope2, baseDir, List.of(new ClientFileDto(fileUri2, baseDir.relativize(filePath2), configScope2, false, null, filePath2, null, null, true)))\n      .build();\n    var projectKey = \"projectKey\";\n    var connectionId = \"connectionId\";\n    var projectKey2 = \"projectKey-2\";\n    var connectionId2 = \"connectionId-2\";\n    var server = harness.newFakeSonarQubeServer()\n      .withPlugin(TestPlugin.XML)\n      .withProject(projectKey)\n      .withProject(projectKey2)\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(connectionId, server,\n        storage -> storage.withPlugin(TestPlugin.XML).withProject(projectKey,\n          project -> project.withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"BLOCKER\"))))\n      .withSonarQubeConnection(connectionId2, server,\n        storage -> storage.withPlugin(TestPlugin.XML).withProject(projectKey2,\n          project -> project.withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"BLOCKER\"))))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, connectionId, projectKey)\n      .withBoundConfigScope(configScope2, connectionId2, projectKey2)\n      .withExtraEnabledLanguagesInConnectedMode(Language.XML)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n    backend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(\n      new ConfigurationScopeDto(configScope2, null, true, configScope2,\n        new BindingConfigurationDto(connectionId2, projectKey2, true)))));\n\n    // analyse files to warmup caches\n    var analysisId1 = UUID.randomUUID();\n    backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId1, List.of(fileUri), Map.of(), true, System.currentTimeMillis()))\n      .join();\n    var analysisId2 = UUID.randomUUID();\n    backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(configScope2, analysisId2, List.of(fileUri2), Map.of(), true, System.currentTimeMillis()))\n      .join();\n\n    // unload one of the projects\n    backend.getConfigurationService().didRemoveConfigurationScope(new DidRemoveConfigurationScopeParams(configScope2));\n\n    // expect corresponding cache to be evicted\n    await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getLogMessages()).contains(\"Evict cached rules definitions for connection 'connectionId-2'\"));\n  }\n\n  @SonarLintTest\n  void it_should_not_unload_rules_cache_on_config_scope_closed_if_another_config_scope_still_opened(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n    var filePath2 = createFile(baseDir, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n    var filePath3 = createFile(baseDir, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var fileUri2 = filePath2.toUri();\n    var fileUri3 = filePath3.toUri();\n    var configScope2 = \"configScope2\";\n    var configScope3 = \"configScope3\";\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .withInitialFs(configScope2, baseDir, List.of(new ClientFileDto(fileUri2, baseDir.relativize(filePath2), configScope2, false, null, filePath2, null, null, true)))\n      .withInitialFs(configScope3, baseDir, List.of(new ClientFileDto(fileUri3, baseDir.relativize(filePath3), configScope3, false, null, filePath3, null, null, true)))\n      .build();\n    var projectKey = \"projectKey\";\n    var connectionId = \"connectionId\";\n    var projectKey2 = \"projectKey-2\";\n    var connectionId2 = \"connectionId-2\";\n    var server = harness.newFakeSonarQubeServer().withProject(projectKey).withProject(projectKey2).start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(connectionId, server,\n        storage -> storage.withPlugin(TestPlugin.XML).withProject(projectKey,\n          project -> project.withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"BLOCKER\"))))\n      .withSonarQubeConnection(connectionId2, server,\n        storage -> storage.withPlugin(TestPlugin.XML).withProject(projectKey2,\n          project -> project.withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"BLOCKER\"))))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, connectionId, projectKey)\n      .withBoundConfigScope(configScope2, connectionId2, projectKey2)\n      .withExtraEnabledLanguagesInConnectedMode(Language.XML)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n    backend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(\n      new ConfigurationScopeDto(configScope2, null, true, configScope2,\n        new BindingConfigurationDto(connectionId2, projectKey2, true)))));\n    backend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(\n      new ConfigurationScopeDto(configScope3, null, true, configScope3,\n        new BindingConfigurationDto(connectionId2, projectKey2, true)))));\n\n    // analyse files to warmup caches\n    var analysisId1 = UUID.randomUUID();\n    backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId1, List.of(fileUri), Map.of(), true, System.currentTimeMillis()))\n      .join();\n    var analysisId2 = UUID.randomUUID();\n    backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(configScope2, analysisId2, List.of(fileUri2), Map.of(), true, System.currentTimeMillis()))\n      .join();\n\n    // unload one of the projects\n    backend.getConfigurationService().didRemoveConfigurationScope(new DidRemoveConfigurationScopeParams(configScope2));\n\n    // expect corresponding cache not to be evicted\n    await().during(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getLogMessages())\n      .doesNotContain(\"Evict cached rules definitions for connection 'connectionId-2'\"));\n  }\n\n  @SonarLintTest\n  void it_should_analyse_file_with_non_file_uri_schema(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath1 = createFile(baseDir, \"Foo.java\", \"public interface Foo {}\");\n    var fileUri1 = URI.create(\"temp:///file/path/Foo.java\");\n    var filePath2 = createFile(baseDir, \"Bar.java\", \"public interface Bar {}\");\n    var fileUri2 = URI.create(\"http:///localhost:12345/file/path/Bar.java\");\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(\n        new ClientFileDto(fileUri1, baseDir.relativize(filePath1), CONFIG_SCOPE_ID, false, null, filePath1, null, null, true),\n        new ClientFileDto(fileUri2, baseDir.relativize(filePath2), CONFIG_SCOPE_ID, false, null, filePath2, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(), List.of(fileUri1), Map.of(), false, System.currentTimeMillis())).join();\n\n    var raisedIssues = awaitRaisedIssuesNotification(client, CONFIG_SCOPE_ID);\n    assertThat(raisedIssues).hasSize(1);\n    client.cleanRaisedIssues();\n\n    backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(), List.of(fileUri2), Map.of(), false, System.currentTimeMillis())).join();\n    await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).hasSize(1));\n\n    raisedIssues = awaitRaisedIssuesNotification(client, CONFIG_SCOPE_ID);\n    assertThat(raisedIssues).hasSize(1);\n  }\n\n  @SonarLintTest\n  void it_should_respect_gitignore_exclusions(SonarLintTestHarness harness, @TempDir Path baseDir) throws GitAPIException, IOException {\n    var filePath = createFile(baseDir, \"Foo.java\", \"public interface Foo {}\");\n    GitUtils.createRepository(baseDir);\n    var gitignorePath = createFile(baseDir, \".gitignore\", \"*.java\");\n    var fileUri = filePath.toUri();\n    var gitignoreUri = gitignorePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(\n        new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true),\n        new ClientFileDto(gitignoreUri, baseDir.relativize(gitignorePath), CONFIG_SCOPE_ID, false, null, gitignorePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(), List.of(fileUri), Map.of(), false, System.currentTimeMillis())).join();\n    await().during(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).isEmpty());\n  }\n\n  @SonarLintTest\n  void it_should_not_use_enterprise_csharp_analyzer_in_standalone(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .start();\n\n    var response = backend.getAnalysisService().shouldUseEnterpriseCSharpAnalyzer(new ShouldUseEnterpriseCSharpAnalyzerParams(CONFIG_SCOPE_ID)).join();\n\n    await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(response.shouldUseEnterpriseAnalyzer()).isFalse());\n  }\n\n  @SonarLintTest\n  void it_should_not_use_enterprise_csharp_analyzer_when_connected_to_community(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer(\"10.8\").start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\",\n        server,\n        storage -> storage.withPlugin(TestPlugin.XML).withProject(\"projectKey\", project -> project.withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"BLOCKER\"))))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, \"connectionId\", \"projectKey\")\n      .withExtraEnabledLanguagesInConnectedMode(Language.XML)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start();\n\n    var result = backend.getAnalysisService().shouldUseEnterpriseCSharpAnalyzer(new ShouldUseEnterpriseCSharpAnalyzerParams(CONFIG_SCOPE_ID)).join();\n\n    assertThat(result.shouldUseEnterpriseAnalyzer()).isFalse();\n  }\n\n  @SonarLintTest\n  void it_should_use_enterprise_csharp_analyzer_when_connected_to_community_non_repackaged(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer(\"10.7\").start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\",\n        server,\n        storage -> storage\n          .withPlugin(TestPlugin.XML)\n          .withProject(\"projectKey\", project -> project.withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"BLOCKER\")))\n          .withServerVersion(\"10.7\"))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, \"connectionId\", \"projectKey\")\n      .withExtraEnabledLanguagesInConnectedMode(Language.XML)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start();\n\n    var result = backend.getAnalysisService().shouldUseEnterpriseCSharpAnalyzer(new ShouldUseEnterpriseCSharpAnalyzerParams(CONFIG_SCOPE_ID)).join();\n\n    assertThat(result.shouldUseEnterpriseAnalyzer()).isTrue();\n  }\n\n  @SonarLintTest\n  void it_should_analyze_xml_file_in_non_ascii_directory(SonarLintTestHarness harness, @TempDir Path baseDir) throws IOException {\n    var directoryPath = baseDir.resolve(\"中文字符\");\n    Files.createDirectory(directoryPath);\n    var filePath = createFile(directoryPath, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n    var analysisId = UUID.randomUUID();\n\n    var result = backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(fileUri), Map.of(), false, System.currentTimeMillis())).join();\n\n    assertThat(result.getFailedAnalysisFiles()).isEmpty();\n    var raisedIssueDto = awaitRaisedIssuesNotification(client, CONFIG_SCOPE_ID);\n    assertThat(raisedIssueDto).isNotEmpty();\n  }\n\n  @SonarLintTest\n  void it_should_analyze_xml_file_in_non_ascii_directory_and_non_decoded_uri(SonarLintTestHarness harness, @TempDir Path baseDir) throws IOException {\n    var directoryPath = baseDir.resolve(\"中文字符\");\n    Files.createDirectory(directoryPath);\n    var filePath = createFile(directoryPath, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n    var fileUri = URI.create(URLDecoder.decode(filePath.toUri().toString(), StandardCharsets.UTF_8));\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n    var analysisId = UUID.randomUUID();\n\n    var result = backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(fileUri), Map.of(), false, System.currentTimeMillis())).join();\n\n    assertThat(result.getFailedAnalysisFiles()).isEmpty();\n    var raisedIssueDto = awaitRaisedIssuesNotification(client, CONFIG_SCOPE_ID);\n    assertThat(raisedIssueDto).isNotEmpty();\n  }\n\n  private ClientInputFile prepareInputFile(String relativePath, String content, final boolean isTest, Charset encoding,\n    @Nullable SonarLanguage language, Path baseDir) throws IOException {\n    final var file = new File(baseDir.toFile(), relativePath);\n    FileUtils.write(file, content, encoding);\n    return new OnDiskTestClientInputFile(file.toPath(), relativePath, isTest, encoding, language);\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/analysis/AnalysisReadinessMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.analysis;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.jupiter.api.TestInstance;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingMode;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidOpenFileParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedFindingDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.clearInvocations;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.verify;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static utils.AnalysisUtils.getPublishedIssues;\n\n@TestInstance(TestInstance.Lifecycle.PER_METHOD)\nclass AnalysisReadinessMediumTests {\n\n  private static final String CONFIG_SCOPE_ID = \"CONFIG_SCOPE_ID\";\n\n  @SonarLintTest\n  void it_should_be_immediately_consider_analysis_to_be_ready_when_adding_a_non_bound_configuration_scope(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .start(client);\n\n    backend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, \"name\", null))));\n\n    verify(client, timeout(1000)).didChangeAnalysisReadiness(Set.of(CONFIG_SCOPE_ID), true);\n  }\n\n  @SonarLintTest\n  void it_should_change_readiness_and_analyze_xml_file_in_connected_mode(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarQubeServer()\n      .withPlugin(TestPlugin.XML)\n      .withProject(\"projectKey\", project -> project.withQualityProfile(\"qp\"))\n      .withQualityProfile(\"qp\", qualityProfile -> qualityProfile.withLanguage(\"xml\").withActiveRule(\"xml:S3421\", activeRule -> activeRule.withSeverity(IssueSeverity.MAJOR)))\n      .start();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server.baseUrl())\n      .withBoundConfigScope(CONFIG_SCOPE_ID, \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .withExtraEnabledLanguagesInConnectedMode(Language.XML)\n      .start(client);\n\n    verify(client, never()).didChangeAnalysisReadiness(Set.of(CONFIG_SCOPE_ID), true);\n\n    // File opened but not analyzed since analysis is not ready yet\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n    verify(client, never()).raiseIssues(eq(CONFIG_SCOPE_ID), any(), eq(false), any());\n\n    client.waitForSynchronization();\n\n    // analysis is ready\n    await().atMost(1, TimeUnit.SECONDS)\n      .untilAsserted(() -> verify(client).didChangeAnalysisReadiness(Set.of(CONFIG_SCOPE_ID), true));\n    await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID)).isNotEmpty());\n\n    var publishedIssues = getPublishedIssues(client, CONFIG_SCOPE_ID);\n    assertThat(publishedIssues).containsOnlyKeys(fileUri);\n  }\n\n  @SonarLintTest\n  void it_should_reanalyse_open_files_after_unbinding(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarQubeServer()\n      .withPlugin(TestPlugin.XML)\n      .withProject(\"projectKey\", project -> project.withQualityProfile(\"qp\"))\n      .withProject(\"projectKey2\", project -> project.withQualityProfile(\"qp2\"))\n      .withQualityProfile(\"qp\", qualityProfile -> qualityProfile.withLanguage(\"xml\").withActiveRule(\"xml:S3421\", activeRule -> activeRule.withSeverity(IssueSeverity.MAJOR)))\n      .withQualityProfile(\"qp2\")\n      .start();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server.baseUrl())\n      .withBoundConfigScope(CONFIG_SCOPE_ID, \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .withExtraEnabledLanguagesInConnectedMode(Language.XML)\n      .start(client);\n\n    verify(client, never()).didChangeAnalysisReadiness(Set.of(CONFIG_SCOPE_ID), true);\n\n    // File opened but not analyzed since analysis is not ready yet\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n    verify(client, never()).raiseIssues(eq(CONFIG_SCOPE_ID), any(), eq(false), any());\n\n    client.waitForSynchronization();\n\n    // analysis is ready\n    await().atMost(2, TimeUnit.SECONDS)\n      .untilAsserted(() -> verify(client).didChangeAnalysisReadiness(Set.of(CONFIG_SCOPE_ID), true));\n    await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID)).isNotEmpty());\n\n    var publishedIssues = getPublishedIssues(client, CONFIG_SCOPE_ID);\n    assertThat(publishedIssues).containsOnlyKeys(fileUri);\n    clearInvocations(client);\n\n    // bind to 2nd project\n    backend.getConfigurationService()\n      .didUpdateBinding(new DidUpdateBindingParams(CONFIG_SCOPE_ID, new BindingConfigurationDto(\"connectionId\", \"projectKey2\", true), BindingMode.MANUAL, null));\n\n    // analysis becomes not ready after binding change\n    verify(client, timeout(500)).didChangeAnalysisReadiness(Set.of(CONFIG_SCOPE_ID), false);\n\n    client.waitForSynchronization();\n\n    // analysis is ready, no issues are raised by the second QP\n    await().atMost(2, TimeUnit.SECONDS)\n      .untilAsserted(() -> verify(client).didChangeAnalysisReadiness(Set.of(CONFIG_SCOPE_ID), true));\n    await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID)).isEmpty());\n\n    // bind again to 1st project that should be ready\n    backend.getConfigurationService()\n      .didUpdateBinding(new DidUpdateBindingParams(CONFIG_SCOPE_ID, new BindingConfigurationDto(\"connectionId\", \"projectKey\", true), BindingMode.MANUAL, null));\n\n    await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID))\n      .extracting(RaisedFindingDto::getRuleKey).containsExactly(\"xml:S3421\"));\n  }\n\n  private static Path createFile(Path folderPath, String fileName, String content) {\n    var filePath = folderPath.resolve(fileName);\n    try {\n      Files.writeString(filePath, content);\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n    return filePath;\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/analysis/AnalysisSchedulingMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.analysis;\n\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.TimeUnit;\nimport mediumtest.analysis.sensor.WaitingCancellationSensor;\nimport mediumtest.analysis.sensor.WaitingSensor;\nimport org.assertj.core.api.Condition;\nimport org.assertj.core.api.InstanceOfAssertFactories;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseError;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidRemoveConfigurationScopeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidOpenFileParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.progress.CancelTaskParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedFindingDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintBackendFixture;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static mediumtest.analysis.sensor.WaitingCancellationSensor.CANCELLATION_FILE_PATH_PROPERTY_NAME;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode.RequestCancelled;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.verify;\nimport static org.sonarsource.sonarlint.core.analysis.AnalysisQueue.ANALYSIS_EXPIRATION_DELAY_PROPERTY_NAME;\nimport static org.sonarsource.sonarlint.core.test.utils.plugins.SonarPluginBuilder.newSonarPlugin;\nimport static utils.AnalysisUtils.createFile;\n\nclass AnalysisSchedulingMediumTests {\n\n  private static final String CONFIG_SCOPE_ID = \"CONFIG_SCOPE_ID\";\n  private static final String CONNECTION_ID = \"connectionId\";\n\n  @AfterEach\n  void init_property() {\n    System.clearProperty(ANALYSIS_EXPIRATION_DELAY_PROPERTY_NAME);\n  }\n\n  @SonarLintTest\n  void it_should_cancel_progress_monitor_when_analysis_request_is_canceled(SonarLintTestHarness harness, @TempDir Path baseDir) throws InterruptedException {\n    var filePath = createFile(baseDir, \"pom.xml\", \"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false,\n        null, filePath, null, null, true)))\n      .build();\n    var plugin = newSonarPlugin(\"xml\")\n      .withSensor(WaitingCancellationSensor.class)\n      .generate(baseDir);\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPlugin(plugin)\n      .withEnabledLanguageInStandaloneMode(Language.XML)\n      .start(client);\n    var cancelationFilePath = baseDir.resolve(\"cancellation.result\");\n    var future = backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(), List.of(fileUri),\n        Map.of(CANCELLATION_FILE_PATH_PROPERTY_NAME, cancelationFilePath.toString()), false, System.currentTimeMillis()));\n    await().untilAsserted(() -> assertThat(client.getProgressReportsByTaskId().keySet()).hasSize(1));\n    Thread.sleep(2000);\n\n    future.cancel(false);\n\n    assertThat(future.isCancelled()).isTrue();\n    await().atMost(3, TimeUnit.SECONDS)\n      .untilAsserted(() -> assertThat(cancelationFilePath).hasContent(\"CANCELED\"));\n  }\n\n  @SonarLintTest\n  void it_should_cancel_analysis_when_not_ready_for_a_while(SonarLintTestHarness harness, @TempDir Path baseDir) throws InterruptedException {\n    System.setProperty(ANALYSIS_EXPIRATION_DELAY_PROPERTY_NAME, \"PT0S\");\n    var filePath = createFile(baseDir, \"pom.xml\", \"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false,\n        null, filePath, null, null, true)))\n      .build();\n    var server = harness.newFakeSonarQubeServer()\n      .withPlugin(TestPlugin.XML)\n      .withProject(\"projectKey\")\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server, storage -> storage\n        .withPlugins(TestPlugin.XML)\n        .withProject(\"projectKey2\", project -> project.withMainBranch(\"main\")))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .withBoundConfigScope(\"otherConfigScope\", CONNECTION_ID, \"projectKey2\")\n      .withExtraEnabledLanguagesInConnectedMode(Language.XML)\n      .start(client);\n    // this first analysis will never be ready\n    var firstAnalysisFuture = backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(), List.of(fileUri), Map.of(), false, System.currentTimeMillis()));\n    Thread.sleep(500);\n\n    // trigger analysis queue cleanup by posting a new analysis\n    backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(\"otherConfigScope\", UUID.randomUUID(), List.of(fileUri), Map.of(), false, System.currentTimeMillis()));\n\n    assertThat(firstAnalysisFuture)\n      .failsWithin(2, TimeUnit.SECONDS)\n      .withThrowableThat()\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class)\n      .asInstanceOf(InstanceOfAssertFactories.type(ResponseErrorException.class))\n      .extracting(ResponseErrorException::getResponseError)\n      .extracting(ResponseError::getCode)\n      .isEqualTo(RequestCancelled.getValue());\n  }\n\n  @SonarLintTest\n  void it_should_cancel_pending_analyses_when_closing_a_configuration_scope(SonarLintTestHarness harness, @TempDir Path baseDir) throws InterruptedException {\n    System.setProperty(ANALYSIS_EXPIRATION_DELAY_PROPERTY_NAME, \"PT0S\");\n    var filePath = createFile(baseDir, \"pom.xml\", \"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false,\n        null, filePath, null, null, true)))\n      .build();\n    var plugin = newSonarPlugin(\"xml\")\n      .withSensor(WaitingSensor.class)\n      .generate(baseDir);\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, harness.newFakeSonarQubeServer().withPlugin(TestPlugin.XML).start(), storage -> storage\n        .withPlugin(\"xml\", plugin, TestPlugin.XML.getHash())\n        .withProject(\"projectKey\", project -> project.withMainBranch(\"main\")))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .withExtraEnabledLanguagesInConnectedMode(Language.XML)\n      .start(client);\n\n    var future = backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(), List.of(fileUri), Map.of(), false, System.currentTimeMillis()));\n    Thread.sleep(500);\n\n    backend.getConfigurationService().didRemoveConfigurationScope(new DidRemoveConfigurationScopeParams(CONFIG_SCOPE_ID));\n\n    assertThat(future)\n      .failsWithin(5, TimeUnit.SECONDS)\n      .withThrowableThat()\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class)\n      .asInstanceOf(InstanceOfAssertFactories.type(ResponseErrorException.class))\n      .extracting(ResponseErrorException::getResponseError)\n      .extracting(ResponseError::getCode)\n      .isEqualTo(RequestCancelled.getValue());\n  }\n\n  @SonarLintTest\n  void it_should_cancel_automatic_analysis_when_canceling_task_via_request(SonarLintTestHarness harness, @TempDir Path baseDir) throws InterruptedException {\n    var filePath = createFile(baseDir, \"pom.xml\", \"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false,\n        null, filePath, null, null, true)))\n      .build();\n    var plugin = newSonarPlugin(\"xml\")\n      .withSensor(WaitingCancellationSensor.class)\n      .generate(baseDir);\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPlugin(plugin)\n      .withEnabledLanguageInStandaloneMode(Language.XML)\n      .start(client);\n    var cancelationFilePath = baseDir.resolve(\"cancellation.result\");\n    client.setInferredAnalysisProperties(CONFIG_SCOPE_ID, Map.of(CANCELLATION_FILE_PATH_PROPERTY_NAME, cancelationFilePath.toString()));\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n    await().untilAsserted(() -> assertThat(client.getProgressReportsByTaskId().keySet()).hasSize(1));\n    Thread.sleep(1000);\n    var taskId = client.getProgressReportsByTaskId().keySet().iterator().next();\n\n    backend.getTaskProgressRpcService().cancelTask(new CancelTaskParams(taskId));\n\n    await().atMost(3, TimeUnit.SECONDS)\n      .untilAsserted(() -> assertThat(cancelationFilePath).hasContent(\"CANCELED\"));\n  }\n\n  @SonarLintTest\n  void should_cancel_previous_similar_analysis(SonarLintTestHarness harness, @TempDir Path baseDir) throws InterruptedException {\n    var filePath = createFile(baseDir, \"pom.xml\", \"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false,\n        null, filePath, null, null, true)))\n      .build();\n    var plugin = newSonarPlugin(\"xml\")\n      .withSensor(WaitingCancellationSensor.class)\n      .generate(baseDir);\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPlugin(plugin)\n      .withEnabledLanguageInStandaloneMode(Language.XML)\n      .start(client);\n    var cancelationFilePath = baseDir.resolve(\"cancellation.result\");\n    var future = backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(), List.of(fileUri),\n        Map.of(CANCELLATION_FILE_PATH_PROPERTY_NAME, cancelationFilePath.toString()), false));\n    await().untilAsserted(() -> assertThat(client.getProgressReportsByTaskId().keySet()).hasSize(1));\n    Thread.sleep(2000);\n    var secondFuture = backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(), List.of(fileUri),\n        Map.of(CANCELLATION_FILE_PATH_PROPERTY_NAME, cancelationFilePath.toString()), false));\n\n    await().atMost(10, TimeUnit.SECONDS)\n      .untilAsserted(() -> assertThat(cancelationFilePath).hasContent(\"CANCELED\"));\n    assertThat(future).isCompletedExceptionally();\n    // wait for the other future to complete so the scheduler can be stopped gracefully\n    assertThat(secondFuture).succeedsWithin(Duration.of(10, ChronoUnit.SECONDS));\n  }\n\n  @SonarLintTest\n  void should_batch_similar_analyses_into_a_single_one(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    final var OTHER_CONFIG_SCOPE_ID = CONFIG_SCOPE_ID + \"2\";\n    var firstFilePath = createFile(baseDir, \"pom.xml\", \"\");\n    var secondFilePath = createFile(baseDir, \"pom2.xml\", \"\"\"\n      <!-- Generated file -->  <!--  Noncompliant  -->\n      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <firstNode>\n        content\n      </firstNode>\n      \"\"\");\n    var thirdFilePath = createFile(baseDir, \"pom3.xml\", \"\"\"\n      <!-- TODO Drop this dependency -->\n      <dependency>\n        <groupId>org.apache.commons</groupId>\n        <artifactId>commons-lang3</artifactId>\n        <version>3.8.1</version>\n      </dependency>\n      \"\"\");\n    var firstFileUri = firstFilePath.toUri();\n    var secondFileUri = secondFilePath.toUri();\n    var thirdFileUri = thirdFilePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(firstFileUri, baseDir.relativize(firstFilePath), CONFIG_SCOPE_ID, false,\n        null, firstFilePath, null, null, true)))\n      .withInitialFs(OTHER_CONFIG_SCOPE_ID, baseDir, List.of(\n        new ClientFileDto(secondFileUri, baseDir.relativize(secondFilePath), OTHER_CONFIG_SCOPE_ID, false,\n          null, secondFilePath, null, null, true),\n        new ClientFileDto(thirdFileUri, baseDir.relativize(thirdFilePath), OTHER_CONFIG_SCOPE_ID, false,\n          null, thirdFilePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withUnboundConfigScope(OTHER_CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, firstFileUri));\n    backend.getFileService().didOpenFile(new DidOpenFileParams(OTHER_CONFIG_SCOPE_ID, secondFileUri));\n    backend.getFileService().didOpenFile(new DidOpenFileParams(OTHER_CONFIG_SCOPE_ID, thirdFileUri));\n\n    verify(client, timeout(2000).times(1)).raiseIssues(eq(OTHER_CONFIG_SCOPE_ID), any(), eq(false), any());\n    assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).isEqualTo(Map.of(firstFileUri, List.of()));\n    var raisedIssuesPerFile = client.getRaisedIssuesForScopeId(OTHER_CONFIG_SCOPE_ID);\n    assertThat(raisedIssuesPerFile.get(secondFileUri))\n      .extracting(RaisedFindingDto::getRuleKey)\n      .containsExactly(\"xml:S1778\");\n    assertThat(raisedIssuesPerFile.get(thirdFileUri))\n      .extracting(RaisedFindingDto::getRuleKey)\n      .containsExactly(\"xml:S1135\");\n    assertThat(client.getProgressReportsByTaskId())\n      .values()\n      .extracting(SonarLintBackendFixture.FakeSonarLintRpcClient.ProgressReport::getConfigurationScopeId)\n      .containsOnly(CONFIG_SCOPE_ID, OTHER_CONFIG_SCOPE_ID);\n  }\n\n  @SonarLintTest\n  void should_cancel_analyses_when_removing_a_config_scope(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    final var OTHER_CONFIG_SCOPE_ID = CONFIG_SCOPE_ID + \"2\";\n    var firstFilePath = createFile(baseDir, \"pom.xml\", \"\");\n    var secondFilePath = createFile(baseDir, \"pom2.xml\", \"\"\"\n      <!-- Generated file -->  <!--  Noncompliant  -->\n      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <firstNode>\n        content\n      </firstNode>\n      \"\"\");\n    var thirdFilePath = createFile(baseDir, \"pom3.xml\", \"\"\"\n      <!-- TODO Drop this dependency -->\n      <dependency>\n        <groupId>org.apache.commons</groupId>\n        <artifactId>commons-lang3</artifactId>\n        <version>3.8.1</version>\n      </dependency>\n      \"\"\");\n    var firstFileUri = firstFilePath.toUri();\n    var secondFileUri = secondFilePath.toUri();\n    var thirdFileUri = thirdFilePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(firstFileUri, baseDir.relativize(firstFilePath), CONFIG_SCOPE_ID, false,\n        null, firstFilePath, null, null, true)))\n      .withInitialFs(OTHER_CONFIG_SCOPE_ID, baseDir, List.of(\n        new ClientFileDto(secondFileUri, baseDir.relativize(secondFilePath), OTHER_CONFIG_SCOPE_ID, false,\n          null, secondFilePath, null, null, true),\n        new ClientFileDto(thirdFileUri, baseDir.relativize(thirdFilePath), OTHER_CONFIG_SCOPE_ID, false,\n          null, thirdFilePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withUnboundConfigScope(OTHER_CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, firstFileUri));\n    backend.getFileService().didOpenFile(new DidOpenFileParams(OTHER_CONFIG_SCOPE_ID, secondFileUri));\n    backend.getFileService().didOpenFile(new DidOpenFileParams(OTHER_CONFIG_SCOPE_ID, thirdFileUri));\n    backend.getConfigurationService().didRemoveConfigurationScope(new DidRemoveConfigurationScopeParams(OTHER_CONFIG_SCOPE_ID));\n\n    await().untilAsserted(() -> assertThat(client.getProgressReportsByTaskId())\n      .hasValueSatisfying(new Condition<>(report -> CONFIG_SCOPE_ID.equals(report.getConfigurationScopeId()), \"Report is for first config scope\")));\n    await().untilAsserted(() -> assertThat(client.getLogMessages()).contains(\"Canceling 2 analyses expired by module unregistration\"));\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/analysis/AnalysisTriggeringMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.analysis;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.URI;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.DidChangeAnalysisPropertiesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.DidChangeAutomaticAnalysisSettingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.DidChangePathToCompileCommandsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidCloseFileParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidOpenFileParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidUpdateFileSystemParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.StandaloneRuleConfigDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.UpdateStandaloneRulesConfigurationParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedFindingDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryLocalStorage;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.clearInvocations;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.reset;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.verify;\nimport static utils.AnalysisUtils.awaitRaisedIssuesNotification;\nimport static utils.AnalysisUtils.createFile;\nimport static utils.AnalysisUtils.getPublishedIssues;\nimport static utils.AnalysisUtils.waitForRaisedIssues;\n\nclass AnalysisTriggeringMediumTests {\n\n  private static final String CONFIG_SCOPE_ID = \"CONFIG_SCOPE_ID\";\n\n  @SonarLintTest\n  void it_should_analyze_file_on_open(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false,\n        null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n\n    var publishedIssues = getPublishedIssues(client, CONFIG_SCOPE_ID);\n    assertThat(publishedIssues)\n      .containsOnlyKeys(fileUri)\n      .hasEntrySatisfying(fileUri, issues -> assertThat(issues)\n        .extracting(RaisedIssueDto::getPrimaryMessage)\n        .containsExactly(\"Replace \\\"pom.version\\\" with \\\"project.version\\\".\"));\n  }\n\n  @SonarLintTest\n  void it_should_not_fail_an_analysis_of_windows_shortcut_file_and_skip_the_file_analysis(SonarLintTestHarness harness) {\n    var baseDir = new File(\"src/test/projects/windows-shortcut\").getAbsoluteFile().toPath();\n    var actualFile = Paths.get(baseDir.toString(), \"hello.py\");\n    var windowsShortcut = Paths.get(baseDir.toString(), \"hello.py.lnk\");\n    var fakeWindowsShortcut = Paths.get(baseDir.toString(), \"hello.py.fake.lnk\");\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(\n        new ClientFileDto(actualFile.toUri(), baseDir.relativize(actualFile), CONFIG_SCOPE_ID, false, null, actualFile, null, null, true),\n        new ClientFileDto(windowsShortcut.toUri(), baseDir.relativize(windowsShortcut), CONFIG_SCOPE_ID, false, null, windowsShortcut,\n          null, null, true),\n        new ClientFileDto(fakeWindowsShortcut.toUri(), baseDir.relativize(fakeWindowsShortcut), CONFIG_SCOPE_ID, false, null,\n          fakeWindowsShortcut, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.TEXT)\n      .start(client);\n\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, windowsShortcut.toUri()));\n    await().during(5, TimeUnit.SECONDS).untilAsserted(() -> {\n      assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).isEmpty();\n      assertThat(client.getLogMessages().stream()\n        .filter(message -> message.startsWith(\"Filtered out URIs that are Windows shortcuts: \"))\n        .toList()).isNotEmpty();\n    });\n\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fakeWindowsShortcut.toUri()));\n    await().during(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).isNotEmpty());\n  }\n\n  @SonarLintTest\n  void it_should_not_fail_an_analysis_of_symlink_file_and_skip_the_file_analysis(SonarLintTestHarness harness, @TempDir Path baseDir) throws IOException {\n    var filePath = createFile(baseDir, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n    var link = Paths.get(baseDir.toString(), \"pom-link.xml\");\n    Files.createSymbolicLink(link, filePath);\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(\n        new ClientFileDto(link.toUri(), baseDir.relativize(link), CONFIG_SCOPE_ID, false, null, link, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, link.toUri()));\n    await().during(5, TimeUnit.SECONDS).untilAsserted(() -> {\n      assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).isEmpty();\n      assertThat(client.getLogMessages().stream()\n        .filter(message -> message.startsWith(\"Filtered out URIs that are symbolic links: \"))\n        .toList()).isNotEmpty();\n    });\n  }\n\n  @SonarLintTest\n  void it_should_analyze_open_file_on_content_change(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", \"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false,\n        null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n    var publishedIssues = getPublishedIssues(client, CONFIG_SCOPE_ID);\n    assertThat(publishedIssues)\n      .containsOnlyKeys(fileUri)\n      .hasEntrySatisfying(fileUri, issues -> assertThat(issues).isEmpty());\n    reset(client);\n\n    backend.getFileService()\n      .didUpdateFileSystem(new DidUpdateFileSystemParams(\n        Collections.emptyList(),\n        List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, \"\"\"\n          <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n          <project>\n            <modelVersion>4.0.0</modelVersion>\n            <groupId>com.foo</groupId>\n            <artifactId>bar</artifactId>\n            <version>${pom.version}</version>\n          </project>\"\"\", null, true)),\n        Collections.emptyList()));\n\n    waitForRaisedIssues(client, CONFIG_SCOPE_ID);\n    publishedIssues = getPublishedIssues(client, CONFIG_SCOPE_ID);\n    assertThat(publishedIssues)\n      .containsOnlyKeys(fileUri)\n      .hasEntrySatisfying(fileUri, issues -> assertThat(issues)\n        .extracting(RaisedIssueDto::getPrimaryMessage)\n        .containsExactly(\"Replace \\\"pom.version\\\" with \\\"project.version\\\".\"));\n  }\n\n  @SonarLintTest\n  void it_should_analyze_closed_file_on_content_change(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", \"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false,\n        null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n    var publishedIssues = getPublishedIssues(client, CONFIG_SCOPE_ID);\n    assertThat(publishedIssues)\n      .containsOnlyKeys(fileUri)\n      .hasEntrySatisfying(fileUri, issues -> assertThat(issues).isEmpty());\n    reset(client);\n    backend.getFileService().didCloseFile(new DidCloseFileParams(CONFIG_SCOPE_ID, fileUri));\n\n    backend.getFileService()\n      .didUpdateFileSystem(new DidUpdateFileSystemParams(\n        List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, \"\"\"\n          <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n          <project>\n            <modelVersion>4.0.0</modelVersion>\n            <groupId>com.foo</groupId>\n            <artifactId>bar</artifactId>\n            <version>${pom.version}</version>\n          </project>\"\"\", null, true)),\n        Collections.emptyList(),\n        Collections.emptyList()));\n\n    verify(client, timeout(500).times(0)).raiseIssues(eq(CONFIG_SCOPE_ID), any(), eq(false), any());\n  }\n\n  @SonarLintTest\n  void it_should_analyze_open_files_when_re_enabling_automatic_analysis(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", \"\"\"\n      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <project>\n        <modelVersion>4.0.0</modelVersion>\n        <groupId>com.foo</groupId>\n        <artifactId>bar</artifactId>\n        <version>${pom.version}</version>\n      </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false,\n        null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withAutomaticAnalysisEnabled(false)\n      .start(client);\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n\n    backend.getAnalysisService().didChangeAutomaticAnalysisSetting(new DidChangeAutomaticAnalysisSettingParams(true));\n\n    var publishedIssues = getPublishedIssues(client, CONFIG_SCOPE_ID);\n    assertThat(publishedIssues)\n      .containsOnlyKeys(fileUri)\n      .hasEntrySatisfying(fileUri, issues -> assertThat(issues)\n        .extracting(RaisedIssueDto::getPrimaryMessage)\n        .containsExactly(\"Replace \\\"pom.version\\\" with \\\"project.version\\\".\"));\n  }\n\n  @SonarLintTest\n  void it_should_analyze_open_files_when_re_enabling_automatic_analysis_when_same_file_opened_twice(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", \"\"\"\n      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <project>\n        <modelVersion>4.0.0</modelVersion>\n        <groupId>com.foo</groupId>\n        <artifactId>bar</artifactId>\n        <version>${pom.version}</version>\n      </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false,\n        null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withAutomaticAnalysisEnabled(false)\n      .start(client);\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n\n    backend.getAnalysisService().didChangeAutomaticAnalysisSetting(new DidChangeAutomaticAnalysisSettingParams(true));\n\n    var publishedIssues = getPublishedIssues(client, CONFIG_SCOPE_ID);\n    assertThat(publishedIssues)\n      .containsOnlyKeys(fileUri)\n      .hasEntrySatisfying(fileUri, issues -> assertThat(issues)\n        .extracting(RaisedIssueDto::getPrimaryMessage)\n        .containsExactly(\"Replace \\\"pom.version\\\" with \\\"project.version\\\".\"));\n  }\n\n  @SonarLintTest\n  void it_should_save_automatic_analysis_setting_and_trigger_telemetry_on_toggle(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withAutomaticAnalysisEnabled(false)\n      .withTelemetryEnabled()\n      .start();\n\n    backend.getAnalysisService().didChangeAutomaticAnalysisSetting(new DidChangeAutomaticAnalysisSettingParams(true));\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent())\n      .extracting(TelemetryLocalStorage::isAutomaticAnalysisEnabled, TelemetryLocalStorage::getAutomaticAnalysisToggledCount)\n      .containsExactly(true, 1));\n\n    backend.getAnalysisService().didChangeAutomaticAnalysisSetting(new DidChangeAutomaticAnalysisSettingParams(false));\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent())\n      .extracting(TelemetryLocalStorage::isAutomaticAnalysisEnabled, TelemetryLocalStorage::getAutomaticAnalysisToggledCount)\n      .containsExactly(false, 2));\n  }\n\n  @SonarLintTest\n  void it_should_not_update_automatic_analysis_setting_if_not_changed(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withAutomaticAnalysisEnabled(true)\n      .withTelemetryEnabled()\n      .start();\n\n    backend.getAnalysisService().didChangeAutomaticAnalysisSetting(new DidChangeAutomaticAnalysisSettingParams(true));\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent())\n      .extracting(TelemetryLocalStorage::isAutomaticAnalysisEnabled, TelemetryLocalStorage::getAutomaticAnalysisToggledCount)\n      .containsExactly(true, 0));\n  }\n\n  @SonarLintTest\n  void it_should_not_analyze_opened_file_if_it_was_already_open(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false,\n        null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n    var publishedIssues = getPublishedIssues(client, CONFIG_SCOPE_ID);\n    assertThat(publishedIssues)\n      .containsOnlyKeys(fileUri)\n      .hasEntrySatisfying(fileUri, issues -> assertThat(issues)\n        .extracting(RaisedIssueDto::getPrimaryMessage)\n        .containsExactly(\"Replace \\\"pom.version\\\" with \\\"project.version\\\".\"));\n    clearInvocations(client);\n\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n\n    // check that no analysis is started\n    verify(client, timeout(500).times(0)).startProgress(any());\n  }\n\n  @SonarLintTest\n  void it_should_analyze_open_files_when_enabling_rule(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", \"\"\"\n      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <project>\n        <modelVersion>4.0.0</modelVersion>\n        <groupId>com.foo</groupId>\n        <artifactId>My_Project</artifactId>\n      </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false,\n        null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n    var publishedIssues = getPublishedIssues(client, CONFIG_SCOPE_ID);\n    assertThat(publishedIssues)\n      .containsOnlyKeys(fileUri)\n      .hasEntrySatisfying(fileUri, issues -> assertThat(issues).isEmpty());\n    reset(client);\n\n    backend.getRulesService().updateStandaloneRulesConfiguration(new UpdateStandaloneRulesConfigurationParams(Map.of(\"xml:S3420\",\n      new StandaloneRuleConfigDto(true, Map.of()))));\n\n    waitForRaisedIssues(client, CONFIG_SCOPE_ID);\n    publishedIssues = getPublishedIssues(client, CONFIG_SCOPE_ID);\n    assertThat(publishedIssues)\n      .containsOnlyKeys(fileUri)\n      .hasEntrySatisfying(fileUri, issues -> assertThat(issues)\n        .extracting(RaisedIssueDto::getPrimaryMessage)\n        .containsExactly(\"Update this \\\"artifactId\\\" to match the provided regular expression: '[a-z][a-z-0-9]+'\"));\n  }\n\n  @SonarLintTest\n  void it_should_not_analyze_open_files_but_should_clear_and_report_issues_when_disabling_rule(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", \"\"\"\n      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <project>\n        <modelVersion>4.0.0</modelVersion>\n        <groupId>com.foo</groupId>\n        <artifactId>bar</artifactId>\n        <version>${pom.version}</version>\n      </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false,\n        null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n    var publishedIssues = getPublishedIssues(client, CONFIG_SCOPE_ID);\n    assertThat(publishedIssues)\n      .containsOnlyKeys(fileUri)\n      .hasEntrySatisfying(fileUri, issues -> assertThat(issues).extracting(RaisedFindingDto::getRuleKey).containsOnly(\"xml:S3421\"));\n    reset(client);\n\n    backend.getRulesService().updateStandaloneRulesConfiguration(new UpdateStandaloneRulesConfigurationParams(Map.of(\"xml:S3421\",\n      new StandaloneRuleConfigDto(false, Map.of()))));\n\n    // No new analysis triggered\n    verify(client, never()).log(any());\n\n    // issues related to the disabled rule has been removed and reported\n    publishedIssues = getPublishedIssues(client, CONFIG_SCOPE_ID);\n    assertThat(publishedIssues)\n      .containsOnlyKeys(fileUri)\n      .hasEntrySatisfying(fileUri, issues -> assertThat(issues).isEmpty());\n  }\n\n  @SonarLintTest\n  void it_should_trigger_analysis_after_analysis_properties_change(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n    var fileUri = URI.create(filePath.toUri().toString());\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n    var raisedIssueDto = awaitRaisedIssuesNotification(client, CONFIG_SCOPE_ID);\n    assertThat(raisedIssueDto).isNotEmpty();\n\n    client.cleanRaisedIssues();\n    assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID)).isEmpty();\n\n    backend.getAnalysisService().didSetUserAnalysisProperties(new DidChangeAnalysisPropertiesParams(CONFIG_SCOPE_ID, Map.of(\"foo\", \"bar\")));\n\n    raisedIssueDto = awaitRaisedIssuesNotification(client, CONFIG_SCOPE_ID);\n    assertThat(raisedIssueDto).isNotEmpty();\n\n    client.cleanRaisedIssues();\n    assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID)).isEmpty();\n\n    backend.getAnalysisService().didSetUserAnalysisProperties(new DidChangeAnalysisPropertiesParams(CONFIG_SCOPE_ID, Map.of(\"foo\", \"bar\")));\n\n    await().during(1, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID)).isEmpty());\n  }\n\n  @SonarLintTest\n  void it_should_trigger_analysis_after_path_to_compile_commands_change(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n    var fileUri = URI.create(filePath.toUri().toString());\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n    var raisedIssueDto = awaitRaisedIssuesNotification(client, CONFIG_SCOPE_ID);\n    assertThat(raisedIssueDto).isNotEmpty();\n\n    client.cleanRaisedIssues();\n    assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID)).isEmpty();\n\n    backend.getAnalysisService().didChangePathToCompileCommands(new DidChangePathToCompileCommandsParams(CONFIG_SCOPE_ID, \"/path/to/compile_commands.json\"));\n\n    raisedIssueDto = awaitRaisedIssuesNotification(client, CONFIG_SCOPE_ID);\n    assertThat(raisedIssueDto).isNotEmpty();\n\n    client.cleanRaisedIssues();\n    assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID)).isEmpty();\n\n    backend.getAnalysisService().didChangePathToCompileCommands(new DidChangePathToCompileCommandsParams(CONFIG_SCOPE_ID, \"/path/to/compile_commands.json\"));\n\n    await().during(1, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID)).isEmpty());\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/analysis/NodeJsMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.analysis;\n\nimport java.nio.file.Paths;\nimport java.time.Duration;\nimport java.util.concurrent.ExecutionException;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.DidChangeClientNodeJsPathParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.GetStandaloneRuleDescriptionParams;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass NodeJsMediumTests {\n\n  private static final String JAVASCRIPT_S1481 = \"javascript:S1481\";\n\n  @SonarLintTest\n  void wrong_node_path_prevents_loading_sonar_js_rules(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVASCRIPT)\n      .withClientNodeJsPath(Paths.get(\"wrong\"))\n      .start(client);\n\n    var futureRuleDetails = backend.getRulesService().getStandaloneRuleDetails(new GetStandaloneRuleDescriptionParams(JAVASCRIPT_S1481));\n\n    assertThat(futureRuleDetails).failsWithin(Duration.ofSeconds(1))\n      .withThrowableOfType(ExecutionException.class)\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class)\n      .withMessage(\"Could not find rule 'javascript:S1481' in embedded rules\");\n    assertThat(client.getLogMessages()).contains(\"Unable to query node version\");\n  }\n\n  @SonarLintTest\n  void can_retrieve_auto_detected_node_js(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVASCRIPT)\n      .start(client);\n\n    var nodeJsDetails = backend.getAnalysisService().getAutoDetectedNodeJs().join().getDetails();\n\n    assertThat(nodeJsDetails).isNotNull();\n    assertThat(nodeJsDetails.getPath()).isNotNull();\n    assertThat(nodeJsDetails.getVersion()).isNotNull();\n  }\n\n  @SonarLintTest\n  void can_retrieve_forced_node_js(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVASCRIPT)\n      .start(client);\n\n    var nodeJsDetails = backend.getAnalysisService().didChangeClientNodeJsPath(new DidChangeClientNodeJsPathParams(null)).join().getDetails();\n\n    assertThat(nodeJsDetails).isNotNull();\n    assertThat(nodeJsDetails.getPath()).isNotNull();\n    assertThat(nodeJsDetails.getVersion()).isNotNull();\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/analysis/PropertyDumpingSensor.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.analysis;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport org.sonar.api.batch.sensor.Sensor;\nimport org.sonar.api.batch.sensor.SensorContext;\nimport org.sonar.api.batch.sensor.SensorDescriptor;\n\npublic class PropertyDumpingSensor implements Sensor {\n\n  public static final String PROPERTY_NAME_TO_DUMP = \"PROPERTY_NAME_TO_CHECK\";\n\n  @Override\n  public void describe(SensorDescriptor sensorDescriptor) {\n    sensorDescriptor.name(\"Configuration dumping sensor\");\n  }\n\n  @Override\n  public void execute(SensorContext sensorContext) {\n    var propertyName = sensorContext.config().get(PROPERTY_NAME_TO_DUMP).orElseThrow();\n    sensorContext.config().get(propertyName).ifPresent(value -> dump(value, sensorContext.fileSystem().baseDir()));\n  }\n\n  private void dump(String propertyValue, File baseDir) {\n    try {\n      Files.writeString(baseDir.toPath().resolve(\"property.dump\"), propertyValue);\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/analysis/RulesInConnectedModeMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.analysis;\n\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SECURITY_HOTSPOTS;\nimport static org.sonarsource.sonarlint.core.test.utils.plugins.SonarPluginBuilder.newSonarPlugin;\nimport static utils.AnalysisUtils.createFile;\n\nclass RulesInConnectedModeMediumTests {\n\n  private static final String CONFIG_SCOPE_ID = \"config scope id\";\n  private static final String CONNECTION_ID = \"myConnection\";\n  private static final String JAVA_MODULE_KEY = \"sample-project\";\n\n  @SonarLintTest\n  void should_ignore_unknown_active_rule_parameters_and_convert_deprecated_keys(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"Class.java\", \"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false,\n        null, filePath, null, null, true)))\n      .build();\n    var activeRulesDumpingPlugin = newSonarPlugin(\"php\")\n      .withSensor(ActiveRulesDumpingSensor.class)\n      .generate(baseDir);\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, harness.newFakeSonarQubeServer().withPlugin(TestPlugin.JAVA).withPlugin(TestPlugin.PHP).start())\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, JAVA_MODULE_KEY)\n      .withExtraEnabledLanguagesInConnectedMode(Language.JAVA)\n      .withExtraEnabledLanguagesInConnectedMode(Language.PHP)\n      .withStorage(CONNECTION_ID, s -> s\n        .withPlugin(TestPlugin.JAVA)\n        .withPlugin(\"php\", activeRulesDumpingPlugin, TestPlugin.PHP.getHash())\n        .withProject(JAVA_MODULE_KEY, project -> project\n          .withMainBranch(\"main\")\n          .withRuleSet(\"java\", ruleSet -> ruleSet\n            // Emulate server returning a deprecated key for local analyzer\n            .withActiveRule(\"squid:S106\", \"BLOCKER\")\n            .withActiveRule(\"java:S3776\", \"BLOCKER\", Map.of(\"blah\", \"blah\"))\n            // Emulate server returning a deprecated template key\n            .withCustomActiveRule(\"squid:myCustomRule\", \"squid:S124\", \"MAJOR\", Map.of(\"message\", \"Needs to be reviewed\", \"regularExpression\", \".*REVIEW.*\")))))\n      .start(client);\n\n    backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(), List.of(fileUri), Map.of(), false, System.currentTimeMillis()));\n\n    await().atMost(3, TimeUnit.SECONDS)\n      .untilAsserted(() -> assertThat(baseDir.resolve(\"activerules.dump\")).content()\n        .contains(\"java:S106;java;null;\")\n        .contains(\"java:S3776;java;null;{Threshold=15}\")\n        .contains(\"java:myCustomRule;java;S124;{message=Needs to be reviewed, regularExpression=.*REVIEW.*}\"));\n  }\n\n  @SonarLintTest\n  void hotspot_rules_should_be_active_when_feature_flag_is_enabled(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"Class.java\", \"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false,\n        null, filePath, null, null, true)))\n      .build();\n    var activeRulesDumpingPlugin = newSonarPlugin(\"php\")\n      .withSensor(ActiveRulesDumpingSensor.class)\n      .generate(baseDir);\n    var backend = harness.newBackend()\n      .withBackendCapability(SECURITY_HOTSPOTS)\n      .withSonarQubeConnection(CONNECTION_ID, harness.newFakeSonarQubeServer().withPlugin(TestPlugin.JAVA).withPlugin(TestPlugin.PHP).start())\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, JAVA_MODULE_KEY)\n      .withExtraEnabledLanguagesInConnectedMode(Language.JAVA)\n      .withExtraEnabledLanguagesInConnectedMode(Language.PHP)\n      .withStorage(CONNECTION_ID,\n        s -> s\n          .withServerVersion(\"9.7\")\n          .withPlugin(TestPlugin.JAVA)\n          .withPlugin(\"php\", activeRulesDumpingPlugin, TestPlugin.PHP.getHash())\n          .withProject(JAVA_MODULE_KEY, project -> project\n            .withMainBranch(\"main\")\n            .withRuleSet(\"java\", ruleSet -> ruleSet\n              .withActiveRule(\"java:S4792\", \"INFO\"))))\n      .start(client);\n\n    backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(), List.of(fileUri), Map.of(), false, System.currentTimeMillis()));\n\n    await().atMost(3, TimeUnit.SECONDS)\n      .untilAsserted(() -> assertThat(baseDir.resolve(\"activerules.dump\")).content()\n        .contains(\"java:S4792;java;null;\"));\n  }\n\n  @SonarLintTest\n  void hotspot_rules_should_not_be_active_when_feature_flag_is_disabled(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"Class.java\", \"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false,\n        null, filePath, null, null, true)))\n      .build();\n    var activeRulesDumpingPlugin = newSonarPlugin(\"php\")\n      .withSensor(ActiveRulesDumpingSensor.class)\n      .generate(baseDir);\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, harness.newFakeSonarQubeServer().withPlugin(TestPlugin.PHP).start())\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, JAVA_MODULE_KEY)\n      .withExtraEnabledLanguagesInConnectedMode(Language.JAVA)\n      .withExtraEnabledLanguagesInConnectedMode(Language.PHP)\n      .withStorage(CONNECTION_ID,\n        s -> s\n          .withServerVersion(\"9.7\")\n          .withPlugin(\"php\", activeRulesDumpingPlugin, TestPlugin.PHP.getHash())\n          .withProject(JAVA_MODULE_KEY, project -> project\n            .withMainBranch(\"main\")\n            .withRuleSet(\"java\", ruleSet -> ruleSet\n              .withActiveRule(\"java:S4792\", \"INFO\"))))\n      .start(client);\n\n    backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(), List.of(fileUri), Map.of(), false, System.currentTimeMillis()));\n\n    await().atMost(3, TimeUnit.SECONDS)\n      .untilAsserted(() -> assertThat(baseDir.resolve(\"activerules.dump\")).content()\n        .doesNotContain(\"java:S4792;java;null;\"));\n  }\n\n  @SonarLintTest\n  void should_use_ipython_standalone_active_rules_in_connected_mode(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"mod.py\", \"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false,\n        null, filePath, null, null, true)))\n      .build();\n    var activeRulesDumpingPlugin = newSonarPlugin(\"php\")\n      .withSensor(ActiveRulesDumpingSensor.class)\n      .generate(baseDir);\n    var backend = harness.newBackend()\n      .withStandaloneEmbeddedPlugin(TestPlugin.PYTHON)\n      .withEnabledLanguageInStandaloneMode(Language.IPYTHON)\n      .withExtraEnabledLanguagesInConnectedMode(Language.PHP)\n      .withSonarQubeConnection(CONNECTION_ID, harness.newFakeSonarQubeServer().withPlugin(TestPlugin.PHP).start())\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, JAVA_MODULE_KEY)\n      .withStorage(CONNECTION_ID,\n        s -> s\n          .withServerVersion(\"9.7\")\n          .withPlugin(\"php\", activeRulesDumpingPlugin, TestPlugin.PHP.getHash())\n          .withProject(JAVA_MODULE_KEY, project -> project\n            .withMainBranch(\"main\")\n            .withRuleSet(\"java\", ruleSet -> ruleSet\n              .withActiveRule(\"java:S4792\", \"INFO\"))))\n      .start(client);\n\n    backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(), List.of(fileUri), Map.of(), false, System.currentTimeMillis()));\n\n    await().atMost(3, TimeUnit.SECONDS)\n      .untilAsserted(() -> assertThat(baseDir.resolve(\"activerules.dump\")).content()\n        .contains(\"ipython:PrintStatementUsage\"));\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/analysis/SupportedFilePatternsMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.analysis;\n\nimport java.util.concurrent.ExecutionException;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.GetSupportedFilePatternsParams;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JAVA;\n\nclass SupportedFilePatternsMediumTests {\n\n  @SonarLintTest\n  void it_should_return_default_supported_file_patterns_in_standalone_mode(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(JAVA)\n      .start();\n\n    var patterns = backend.getAnalysisService().getSupportedFilePatterns(new GetSupportedFilePatternsParams(\"configScopeId\")).get().getPatterns();\n    assertThat(patterns).containsOnly(\"**/*.java\", \"**/*.jav\");\n  }\n\n  @SonarLintTest\n  void it_should_return_default_supported_file_patterns_in_connected_mode_when_not_override_on_server(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    var client = harness.newFakeClient().withMatchedBranch(\"configScopeId\", \"branchName\").build();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", storage -> storage.withPlugin(TestPlugin.JAVA)\n        .withProject(\"projectKey\"))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withExtraEnabledLanguagesInConnectedMode(JAVA)\n      .start(client);\n\n    var patterns = backend.getAnalysisService().getSupportedFilePatterns(new GetSupportedFilePatternsParams(\"configScopeId\")).get().getPatterns();\n    assertThat(patterns).containsOnly(\"**/*.java\", \"**/*.jav\");\n  }\n\n  @SonarLintTest\n  void it_should_return_supported_file_patterns_with_server_defined_file_suffixes(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    var client = harness.newFakeClient().withMatchedBranch(\"configScopeId\", \"branchName\").build();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", storage -> storage.withPlugin(TestPlugin.JAVA)\n        .withProject(\"projectKey\", project -> project.withSetting(\"sonar.java.file.suffixes\", \".foo, .bar\")))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withEnabledLanguageInStandaloneMode(JAVA)\n      .start(client);\n\n    var patterns = backend.getAnalysisService().getSupportedFilePatterns(new GetSupportedFilePatternsParams(\"configScopeId\")).get().getPatterns();\n    assertThat(patterns).containsOnly(\"**/*.foo\", \"**/*.bar\");\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/analysis/ToggleAutomaticAnalysisMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.analysis;\n\nimport com.google.gson.Gson;\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.embedded.server.ToggleAutomaticAnalysisRequestHandler;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.EMBEDDED_SERVER;\n\nclass ToggleAutomaticAnalysisMediumTests {\n\n  @RegisterExtension\n  SonarLintLogTester logTester = new SonarLintLogTester(true);\n\n  private final Gson gson = new Gson();\n\n  @SonarLintTest\n  void it_should_enable_automatic_analysis(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start();\n\n    var response = executePostAutomaticAnalysisEnablementRequest(backend, \"enabled=true\");\n    await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(response.statusCode()).isEqualTo(200));\n  }\n\n  @SonarLintTest\n  void it_should_disable_automatic_analysis(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start();\n\n    var response = executePostAutomaticAnalysisEnablementRequest(backend, \"enabled=false\");\n    await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(response.statusCode()).isEqualTo(200));\n  }\n\n  @SonarLintTest\n  void it_should_return_bad_request_for_missing_parameter(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start();\n\n    var response = executePostAutomaticAnalysisEnablementRequest(backend, \"invalid=param\");\n    await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(response.statusCode()).isEqualTo(400));\n    var errorMessage = gson.fromJson(response.body(), ToggleAutomaticAnalysisRequestHandler.ErrorMessage.class);\n    assertThat(errorMessage.message()).isEqualTo(\"Missing 'enabled' query parameter\");\n  }\n\n  @SonarLintTest\n  void it_should_return_bad_request_for_get_request(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start();\n\n    await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> \n      assertThat(backend.getEmbeddedServerPort()).isGreaterThan(0));\n\n    var response = executeGetAutomaticAnalysisEnablementRequest(backend);\n    await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(response.statusCode()).isEqualTo(400));\n  }\n\n  private HttpResponse<String> executeGetAutomaticAnalysisEnablementRequest(SonarLintTestRpcServer backend) throws IOException, InterruptedException {\n    return HttpClient.newHttpClient().send(\n      executeToggleAutomaticAnalysisRequest(backend, null).GET().build(),\n      HttpResponse.BodyHandlers.ofString()\n    );\n  }\n\n  private HttpResponse<String> executePostAutomaticAnalysisEnablementRequest(SonarLintTestRpcServer backend, String queryParams) throws IOException, InterruptedException {\n    return HttpClient.newHttpClient().send(\n      executeToggleAutomaticAnalysisRequest(backend, queryParams).POST(HttpRequest.BodyPublishers.noBody()).build(),\n      HttpResponse.BodyHandlers.ofString()\n    );\n  }\n\n  private HttpRequest.Builder executeToggleAutomaticAnalysisRequest(SonarLintTestRpcServer backend, String queryParams) {\n    var uri = \"http://localhost:\" + backend.getEmbeddedServerPort() + \"/sonarlint/api/analysis/automatic/config\" + (queryParams != null && !queryParams.isEmpty() ? (\"?\" + queryParams) : \"\");\n    return HttpRequest.newBuilder()\n      .uri(URI.create(uri))\n      .header(\"Origin\", \"http://localhost\");\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/analysis/sensor/ThrowingSensorConstructor.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.analysis.sensor;\n\nimport org.sonar.api.batch.sensor.Sensor;\nimport org.sonar.api.batch.sensor.SensorContext;\nimport org.sonar.api.batch.sensor.SensorDescriptor;\n\npublic class ThrowingSensorConstructor implements Sensor {\n  public ThrowingSensorConstructor() {\n    throw new IllegalStateException(\"kaboom\");\n  }\n\n  @Override\n  public void describe(SensorDescriptor sensorDescriptor) {\n    throw new IllegalStateException(\"This is unreachable\");\n  }\n\n  @Override\n  public void execute(SensorContext sensorContext) {\n    throw new IllegalStateException(\"This is unreachable\");\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/analysis/sensor/WaitingCancellationSensor.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.analysis.sensor;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport org.sonar.api.batch.sensor.Sensor;\nimport org.sonar.api.batch.sensor.SensorContext;\nimport org.sonar.api.batch.sensor.SensorDescriptor;\n\npublic class WaitingCancellationSensor implements Sensor {\n\n  public static final String CANCELLATION_FILE_PATH_PROPERTY_NAME = \"cancellation.file.path\";\n\n  @Override\n  public void describe(SensorDescriptor sensorDescriptor) {\n    sensorDescriptor.name(\"WaitingCancellationSensor\");\n  }\n\n  @Override\n  public void execute(SensorContext sensorContext) {\n    var cancellationFilePath = Path.of(sensorContext.config().get(CANCELLATION_FILE_PATH_PROPERTY_NAME)\n      .orElseThrow(() -> new IllegalArgumentException(\"Missing '\" + CANCELLATION_FILE_PATH_PROPERTY_NAME + \"' property\")));\n    var startTime = System.currentTimeMillis();\n    while (!sensorContext.isCancelled() && startTime + 8000 > System.currentTimeMillis()) {\n      System.out.println(\"Helloooo\");\n      try {\n        Thread.sleep(200);\n      } catch (InterruptedException e) {\n        throw new RuntimeException(e);\n      }\n    }\n    System.out.println(\"Context cancelled: \" + sensorContext.isCancelled());\n    if (sensorContext.isCancelled()) {\n      try {\n        Files.writeString(cancellationFilePath, \"CANCELED\");\n        System.out.println(\"Wrote to cancellation file: \" + cancellationFilePath);\n      } catch (IOException e) {\n        throw new RuntimeException(e);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/analysis/sensor/WaitingSensor.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.analysis.sensor;\n\nimport org.sonar.api.batch.sensor.Sensor;\nimport org.sonar.api.batch.sensor.SensorContext;\nimport org.sonar.api.batch.sensor.SensorDescriptor;\n\npublic class WaitingSensor implements Sensor {\n\n  @Override\n  public void describe(SensorDescriptor sensorDescriptor) {\n    sensorDescriptor.name(\"WaitingSensor\");\n  }\n\n  @Override\n  public void execute(SensorContext sensorContext) {\n    try {\n      Thread.sleep(3000);\n    } catch (InterruptedException e) {\n      throw new RuntimeException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/branch/SonarProjectBranchMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.branch;\n\nimport java.time.Duration;\nimport java.util.Set;\nimport org.sonarsource.sonarlint.core.rpc.client.ConfigScopeNotFoundException;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.branch.DidVcsRepositoryChangeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.branch.GetMatchedSonarProjectBranchParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.branch.GetMatchedSonarProjectBranchResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.PROJECT_SYNCHRONIZATION;\n\nclass SonarProjectBranchMediumTests {\n\n  @SonarLintTest\n  void it_should_not_request_client_to_match_branch_when_vcs_repo_change_occurs_on_unbound_project(SonarLintTestHarness harness) throws InterruptedException {\n    var client = harness.newFakeClient().build();\n\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(\"configScopeId\")\n      .start(client);\n\n    notifyVcsRepositoryChanged(backend, \"configScopeId\");\n\n    Thread.sleep(200);\n    verify(client, never()).matchSonarProjectBranch(any(), any(), any(), any());\n    verify(client, never()).didChangeMatchedSonarProjectBranch(any(), any());\n  }\n\n  @SonarLintTest\n  void it_should_request_client_to_match_branch_when_vcs_repo_change_occurs_on_bound_project(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n    when(client.matchSonarProjectBranch(eq(\"configScopeId\"), eq(\"main\"), eq(Set.of(\"main\", \"myBranch\")), any())).thenReturn(\"myBranch\");\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\",\n        storage -> storage.withProject(\"projectKey\",\n          project -> project.withMainBranch(\"main\").withNonMainBranch(\"myBranch\")))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .start(client);\n\n    notifyVcsRepositoryChanged(backend, \"configScopeId\");\n\n    verify(client, timeout(2000)).didChangeMatchedSonarProjectBranch(\"configScopeId\", \"myBranch\");\n  }\n\n  @SonarLintTest\n  void it_should_not_notify_client_if_matched_branch_did_not_change(SonarLintTestHarness harness) throws InterruptedException {\n    var client = harness.newFakeClient()\n      .build();\n    when(client.matchSonarProjectBranch(eq(\"configScopeId\"), eq(\"main\"), eq(Set.of(\"main\", \"myBranch\")), any())).thenReturn(\"myBranch\");\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\",\n        storage -> storage.withProject(\"projectKey\",\n          project -> project.withMainBranch(\"main\").withNonMainBranch(\"myBranch\")))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .start(client);\n\n    // Wait for the first branch matching\n    verify(client, timeout(5000)).didChangeMatchedSonarProjectBranch(eq(\"configScopeId\"), any());\n\n    // Trigger another branch matching\n    notifyVcsRepositoryChanged(backend, \"configScopeId\");\n\n    verify(client, timeout(1000).times(2)).matchSonarProjectBranch(eq(\"configScopeId\"), eq(\"main\"), eq(Set.of(\"main\", \"myBranch\")), any());\n    Thread.sleep(200);\n    verify(client, times(1)).didChangeMatchedSonarProjectBranch(any(), any());\n  }\n\n  @SonarLintTest\n  void it_should_default_to_the_main_branch_if_client_unable_to_match_branch(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n    when(client.matchSonarProjectBranch(any(), any(), any(), any())).thenReturn(null);\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\",\n        storage -> storage.withProject(\"projectKey\",\n          project -> project.withMainBranch(\"main\").withNonMainBranch(\"myBranch\")))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .start(client);\n\n    notifyVcsRepositoryChanged(backend, \"configScopeId\");\n\n    verify(client, timeout(2000)).didChangeMatchedSonarProjectBranch(\"configScopeId\", \"main\");\n  }\n\n  @SonarLintTest\n  void it_should_not_match_any_branch_if_there_is_none_in_the_storage(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\")\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .start(client);\n\n    notifyVcsRepositoryChanged(backend, \"configScopeId\");\n\n    await().untilAsserted(() -> assertThat(client.getLogMessages()).contains(\"Cannot match Sonar branch, storage is empty\"));\n    verify(client, never()).matchSonarProjectBranch(any(), any(), any(), any());\n    verify(client, never()).didChangeMatchedSonarProjectBranch(any(), any());\n  }\n\n  @SonarLintTest\n  void it_should_not_notify_client_when_error_occurs_during_client_branch_matching_and_default_to_main_branch(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n    when(client.matchSonarProjectBranch(any(), any(), any(), any())).thenThrow(new ConfigScopeNotFoundException());\n    harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\",\n        storage -> storage.withProject(\"projectKey\",\n          project -> project.withMainBranch(\"main\").withNonMainBranch(\"myBranch\")))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .start(client);\n\n    verify(client, timeout(1000)).didChangeMatchedSonarProjectBranch(\"configScopeId\", \"main\");\n  }\n\n  @SonarLintTest\n  void verify_that_multiple_quick_branch_notifications_are_not_running_in_race_conditions(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient()\n      .build();\n    doReturn(\"branchA\", \"branchB\").when(client).matchSonarProjectBranch(any(), any(), any(), any());\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\",\n        storage -> storage.withProject(\"projectKey\",\n          project -> project.withMainBranch(\"main\").withNonMainBranch(\"myBranch\")))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .start(client);\n\n    // Wait for initial branch matching\n    verify(client, timeout(5000)).didChangeMatchedSonarProjectBranch(\"configScopeId\", \"branchA\");\n\n    backend.getSonarProjectBranchService().didVcsRepositoryChange(new DidVcsRepositoryChangeParams(\"configScopeId\"));\n    backend.getSonarProjectBranchService().didVcsRepositoryChange(new DidVcsRepositoryChangeParams(\"configScopeId\"));\n    backend.getSonarProjectBranchService().didVcsRepositoryChange(new DidVcsRepositoryChangeParams(\"configScopeId\"));\n    backend.getSonarProjectBranchService().didVcsRepositoryChange(new DidVcsRepositoryChangeParams(\"configScopeId\"));\n\n    verify(client, timeout(5000)).didChangeMatchedSonarProjectBranch(\"configScopeId\", \"branchB\");\n  }\n\n  @SonarLintTest\n  void it_should_return_matched_branch_after_matching(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n    doReturn(\"main\", \"myBranch\")\n      .when(client).matchSonarProjectBranch(eq(\"configScopeId\"), eq(\"main\"), eq(Set.of(\"main\", \"myBranch\")), any());\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", harness.newFakeSonarQubeServer().start(),\n        storage -> storage.withProject(\"projectKey\",\n          project -> project.withMainBranch(\"main\").withNonMainBranch(\"myBranch\")))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .start(client);\n\n    // Initial matching\n    verify(client, timeout(1000)).didChangeMatchedSonarProjectBranch(\"configScopeId\", \"main\");\n\n    notifyVcsRepositoryChanged(backend, \"configScopeId\");\n\n    verify(client, timeout(1000)).didChangeMatchedSonarProjectBranch(\"configScopeId\", \"myBranch\");\n\n    assertThat(backend.getSonarProjectBranchService().getMatchedSonarProjectBranch(new GetMatchedSonarProjectBranchParams(\"configScopeId\")))\n      .succeedsWithin(Duration.ofSeconds(1))\n      .extracting(GetMatchedSonarProjectBranchResponse::getMatchedSonarProjectBranch)\n      .isEqualTo(\"myBranch\");\n  }\n\n  @SonarLintTest\n  void it_should_trigger_branch_specific_synchronization_if_the_branch_changed(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(\"projectKey\", project -> project.withBranch(\"myBranch\", branch -> branch.withIssue(\"issueKey\")))\n      .start();\n    var client = harness.newFakeClient()\n      .build();\n    doReturn(\"main\", \"myBranch\")\n      .when(client)\n      .matchSonarProjectBranch(eq(\"configScopeId\"), eq(\"main\"), eq(Set.of(\"main\", \"myBranch\")), any());\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server,\n        storage -> storage.withProject(\"projectKey\",\n          project -> project.withMainBranch(\"main\").withNonMainBranch(\"myBranch\")))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(PROJECT_SYNCHRONIZATION)\n      .start(client);\n\n    // Wait for first sync\n    verify(client, timeout(5000)).didChangeMatchedSonarProjectBranch(\"configScopeId\", \"main\");\n    verify(client, timeout(5000)).didSynchronizeConfigurationScopes(Set.of(\"configScopeId\"));\n\n    // Now emulate a branch change\n    backend.getSonarProjectBranchService().didVcsRepositoryChange(new DidVcsRepositoryChangeParams(\"configScopeId\"));\n\n    verify(client, timeout(5000)).didChangeMatchedSonarProjectBranch(\"configScopeId\", \"myBranch\");\n    verify(client, timeout(5000).times(2)).didSynchronizeConfigurationScopes(Set.of(\"configScopeId\"));\n  }\n\n  @SonarLintTest\n  void it_should_clear_the_matched_branch_when_the_binding_changes(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(\"projectKey\", project -> project.withBranch(\"myBranch\", branch -> branch.withIssue(\"issueKey\")))\n      .start();\n    var client = harness.newFakeClient().build();\n    doReturn(\"myBranch\")\n      .when(client)\n      .matchSonarProjectBranch(eq(\"configScopeId\"), any(), any(), any());\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server,\n        storage -> storage.withProject(\"projectKey\",\n          project -> project.withMainBranch(\"main\").withNonMainBranch(\"myBranch\")))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .start(client);\n\n    verify(client, timeout(5000)).didChangeMatchedSonarProjectBranch(\"configScopeId\", \"myBranch\");\n\n    // Emulate a binding change to a project having no branches\n    bind(backend, \"configScopeId\", \"connectionId\", \"projectKey2\");\n\n    assertThat(backend.getSonarProjectBranchService().getMatchedSonarProjectBranch(new GetMatchedSonarProjectBranchParams(\"configScopeId\")))\n      .succeedsWithin(Duration.ofSeconds(5))\n      .extracting(GetMatchedSonarProjectBranchResponse::getMatchedSonarProjectBranch)\n      .isNull();\n  }\n\n  @SonarLintTest\n  void it_should_match_project_branch(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n\n    client.matchProjectBranch(any(), any(), any());\n\n    verify(client).matchProjectBranch(any(), any(), any());\n  }\n\n  private void bind(SonarLintTestRpcServer backend, String configScopeId, String connectionId, String projectKey) {\n    backend.getConfigurationService().didUpdateBinding(new DidUpdateBindingParams(configScopeId, new BindingConfigurationDto(connectionId, projectKey, true)));\n  }\n\n  private void notifyVcsRepositoryChanged(SonarLintTestRpcServer backend, String configScopeId) {\n    backend.getSonarProjectBranchService().didVcsRepositoryChange(new DidVcsRepositoryChangeParams(configScopeId));\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/connection/ConnectionGetAllProjectsMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.connection;\n\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarCloudConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarQubeConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.FuzzySearchProjectsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.GetAllProjectsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.GetAllProjectsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.SonarProjectDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.get;\nimport static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\n\nclass ConnectionGetAllProjectsMediumTests {\n\n  @SonarLintTest\n  void it_should_return_an_empty_response_if_no_projects_in_sonarqube(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer().start();\n    var backend = harness.newBackend().start();\n\n    var response = getAllProjects(backend, new TransientSonarQubeConnectionDto(server.baseUrl(), Either.forLeft(new TokenDto(null))));\n\n    assertThat(response.getSonarProjects()).isEmpty();\n  }\n\n  @SonarLintTest\n  void it_should_return_an_empty_response_if_no_projects_in_sonarcloud_organization(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"myOrg\")\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .start();\n\n    var response = getAllProjects(backend, new TransientSonarCloudConnectionDto(\"myOrg\", Either.forLeft(new TokenDto(\"token\")), SonarCloudRegion.EU));\n\n    assertThat(response.getSonarProjects()).isEmpty();\n  }\n\n  @SonarLintTest\n  void it_should_return_the_list_of_projects_on_sonarqube(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(\"projectKey1\", project -> project.withName(\"MyProject1\"))\n      .withProject(\"projectKey2\", project -> project.withName(\"MyProject2\"))\n      .start();\n    var backend = harness.newBackend().start();\n\n    var response = getAllProjects(backend, new TransientSonarQubeConnectionDto(server.baseUrl(), Either.forLeft(new TokenDto(\"token\"))));\n\n    assertThat(response.getSonarProjects())\n      .extracting(SonarProjectDto::getKey, SonarProjectDto::getName)\n      .containsOnly(tuple(\"projectKey1\", \"MyProject1\"), tuple(\"projectKey2\", \"MyProject2\"));\n  }\n\n  @SonarLintTest\n  void it_should_fuzzy_search_for_projects_on_sonarqube(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(\"mycompany:project-foo1\", project -> project.withName(\"My Company Project Foo 1\"))\n      .withProject(\"mycompany:project-foo2\", project -> project.withName(\"My Company Project Foo 2\"))\n      .withProject(\"mycompany:project-bar\", project -> project.withName(\"My Company Project Bar\"))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server.baseUrl())\n      .start();\n\n    var emptySearch = backend.getConnectionService().fuzzySearchProjects(new FuzzySearchProjectsParams(\"connectionId\", \"\")).join();\n    assertThat(emptySearch.getTopResults())\n      .isEmpty();\n\n    var searchMy = backend.getConnectionService().fuzzySearchProjects(new FuzzySearchProjectsParams(\"connectionId\", \"My\")).join();\n    assertThat(searchMy.getTopResults())\n      .extracting(SonarProjectDto::getKey, SonarProjectDto::getName)\n      .containsExactly(\n        tuple(\"mycompany:project-bar\", \"My Company Project Bar\"),\n        tuple(\"mycompany:project-foo1\", \"My Company Project Foo 1\"),\n        tuple(\"mycompany:project-foo2\", \"My Company Project Foo 2\"));\n\n    var searchFooByName = backend.getConnectionService().fuzzySearchProjects(new FuzzySearchProjectsParams(\"connectionId\", \"Foo\")).join();\n    assertThat(searchFooByName.getTopResults())\n      .extracting(SonarProjectDto::getKey, SonarProjectDto::getName)\n      .containsExactly(\n        tuple(\"mycompany:project-foo1\", \"My Company Project Foo 1\"),\n        tuple(\"mycompany:project-foo2\", \"My Company Project Foo 2\"));\n\n    var searchBarByKey = backend.getConnectionService().fuzzySearchProjects(new FuzzySearchProjectsParams(\"connectionId\", \"project-bar\")).join();\n    assertThat(searchBarByKey.getTopResults())\n      .extracting(SonarProjectDto::getKey, SonarProjectDto::getName)\n      .containsExactly(\n        tuple(\"mycompany:project-bar\", \"My Company Project Bar\"));\n  }\n\n  @SonarLintTest\n  void it_should_return_the_list_of_projects_on_sonarcloud(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"myOrg\", organization -> organization\n        .withProject(\"projectKey1\", project -> project.withName(\"MyProject1\"))\n        .withProject(\"projectKey2\", project -> project.withName(\"MyProject2\")))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .start();\n\n    var response = getAllProjects(backend, new TransientSonarCloudConnectionDto(\"myOrg\", Either.forLeft(new TokenDto(\"token\")), SonarCloudRegion.EU));\n\n    assertThat(response.getSonarProjects())\n      .extracting(SonarProjectDto::getKey, SonarProjectDto::getName)\n      .containsOnly(tuple(\"projectKey1\", \"MyProject1\"), tuple(\"projectKey2\", \"MyProject2\"));\n  }\n\n  @SonarLintTest\n  void it_should_support_cancellation(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer().start();\n    server.getMockServer().stubFor(get(\"/api/components/search.protobuf?qualifiers=TRK&ps=500&p=1\").willReturn(aResponse()\n      .withStatus(200)\n      .withFixedDelay(2000)));\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend().start(client);\n\n    var connectionDto = new TransientSonarQubeConnectionDto(server.baseUrl(), Either.forLeft(new TokenDto(null)));\n\n    var future = backend.getConnectionService().getAllProjects(new GetAllProjectsParams(connectionDto));\n    await().untilAsserted(() -> server.getMockServer().verify(getRequestedFor(urlEqualTo(\"/api/components/search.protobuf?qualifiers=TRK&ps=500&p=1\"))));\n\n    future.cancel(true);\n\n    await().untilAsserted(() -> assertThat(client.getLogMessages()).contains(\"Request cancelled\"));\n  }\n\n  @SonarLintTest\n  void it_should_throw_ResponseErrorException_when_getAllProjects_unauthorized(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer().start();\n    server.getMockServer().stubFor(get(\"/api/components/search.protobuf?qualifiers=TRK&ps=500&p=1\").willReturn(aResponse()\n      .withStatus(401)\n      .withBody(\"Unauthorized\")));\n    var backend = harness.newBackend().start();\n\n    var connectionDto = new TransientSonarQubeConnectionDto(server.baseUrl(), Either.forLeft(new TokenDto(\"invalid-token\")));\n\n    var future = backend.getConnectionService().getAllProjects(new GetAllProjectsParams(connectionDto));\n\n    assertThatThrownBy(future::join)\n      .hasCauseInstanceOf(ResponseErrorException.class);\n  }\n\n  private GetAllProjectsResponse getAllProjects(SonarLintTestRpcServer backend, TransientSonarQubeConnectionDto connectionDto) {\n    return backend.getConnectionService().getAllProjects(new GetAllProjectsParams(connectionDto)).join();\n  }\n\n  private GetAllProjectsResponse getAllProjects(SonarLintTestRpcServer backend, TransientSonarCloudConnectionDto connectionDto) {\n    return backend.getConnectionService().getAllProjects(new GetAllProjectsParams(connectionDto)).join();\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/connection/ConnectionGetProjectNameByKeyMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.connection;\n\nimport java.util.List;\nimport java.util.Map;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarCloudConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarQubeConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.GetProjectNamesByKeyParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.GetProjectNamesByKeyResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.get;\nimport static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\n\nclass ConnectionGetProjectNameByKeyMediumTests {\n\n  @SonarLintTest\n  void it_should_return_null_if_no_projects_in_sonarqube(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer().start();\n    var backend = harness.newBackend().start();\n\n    var response = getProjectNamesByKey(backend, new TransientSonarQubeConnectionDto(server.baseUrl(), Either.forLeft(new TokenDto(\"token\"))),\n      List.of(\"myProject\"));\n\n    assertThat(response.getProjectNamesByKey().entrySet()).extracting(Map.Entry::getKey, Map.Entry::getValue)\n      .containsExactlyInAnyOrder(tuple(\"myProject\", null));\n  }\n\n  @SonarLintTest\n  void it_should_return_null_if_no_projects_in_sonarcloud_organization(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarCloudServer().start();\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .start();\n\n    var response = getProjectNamesByKey(backend, new TransientSonarCloudConnectionDto(\"myOrg\", Either.forLeft(new TokenDto(\"token\")), SonarCloudRegion.EU), List.of(\n      \"myProject\"));\n\n    assertThat(response.getProjectNamesByKey().entrySet()).extracting(Map.Entry::getKey, Map.Entry::getValue)\n      .containsExactlyInAnyOrder(tuple(\"myProject\", null));\n  }\n\n  @SonarLintTest\n  void it_should_find_project_name_if_available_on_sonarqube(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(\"project-foo1\", project -> project.withName(\"My Company Project Foo 1\"))\n      .withProject(\"project-foo2\", project -> project.withName(\"My Company Project Foo 2\"))\n      .withProject(\"project-foo3\", project -> project.withName(\"My Company Project Foo 3\"))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server.baseUrl())\n      .start();\n\n    var response = getProjectNamesByKey(backend, new TransientSonarQubeConnectionDto(server.baseUrl(), Either.forLeft(new TokenDto(\"token\"))),\n      List.of(\"project-foo2\", \"project-foo3\", \"project-foo4\"));\n\n    assertThat(response.getProjectNamesByKey().entrySet()).extracting(Map.Entry::getKey, Map.Entry::getValue)\n      .containsExactlyInAnyOrder(tuple(\"project-foo4\", null), tuple(\"project-foo2\", \"My Company Project Foo 2\"), tuple(\"project-foo3\",\n        \"My Company Project Foo 3\"));\n  }\n\n  @SonarLintTest\n  void it_should_find_project_names_if_available_on_sonarcloud(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"myOrg\", organization -> organization\n        .withProject(\"projectKey1\", project -> project.withName(\"MyProject1\"))\n        .withProject(\"projectKey2\", project -> project.withName(\"MyProject2\"))\n        .withProject(\"projectKey3\", project -> project.withName(\"MyProject3\")))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .start();\n\n    var response = getProjectNamesByKey(backend, new TransientSonarCloudConnectionDto(\"myOrg\", Either.forLeft(new TokenDto(\"token\")), SonarCloudRegion.EU),\n      List.of(\"projectKey2\", \"projectKey3\", \"projectKey4\"));\n\n    assertThat(response.getProjectNamesByKey().entrySet()).extracting(Map.Entry::getKey, Map.Entry::getValue)\n      .containsExactlyInAnyOrder(tuple(\"projectKey4\", null), tuple(\"projectKey2\", \"MyProject2\"), tuple(\"projectKey3\", \"MyProject3\"));\n  }\n\n  @SonarLintTest\n  void it_should_support_cancellation(SonarLintTestHarness harness) {\n    var myProjectKey = \"myProjectKey\";\n    var server = harness.newFakeSonarQubeServer().start();\n    server.getMockServer().stubFor(get(\"/api/components/show.protobuf?component=\" + myProjectKey).willReturn(aResponse()\n      .withStatus(200)\n      .withFixedDelay(2000)));\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend().start(client);\n\n    var connectionDto = new TransientSonarQubeConnectionDto(server.baseUrl(), Either.forLeft(new TokenDto(null)));\n\n    var future = backend.getConnectionService().getProjectNamesByKey(new GetProjectNamesByKeyParams(connectionDto, List.of(myProjectKey)));\n    await().untilAsserted(() -> server.getMockServer().verify(getRequestedFor(urlEqualTo(\"/api/components/show.protobuf?component=\" + myProjectKey))));\n\n    future.cancel(true);\n\n    await().untilAsserted(() -> assertThat(client.getLogMessages()).contains(\"Request cancelled\"));\n  }\n\n  private GetProjectNamesByKeyResponse getProjectNamesByKey(SonarLintTestRpcServer backend, TransientSonarQubeConnectionDto connectionDto, List<String> projectKey) {\n    return backend.getConnectionService().getProjectNamesByKey(new GetProjectNamesByKeyParams(connectionDto, projectKey)).join();\n  }\n\n  private GetProjectNamesByKeyResponse getProjectNamesByKey(SonarLintTestRpcServer backend, TransientSonarCloudConnectionDto connectionDto, List<String> projectKey) {\n    return backend.getConnectionService().getProjectNamesByKey(new GetProjectNamesByKeyParams(connectionDto, projectKey)).join();\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/connection/ConnectionValidatorMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.connection;\n\nimport com.github.tomakehurst.wiremock.junit5.WireMockExtension;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarCloudConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarQubeConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.validate.ValidateConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarcloud.ws.Organizations;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.get;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.sonarsource.sonarlint.core.test.utils.ProtobufUtils.protobufBody;\n\nclass ConnectionValidatorMediumTests {\n\n  public static final Either<TokenDto, UsernamePasswordDto> A_TOKEN = Either.forLeft(new TokenDto(\"aToken\"));\n\n  @RegisterExtension\n  static WireMockExtension serverMock = WireMockExtension.newInstance()\n    .options(wireMockConfig().dynamicPort())\n    .build();\n\n  @SonarLintTest\n  void test_connection_without_credentials_fail(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(serverMock.baseUrl())\n      .start();\n\n    var response = backend.getConnectionService().validateConnection(new ValidateConnectionParams(new TransientSonarQubeConnectionDto(serverMock.baseUrl(), Either.forLeft(new TokenDto(null))))).join();\n\n    assertThat(response.isSuccess()).isFalse();\n  }\n\n  @SonarLintTest\n  void test_connection_ok(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(serverMock.baseUrl())\n      .start();\n    serverMock.stubFor(get(\"/api/system/status\")\n      .willReturn(aResponse().withBody(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"9.9\\\",\\\"status\\\": \\\"UP\\\"}\")));\n    serverMock.stubFor(get(\"/api/authentication/validate?format=json\")\n      .willReturn(aResponse().withBody(\"{\\\"valid\\\": true}\")));\n\n    var response = backend.getConnectionService().validateConnection(new ValidateConnectionParams(new TransientSonarQubeConnectionDto(serverMock.baseUrl(), A_TOKEN))).join();\n\n    assertThat(response.isSuccess()).isTrue();\n  }\n\n  @SonarLintTest\n  void test_connection_organization_not_found(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(serverMock.baseUrl())\n      .start();\n    serverMock.stubFor(get(\"/api/system/status\")\n      .willReturn(aResponse().withBody(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"9.9\\\",\\\"status\\\": \\\"UP\\\"}\")));\n    serverMock.stubFor(get(\"/api/authentication/validate?format=json\")\n      .willReturn(aResponse().withBody(\"{\\\"valid\\\": true}\")));\n    serverMock.stubFor(get(\"/api/organizations/search.protobuf?organizations=myOrg&ps=500&p=1\")\n      .willReturn(aResponse().withResponseBody(protobufBody(Organizations.SearchWsResponse.newBuilder().build()))));\n\n    var response = backend.getConnectionService().validateConnection(new ValidateConnectionParams(new TransientSonarCloudConnectionDto(\"myOrg\", A_TOKEN, SonarCloudRegion.EU))).join();\n\n    assertThat(response.isSuccess()).isFalse();\n    assertThat(response.getMessage()).isEqualTo(\"No organizations found for key: myOrg\");\n  }\n\n  @SonarLintTest\n  void test_connection_ok_with_org(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(serverMock.baseUrl())\n      .start();\n    serverMock.stubFor(get(\"/api/system/status\")\n      .willReturn(aResponse().withBody(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"9.9\\\",\\\"status\\\": \\\"UP\\\"}\")));\n    serverMock.stubFor(get(\"/api/authentication/validate?format=json\")\n      .willReturn(aResponse().withBody(\"{\\\"valid\\\": true}\")));\n    serverMock.stubFor(get(\"/api/organizations/search.protobuf?organizations=myOrg&ps=500&p=1\")\n      .willReturn(aResponse().withResponseBody(protobufBody(Organizations.SearchWsResponse.newBuilder()\n        .addOrganizations(Organizations.Organization.newBuilder()\n          .setKey(\"myOrg\")\n          .setName(\"My Org\")\n          .build())\n        .build()))));\n    serverMock.stubFor(get(\"/api/organizations/search.protobuf?organizations=myOrg&ps=500&p=2\")\n      .willReturn(aResponse().withResponseBody(protobufBody(Organizations.SearchWsResponse.newBuilder().build()))));\n\n    var response = backend.getConnectionService().validateConnection(new ValidateConnectionParams(new TransientSonarCloudConnectionDto(\"myOrg\", A_TOKEN, SonarCloudRegion.EU))).join();\n\n    assertThat(response.isSuccess()).isTrue();\n  }\n\n  @SonarLintTest\n  void test_connection_ok_without_org(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(serverMock.baseUrl())\n      .start();\n    serverMock.stubFor(get(\"/api/system/status\")\n      .willReturn(aResponse().withBody(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"9.9\\\",\\\"status\\\": \\\"UP\\\"}\")));\n    serverMock.stubFor(get(\"/api/authentication/validate?format=json\")\n      .willReturn(aResponse().withBody(\"{\\\"valid\\\": true}\")));\n    var response = backend.getConnectionService().validateConnection(new ValidateConnectionParams(new TransientSonarCloudConnectionDto(null, A_TOKEN, SonarCloudRegion.EU))).join();\n\n    assertThat(response.isSuccess()).isTrue();\n  }\n\n  @SonarLintTest\n  void test_unsupported_server(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(serverMock.baseUrl())\n      .start();\n    serverMock.stubFor(get(\"/api/system/status\")\n      .willReturn(aResponse().withBody(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"6.7\\\",\\\"status\\\": \\\"UP\\\"}\")));\n\n    var response = backend.getConnectionService().validateConnection(new ValidateConnectionParams(new TransientSonarQubeConnectionDto(serverMock.baseUrl(), A_TOKEN))).join();\n\n    assertThat(response.isSuccess()).isFalse();\n    assertThat(response.getMessage()).isEqualTo(\"Your SonarQube Server instance has version 6.7. Version should be greater or equal to 9.9\");\n  }\n\n  @SonarLintTest\n  void test_client_error(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(serverMock.baseUrl())\n      .start();\n    serverMock.stubFor(get(\"/api/system/status\")\n      .willReturn(aResponse().withStatus(400)));\n\n    var response = backend.getConnectionService().validateConnection(new ValidateConnectionParams(new TransientSonarQubeConnectionDto(serverMock.baseUrl(),\n      Either.forRight(new UsernamePasswordDto(\"foo\", \"bar\"))))).join();\n\n    assertThat(response.isSuccess()).isFalse();\n    assertThat(response.getMessage()).isEqualTo(\"Error 400 on \" + serverMock.baseUrl() + \"/api/system/status\");\n  }\n\n  @SonarLintTest\n  void test_response_error(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(serverMock.baseUrl())\n      .start();\n    serverMock.stubFor(get(\"/api/system/status\")\n      .willReturn(aResponse().withBody(\"{\\\"id\\\": }\")));\n\n    var response = backend.getConnectionService().validateConnection(new ValidateConnectionParams(new TransientSonarQubeConnectionDto(serverMock.baseUrl(),\n      Either.forRight(new UsernamePasswordDto(\"foo\", \"bar\"))))).join();\n\n    assertThat(response.isSuccess()).isFalse();\n    assertThat(response.getMessage()).isEqualTo(\"Unexpected body received\");\n  }\n\n  @SonarLintTest\n  void should_catch_connection_error_to_server(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(serverMock.baseUrl())\n      .start();\n\n    var response = backend.getConnectionService().validateConnection(new ValidateConnectionParams(new TransientSonarQubeConnectionDto(\"https://foo.bar:1234\",\n      Either.forLeft(new TokenDto(\"token\"))))).join();\n\n    assertThat(response.isSuccess()).isFalse();\n    assertThat(response.getMessage()).startsWith(\"Request failed\");\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/connection/OrganizationMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.connection;\n\nimport com.github.tomakehurst.wiremock.junit5.WireMockExtension;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\nimport java.util.concurrent.ExecutionException;\nimport org.assertj.core.api.Assertions;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.FuzzySearchUserOrganizationsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.GetOrganizationParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.ListUserOrganizationsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.OrganizationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarcloud.ws.Organizations;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.equalTo;\nimport static com.github.tomakehurst.wiremock.client.WireMock.exactly;\nimport static com.github.tomakehurst.wiremock.client.WireMock.get;\nimport static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.groups.Tuple.tuple;\nimport static org.sonarsource.sonarlint.core.test.utils.ProtobufUtils.protobufBody;\n\nclass OrganizationMediumTests {\n\n  @RegisterExtension\n  static WireMockExtension sonarcloudMock = WireMockExtension.newInstance()\n    .options(wireMockConfig().dynamicPort())\n    .build();\n\n  @SonarLintTest\n  void it_should_list_empty_user_organizations(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    var fakeClient = harness.newFakeClient()\n      .build();\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(sonarcloudMock.baseUrl())\n      .start(fakeClient);\n    sonarcloudMock.stubFor(get(\"/api/organizations/search.protobuf?member=true&ps=500&p=1\")\n      .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(Organizations.SearchWsResponse.newBuilder()\n        .build()))));\n\n    var details = backend.getConnectionService().listUserOrganizations(new ListUserOrganizationsParams(Either.forLeft(new TokenDto(\"token\")), SonarCloudRegion.EU));\n\n    assertThat(details.get().getUserOrganizations()).isEmpty();\n  }\n\n  @SonarLintTest\n  void it_should_list_user_organizations(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(sonarcloudMock.baseUrl())\n      .start();\n    sonarcloudMock.stubFor(get(\"/api/organizations/search.protobuf?member=true&ps=500&p=1\")\n      .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(Organizations.SearchWsResponse.newBuilder()\n        .addOrganizations(Organizations.Organization.newBuilder()\n          .setKey(\"orgKey1\")\n          .setName(\"orgName1\")\n          .setDescription(\"orgDesc1\")\n          .build())\n        .addOrganizations(Organizations.Organization.newBuilder()\n          .setKey(\"orgKey2\")\n          .setName(\"orgName2\")\n          .setDescription(\"orgDesc2\")\n          .build())\n        .build()))));\n    sonarcloudMock.stubFor(get(\"/api/system/status\")\n      .willReturn(aResponse().withStatus(200).withBody(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"8.0\\\",\\\"status\\\": \" +\n        \"\\\"UP\\\"}\")));\n    sonarcloudMock.stubFor(get(\"/api/organizations/search.protobuf?member=true&ps=500&p=2\")\n      .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(Organizations.SearchWsResponse.newBuilder().build()))));\n\n    var details = backend.getConnectionService().listUserOrganizations(new ListUserOrganizationsParams(Either.forLeft(new TokenDto(\"token\")), SonarCloudRegion.EU));\n\n    assertThat(details.get().getUserOrganizations()).extracting(OrganizationDto::getKey, OrganizationDto::getName, OrganizationDto::getDescription)\n      .containsExactlyInAnyOrder(\n        tuple(\"orgKey1\", \"orgName1\", \"orgDesc1\"),\n        tuple(\"orgKey2\", \"orgName2\", \"orgDesc2\"));\n\n    sonarcloudMock.verify(getRequestedFor(urlEqualTo(\"/api/organizations/search.protobuf?member=true&ps=500&p=1\"))\n      .withHeader(\"Authorization\", equalTo(\"Bearer token\")));\n  }\n\n  @SonarLintTest\n  void it_should_get_organizations_by_key(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(sonarcloudMock.baseUrl())\n      .start();\n    sonarcloudMock.stubFor(get(\"/api/organizations/search.protobuf?organizations=myCustomOrg&ps=500&p=1\")\n      .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(Organizations.SearchWsResponse.newBuilder()\n        .addOrganizations(Organizations.Organization.newBuilder()\n          .setKey(\"myCustom\")\n          .setName(\"orgName\")\n          .setDescription(\"orgDesc\")\n          .build())\n        .build()))));\n    sonarcloudMock.stubFor(get(\"/api/organizations/search.protobuf?organizations=myCustomOrg&ps=500&p=2\")\n      .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(Organizations.SearchWsResponse.newBuilder().build()))));\n\n    var details = backend.getConnectionService().getOrganization(new GetOrganizationParams(Either.forRight(new UsernamePasswordDto(\"user\", \"pwd\")), \"myCustomOrg\", SonarCloudRegion.EU));\n\n    var organization = details.get().getOrganization();\n    assertThat(organization.getKey()).isEqualTo(\"myCustom\");\n    assertThat(organization.getName()).isEqualTo(\"orgName\");\n    assertThat(organization.getDescription()).isEqualTo(\"orgDesc\");\n\n    sonarcloudMock.verify(getRequestedFor(urlEqualTo(\"/api/organizations/search.protobuf?organizations=myCustomOrg&ps=500&p=1\"))\n      .withHeader(\"Authorization\", equalTo(\"Basic \" + Base64.getEncoder().encodeToString(\"user:pwd\".getBytes(StandardCharsets.UTF_8)))));\n  }\n\n  @SonarLintTest\n  void it_should_fuzzy_search_and_cache_organizations_on_sonarcloud(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(sonarcloudMock.baseUrl())\n      .start();\n    sonarcloudMock.stubFor(get(\"/api/organizations/search.protobuf?member=true&ps=500&p=1\")\n      .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(Organizations.SearchWsResponse.newBuilder()\n        .addOrganizations(Organizations.Organization.newBuilder()\n          .setKey(\"org-foo1\")\n          .setName(\"My Company Org Foo 1\")\n          .setDescription(\"orgDesc 1\")\n          .build())\n        .addOrganizations(Organizations.Organization.newBuilder()\n          .setKey(\"org-foo2\")\n          .setName(\"My Company Org Foo 2\")\n          .setDescription(\"orgDesc 2\")\n          .build())\n        .addOrganizations(Organizations.Organization.newBuilder()\n          .setKey(\"org-bar\")\n          .setName(\"My Company Org Bar\")\n          .setDescription(\"orgDesc 3\")\n          .build())\n        .build()))));\n    sonarcloudMock.stubFor(get(\"/api/organizations/search.protobuf?member=true&ps=500&p=2\")\n      .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(Organizations.SearchWsResponse.newBuilder().build()))));\n\n    var credentials = Either.<TokenDto, UsernamePasswordDto>forRight(new UsernamePasswordDto(\"user\", \"pwd\"));\n    var emptySearch = backend.getConnectionService().fuzzySearchUserOrganizations(new FuzzySearchUserOrganizationsParams(credentials, \"\", org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.EU)).join();\n    assertThat(emptySearch.getTopResults())\n      .isEmpty();\n\n    var searchMy = backend.getConnectionService().fuzzySearchUserOrganizations(new FuzzySearchUserOrganizationsParams(credentials, \"My\", org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.EU)).join();\n    assertThat(searchMy.getTopResults())\n      .extracting(OrganizationDto::getKey, OrganizationDto::getName)\n      .containsExactly(\n        Assertions.tuple(\"org-bar\", \"My Company Org Bar\"),\n        Assertions.tuple(\"org-foo1\", \"My Company Org Foo 1\"),\n        Assertions.tuple(\"org-foo2\", \"My Company Org Foo 2\"));\n\n    var searchFooByName = backend.getConnectionService().fuzzySearchUserOrganizations(new FuzzySearchUserOrganizationsParams(credentials, \"Foo\", org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.EU)).join();\n    assertThat(searchFooByName.getTopResults())\n      .extracting(OrganizationDto::getKey, OrganizationDto::getName)\n      .containsExactly(\n        Assertions.tuple(\"org-foo1\", \"My Company Org Foo 1\"),\n        Assertions.tuple(\"org-foo2\", \"My Company Org Foo 2\"));\n\n    var searchBarByKey = backend.getConnectionService().fuzzySearchUserOrganizations(new FuzzySearchUserOrganizationsParams(credentials, \"org-bar\", org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion.EU)).join();\n    assertThat(searchBarByKey.getTopResults())\n      .extracting(OrganizationDto::getKey, OrganizationDto::getName)\n      .containsExactly(\n        Assertions.tuple(\"org-bar\", \"My Company Org Bar\"));\n\n    // Verify that the cache is used\n    sonarcloudMock.verify(exactly(1), getRequestedFor(urlEqualTo(\"/api/organizations/search.protobuf?member=true&ps=500&p=1\")));\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/dogfooding/DogfoodingRpcServiceMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.dogfooding;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport uk.org.webcompere.systemstubs.environment.EnvironmentVariables;\nimport uk.org.webcompere.systemstubs.jupiter.SystemStub;\nimport uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension;\n\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.sonarsource.sonarlint.core.commons.dogfood.DogfoodEnvironmentDetectionService.SONARSOURCE_DOGFOODING_ENV_VAR_KEY;\n\n@ExtendWith(SystemStubsExtension.class)\nclass DogfoodingRpcServiceMediumTests {\n\n  @SystemStub\n  EnvironmentVariables environmentVariables;\n\n  @BeforeEach\n  @AfterEach\n  void setUp() {\n    // this is to ignore env variable on dev machine in dogfooding mode and have green tests locally\n    environmentVariables.remove(SONARSOURCE_DOGFOODING_ENV_VAR_KEY);\n  }\n\n  @SonarLintTest\n  void should_return_true_when_env_variable_is_set(SonarLintTestHarness harness) {\n    environmentVariables.set(SONARSOURCE_DOGFOODING_ENV_VAR_KEY, \"1\");\n    var backend = harness.newBackend().start();\n\n    var result = backend.getDogfoodingService().isDogfoodingEnvironment().join();\n\n    assertTrue(result.isDogfoodingEnvironment());\n  }\n\n  @SonarLintTest\n  void should_return_false_when_env_var_is_absent(SonarLintTestHarness harness) {\n    var backend = harness.newBackend().start();\n\n    var result = backend.getDogfoodingService().isDogfoodingEnvironment().join();\n\n    assertFalse(result.isDogfoodingEnvironment());\n  }\n\n  @SonarLintTest\n  void should_return_false_when_env_var_is_false(SonarLintTestHarness harness) {\n    var backend = harness.newBackend().start();\n    environmentVariables.set(SONARSOURCE_DOGFOODING_ENV_VAR_KEY, \"0\");\n\n    var result = backend.getDogfoodingService().isDogfoodingEnvironment().join();\n\n    assertFalse(result.isDogfoodingEnvironment());\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/file/ClientFileExclusionsMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.file;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonar.scanner.protocol.Constants;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidOpenFileParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.GetFilesStatusParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.AnalysisUtils;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.verify;\nimport static utils.AnalysisUtils.getPublishedIssues;\n\nclass ClientFileExclusionsMediumTests {\n  private static final String CONFIG_SCOPE_ID = \"CONFIG_SCOPE_ID\";\n\n  @SonarLintTest\n  void it_should_not_analyze_excluded_file_on_open(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createXmlFile(baseDir);\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .withFileExclusions(CONFIG_SCOPE_ID, Set.of(\"**/*.xml\"))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n\n    await().pollDelay(1, TimeUnit.SECONDS).atMost(2, TimeUnit.SECONDS)\n      .untilAsserted(() -> verify(client).raiseIssues(eq(CONFIG_SCOPE_ID), any(), eq(false), any()));\n    assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).isEmpty();\n  }\n\n  @SonarLintTest\n  void it_should_analyze_not_excluded_file_on_open(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createXmlFile(baseDir);\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .withFileExclusions(CONFIG_SCOPE_ID, Set.of(\"**/*.java\"))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n\n    var publishedIssues = getPublishedIssues(client, CONFIG_SCOPE_ID);\n    assertThat(publishedIssues)\n      .containsOnlyKeys(fileUri)\n      .hasEntrySatisfying(fileUri, issues -> assertThat(issues)\n        .extracting(RaisedIssueDto::getPrimaryMessage)\n        .containsExactly(\"Replace \\\"pom.version\\\" with \\\"project.version\\\".\"));\n  }\n\n  @SonarLintTest\n  void it_should_not_analyze_non_user_defined_file_on_open(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createXmlFile(baseDir);\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, false)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n\n    await().pollDelay(1, TimeUnit.SECONDS).atMost(2, TimeUnit.SECONDS)\n      .untilAsserted(() -> verify(client).raiseIssues(eq(CONFIG_SCOPE_ID), any(), eq(false), any()));\n    assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).isEmpty();\n  }\n\n  @SonarLintTest\n  void it_should_analyze_user_defined_file_on_open(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createXmlFile(baseDir);\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n\n    var publishedIssues = getPublishedIssues(client, CONFIG_SCOPE_ID);\n    assertThat(publishedIssues)\n      .containsOnlyKeys(fileUri)\n      .hasEntrySatisfying(fileUri, issues -> assertThat(issues)\n        .extracting(RaisedIssueDto::getPrimaryMessage)\n        .containsExactly(\"Replace \\\"pom.version\\\" with \\\"project.version\\\".\"));\n  }\n\n  @SonarLintTest\n  void it_should_not_exclude_client_defined_file_exclusion_in_connected_mode(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var ideFilePath = \"Foo.java\";\n    var filePath = AnalysisUtils.createFile(baseDir, ideFilePath,\n      \"\"\"\n        // FIXME foo bar\n        public class Foo {\n        }\"\"\");\n    var projectKey = \"projectKey\";\n    var connectionId = \"connectionId\";\n    var branchName = \"main\";\n    var ruleKey = \"java:S1134\";\n    var message = \"Take the required action to fix the issue indicated by this comment.\";\n\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .withFileExclusions(CONFIG_SCOPE_ID, Set.of(\"**/*.java\"))\n      .build();\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\", branch -> branch\n        .withIssue(\"uuid\", \"java:S1134\", message, \"author\", ideFilePath, \"395d7a96efa8afd1b66ab6b680d0e637\", Constants.Severity.BLOCKER,\n          org.sonarsource.sonarlint.core.commons.RuleType.BUG,\n          \"OPEN\", null, Instant.ofEpochMilli(123456789L), new TextRange(2, 0, 2, 16))))\n      .withQualityProfile(\"qp\", qualityProfile -> qualityProfile.withLanguage(\"java\")\n        .withActiveRule(ruleKey, activeRule -> activeRule.withSeverity(IssueSeverity.MAJOR)))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(connectionId, server,\n        storage -> storage.withPlugin(TestPlugin.JAVA).withProject(projectKey,\n          project -> project.withRuleSet(\"java\", ruleSet -> ruleSet.withActiveRule(ruleKey, \"MINOR\"))\n            .withMainBranch(branchName)))\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    backend.getConfigurationService()\n      .didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(\n        new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, CONFIG_SCOPE_ID,\n          new BindingConfigurationDto(connectionId, projectKey, true)))));\n\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n\n    var publishedIssues = getPublishedIssues(client, CONFIG_SCOPE_ID);\n    assertThat(publishedIssues).isNotEmpty();\n  }\n\n  @SonarLintTest\n  void it_should_exclude_non_user_defined_files_in_connected_mode(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var ideFilePath = \"Foo.java\";\n    var filePath = AnalysisUtils.createFile(baseDir, ideFilePath,\n      \"\"\"\n        // FIXME foo bar\n        public class Foo {\n        }\"\"\");\n    var projectKey = \"projectKey\";\n    var connectionId = \"connectionId\";\n    var branchName = \"main\";\n    var ruleKey = \"java:S1134\";\n    var message = \"Take the required action to fix the issue indicated by this comment.\";\n\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, false)))\n      .build();\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\", branch -> branch\n        .withIssue(\"uuid\", \"java:S1134\", message, \"author\", ideFilePath, \"395d7a96efa8afd1b66ab6b680d0e637\", Constants.Severity.BLOCKER,\n          org.sonarsource.sonarlint.core.commons.RuleType.BUG,\n          \"OPEN\", null, Instant.ofEpochMilli(123456789L), new TextRange(2, 0, 2, 16))))\n      .withQualityProfile(\"qp\", qualityProfile -> qualityProfile.withLanguage(\"java\")\n        .withActiveRule(ruleKey, activeRule -> activeRule.withSeverity(IssueSeverity.MAJOR)))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(connectionId, server,\n        storage -> storage.withPlugin(TestPlugin.JAVA).withProject(projectKey,\n          project -> project.withRuleSet(\"java\", ruleSet -> ruleSet.withActiveRule(ruleKey, \"MINOR\"))\n            .withMainBranch(branchName)))\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    backend.getConfigurationService()\n      .didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(\n        new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, CONFIG_SCOPE_ID,\n          new BindingConfigurationDto(connectionId, projectKey, true)))));\n\n    backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n\n    await().pollDelay(1, TimeUnit.SECONDS).atMost(2, TimeUnit.SECONDS)\n      .untilAsserted(() -> verify(client).raiseIssues(eq(CONFIG_SCOPE_ID), any(), eq(false), any()));\n    assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).isEmpty();\n  }\n\n  @SonarLintTest\n  void it_should_include_client_exclusions_when_getting_file_status(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createXmlFile(baseDir);\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .withFileExclusions(CONFIG_SCOPE_ID, Set.of(\"**/*.xml\"))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n\n    var future = backend.getFileService().getFilesStatus(new GetFilesStatusParams(Map.of(CONFIG_SCOPE_ID, List.of(fileUri))));\n\n    assertThat(future).succeedsWithin(5, TimeUnit.SECONDS);\n    assertThat(future.join().getFileStatuses().entrySet())\n      .extracting(Map.Entry::getKey, e -> e.getValue().isExcluded())\n      .containsExactlyInAnyOrder(\n        tuple(fileUri, true));\n  }\n\n  private static Path createXmlFile(Path baseDir) {\n    return AnalysisUtils.createFile(baseDir, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/file/ConnectedFileExclusionsMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.file;\n\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.mockito.Mockito;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.FileStatusDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.GetFilesStatusParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Settings;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintBackendFixture;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport org.sonarsource.sonarlint.core.test.utils.server.ServerFixture;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.get;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.verify;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.PROJECT_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.test.utils.ProtobufUtils.protobufBody;\n\nclass ConnectedFileExclusionsMediumTests {\n\n  private static final String MYSONAR = \"mysonar\";\n  private static final String CONFIG_SCOPE_ID = \"myProject1\";\n  private static final String PROJECT_KEY = \"test-project-2\";\n\n  private String previousSyncPeriod;\n\n  @BeforeEach\n  void prepare() {\n    previousSyncPeriod = System.getProperty(\"sonarlint.internal.synchronization.scope.period\");\n    System.setProperty(\"sonarlint.internal.synchronization.scope.period\", \"1\");\n  }\n\n  @AfterEach\n  void stop() {\n    if (previousSyncPeriod != null) {\n      System.setProperty(\"sonarlint.internal.synchronization.scope.period\", previousSyncPeriod);\n    } else {\n      System.clearProperty(\"sonarlint.internal.synchronization.scope.period\");\n    }\n  }\n\n  @SonarLintTest\n  void fileInclusionsExclusions(SonarLintTestHarness harness, @TempDir Path tmp) throws InterruptedException {\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY)\n      .start();\n\n    var mainFile1 = tmp.resolve(\"foo.xoo\");\n    var mainFile1Dto = new ClientFileDto(mainFile1.toUri(), tmp.resolve(mainFile1), CONFIG_SCOPE_ID, false, StandardCharsets.UTF_8.name(), mainFile1, null, null, true);\n    var mainFile2 = tmp.resolve(\"src/foo2.xoo\");\n    var mainFile2Dto = new ClientFileDto(mainFile2.toUri(), tmp.resolve(mainFile2), CONFIG_SCOPE_ID, false, StandardCharsets.UTF_8.name(), mainFile2, null, null, true);\n    var testFile1 = tmp.resolve(\"fooTest.xoo\");\n    var testFile1Dto = new ClientFileDto(testFile1.toUri(), tmp.resolve(testFile1), CONFIG_SCOPE_ID, true, StandardCharsets.UTF_8.name(), testFile1, null, null, true);\n    var testFile2 = tmp.resolve(\"test/foo2Test.xoo\");\n    var testFile2Dto = new ClientFileDto(testFile2.toUri(), tmp.resolve(testFile2), CONFIG_SCOPE_ID, true, StandardCharsets.UTF_8.name(), testFile2, null, null, true);\n\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID,\n        List.of(mainFile1Dto, mainFile2Dto, testFile1Dto, testFile2Dto))\n      .build();\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(MYSONAR, server)\n      .withBoundConfigScope(CONFIG_SCOPE_ID, MYSONAR, PROJECT_KEY)\n      .withBackendCapability(FULL_SYNCHRONIZATION, PROJECT_SYNCHRONIZATION)\n      .start(fakeClient);\n\n    verify(fakeClient, timeout(5000).times(1)).didSynchronizeConfigurationScopes(Set.of(CONFIG_SCOPE_ID));\n\n    var future1 = backend.getFileService()\n      .getFilesStatus(new GetFilesStatusParams(Map.of(CONFIG_SCOPE_ID, List.of(mainFile1.toUri(), mainFile2.toUri(), testFile1.toUri(), testFile2.toUri()))));\n    assertThat(future1).succeedsWithin(5, TimeUnit.SECONDS);\n    assertThat(future1.join().getFileStatuses().entrySet())\n      .extracting(Map.Entry::getKey, e -> e.getValue().isExcluded())\n      .containsExactlyInAnyOrder(\n        tuple(mainFile1.toUri(), false),\n        tuple(mainFile2.toUri(), false),\n        tuple(testFile1.toUri(), false),\n        tuple(testFile2.toUri(), false));\n\n    mockSonarProjectSettings(server, Map.of(\"sonar.inclusions\", \"src/**\"));\n    forceSyncOfConfigScope(backend, fakeClient);\n\n    var future2 = backend.getFileService()\n      .getFilesStatus(new GetFilesStatusParams(Map.of(CONFIG_SCOPE_ID, List.of(mainFile1.toUri(), mainFile2.toUri(), testFile1.toUri(), testFile2.toUri()))));\n    await().untilAsserted(() -> assertThat(future2.join().getFileStatuses().entrySet())\n      .extracting(Map.Entry::getKey, e -> e.getValue().isExcluded())\n      .containsExactlyInAnyOrder(\n        tuple(mainFile1.toUri(), true),\n        tuple(mainFile2.toUri(), false),\n        tuple(testFile1.toUri(), false),\n        tuple(testFile2.toUri(), false)));\n\n    mockSonarProjectSettings(server, Map.of(\"sonar.inclusions\", \"file:**/src/**\"));\n    forceSyncOfConfigScope(backend, fakeClient);\n\n    var future3 = backend.getFileService()\n      .getFilesStatus(new GetFilesStatusParams(Map.of(CONFIG_SCOPE_ID, List.of(mainFile1.toUri(), mainFile2.toUri(), testFile1.toUri(), testFile2.toUri()))));\n    await().untilAsserted(() -> assertThat(future3.join().getFileStatuses().entrySet())\n      .extracting(Map.Entry::getKey, e -> e.getValue().isExcluded())\n      .containsExactlyInAnyOrder(\n        tuple(mainFile1.toUri(), true),\n        tuple(mainFile2.toUri(), false),\n        tuple(testFile1.toUri(), false),\n        tuple(testFile2.toUri(), false)));\n\n    mockSonarProjectSettings(server, Map.of(\"sonar.exclusions\", \"src/**\"));\n    forceSyncOfConfigScope(backend, fakeClient);\n\n    var future4 = backend.getFileService()\n      .getFilesStatus(new GetFilesStatusParams(Map.of(CONFIG_SCOPE_ID, List.of(mainFile1.toUri(), mainFile2.toUri(), testFile1.toUri(), testFile2.toUri()))));\n    await().untilAsserted(() -> assertThat(future4.join().getFileStatuses().entrySet())\n      .extracting(Map.Entry::getKey, e -> e.getValue().isExcluded())\n      .containsExactlyInAnyOrder(\n        tuple(mainFile1.toUri(), false),\n        tuple(mainFile2.toUri(), true),\n        tuple(testFile1.toUri(), false),\n        tuple(testFile2.toUri(), false)));\n\n    mockSonarProjectSettings(server, Map.of(\"sonar.test.inclusions\", \"test/**\"));\n    forceSyncOfConfigScope(backend, fakeClient);\n\n    var future5 = backend.getFileService()\n      .getFilesStatus(new GetFilesStatusParams(Map.of(CONFIG_SCOPE_ID, List.of(mainFile1.toUri(), mainFile2.toUri(), testFile1.toUri(), testFile2.toUri()))));\n    await().untilAsserted(() -> assertThat(future5.join().getFileStatuses().entrySet())\n      .extracting(Map.Entry::getKey, e -> e.getValue().isExcluded())\n      .containsExactlyInAnyOrder(\n        tuple(mainFile1.toUri(), false),\n        tuple(mainFile2.toUri(), false),\n        tuple(testFile1.toUri(), true),\n        tuple(testFile2.toUri(), false)));\n\n    mockSonarProjectSettings(server, Map.of(\"sonar.test.exclusions\", \"test/**\"));\n    forceSyncOfConfigScope(backend, fakeClient);\n\n    var future6 = backend.getFileService()\n      .getFilesStatus(new GetFilesStatusParams(Map.of(CONFIG_SCOPE_ID, List.of(mainFile1.toUri(), mainFile2.toUri(), testFile1.toUri(), testFile2.toUri()))));\n    await().untilAsserted(() -> assertThat(future6.join().getFileStatuses().entrySet())\n      .extracting(Map.Entry::getKey, e -> e.getValue().isExcluded())\n      .containsExactlyInAnyOrder(\n        tuple(mainFile1.toUri(), false),\n        tuple(mainFile2.toUri(), false),\n        tuple(testFile1.toUri(), false),\n        tuple(testFile2.toUri(), true)));\n\n    mockSonarProjectSettings(server, Map.of(\"sonar.inclusions\", \"file:**/src/**\", \"sonar.test.exclusions\", \"**/*Test.*\"));\n    forceSyncOfConfigScope(backend, fakeClient);\n\n    var future7 = backend.getFileService()\n      .getFilesStatus(new GetFilesStatusParams(Map.of(CONFIG_SCOPE_ID, List.of(mainFile1.toUri(), mainFile2.toUri(), testFile1.toUri(), testFile2.toUri()))));\n    await().untilAsserted(() -> assertThat(future7.join().getFileStatuses().entrySet())\n      .extracting(Map.Entry::getKey, e -> e.getValue().isExcluded())\n      .containsExactlyInAnyOrder(\n        tuple(mainFile1.toUri(), true),\n        tuple(mainFile2.toUri(), false),\n        tuple(testFile1.toUri(), true),\n        tuple(testFile2.toUri(), true)));\n  }\n\n  @SonarLintTest\n  void it_should_not_try_to_compute_exclusions_when_storage_is_empty(SonarLintTestHarness harness, @TempDir Path tmp) {\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY)\n      .start();\n\n    var mainFile1 = tmp.resolve(\"src/foo1.xoo\");\n    var mainFile1Dto = new ClientFileDto(mainFile1.toUri(), tmp.resolve(mainFile1), CONFIG_SCOPE_ID, false, StandardCharsets.UTF_8.name(), mainFile1, null, null, true);\n\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID,\n        List.of(mainFile1Dto))\n      .build();\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(MYSONAR, server)\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withBackendCapability(FULL_SYNCHRONIZATION, PROJECT_SYNCHRONIZATION)\n      .start(fakeClient);\n    backend.getConfigurationService().didUpdateBinding(new DidUpdateBindingParams(CONFIG_SCOPE_ID, new BindingConfigurationDto(MYSONAR, PROJECT_KEY, true)));\n\n    // get statuses while synchronization happens in background\n    var response = backend.getFileService().getFilesStatus(new GetFilesStatusParams(Map.of(CONFIG_SCOPE_ID, List.of((mainFile1.toUri()))))).join();\n\n    assertThat(response.getFileStatuses().values())\n      .extracting(FileStatusDto::isExcluded)\n      .containsOnly(false);\n  }\n\n  @SonarLintTest\n  void it_should_fallback_to_default_charset_if_encoding_is_unknown(SonarLintTestHarness harness, @TempDir Path tmp) throws InterruptedException {\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY)\n      .start();\n\n    var mainFile1 = tmp.resolve(\"src/foo1.xoo\");\n    var mainFile1Dto = new ClientFileDto(mainFile1.toUri(), tmp.resolve(mainFile1), CONFIG_SCOPE_ID, false, \"wrongCharset\", mainFile1, \"Toto\", null, true);\n\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, List.of(mainFile1Dto))\n      .build();\n    mockSonarProjectSettings(server, Map.of(\"sonar.exclusions\", \"src/**\"));\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(MYSONAR, server)\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withBackendCapability(FULL_SYNCHRONIZATION, PROJECT_SYNCHRONIZATION)\n      .start(fakeClient);\n    mockSonarProjectSettings(server, Map.of(\"sonar.exclusions\", \"src/**\"));\n    forceSyncOfConfigScope(backend, fakeClient);\n\n    var response = backend.getFileService().getFilesStatus(new GetFilesStatusParams(Map.of(CONFIG_SCOPE_ID, List.of((mainFile1.toUri()))))).join();\n\n    assertThat(response.getFileStatuses().values())\n      .extracting(FileStatusDto::isExcluded)\n      .containsOnly(true);\n  }\n\n  private void forceSyncOfConfigScope(SonarLintTestRpcServer backend, SonarLintBackendFixture.FakeSonarLintRpcClient fakeClient) throws InterruptedException {\n    Thread.sleep(100);\n    Mockito.clearInvocations(fakeClient);\n    backend.getConfigurationService().didUpdateBinding(new DidUpdateBindingParams(CONFIG_SCOPE_ID, new BindingConfigurationDto(null, null, true)));\n    backend.getConfigurationService().didUpdateBinding(new DidUpdateBindingParams(CONFIG_SCOPE_ID, new BindingConfigurationDto(MYSONAR, PROJECT_KEY, true)));\n    verify(fakeClient, timeout(5000).atLeastOnce()).didSynchronizeConfigurationScopes(Set.of(CONFIG_SCOPE_ID));\n  }\n\n  private void mockSonarProjectSettings(ServerFixture.Server server, Map<String, String> settings) {\n    var reponseBuilder = Settings.ValuesWsResponse.newBuilder();\n    settings.forEach((k, v) -> reponseBuilder.addSettings(Settings.Setting.newBuilder()\n      .setKey(k)\n      .setValue(v)));\n    server.getMockServer().stubFor(get(\"/api/settings/values.protobuf?component=\" + PROJECT_KEY)\n      .willReturn(aResponse().withResponseBody(protobufBody(reponseBuilder.build()))));\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/fixtures/LocalOnlyIssueFixtures.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.fixtures;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.UUID;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.LineWithHash;\nimport org.sonarsource.sonarlint.core.commons.LocalOnlyIssue;\nimport org.sonarsource.sonarlint.core.commons.LocalOnlyIssueResolution;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\n\npublic class LocalOnlyIssueFixtures {\n\n  public static LocalOnlyIssue aLocalOnlyIssueResolved() {\n    return aLocalOnlyIssueResolved(UUID.randomUUID());\n  }\n\n  public static LocalOnlyIssue aLocalOnlyIssueResolved(Instant resolutionDate) {\n    return aLocalOnlyIssueResolved(UUID.randomUUID(), resolutionDate);\n  }\n\n  public static LocalOnlyIssue aLocalOnlyIssueResolved(UUID id) {\n    return aLocalOnlyIssueResolved(id, Instant.now());\n  }\n\n  public static LocalOnlyIssue aLocalOnlyIssueResolved(UUID id, Instant resolutionDate) {\n    return new LocalOnlyIssue(\n      id,\n      Path.of(\"file/path\"),\n      new TextRangeWithHash(1, 2, 3, 4, \"ab12\"),\n      new LineWithHash(1, \"linehash\"),\n      \"ruleKey\",\n      \"message\",\n      new LocalOnlyIssueResolution(IssueStatus.WONT_FIX, resolutionDate.truncatedTo(ChronoUnit.MILLIS), \"comment\")\n    );\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/gessie/GessieIntegrationMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.gessie;\n\nimport com.github.tomakehurst.wiremock.junit5.WireMockExtension;\nimport com.github.tomakehurst.wiremock.matching.EqualToPattern;\nimport com.github.tomakehurst.wiremock.stubbing.Scenario;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.telemetry.gessie.GessieSpringConfig;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.anyRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;\nimport static com.github.tomakehurst.wiremock.client.WireMock.post;\nimport static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static org.awaitility.Awaitility.await;\n\n@ExtendWith(SystemStubsExtension.class)\nclass GessieIntegrationMediumTests {\n\n  private static final String IDE_ENDPOINT = \"/ide\";\n  private static final String FAILED_ONCE = \"Failed once\";\n\n  @RegisterExtension\n  static WireMockExtension gessieEndpointMock = WireMockExtension.newInstance()\n    .options(wireMockConfig().dynamicPort())\n    .build();\n\n  @BeforeAll\n  static void mockGessieEndpoint() {\n    gessieEndpointMock.stubFor(post(\"/ide\").willReturn(aResponse().withStatus(202)));\n  }\n\n  @BeforeEach\n  void setUp() {\n    System.setProperty(\"sonarlint.http.retry.interval.seconds\", \"0\");\n    System.setProperty(GessieSpringConfig.PROPERTY_GESSIE_API_KEY, \"value\");\n  }\n\n  @AfterEach\n  void tearDown() {\n    System.clearProperty(\"sonarlint.http.retry.interval.seconds\");\n    System.clearProperty(GessieSpringConfig.PROPERTY_GESSIE_API_KEY);\n  }\n\n  @SonarLintTest\n  void it_should_send_startup_event(SonarLintTestHarness harness) throws URISyntaxException, IOException {\n    harness.newBackend()\n      .withGessieTelemetryEnabled(gessieEndpointMock.baseUrl())\n      .start();\n\n    var fileContent = getTestJson(\"GessieRequest\");\n    await().untilAsserted(() -> gessieEndpointMock.verify(postRequestedFor(urlEqualTo(IDE_ENDPOINT))\n      .withHeader(\"x-api-key\", new EqualToPattern(\"value\"))\n      .withRequestBody(equalToJson(fileContent))));\n  }\n\n  @SonarLintTest\n  void it_should_not_send_anything_if_gessie_telemetry_is_disabled(SonarLintTestHarness harness) {\n    harness.newBackend()\n      .start();\n\n    await().untilAsserted(() -> gessieEndpointMock.verify(0, anyRequestedFor(urlEqualTo(IDE_ENDPOINT))));\n  }\n\n  @SonarLintTest\n  void it_should_retry_503_error(SonarLintTestHarness harness) throws URISyntaxException, IOException {\n    gessieEndpointMock.stubFor(post(\"/ide\")\n      .inScenario(\"Retry\")\n      .whenScenarioStateIs(Scenario.STARTED)\n      .willSetStateTo(FAILED_ONCE)\n      .willReturn(aResponse().withStatus(503)));\n    gessieEndpointMock.stubFor(post(\"/ide\")\n      .inScenario(\"Retry\")\n      .whenScenarioStateIs(FAILED_ONCE)\n      .willReturn(aResponse().withStatus(202)));\n\n    harness.newBackend()\n      .withGessieTelemetryEnabled(gessieEndpointMock.baseUrl())\n      .start();\n\n    var fileContent = getTestJson(\"GessieRequest\");\n    await().atMost(15, TimeUnit.SECONDS)\n      .untilAsserted(() -> gessieEndpointMock.verify(2, postRequestedFor(urlEqualTo(IDE_ENDPOINT))\n      .withHeader(\"x-api-key\", new EqualToPattern(\"value\"))\n      .withRequestBody(equalToJson(fileContent))));\n  }\n\n  @SonarLintTest\n  void it_should_retry_503_error_only_twice(SonarLintTestHarness harness) throws URISyntaxException, IOException {\n    gessieEndpointMock.stubFor(post(\"/ide\")\n      .willReturn(aResponse().withStatus(503)));\n\n    harness.newBackend()\n      .withGessieTelemetryEnabled(gessieEndpointMock.baseUrl())\n      .start();\n\n    var fileContent = getTestJson(\"GessieRequest\");\n    // Wait for timeframe enough for more than 2 retries.\n    await().timeout(5, TimeUnit.SECONDS)\n      .pollDelay(2, TimeUnit.SECONDS)\n      .untilAsserted(() -> gessieEndpointMock.verify(3, postRequestedFor(urlEqualTo(IDE_ENDPOINT))\n      .withHeader(\"x-api-key\", new EqualToPattern(\"value\"))\n      .withRequestBody(equalToJson(fileContent))));\n  }\n\n  private String getTestJson(String fileName) throws URISyntaxException, IOException {\n    var resource = Objects.requireNonNull(getClass().getResource(\"/response/gessie/GessieIntegrationMediumTests/\" + fileName + \".json\"))\n      .toURI();\n    return Files.readString(Path.of(resource));\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/hotspots/HotspotCheckStatusChangePermittedMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.hotspots;\n\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutionException;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.CheckStatusChangePermittedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.CheckStatusChangePermittedResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.HotspotStatus;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport org.sonarsource.sonarlint.core.test.utils.server.ServerFixture.AbstractServerBuilder.ServerProjectBuilder.HotspotBuilder;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass HotspotCheckStatusChangePermittedMediumTests {\n\n  @SonarLintTest\n  void it_should_fail_when_the_connection_is_unknown(SonarLintTestHarness harness) {\n    var backend = harness.newBackend().start();\n\n    var response = checkStatusChangePermitted(backend, \"connectionId\");\n\n    assertThat(response)\n      .failsWithin(Duration.ofSeconds(2))\n      .withThrowableOfType(ExecutionException.class)\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class)\n      .withMessage(\"Connection 'connectionId' is not valid\");\n  }\n\n  @SonarLintTest\n  void it_should_return_3_statuses_for_sonarcloud(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"orgKey\", organization -> organization\n        .withProject(\"projectKey\", project -> project\n          .withDefaultBranch(branch -> branch.withHotspot(\"hotspotKey\"))))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"orgKey\")\n      .start();\n\n    var response = checkStatusChangePermitted(backend, \"connectionId\");\n\n    assertThat(response)\n      .succeedsWithin(Duration.ofSeconds(2))\n      .extracting(CheckStatusChangePermittedResponse::isPermitted, CheckStatusChangePermittedResponse::getNotPermittedReason,\n        CheckStatusChangePermittedResponse::getAllowedStatuses)\n      .containsExactly(true, null, List.of(HotspotStatus.TO_REVIEW, HotspotStatus.FIXED, HotspotStatus.SAFE));\n  }\n\n  @SonarLintTest\n  void it_should_return_4_statuses_for_sonarqube(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer().withProject(\"projectKey\", project -> project.withDefaultBranch(branch -> branch.withHotspot(\"hotspotKey\"))).start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server)\n      .start();\n\n    var response = checkStatusChangePermitted(backend, \"connectionId\");\n\n    assertThat(response)\n      .succeedsWithin(Duration.ofSeconds(2))\n      .extracting(CheckStatusChangePermittedResponse::isPermitted, CheckStatusChangePermittedResponse::getNotPermittedReason,\n        CheckStatusChangePermittedResponse::getAllowedStatuses)\n      .containsExactly(true, null, List.of(HotspotStatus.TO_REVIEW, HotspotStatus.ACKNOWLEDGED, HotspotStatus.FIXED, HotspotStatus.SAFE));\n  }\n\n  @SonarLintTest\n  void it_should_not_be_changeable_when_permission_missing(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(\"projectKey\",\n        project -> project.withDefaultBranch(branch -> branch.withHotspot(\"hotspotKey\",\n          HotspotBuilder::withoutStatusChangePermission)))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server)\n      .start();\n\n    var response = checkStatusChangePermitted(backend, \"connectionId\");\n\n    assertThat(response)\n      .succeedsWithin(Duration.ofSeconds(2))\n      .extracting(CheckStatusChangePermittedResponse::isPermitted, CheckStatusChangePermittedResponse::getNotPermittedReason,\n        CheckStatusChangePermittedResponse::getAllowedStatuses)\n      .containsExactly(false, \"Changing a hotspot's status requires the 'Administer Security Hotspot' permission.\",\n        List.of(HotspotStatus.TO_REVIEW, HotspotStatus.ACKNOWLEDGED, HotspotStatus.FIXED, HotspotStatus.SAFE));\n  }\n\n  private CompletableFuture<CheckStatusChangePermittedResponse> checkStatusChangePermitted(SonarLintTestRpcServer backend, String connectionId) {\n    return backend.getHotspotService().checkStatusChangePermitted(new CheckStatusChangePermittedParams(connectionId, \"hotspotKey\"));\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/hotspots/HotspotEventsMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.hotspots;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Set;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.HotspotStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.serverapi.hotspot.ServerHotspot;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.when;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SECURITY_HOTSPOTS;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SERVER_SENT_EVENTS;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JAVA;\nimport static org.sonarsource.sonarlint.core.test.utils.storage.ServerSecurityHotspotFixture.aServerHotspot;\nimport static utils.AnalysisUtils.analyzeFileAndGetHotspots;\n\nclass HotspotEventsMediumTests {\n\n  @RegisterExtension\n  static SonarLintLogTester logTester = new SonarLintLogTester();\n  private static final String CONFIG_SCOPE_ID = \"CONFIG_SCOPE_ID\";\n\n  @Nested\n  class WhenReceivingSecurityHotspotRaisedEvent {\n    @SonarLintTest\n    void it_should_add_hotspot_in_storage(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server,\n          storage -> storage.withProject(\"projectKey\", project -> project.withMainBranch(\"branchName\")))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n\n      server.pushEvent(\"\"\"\n        event: SecurityHotspotRaised\n        data: {\\\n          \"status\": \"TO_REVIEW\",\\\n          \"vulnerabilityProbability\": \"MEDIUM\",\\\n          \"creationDate\": 1685006550000,\\\n          \"mainLocation\": {\\\n            \"filePath\": \"file/path\",\\\n            \"message\": \"Make sure that using this pseudorandom number generator is safe here.\",\\\n            \"textRange\": {\\\n              \"startLine\": 12,\\\n              \"startLineOffset\": 29,\\\n              \"endLine\": 12,\\\n              \"endLineOffset\": 36,\\\n              \"hash\": \"43b5c9175984c071f30b873fdce0a000\"\\\n            }\\\n          },\\\n          \"ruleKey\": \"java:S2245\",\\\n          \"key\": \"AYhSN6mVrRF_krvNbHl1\",\\\n          \"projectKey\": \"projectKey\",\\\n          \"branch\": \"branchName\"\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(20)).untilAsserted(() -> assertThat(readHotspots(backend, \"connectionId\", \"projectKey\", \"branchName\", \"file/path\"))\n        .extracting(ServerHotspot::getKey)\n        .containsOnly(\"AYhSN6mVrRF_krvNbHl1\"));\n    }\n\n    @SonarLintTest\n    void it_should_add_reviewed_hotspot_in_storage(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server,\n          storage -> storage.withProject(\"projectKey\", project -> project.withMainBranch(\"branchName\")))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n\n      server.pushEvent(\"\"\"\n        event: SecurityHotspotRaised\n        data: {\\\n          \"status\": \"REVIEWED\",\\\n          \"resolution\": \"ACKNOWLEDGED\",\\\n          \"vulnerabilityProbability\": \"MEDIUM\",\\\n          \"creationDate\": 1685006550000,\\\n          \"mainLocation\": {\\\n            \"filePath\": \"file/path\",\\\n            \"message\": \"Make sure that using this pseudorandom number generator is safe here.\",\\\n            \"textRange\": {\\\n              \"startLine\": 12,\\\n              \"startLineOffset\": 29,\\\n              \"endLine\": 12,\\\n              \"endLineOffset\": 36,\\\n              \"hash\": \"43b5c9175984c071f30b873fdce0a000\"\\\n            }\\\n          },\\\n          \"ruleKey\": \"java:S2245\",\\\n          \"key\": \"AYhSN6mVrRF_krvNbHl1\",\\\n          \"projectKey\": \"projectKey\",\\\n          \"branch\": \"branchName\"\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(20)).untilAsserted(() -> assertThat(readHotspots(backend, \"connectionId\", \"projectKey\", \"branchName\", \"file/path\"))\n        .extracting(ServerHotspot::getKey, ServerHotspot::getStatus)\n        .containsExactly(tuple(\"AYhSN6mVrRF_krvNbHl1\", HotspotReviewStatus.ACKNOWLEDGED)));\n    }\n  }\n\n  @Nested\n  class WhenReceivingSecurityHotspotClosedEvent {\n    @SonarLintTest\n    void it_should_remove_hotspot_from_storage(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server,\n          storage -> storage.withProject(\"projectKey\", project -> project.withMainBranch(\"branchName\", branch -> branch.withHotspot(aServerHotspot(\"hotspotKey\")))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n\n      server.pushEvent(\"\"\"\n        event: SecurityHotspotClosed\n        data: {\\\n            \"key\": \"hotspotKey\",\\\n            \"projectKey\": \"projectKey\",\\\n            \"filePath\": \"file/path\"\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(4)).untilAsserted(() -> assertThat(readHotspots(backend, \"connectionId\", \"projectKey\", \"branchName\", \"file/path\"))\n        .isEmpty());\n    }\n\n    @SonarLintTest\n    void should_republish_hotspots_without_closed_one(SonarLintTestHarness harness, @TempDir Path baseDir) {\n      var filePath = createFile(baseDir, \"Foo.java\",\n        \"\"\"\n          public class Foo {\n            void foo() throws Exception {\n              java.security.MessageDigest md1 = java.security.MessageDigest.getInstance(\"MD5\");\n              java.security.MessageDigest md2 = java.security.MessageDigest.getInstance(\"SHA1\");\n            }\n          }\n          \"\"\");\n      var fileUri = filePath.toUri();\n      var connectionId = \"connectionId\";\n      var branchName = \"branchName\";\n      var projectKey = \"projectKey\";\n      var serverHotspotKey1 = \"myHotspotKey1\";\n      var serverHotspotKey2 = \"myHotspotKey2\";\n      var client = harness.newFakeClient()\n        .withToken(connectionId, \"token\")\n        .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n        .build();\n      when(client.matchSonarProjectBranch(eq(CONFIG_SCOPE_ID), eq(\"main\"), eq(Set.of(\"main\", branchName)), any())).thenReturn(branchName);\n      var introductionDate = Instant.now().truncatedTo(ChronoUnit.SECONDS);\n      var serverWithHotspots = harness.newFakeSonarQubeServer(\"10.4\")\n        .withServerSentEventsEnabled()\n        .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile.withLanguage(\"java\").withActiveRule(\"java:S4790\", activeRule -> activeRule\n          .withSeverity(IssueSeverity.MAJOR)))\n        .withProject(projectKey,\n          project -> project\n            .withQualityProfile(\"qpKey\")\n            .withBranch(branchName,\n              branch -> branch.withHotspot(serverHotspotKey1, hotspot -> hotspot\n                  .withFilePath(baseDir.relativize(filePath).toString())\n                  .withStatus(HotspotReviewStatus.TO_REVIEW)\n                  .withVulnerabilityProbability(VulnerabilityProbability.HIGH)\n                  .withTextRange(new TextRange(3, 39, 3, 50))\n                  .withRuleKey(\"java:S4790\")\n                  .withMessage(\"Make sure this weak hash algorithm is not used in a sensitive context here.\")\n                  .withCreationDate(introductionDate)\n                  .withAuthor(\"author\"))\n                .withHotspot(serverHotspotKey2, hotspot -> hotspot\n                  .withFilePath(baseDir.relativize(filePath).toString())\n                  .withStatus(HotspotReviewStatus.TO_REVIEW)\n                  .withVulnerabilityProbability(VulnerabilityProbability.HIGH)\n                  .withTextRange(new TextRange(4, 39, 4, 51))\n                  .withRuleKey(\"java:S4790\")\n                  .withMessage(\"Make sure this weak hash algorithm is not used in a sensitive context here.\")\n                  .withCreationDate(introductionDate)\n                  .withAuthor(\"author\"))))\n        .withPlugin(TestPlugin.JAVA)\n        .start();\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SECURITY_HOTSPOTS, SERVER_SENT_EVENTS, FULL_SYNCHRONIZATION)\n        .withSonarQubeConnection(connectionId, serverWithHotspots)\n        .withBoundConfigScope(CONFIG_SCOPE_ID, connectionId, projectKey)\n        .start(client);\n      await().atMost(Duration.ofSeconds(20)).untilAsserted(() -> assertThat(client.getSynchronizedConfigScopeIds()).contains(CONFIG_SCOPE_ID));\n      analyzeFileAndGetHotspots(fileUri, client, backend, CONFIG_SCOPE_ID);\n      var raisedHotspots = client.getRaisedHotspotsForScopeId(CONFIG_SCOPE_ID).get(fileUri);\n      assertThat(raisedHotspots).hasSize(2);\n      client.cleanRaisedHotspots();\n\n      serverWithHotspots.pushEvent(\"\"\"\n        event: SecurityHotspotClosed\n        data: {\\\n            \"key\": \"myHotspotKey1\",\\\n            \"projectKey\": \"projectKey\",\\\n            \"filePath\": \"Foo.java\"\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(15)).untilAsserted(() -> assertThat(client.getRaisedHotspotsForScopeId(CONFIG_SCOPE_ID)).isNotEmpty());\n      raisedHotspots = client.getRaisedHotspotsForScopeId(CONFIG_SCOPE_ID).get(fileUri);\n\n      assertThat(raisedHotspots).hasSize(1);\n      var raisedHotspot = raisedHotspots.get(0);\n      assertThat(raisedHotspot.getServerKey()).isEqualTo(serverHotspotKey2);\n    }\n  }\n\n  @Nested\n  class WhenReceivingSecurityHotspotChangedEvent {\n\n    @SonarLintTest\n    void it_should_update_hotspot_in_storage_when_changing_status(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server,\n          storage -> storage.withProject(\"projectKey\",\n            project -> project.withMainBranch(\"branchName\", branch -> branch.withHotspot(aServerHotspot(\"AYhSN6mVrRF_krvNbHl1\").withStatus(HotspotReviewStatus.TO_REVIEW)))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n\n      server.pushEvent(\"\"\"\n        event: SecurityHotspotChanged\n        data: {\\\n          \"key\": \"AYhSN6mVrRF_krvNbHl1\",\\\n          \"projectKey\": \"projectKey\",\\\n          \"updateDate\": 1685007187000,\\\n          \"status\": \"REVIEWED\",\\\n          \"assignee\": \"assigneeEmail\",\\\n          \"resolution\": \"SAFE\",\\\n          \"filePath\": \"file/path\"\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(readHotspots(backend, \"connectionId\", \"projectKey\", \"branchName\", \"file/path\"))\n        .extracting(ServerHotspot::getKey, ServerHotspot::getStatus)\n        .containsOnly(tuple(\"AYhSN6mVrRF_krvNbHl1\", HotspotReviewStatus.SAFE)));\n    }\n\n    @SonarLintTest\n    void it_should_update_known_findings_store_when_changing_status(SonarLintTestHarness harness, @TempDir Path baseDir) {\n      // create a file with hotspot in it\n      var filePath = createFile(baseDir, \"Foo.java\", \"\"\"\n        public class Foo {\n          String ip = \"192.168.12.42\"; // Sensitive\n          Socket socket = new Socket(ip, 6667);\n        }\n        \"\"\");\n      var fileUri = filePath.toUri();\n\n      var connectionId = \"connectionId\";\n      var branchName = \"branchName\";\n      String projectKey = \"projectKey\";\n      var serverHotspotKey = \"myHotspotKey\";\n\n      var client = harness.newFakeClient()\n        .withToken(connectionId, \"token\")\n        .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n        .withMatchedBranch(CONFIG_SCOPE_ID, branchName)\n        .build();\n\n      var server = harness.newFakeSonarQubeServer(\"10.4\")\n        .withPlugin(TestPlugin.JAVA)\n        .withProject(projectKey,\n          project -> project\n            .withBranch(branchName))\n        .withServerSentEventsEnabled()\n        .start();\n\n      // initialize backend with pre-filled storage with matching hotspot and active rule\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS, SECURITY_HOTSPOTS)\n        .withSonarQubeConnection(connectionId, server,\n          storage -> storage.withProject(projectKey,\n              project -> project\n                .withMainBranch(branchName, branch -> branch.withHotspot(aServerHotspot(serverHotspotKey)\n                  .withStatus(HotspotReviewStatus.TO_REVIEW)\n                  .withFilePath(baseDir.relativize(filePath).toString())\n                  .withTextRange(new TextRangeWithHash(2, 14, 2, 29, \"c50a46d24d0975188e037e408583ad30\"))\n                  .withRuleKey(\"java:S1313\")\n                  .withMessage(\"Make sure using this hardcoded IP address is safe here.\")\n                  .withVulnerabilityProbability(VulnerabilityProbability.LOW)\n                ))\n                .withRuleSet(\"java\", ruleSetBuilder -> ruleSetBuilder.withActiveRule(\"java:S1313\", \"BLOCKER\"))\n            )\n            .withPlugin(TestPlugin.JAVA))\n        .withBoundConfigScope(CONFIG_SCOPE_ID, connectionId, projectKey)\n        .start(client);\n\n      // run analysis in order to populate known findings repository\n      analyzeFileAndGetHotspots(fileUri, client, backend, CONFIG_SCOPE_ID);\n\n      // trigger an SSE\n      server.pushEvent(String.format(\"\"\"\n        event: SecurityHotspotChanged\n        data: {\\\n          \"key\": \"%s\",\\\n          \"projectKey\": %s,\\\n          \"updateDate\": 1685007187000,\\\n          \"status\": \"REVIEWED\",\\\n          \"assignee\": \"assigneeEmail\",\\\n          \"resolution\": \"SAFE\",\\\n          \"filePath\": \"%s\"\\\n        }\n        \n        \"\"\", serverHotspotKey, projectKey, baseDir.relativize(filePath)));\n\n      // assert that the backend has updated the hotspot status\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(readHotspots(backend, connectionId, projectKey, branchName, baseDir.relativize(filePath).toString()))\n        .extracting(ServerHotspot::getKey, ServerHotspot::getStatus)\n        .containsOnly(tuple(serverHotspotKey, HotspotReviewStatus.SAFE)));\n\n      // assert that the client has updated the known findings store\n      await().atMost(Duration.ofMinutes(1)).untilAsserted(() -> assertThat(client.getRaisedHotspotsForScopeId(CONFIG_SCOPE_ID)).isNotEmpty());\n      var raisedHotspots = client.getRaisedHotspotsForScopeId(CONFIG_SCOPE_ID).get(fileUri);\n\n      assertThat(raisedHotspots).isNotEmpty();\n      var raisedIssueDto = raisedHotspots.get(0);\n      assertTrue(raisedIssueDto.isResolved());\n      assertThat(raisedIssueDto.getServerKey()).isEqualTo(serverHotspotKey);\n    }\n\n    @SonarLintTest\n    void it_should_update_hotspot_in_storage_when_changing_assignee(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server,\n          storage -> storage.withProject(\"projectKey\",\n            project -> project.withMainBranch(\"branchName\", branch -> branch.withHotspot(aServerHotspot(\"AYhSN6mVrRF_krvNbHl1\").withAssignee(\"previousAssignee\")))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n\n      server.pushEvent(\"\"\"\n        event: SecurityHotspotChanged\n        data: {\\\n          \"key\": \"AYhSN6mVrRF_krvNbHl1\",\\\n          \"projectKey\": \"projectKey\",\\\n          \"updateDate\": 1685007187000,\\\n          \"status\": \"REVIEWED\",\\\n          \"assignee\": \"assigneeEmail\",\\\n          \"resolution\": \"SAFE\",\\\n          \"filePath\": \"file/path\"\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(readHotspots(backend, \"connectionId\", \"projectKey\", \"branchName\", \"file/path\"))\n        .extracting(ServerHotspot::getKey, ServerHotspot::getAssignee)\n        .containsOnly(tuple(\"AYhSN6mVrRF_krvNbHl1\", \"assigneeEmail\")));\n    }\n\n    @SonarLintTest\n    void should_raise_hotspot_with_changed_data(SonarLintTestHarness harness, @TempDir Path baseDir) {\n      var filePath = createFile(baseDir, \"Foo.java\",\n        \"\"\"\n          public class Foo {\n            void foo() throws Exception {\n              java.security.MessageDigest md = java.security.MessageDigest.getInstance(\"MD5\");\n            }\n          }\n          \"\"\");\n      var fileUri = filePath.toUri();\n      var connectionId = \"connectionId\";\n      var branchName = \"branchName\";\n      var projectKey = \"projectKey\";\n      var serverHotspotKey = \"myHotspotKey\";\n      var client = harness.newFakeClient()\n        .withToken(connectionId, \"token\")\n        .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n        .build();\n      when(client.matchSonarProjectBranch(eq(CONFIG_SCOPE_ID), eq(\"main\"), eq(Set.of(\"main\", branchName)), any())).thenReturn(branchName);\n      var introductionDate = Instant.now().truncatedTo(ChronoUnit.SECONDS);\n      var serverWithHotspots = harness.newFakeSonarQubeServer(\"10.4\")\n        .withServerSentEventsEnabled()\n        .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile.withLanguage(\"java\").withActiveRule(\"java:S4790\", activeRule -> activeRule\n          .withSeverity(IssueSeverity.MAJOR)))\n        .withProject(projectKey,\n          project -> project\n            .withQualityProfile(\"qpKey\")\n            .withBranch(branchName,\n              branch -> branch.withHotspot(serverHotspotKey, hotspot -> hotspot\n                .withFilePath(baseDir.relativize(filePath).toString())\n                .withStatus(HotspotReviewStatus.TO_REVIEW)\n                .withVulnerabilityProbability(VulnerabilityProbability.HIGH)\n                .withTextRange(new TextRange(3, 37, 3, 48))\n                .withRuleKey(\"java:S4790\")\n                .withMessage(\"Make sure this weak hash algorithm is not used in a sensitive context here.\")\n                .withCreationDate(introductionDate)\n                .withAuthor(\"author\"))))\n        .withPlugin(TestPlugin.JAVA)\n        .start();\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SECURITY_HOTSPOTS, SERVER_SENT_EVENTS, FULL_SYNCHRONIZATION)\n        .withSonarQubeConnection(connectionId, serverWithHotspots)\n        .withBoundConfigScope(CONFIG_SCOPE_ID, connectionId, projectKey)\n        .start(client);\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(client.getSynchronizedConfigScopeIds()).contains(CONFIG_SCOPE_ID));\n      analyzeFileAndGetHotspots(fileUri, client, backend, CONFIG_SCOPE_ID);\n      client.cleanRaisedHotspots();\n\n      serverWithHotspots.pushEvent(\"event: SecurityHotspotChanged\\n\" +\n        \"data: {\" +\n        \"  \\\"key\\\": \\\"myHotspotKey\\\",\" +\n        \"  \\\"projectKey\\\": \\\"projectKey\\\",\" +\n        \"  \\\"updateDate\\\": 1685007187000,\" +\n        \"  \\\"status\\\": \\\"REVIEWED\\\",\" +\n        \"  \\\"assignee\\\": \\\"assigneeEmail\\\",\" +\n        \"  \\\"resolution\\\": \\\"SAFE\\\",\" +\n        \"  \\\"filePath\\\": \\\"\" + baseDir.relativize(filePath) + \"\\\"\" +\n        \"}\\n\\n\");\n\n      await().atMost(Duration.ofSeconds(20)).untilAsserted(() -> assertThat(client.getRaisedHotspotsForScopeIdAsList(CONFIG_SCOPE_ID)).isNotEmpty());\n      var raisedHotspots = client.getRaisedHotspotsForScopeId(CONFIG_SCOPE_ID).get(fileUri);\n\n      assertThat(raisedHotspots).hasSize(1);\n      var raisedHotspot = raisedHotspots.get(0);\n      assertThat(raisedHotspot.getServerKey()).isEqualTo(serverHotspotKey);\n      assertThat(raisedHotspot.getStatus()).isEqualTo(HotspotStatus.SAFE);\n    }\n  }\n\n  private static Path createFile(Path folderPath, String fileName, String content) {\n    var filePath = folderPath.resolve(fileName);\n    try {\n      Files.writeString(filePath, content);\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n    return filePath;\n  }\n\n  private Collection<ServerHotspot> readHotspots(SonarLintTestRpcServer backend, String connectionId, String projectKey, String branchName, String filePath) {\n    return backend.getIssueStorageService().connection(connectionId).project(projectKey).findings().loadHotspots(branchName, Path.of(filePath));\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/hotspots/HotspotLocalDetectionSupportMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.hotspots;\n\nimport java.time.Duration;\nimport java.util.concurrent.ExecutionException;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.CheckLocalDetectionSupportedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.CheckLocalDetectionSupportedResponse;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass HotspotLocalDetectionSupportMediumTests {\n\n  @SonarLintTest\n  void it_should_fail_when_the_configuration_scope_id_is_unknown(SonarLintTestHarness harness) {\n    var backend = harness.newBackend().start();\n\n    var future = backend.getHotspotService().checkLocalDetectionSupported(new CheckLocalDetectionSupportedParams(\"configScopeId\"));\n\n    assertThat(future)\n      .failsWithin(Duration.ofSeconds(2))\n      .withThrowableOfType(ExecutionException.class)\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class)\n      .withMessage(\"The provided configuration scope does not exist: configScopeId\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_when_the_configuration_scope_is_bound_to_an_unknown_connection(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .start();\n\n    var future = backend.getHotspotService().checkLocalDetectionSupported(new CheckLocalDetectionSupportedParams(\"configScopeId\"));\n\n    assertThat(future)\n      .failsWithin(Duration.ofSeconds(2))\n      .withThrowableOfType(ExecutionException.class)\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class)\n      .withMessage(\"The provided configuration scope is bound to an unknown connection: connectionId\");\n  }\n\n  @SonarLintTest\n  void it_should_not_support_local_detection_in_standalone_mode(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(\"configScopeId\")\n      .start();\n\n    var checkResponse = checkLocalDetectionSupported(backend, \"configScopeId\");\n\n    assertThat(checkResponse)\n      .extracting(CheckLocalDetectionSupportedResponse::isSupported, CheckLocalDetectionSupportedResponse::getReason)\n      .containsExactly(false, \"The project is not bound, please bind it to SonarQube (Server, Cloud)\");\n  }\n\n  @SonarLintTest\n  void it_should_support_local_detection_when_connected_to_sonarcloud(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarCloudConnection(\"connectionId\", \"orgKey\")\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .start();\n\n    var checkResponse = checkLocalDetectionSupported(backend, \"configScopeId\");\n\n    assertThat(checkResponse)\n      .extracting(CheckLocalDetectionSupportedResponse::isSupported, CheckLocalDetectionSupportedResponse::getReason)\n      .containsExactly(true, null);\n  }\n\n  @SonarLintTest\n  void it_should_support_local_detection_when_connected_to_sonarqube(SonarLintTestHarness harness) {\n    var configScopeId = \"configScopeId\";\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", storage -> storage.withServerVersion(\"9.9\")\n        .withProject(\"projectKey\"))\n      .withBoundConfigScope(configScopeId, \"connectionId\", \"projectKey\")\n      .start();\n\n    var checkResponse = checkLocalDetectionSupported(backend, configScopeId);\n\n    assertThat(checkResponse)\n      .extracting(CheckLocalDetectionSupportedResponse::isSupported, CheckLocalDetectionSupportedResponse::getReason)\n      .containsExactly(true, null);\n  }\n\n  private CheckLocalDetectionSupportedResponse checkLocalDetectionSupported(SonarLintTestRpcServer backend, String configScopeId) {\n    try {\n      return backend.getHotspotService().checkLocalDetectionSupported(new CheckLocalDetectionSupportedParams(configScopeId)).get();\n    } catch (InterruptedException | ExecutionException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/hotspots/HotspotStatusChangeMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.hotspots;\n\nimport com.github.tomakehurst.wiremock.client.WireMock;\nimport java.time.Duration;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutionException;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.junit.jupiter.api.Disabled;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.ChangeHotspotStatusParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.HotspotStatus;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.equalTo;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.waitAtMost;\nimport static org.sonarsource.sonarlint.core.test.utils.server.ServerFixture.ServerStatus.DOWN;\n\nclass HotspotStatusChangeMediumTests {\n\n  @SonarLintTest\n  void it_should_fail_the_future_when_the_server_returns_an_error(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer().withStatus(DOWN).start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .start();\n\n    var response = setStatusToSafe(backend, \"configScopeId\", \"hotspotKey\");\n\n    assertThat(response)\n      .failsWithin(Duration.ofSeconds(2))\n      .withThrowableOfType(ExecutionException.class)\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class);\n  }\n\n  @SonarLintTest\n  void it_should_do_nothing_when_the_configuration_scope_is_unknown(SonarLintTestHarness harness) {\n    var backend = harness.newBackend().start();\n\n    var response = setStatusToSafe(backend, \"configScopeId\", \"hotspotKey\");\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n  }\n\n  @SonarLintTest\n  void it_should_do_nothing_when_the_configuration_scope_bound_connection_is_unknown(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .start();\n\n    var response = setStatusToSafe(backend, \"configScopeId\", \"hotspotKey\");\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n  }\n\n  @SonarLintTest\n  void it_should_update_the_status_on_sonarcloud_through_the_web_api(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarCloudServer().start();\n\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"orgKey\")\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .start();\n\n    var response = setStatusToSafe(backend, \"configScopeId\", \"hotspotKey\");\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    server.getMockServer()\n      .verify(WireMock.postRequestedFor(urlEqualTo(\"/api/hotspots/change_status\"))\n        .withHeader(\"Content-Type\", equalTo(\"application/x-www-form-urlencoded\"))\n        .withRequestBody(equalTo(\"hotspot=hotspotKey&status=REVIEWED&resolution=SAFE\")));\n  }\n\n  @SonarLintTest\n  void it_should_update_the_status_on_sonarqube_through_the_web_api(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer().start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .start();\n\n    var response = setStatusToSafe(backend, \"configScopeId\", \"hotspotKey\");\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    waitAtMost(2, SECONDS).untilAsserted(() -> server.getMockServer()\n      .verify(WireMock.postRequestedFor(urlEqualTo(\"/api/hotspots/change_status\"))\n        .withHeader(\"Content-Type\", equalTo(\"application/x-www-form-urlencoded\"))\n        .withRequestBody(equalTo(\"hotspot=hotspotKey&status=REVIEWED&resolution=SAFE\"))));\n  }\n\n  @SonarLintTest\n  @Disabled(\"TODO\")\n  void it_should_update_the_hotspot_status_in_the_storage(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer().start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .start();\n\n    var response = setStatusToSafe(backend, \"configScopeId\", \"hotspotKey\");\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    server.getMockServer()\n      .verify(WireMock.postRequestedFor(urlEqualTo(\"/api/hotspots/change_status\"))\n        .withHeader(\"Content-Type\", equalTo(\"application/x-www-form-urlencoded\"))\n        .withRequestBody(equalTo(\"hotspot=hotspotKey&status=REVIEWED&resolution=SAFE\")));\n  }\n\n  @SonarLintTest\n  void it_should_count_status_change_in_telemetry(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer().start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withTelemetryEnabled()\n      .start();\n\n    var response = setStatusToSafe(backend, \"configScopeId\", \"hotspotKey\");\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    assertThat(backend.telemetryFileContent().hotspotStatusChangedCount()).isEqualTo(1);\n  }\n\n  private CompletableFuture<Void> setStatusToSafe(SonarLintTestRpcServer backend, String configScopeId, String hotspotKey) {\n    return backend.getHotspotService().changeStatus(new ChangeHotspotStatusParams(configScopeId, hotspotKey, HotspotStatus.SAFE));\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/hotspots/OpenHotspotInBrowserMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.hotspots;\n\nimport java.net.MalformedURLException;\nimport java.net.URI;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.OpenHotspotInBrowserParams;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.verify;\n\nclass OpenHotspotInBrowserMediumTests {\n\n  @SonarLintTest\n  void it_should_open_hotspot_in_sonarqube(SonarLintTestHarness harness) throws MalformedURLException {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", \"http://localhost:12345\", storage -> storage.withProject(\"projectKey\", project -> project.withMainBranch(\"master\")))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withTelemetryEnabled()\n      .start(fakeClient);\n\n    backend.getHotspotService().openHotspotInBrowser(new OpenHotspotInBrowserParams(\"scopeId\", \"ab12ef45\"));\n\n    verify(fakeClient, timeout(5000)).openUrlInBrowser(URI.create(\"http://localhost:12345/security_hotspots?id=projectKey&branch=master&hotspots=ab12ef45\").toURL());\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().openHotspotInBrowserCount()).isEqualTo(1));\n  }\n\n  @SonarLintTest\n  void it_should_not_open_hotspot_if_unbound(SonarLintTestHarness harness) throws InterruptedException {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(\"scopeId\")\n      .start(fakeClient);\n\n    backend.getHotspotService().openHotspotInBrowser(new OpenHotspotInBrowserParams(\"scopeId\", \"ab12ef45\"));\n\n    Thread.sleep(100);\n\n    verify(fakeClient, never()).openUrlInBrowser(any());\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/hotspots/OpenHotspotInIdeMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.hotspots;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.stubbing.Answer;\nimport org.sonarsource.sonarlint.core.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidUpdateConnectionsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarQubeConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.AssistBindingResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.HotspotDetailsDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.MessageType;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintBackendFixture;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport org.sonarsource.sonarlint.core.test.utils.server.ServerFixture;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.verify;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.EMBEDDED_SERVER;\nimport static org.sonarsource.sonarlint.core.serverapi.UrlUtils.urlEncode;\n\nclass OpenHotspotInIdeMediumTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n  public static final String CONNECTION_ID = \"connectionId\";\n  public static final String SCOPE_ID = \"scopeId\";\n  public static final String PROJECT_KEY = \"projectKey\";\n  public static final String SONAR_PROJECT_NAME = \"Project Name\";\n\n  @SonarLintTest\n  void it_should_fail_request_when_server_parameter_missing(SonarLintTestHarness harness) {\n    var serverWithoutHotspot = harness.newFakeSonarQubeServer(\"1.2.3\").start();\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start();\n\n    var statusCode = requestGetOpenHotspotWithParams(backend, serverWithoutHotspot, \"project=projectKey&hotspot=key\");\n\n    assertThat(statusCode).isEqualTo(400);\n  }\n\n  @SonarLintTest\n  void it_should_fail_request_when_project_parameter_missing(SonarLintTestHarness harness) {\n    var serverWithoutHotspot = harness.newFakeSonarQubeServer(\"1.2.3\").start();\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start();\n\n    var statusCode = requestGetOpenHotspotWithParams(backend, serverWithoutHotspot, \"server=URL&hotspot=key\");\n\n    assertThat(statusCode).isEqualTo(400);\n  }\n\n  @SonarLintTest\n  void it_should_fail_request_when_hotspot_parameter_missing(SonarLintTestHarness harness) {\n    var serverWithoutHotspot = harness.newFakeSonarQubeServer(\"1.2.3\").start();\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start();\n\n    var statusCode = requestGetOpenHotspotWithParams(backend, serverWithoutHotspot, \"server=URL&project=projectKey\");\n\n    assertThat(statusCode).isEqualTo(400);\n  }\n\n  @SonarLintTest\n  void it_should_open_hotspot_in_ide_when_project_bound(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    var serverWithHotspot = buildServerWithHotspot(harness).start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, serverWithHotspot)\n      .withBoundConfigScope(SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start(fakeClient);\n\n    var statusCode = requestGetOpenHotspotWithParams(backend, serverWithHotspot, \"server=\" + urlEncode(serverWithHotspot.baseUrl()) + \"&project=projectKey&hotspot=key\");\n    assertThat(statusCode).isEqualTo(200);\n\n    ArgumentCaptor<HotspotDetailsDto> captor = ArgumentCaptor.captor();\n    verify(fakeClient, timeout(1000)).showHotspot(eq(SCOPE_ID), captor.capture());\n    verify(fakeClient, never()).showMessage(any(), any());\n\n    assertThat(captor.getAllValues())\n      .extracting(HotspotDetailsDto::getKey, HotspotDetailsDto::getMessage, HotspotDetailsDto::getAuthor, HotspotDetailsDto::getIdeFilePath,\n        HotspotDetailsDto::getStatus, HotspotDetailsDto::getResolution, HotspotDetailsDto::getCodeSnippet)\n      .containsExactly(tuple(\"key\", \"msg\", \"author\", Path.of(\"file/path\"), \"REVIEWED\", \"SAFE\", \"source\\ncode\\nfile\"));\n  }\n\n  @SonarLintTest\n  void it_should_update_telemetry_data_when_opening_hotspot_in_ide(SonarLintTestHarness harness) {\n    var serverWithHotspot = buildServerWithHotspot(harness).start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, serverWithHotspot)\n      .withBoundConfigScope(SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .withBackendCapability(EMBEDDED_SERVER)\n      .withTelemetryEnabled()\n      .start();\n\n    requestGetOpenHotspotWithParams(backend, serverWithHotspot, \"server=\" + urlEncode(serverWithHotspot.baseUrl()) + \"&project=projectKey&hotspot=key\");\n\n    await().atMost(2, TimeUnit.SECONDS)\n      .untilAsserted(() -> assertThat(backend.telemetryFileContent().showHotspotRequestsCount()).isEqualTo(1));\n  }\n\n  @SonarLintTest\n  void it_should_assist_creating_the_connection_when_server_url_unknown(SonarLintTestHarness harness) {\n    var serverWithHotspot = buildServerWithHotspot(harness).start();\n    var fakeClient = harness.newFakeClient().build();\n\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(SCOPE_ID, SONAR_PROJECT_NAME)\n      .withBackendCapability(EMBEDDED_SERVER)\n      .beforeInitialize(createdBackend -> {\n        mockAssistCreatingConnection(createdBackend, fakeClient, serverWithHotspot, CONNECTION_ID);\n        mockAssistBinding(createdBackend, fakeClient, SCOPE_ID, CONNECTION_ID, PROJECT_KEY);\n      })\n      .start(fakeClient);\n\n    var statusCode = requestGetOpenHotspotWithParams(backend, serverWithHotspot, \"server=\" + urlEncode(serverWithHotspot.baseUrl()) + \"&project=projectKey&hotspot=key\");\n    assertThat(statusCode).isEqualTo(200);\n\n    ArgumentCaptor<HotspotDetailsDto> captor = ArgumentCaptor.captor();\n    verify(fakeClient, timeout(1000)).showHotspot(eq(SCOPE_ID), captor.capture());\n    verify(fakeClient, never()).showMessage(any(), any());\n\n    assertThat(captor.getAllValues())\n      .extracting(HotspotDetailsDto::getMessage)\n      .containsExactly(\"msg\");\n  }\n\n  @SonarLintTest\n  void it_should_assist_creating_the_binding_if_scope_not_bound(SonarLintTestHarness harness) {\n    var serverWithHotspot = buildServerWithHotspot(harness).start();\n    var fakeClient = harness.newFakeClient().build();\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, serverWithHotspot)\n      .withUnboundConfigScope(SCOPE_ID, SONAR_PROJECT_NAME)\n      .withBackendCapability(EMBEDDED_SERVER)\n      .beforeInitialize(createdBackend -> {\n        mockAssistCreatingConnection(createdBackend, fakeClient, serverWithHotspot, CONNECTION_ID);\n        mockAssistBinding(createdBackend, fakeClient, SCOPE_ID, CONNECTION_ID, PROJECT_KEY);\n      })\n      .start(fakeClient);\n\n    var statusCode = requestGetOpenHotspotWithParams(backend, serverWithHotspot, \"server=\" + urlEncode(serverWithHotspot.baseUrl()) + \"&project=projectKey&hotspot=key\");\n    assertThat(statusCode).isEqualTo(200);\n\n    ArgumentCaptor<HotspotDetailsDto> captor = ArgumentCaptor.captor();\n    verify(fakeClient, timeout(1000)).showHotspot(eq(SCOPE_ID), captor.capture());\n    verify(fakeClient, never()).showMessage(any(), any());\n\n    assertThat(captor.getAllValues())\n      .extracting(HotspotDetailsDto::getMessage)\n      .containsExactly(\"msg\");\n  }\n\n  @SonarLintTest\n  void it_should_display_a_message_when_failing_to_fetch_the_hotspot(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    var serverWithoutHotspot = harness.newFakeSonarQubeServer(\"1.2.3\").start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, serverWithoutHotspot)\n      .withBoundConfigScope(SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start(fakeClient);\n\n    var statusCode = requestGetOpenHotspotWithParams(backend, \"server=\" + urlEncode(serverWithoutHotspot.baseUrl()) + \"&project=projectKey&hotspot=key\",\n      serverWithoutHotspot.baseUrl());\n    assertThat(statusCode).isEqualTo(200);\n\n    verify(fakeClient, timeout(2000)).showMessage(MessageType.ERROR, \"Could not show the hotspot. See logs for more details\");\n    verify(fakeClient, never()).showHotspot(anyString(), any());\n  }\n\n  @SonarLintTest\n  void it_should_not_accept_post_method(SonarLintTestHarness harness) {\n    var serverWithoutHotspot = harness.newFakeSonarQubeServer(\"1.2.3\").start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, serverWithoutHotspot)\n      .withBoundConfigScope(SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start();\n\n    var statusCode = requestPostOpenHotspotWithParams(backend, serverWithoutHotspot, \"server=\" + urlEncode(serverWithoutHotspot.baseUrl()) + \"&project=projectKey&hotspot=key\");\n\n    assertThat(statusCode).isEqualTo(400);\n  }\n\n  @SonarLintTest\n  void it_should_fail_request_when_server_points_to_sonarcloud(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start(client);\n\n    var statusCode = requestGetOpenHotspotWithParams(backend, \"server=\"\n        + urlEncode(SonarCloudRegion.EU.getProductionUri().toString()),\n      \"someOrigin\");\n\n    assertThat(statusCode).isEqualTo(400);\n    verify(client).showMessage(MessageType.ERROR,\n      \"Invalid request to SonarQube backend. The 'server' parameter should not be SonarQube Cloud URL, use it only to specify URL of a SonarQube Server.\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_request_when_server_points_to_sonarcloud_us(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start(client);\n\n    var statusCode = requestGetOpenHotspotWithParams(backend, \"server=\"\n        + urlEncode(SonarCloudRegion.US.getProductionUri().toString()),\n      \"someOrigin\");\n\n    assertThat(statusCode).isEqualTo(400);\n    verify(client, timeout(500)).showMessage(MessageType.ERROR,\n      \"Invalid request to SonarQube backend. The 'server' parameter should not be SonarQube Cloud URL, use it only to specify URL of a SonarQube Server.\");\n  }\n\n  private int requestGetOpenHotspotWithParams(SonarLintTestRpcServer backend, String query, String baseUrl) {\n    return requestOpenHotspotWithParams(backend, query, \"GET\", baseUrl, HttpRequest.BodyPublishers.noBody());\n  }\n\n  private int requestGetOpenHotspotWithParams(SonarLintTestRpcServer backend, ServerFixture.Server server, String query) {\n    return requestOpenHotspotWithParams(backend, query, \"GET\", server.baseUrl(), HttpRequest.BodyPublishers.noBody());\n  }\n\n  private int requestPostOpenHotspotWithParams(SonarLintTestRpcServer backend, ServerFixture.Server server, String query) {\n    return requestOpenHotspotWithParams(backend, query, \"POST\", server.baseUrl(), HttpRequest.BodyPublishers.ofString(\"\"));\n  }\n\n  private int requestOpenHotspotWithParams(SonarLintTestRpcServer backend, String query, String method, String baseUrl, HttpRequest.BodyPublisher bodyPublisher) {\n    var request = HttpRequest.newBuilder()\n      .uri(URI.create(\"http://localhost:\" + backend.getEmbeddedServerPort() + \"/sonarlint/api/hotspots/show?\" + query))\n      .header(\"Origin\", baseUrl)\n      .method(method, bodyPublisher)\n      .build();\n    HttpResponse<String> response;\n    try {\n      response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n    } catch (IOException | InterruptedException e) {\n      throw new RuntimeException(e);\n    }\n    return response.statusCode();\n  }\n\n  private void mockAssistBinding(SonarLintTestRpcServer backend, SonarLintBackendFixture.FakeSonarLintRpcClient fakeClient, String configScopeId, String connectionId,\n    String sonarProjectKey) {\n    doAnswer((Answer<AssistBindingResponse>) invocation -> {\n      backend.getConfigurationService().didUpdateBinding(new DidUpdateBindingParams(configScopeId, new BindingConfigurationDto(connectionId, sonarProjectKey, false)));\n      return new AssistBindingResponse(configScopeId);\n    }).when(fakeClient).assistBinding(any(), any());\n  }\n\n  private void mockAssistCreatingConnection(SonarLintTestRpcServer backend, SonarLintBackendFixture.FakeSonarLintRpcClient fakeClient, ServerFixture.Server serverWithHotspot,\n    String connectionId) {\n    doAnswer((Answer<AssistCreatingConnectionResponse>) invocation -> {\n      backend.getConnectionService().didUpdateConnections(\n        new DidUpdateConnectionsParams(List.of(new SonarQubeConnectionConfigurationDto(connectionId, serverWithHotspot.baseUrl(), true)), Collections.emptyList()));\n      return new AssistCreatingConnectionResponse(connectionId);\n    }).when(fakeClient).assistCreatingConnection(any(), any());\n  }\n\n  private static ServerFixture.AbstractServerBuilder buildServerWithHotspot(SonarLintTestHarness harness) {\n    return harness.newFakeSonarQubeServer(\"1.2.3\")\n      .withProject(PROJECT_KEY,\n        project -> project.withProjectName(SONAR_PROJECT_NAME).withDefaultBranch(branch -> branch.withHotspot(\"key\",\n          hotspot -> hotspot.withRuleKey(\"ruleKey\")\n            .withMessage(\"msg\")\n            .withAuthor(\"author\")\n            .withFilePath(\"file/path\")\n            .withStatus(HotspotReviewStatus.SAFE)\n            .withTextRange(new TextRange(1, 0, 3, 4)))\n          .withSourceFile(\"projectKey:file/path\", sourceFile -> sourceFile.withCode(\"source\\ncode\\nfile\"))));\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/http/AuthenticationMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.http;\n\nimport com.github.tomakehurst.wiremock.junit5.WireMockExtension;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\nimport java.util.Map;\nimport java.util.concurrent.CompletionException;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.GetEffectiveRuleDetailsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Rules;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.equalTo;\nimport static com.github.tomakehurst.wiremock.client.WireMock.get;\nimport static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\nimport static org.mockito.Mockito.when;\nimport static org.sonarsource.sonarlint.core.test.utils.ProtobufUtils.protobufBody;\n\nclass AuthenticationMediumTests {\n\n  @RegisterExtension\n  static WireMockExtension sonarqubeMock = WireMockExtension.newInstance()\n    .options(wireMockConfig().dynamicPort())\n    .build();\n\n  @SonarLintTest\n  void it_should_authenticate_preemptively_on_sonarqube_with_login_password(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient()\n      .withCredentials(\"connectionId\", \"myLogin\", \"myPassword\")\n      .build();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", sonarqubeMock.baseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(SonarLanguage.PYTHON.getSonarLanguageKey(),\n          ruleSet -> ruleSet.withActiveRule(\"python:S139\", \"INFO\", Map.of(\"legalTrailingCommentPattern\", \"blah\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(fakeClient);\n    sonarqubeMock.stubFor(get(\"/api/system/status\")\n      .willReturn(aResponse().withStatus(200).withBody(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"10.8\\\",\\\"status\\\": \" +\n        \"\\\"UP\\\"}\")));\n    sonarqubeMock.stubFor(get(\"/api/rules/show.protobuf?key=python:S139\")\n      .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(Rules.ShowResponse.newBuilder()\n        .setRule(Rules.Rule.newBuilder().setName(\"newName\").setSeverity(\"INFO\").setType(Common.RuleType.BUG).setLang(\"py\").setHtmlNote(\"extendedDesc from server\").build())\n        .build()))));\n\n    getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\");\n\n    sonarqubeMock.verify(getRequestedFor(urlEqualTo(\"/api/rules/show.protobuf?key=python:S139\"))\n      .withHeader(\"Authorization\", equalTo(\"Basic \" + Base64.getEncoder().encodeToString(\"myLogin:myPassword\".getBytes(StandardCharsets.UTF_8)))));\n  }\n\n  @SonarLintTest\n  void it_should_authenticate_preemptively_on_sonarqube_9_9_with_token_and_basic_scheme(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient()\n      .withToken(\"connectionId\", \"myToken\")\n      .build();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", sonarqubeMock.baseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(SonarLanguage.PYTHON.getSonarLanguageKey(),\n          ruleSet -> ruleSet.withActiveRule(\"python:S139\", \"INFO\", Map.of(\"legalTrailingCommentPattern\", \"blah\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(fakeClient);\n    sonarqubeMock.stubFor(get(\"/api/rules/show.protobuf?key=python:S139\")\n      .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(Rules.ShowResponse.newBuilder()\n        .setRule(Rules.Rule.newBuilder().setName(\"newName\").setSeverity(\"INFO\").setType(Common.RuleType.BUG).setLang(\"py\").setHtmlNote(\"extendedDesc from server\").build())\n        .build()))));\n    sonarqubeMock.stubFor(get(\"/api/system/status\")\n      .willReturn(aResponse().withStatus(200).withBody(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"9.9\\\",\\\"status\\\": \" +\n        \"\\\"UP\\\"}\")));\n\n    getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\");\n\n    sonarqubeMock.verify(getRequestedFor(urlEqualTo(\"/api/rules/show.protobuf?key=python:S139\"))\n      .withHeader(\"Authorization\", equalTo(\"Basic \" + Base64.getEncoder().encodeToString(\"myToken:\".getBytes(StandardCharsets.UTF_8)))));\n  }\n\n  @SonarLintTest\n  void it_should_authenticate_preemptively_on_sonarqube_10_4_with_token_and_bearer_scheme(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient()\n      .withToken(\"connectionId\", \"myToken\")\n      .build();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", sonarqubeMock.baseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(SonarLanguage.PYTHON.getSonarLanguageKey(),\n          ruleSet -> ruleSet.withActiveRule(\"python:S139\", \"INFO\", Map.of(\"legalTrailingCommentPattern\", \"blah\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(fakeClient);\n    sonarqubeMock.stubFor(get(\"/api/rules/show.protobuf?key=python:S139\")\n      .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(Rules.ShowResponse.newBuilder()\n        .setRule(Rules.Rule.newBuilder().setName(\"newName\").setSeverity(\"INFO\").setType(Common.RuleType.BUG).setLang(\"py\").setHtmlNote(\"extendedDesc from server\").build())\n        .build()))));\n    sonarqubeMock.stubFor(get(\"/api/system/status\")\n      .willReturn(aResponse().withStatus(200).withBody(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"10.4\\\",\\\"status\\\": \" +\n        \"\\\"UP\\\"}\")));\n\n    getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\");\n\n    sonarqubeMock.verify(getRequestedFor(urlEqualTo(\"/api/rules/show.protobuf?key=python:S139\"))\n      .withHeader(\"Authorization\", equalTo(\"Bearer myToken\")));\n  }\n\n  @SonarLintTest\n  void it_should_fail_the_request_if_credentials_are_not_returned_by_the_client(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    when(fakeClient.getCredentials(\"connectionId\")).thenReturn(null);\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", sonarqubeMock.baseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(SonarLanguage.PYTHON.getSonarLanguageKey(),\n          ruleSet -> ruleSet.withActiveRule(\"python:S139\", \"INFO\", Map.of(\"legalTrailingCommentPattern\", \"blah\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(fakeClient);\n    sonarqubeMock.stubFor(get(\"/api/system/status\").willReturn(aResponse().withStatus(200).withBody(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"10.8\\\",\\\"status\\\": \\\"UP\\\"}\")));\n\n    var throwable = catchThrowable(() -> getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\"));\n\n    assertThat(throwable)\n      .isInstanceOf(CompletionException.class)\n      .cause()\n      .isInstanceOf(ResponseErrorException.class)\n      .hasMessage(\"Connection 'connectionId' is not valid\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_the_request_if_dto_is_not_returned_by_the_client(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    when(fakeClient.getCredentials(\"connectionId\")).thenReturn(Either.forRight(null));\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", sonarqubeMock.baseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(SonarLanguage.PYTHON.getSonarLanguageKey(),\n          ruleSet -> ruleSet.withActiveRule(\"python:S139\", \"INFO\", Map.of(\"legalTrailingCommentPattern\", \"blah\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(fakeClient);\n    sonarqubeMock.stubFor(get(\"/api/system/status\").willReturn(aResponse().withStatus(200).withBody(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"10.8\\\",\\\"status\\\": \\\"UP\\\"}\")));\n\n    var throwable = catchThrowable(() -> getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\"));\n\n    assertThat(throwable)\n      .isInstanceOf(CompletionException.class)\n      .cause()\n      .isInstanceOf(ResponseErrorException.class)\n      .hasMessage(\"Connection 'connectionId' is not valid\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_the_request_if_token_is_not_returned_by_the_client(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    when(fakeClient.getCredentials(\"connectionId\")).thenReturn(Either.forLeft(new TokenDto(null)));\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", sonarqubeMock.baseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(SonarLanguage.PYTHON.getSonarLanguageKey(),\n          ruleSet -> ruleSet.withActiveRule(\"python:S139\", \"INFO\", Map.of(\"legalTrailingCommentPattern\", \"blah\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(fakeClient);\n    sonarqubeMock.stubFor(get(\"/api/system/status\").willReturn(aResponse().withStatus(200).withBody(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"10.8\\\",\\\"status\\\": \\\"UP\\\"}\")));\n\n    var throwable = catchThrowable(() -> getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\"));\n\n    assertThat(throwable)\n      .isInstanceOf(CompletionException.class)\n      .cause()\n      .isInstanceOf(ResponseErrorException.class)\n      .hasMessage(\"Connection 'connectionId' is not valid\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_the_request_if_username_is_not_returned_by_the_client(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    when(fakeClient.getCredentials(\"connectionId\")).thenReturn(Either.forRight(new UsernamePasswordDto(null, \"pass\")));\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", sonarqubeMock.baseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(SonarLanguage.PYTHON.getSonarLanguageKey(),\n          ruleSet -> ruleSet.withActiveRule(\"python:S139\", \"INFO\", Map.of(\"legalTrailingCommentPattern\", \"blah\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(fakeClient);\n    sonarqubeMock.stubFor(get(\"/api/system/status\").willReturn(aResponse().withStatus(200).withBody(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"10.8\\\",\\\"status\\\": \\\"UP\\\"}\")));\n\n    var throwable = catchThrowable(() -> getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\"));\n\n    assertThat(throwable)\n      .isInstanceOf(CompletionException.class)\n      .cause()\n      .isInstanceOf(ResponseErrorException.class)\n      .hasMessage(\"Connection 'connectionId' is not valid\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_the_request_if_password_is_not_returned_by_the_client(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    when(fakeClient.getCredentials(\"connectionId\")).thenReturn(Either.forRight(new UsernamePasswordDto(\"user\", null)));\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", sonarqubeMock.baseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(SonarLanguage.PYTHON.getSonarLanguageKey(),\n          ruleSet -> ruleSet.withActiveRule(\"python:S139\", \"INFO\", Map.of(\"legalTrailingCommentPattern\", \"blah\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(fakeClient);\n    sonarqubeMock.stubFor(get(\"/api/system/status\").willReturn(aResponse().withStatus(200).withBody(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"10.8\\\",\\\"status\\\": \\\"UP\\\"}\")));\n\n    var throwable = catchThrowable(() -> getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\"));\n\n    assertThat(throwable)\n      .isInstanceOf(CompletionException.class)\n      .cause()\n      .isInstanceOf(ResponseErrorException.class)\n      .hasMessage(\"Connection 'connectionId' is not valid\");\n  }\n\n  private void getEffectiveRuleDetails(SonarLintTestRpcServer backend, String configScopeId, String ruleKey) {\n    backend.getRulesService().getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams(configScopeId, ruleKey, null)).join().details();\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/http/ProxyMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.http;\n\nimport com.github.tomakehurst.wiremock.junit5.WireMockExtension;\nimport java.net.Proxy;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ExecutionException;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.TestInfo;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.GetEffectiveRuleDetailsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.GetProxyPasswordAuthenticationResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.ProxyDto;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Rules;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.equalTo;\nimport static com.github.tomakehurst.wiremock.client.WireMock.get;\nimport static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlMatching;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.when;\nimport static org.sonarsource.sonarlint.core.test.utils.ProtobufUtils.protobufBody;\n\nclass ProxyMediumTests {\n\n  public static final String PROXY_AUTH_ENABLED = \"proxy-auth\";\n\n  @RegisterExtension\n  static WireMockExtension sonarqubeMock = WireMockExtension.newInstance()\n    .options(wireMockConfig().dynamicPort())\n    .build();\n\n  @RegisterExtension\n  static WireMockExtension proxyMock = WireMockExtension.newInstance()\n    .options(wireMockConfig().dynamicPort())\n    .build();\n\n  @BeforeEach\n  void configureProxy(TestInfo info) {\n    sonarqubeMock.stubFor(get(\"/api/system/status\")\n      .willReturn(aResponse().withStatus(200).withBody(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"10.8\\\",\\\"status\\\": \" +\n        \"\\\"UP\\\"}\")));\n    proxyMock.stubFor(get(\"/api/system/status\")\n      .willReturn(aResponse().withStatus(200).withBody(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"10.8\\\",\\\"status\\\": \" +\n        \"\\\"UP\\\"}\")));\n\n    if (info.getTags().contains(PROXY_AUTH_ENABLED)) {\n      proxyMock.stubFor(get(urlMatching(\"/api/rules/.*\"))\n        .inScenario(\"Proxy Auth\")\n        .whenScenarioStateIs(STARTED)\n        .willReturn(aResponse()\n          .withStatus(407)\n          .withHeader(\"Proxy-Authenticate\", \"Basic realm=\\\"Access to the proxy\\\"\"))\n        .willSetStateTo(\"Challenge returned\"));\n      proxyMock.stubFor(get(urlMatching(\"/api/rules/.*\"))\n        .inScenario(\"Proxy Auth\")\n        .whenScenarioStateIs(\"Challenge returned\")\n        .willReturn(aResponse().proxiedFrom(sonarqubeMock.baseUrl())));\n    } else {\n      proxyMock.stubFor(get(urlMatching(\"/api/rules/.*\")).willReturn(aResponse().proxiedFrom(sonarqubeMock.baseUrl())));\n    }\n  }\n\n  @SonarLintTest\n  void it_should_honor_http_proxy_settings(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient()\n      .build();\n\n    when(fakeClient.selectProxies(any())).thenReturn(List.of(new ProxyDto(Proxy.Type.HTTP, \"localhost\", proxyMock.getPort())));\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", sonarqubeMock.baseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(SonarLanguage.PYTHON.getSonarLanguageKey(),\n          ruleSet -> ruleSet.withActiveRule(\"python:S139\", \"INFO\", Map.of(\"legalTrailingCommentPattern\", \"blah\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(fakeClient);\n    sonarqubeMock.stubFor(get(\"/api/rules/show.protobuf?key=python:S139\")\n      .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(Rules.ShowResponse.newBuilder()\n        .setRule(Rules.Rule.newBuilder().setName(\"newName\").setSeverity(\"INFO\").setType(Common.RuleType.BUG).setLang(\"py\").setHtmlNote(\"extendedDesc from server\").build())\n        .build()))));\n\n    getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\");\n\n    proxyMock.verify(getRequestedFor(urlEqualTo(\"/api/rules/show.protobuf?key=python:S139\")));\n  }\n\n  @SonarLintTest\n  void it_should_honor_http_direct_proxy_settings(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient()\n      .build();\n\n    when(fakeClient.selectProxies(any())).thenReturn(List.of(ProxyDto.NO_PROXY));\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", sonarqubeMock.baseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(SonarLanguage.PYTHON.getSonarLanguageKey(),\n          ruleSet -> ruleSet.withActiveRule(\"python:S139\", \"INFO\", Map.of(\"legalTrailingCommentPattern\", \"blah\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(fakeClient);\n    sonarqubeMock.stubFor(get(\"/api/rules/show.protobuf?key=python:S139\")\n      .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(Rules.ShowResponse.newBuilder()\n        .setRule(Rules.Rule.newBuilder().setName(\"newName\").setSeverity(\"INFO\").setType(Common.RuleType.BUG).setLang(\"py\").setHtmlNote(\"extendedDesc from server\").build())\n        .build()))));\n\n    getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\");\n\n    sonarqubeMock.verify(getRequestedFor(urlEqualTo(\"/api/rules/show.protobuf?key=python:S139\")));\n    proxyMock.verify(0, getRequestedFor(urlEqualTo(\"/api/rules/show.protobuf?key=python:S139\")));\n  }\n\n  @SonarLintTest\n  @Tag(PROXY_AUTH_ENABLED)\n  void it_should_honor_http_proxy_authentication(SonarLintTestHarness harness) {\n    var proxyLogin = \"proxyLogin\";\n    var proxyPassword = \"proxyPassword\";\n    var fakeClient = harness.newFakeClient().build();\n\n    when(fakeClient.selectProxies(any())).thenReturn(List.of(new ProxyDto(Proxy.Type.HTTP, \"localhost\", proxyMock.getPort())));\n    when(fakeClient.getProxyPasswordAuthentication(anyString(), anyInt(), anyString(), anyString(), anyString(), any()))\n      .thenReturn(new GetProxyPasswordAuthenticationResponse(proxyLogin, proxyPassword));\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", sonarqubeMock.baseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(SonarLanguage.PYTHON.getSonarLanguageKey(),\n          ruleSet -> ruleSet.withActiveRule(\"python:S139\", \"INFO\", Map.of(\"legalTrailingCommentPattern\", \"blah\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(fakeClient);\n    sonarqubeMock.stubFor(get(\"/api/rules/show.protobuf?key=python:S139\")\n      .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(Rules.ShowResponse.newBuilder()\n        .setRule(Rules.Rule.newBuilder().setName(\"newName\").setSeverity(\"INFO\").setType(Common.RuleType.BUG).setLang(\"py\").setHtmlNote(\"extendedDesc from server\").build())\n        .build()))));\n\n    getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\");\n\n    proxyMock.verify(getRequestedFor(urlEqualTo(\"/api/rules/show.protobuf?key=python:S139\"))\n      .withHeader(\"Proxy-Authorization\", equalTo(\"Basic \" + Base64.getEncoder().encodeToString((proxyLogin + \":\" + proxyPassword).getBytes(StandardCharsets.UTF_8)))));\n  }\n\n  @SonarLintTest\n  @Tag(PROXY_AUTH_ENABLED)\n  void it_should_honor_http_proxy_authentication_with_null_password(SonarLintTestHarness harness) {\n    var proxyLogin = \"proxyLogin\";\n    var fakeClient = harness.newFakeClient().build();\n\n    when(fakeClient.selectProxies(any())).thenReturn(List.of(new ProxyDto(Proxy.Type.HTTP, \"localhost\", proxyMock.getPort())));\n    when(fakeClient.getProxyPasswordAuthentication(anyString(), anyInt(), anyString(), anyString(), anyString(), any()))\n      .thenReturn(new GetProxyPasswordAuthenticationResponse(proxyLogin, null));\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", sonarqubeMock.baseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(SonarLanguage.PYTHON.getSonarLanguageKey(),\n          ruleSet -> ruleSet.withActiveRule(\"python:S139\", \"INFO\", Map.of(\"legalTrailingCommentPattern\", \"blah\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(fakeClient);\n    sonarqubeMock.stubFor(get(\"/api/rules/show.protobuf?key=python:S139\")\n      .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(Rules.ShowResponse.newBuilder()\n        .setRule(Rules.Rule.newBuilder().setName(\"newName\").setSeverity(\"INFO\").setType(Common.RuleType.BUG).setLang(\"py\").setHtmlNote(\"extendedDesc from server\").build())\n        .build()))));\n\n    getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\");\n\n    proxyMock.verify(getRequestedFor(urlEqualTo(\"/api/rules/show.protobuf?key=python:S139\"))\n      .withHeader(\"Proxy-Authorization\", equalTo(\"Basic \" + Base64.getEncoder().encodeToString((proxyLogin + \":\").getBytes(StandardCharsets.UTF_8)))));\n  }\n\n  @SonarLintTest\n  @Tag(PROXY_AUTH_ENABLED)\n  void it_should_fail_if_proxy_port_is_smaller_than_valid_range(SonarLintTestHarness harness) {\n    var proxyLogin = \"proxyLogin\";\n    var fakeClient = harness.newFakeClient().build();\n\n    when(fakeClient.selectProxies(any())).thenReturn(List.of(new ProxyDto(Proxy.Type.HTTP, \"localhost\", -1)));\n    when(fakeClient.getProxyPasswordAuthentication(anyString(), anyInt(), anyString(), anyString(), anyString(), any()))\n      .thenReturn(new GetProxyPasswordAuthenticationResponse(proxyLogin, null));\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", sonarqubeMock.baseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(SonarLanguage.PYTHON.getSonarLanguageKey(),\n          ruleSet -> ruleSet.withActiveRule(\"python:S139\", \"INFO\", Map.of(\"legalTrailingCommentPattern\", \"blah\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(fakeClient);\n    sonarqubeMock.stubFor(get(\"/api/rules/show.protobuf?key=python:S139\")\n      .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(Rules.ShowResponse.newBuilder()\n        .setRule(Rules.Rule.newBuilder().setName(\"newName\").setSeverity(\"INFO\").setType(Common.RuleType.BUG).setLang(\"py\").setHtmlNote(\"extendedDesc from server\").build())\n        .build()))));\n\n    getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\");\n\n    assertThat(fakeClient.getLogs())\n      .anySatisfy(\n        l -> {\n          assertThat(l.getMessage()).isEqualTo(\"Unable to get proxy\");\n          assertThat(l.getStackTrace()).contains(\"Port is outside the valid range for hostname: localhost\");\n        }\n      );\n  }\n\n  @SonarLintTest\n  @Tag(PROXY_AUTH_ENABLED)\n  void it_should_fail_if_proxy_port_is_higher_than_valid_range(SonarLintTestHarness harness) {\n    var proxyLogin = \"proxyLogin\";\n    var fakeClient = harness.newFakeClient().build();\n\n    when(fakeClient.selectProxies(any())).thenReturn(List.of(new ProxyDto(Proxy.Type.HTTP, \"localhost\", 70000)));\n    when(fakeClient.getProxyPasswordAuthentication(anyString(), anyInt(), anyString(), anyString(), anyString(), any()))\n      .thenReturn(new GetProxyPasswordAuthenticationResponse(proxyLogin, null));\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", sonarqubeMock.baseUrl(), storage -> storage.withProject(\"projectKey\",\n        projectStorage -> projectStorage.withRuleSet(SonarLanguage.PYTHON.getSonarLanguageKey(),\n          ruleSet -> ruleSet.withActiveRule(\"python:S139\", \"INFO\", Map.of(\"legalTrailingCommentPattern\", \"blah\")))))\n      .withBoundConfigScope(\"scopeId\", \"connectionId\", \"projectKey\")\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(fakeClient);\n    sonarqubeMock.stubFor(get(\"/api/rules/show.protobuf?key=python:S139\")\n      .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(Rules.ShowResponse.newBuilder()\n        .setRule(Rules.Rule.newBuilder().setName(\"newName\").setSeverity(\"INFO\").setType(Common.RuleType.BUG).setLang(\"py\").setHtmlNote(\"extendedDesc from server\").build())\n        .build()))));\n\n    getEffectiveRuleDetails(backend, \"scopeId\", \"python:S139\");\n\n    assertThat(fakeClient.getLogs())\n      .anySatisfy(\n        l -> {\n          assertThat(l.getMessage()).isEqualTo(\"Unable to get proxy\");\n          assertThat(l.getStackTrace()).contains(\"Port is outside the valid range for hostname: localhost\");\n        }\n      );\n  }\n\n  private void getEffectiveRuleDetails(SonarLintTestRpcServer backend, String configScopeId, String ruleKey) {\n    try {\n      backend.getRulesService().getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams(configScopeId, ruleKey, null)).get().details();\n    } catch (InterruptedException | ExecutionException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/http/SslMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.http;\n\nimport com.github.tomakehurst.wiremock.junit5.WireMockExtension;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.KeyStoreException;\nimport java.security.cert.X509Certificate;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.CompletionException;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport nl.altindag.ssl.util.CertificateUtils;\nimport nl.altindag.ssl.util.KeyStoreUtils;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.TestInstance;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.mockito.ArgumentCaptor;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.GetOrganizationParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.X509CertificateDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarcloud.ws.Organizations;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.get;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.sonarsource.sonarlint.core.test.utils.ProtobufUtils.protobufBody;\n\nclass SslMediumTests {\n\n  public static final String KEYSTORE_PWD = \"pwdServerP12\";\n\n  @RegisterExtension\n  SonarLintLogTester logTester = new SonarLintLogTester(true);\n\n  @Nested\n  // TODO Can be removed when switching to Java 16+ and changing sonarcloudMock and mockSonarCloudUrl() to static\n  @TestInstance(TestInstance.Lifecycle.PER_CLASS)\n  class ServerCertificate {\n\n    @RegisterExtension\n    WireMockExtension sonarcloudMock = WireMockExtension.newInstance()\n      .options(wireMockConfig().dynamicHttpsPort().httpDisabled(true)\n        .keystoreType(\"pkcs12\")\n        .keystorePath(toPath(Objects.requireNonNull(SslMediumTests.class.getResource(\"/ssl/server.p12\"))).toString())\n        .keystorePassword(KEYSTORE_PWD)\n        .keyManagerPassword(KEYSTORE_PWD))\n      .build();\n\n    @BeforeEach\n    void prepare() {\n      sonarcloudMock.stubFor(get(\"/api/system/status\")\n        .willReturn(aResponse().withStatus(200).withBody(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"8.0\\\",\\\"status\\\": \" +\n          \"\\\"UP\\\"}\")));\n      sonarcloudMock.stubFor(get(\"/api/organizations/search.protobuf?organizations=myOrg&ps=500&p=1\")\n        .willReturn(aResponse().withStatus(200)\n          .withResponseBody(protobufBody(Organizations.SearchWsResponse.newBuilder()\n            .addOrganizations(Organizations.Organization.newBuilder()\n              .setKey(\"myCustom\")\n              .setName(\"orgName\")\n              .setDescription(\"orgDesc\")\n              .build())\n            .setPaging(Common.Paging.newBuilder()\n              .setTotal(1)\n              .setPageSize(1)\n              .setPageIndex(1)\n              .build())\n            .build()))));\n    }\n\n    @SonarLintTest\n    void it_should_not_trust_server_self_signed_certificate_by_default(SonarLintTestHarness harness) {\n      var fakeClient = harness.newFakeClient().build();\n      var backend = harness.newBackend()\n        .withSonarQubeCloudEuRegionUri(sonarcloudMock.baseUrl())\n        .start(fakeClient);\n\n      var future = backend.getConnectionService().getOrganization(new GetOrganizationParams(Either.forLeft(new TokenDto(\"token\")), \"myOrg\", SonarCloudRegion.EU));\n      var thrown = assertThrows(CompletionException.class, future::join);\n      assertThat(thrown).hasRootCauseInstanceOf(ResponseErrorException.class).hasRootCauseMessage(\"org.sonarsource.sonarlint.core.serverapi.exception.NetworkException: Request failed\");\n      assertThat(future).isCompletedExceptionally();\n    }\n\n    @SonarLintTest\n    void it_should_ask_user_only_once_if_server_certificate_is_trusted(SonarLintTestHarness harness) throws ExecutionException, InterruptedException, KeyStoreException {\n      var fakeClient = harness.newFakeClient().build();\n\n      var backend = harness.newBackend()\n        .withSonarQubeCloudEuRegionUri(sonarcloudMock.baseUrl())\n        .start(fakeClient);\n\n      when(fakeClient.checkServerTrusted(any(), any()))\n        .thenReturn(true);\n\n      // Two concurrent requests should only trigger checkServerTrusted once\n      var future = backend.getConnectionService().getOrganization(new GetOrganizationParams(Either.forLeft(new TokenDto(\"token\")), \"myOrg\", SonarCloudRegion.EU));\n      var future2 = backend.getConnectionService().getOrganization(new GetOrganizationParams(Either.forLeft(new TokenDto(\"token\")), \"myOrg\", SonarCloudRegion.EU));\n\n      future.get();\n      future2.get();\n\n      ArgumentCaptor<List<X509CertificateDto>> captor = ArgumentCaptor.forClass(List.class);\n      verify(fakeClient, times(1)).checkServerTrusted(captor.capture(), eq(\"UNKNOWN\"));\n\n      var chain = captor.getValue();\n\n      assertThat(chain).hasSize(1);\n      var pems = CertificateUtils.parsePemCertificate(chain.get(0).getPem());\n      assertThat(pems).hasSize(1);\n      assertThat(pems.get(0)).isInstanceOf(X509Certificate.class);\n\n      var keyStore = KeyStoreUtils.loadKeyStore(backend.getUserHome().resolve(\"ssl/truststore.p12\"), \"sonarlint\".toCharArray(), \"PKCS12\");\n      assertThat(Collections.list(keyStore.aliases())).containsExactly(\"cn=localhost_o=sonarsource-sa_l=geneva_st=geneva_c=ch\");\n\n    }\n\n  }\n\n  @Nested\n  // TODO Can be removed when switching to Java 16+ and changing sonarcloudMock and mockSonarCloudUrl() to static\n  @TestInstance(TestInstance.Lifecycle.PER_CLASS)\n  class ClientCertificate {\n    @RegisterExtension\n    WireMockExtension sonarcloudMock = WireMockExtension.newInstance()\n      .options(wireMockConfig().dynamicHttpsPort().httpDisabled(true)\n        .keystoreType(\"pkcs12\")\n        .keystorePath(toPath(Objects.requireNonNull(SslMediumTests.class.getResource(\"/ssl/server.p12\"))).toString())\n        .keystorePassword(KEYSTORE_PWD)\n        .keyManagerPassword(KEYSTORE_PWD)\n        .needClientAuth(true)\n        .trustStoreType(\"pkcs12\")\n        .trustStorePath(toPath(Objects.requireNonNull(SslMediumTests.class.getResource(\"/ssl/server-with-client-ca.p12\"))).toString())\n        .trustStorePassword(\"pwdServerWithClientCA\"))\n      .build();\n\n    @BeforeEach\n    void prepare() {\n      sonarcloudMock.stubFor(get(\"/api/system/status\")\n        .willReturn(aResponse().withStatus(200).withBody(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"8.0\\\",\\\"status\\\": \" +\n          \"\\\"UP\\\"}\")));\n      sonarcloudMock.stubFor(get(\"/api/organizations/search.protobuf?organizations=myOrg&ps=500&p=1\")\n        .willReturn(aResponse().withStatus(200)\n          .withResponseBody(protobufBody(Organizations.SearchWsResponse.newBuilder()\n            .addOrganizations(Organizations.Organization.newBuilder()\n              .setKey(\"myCustom\")\n              .setName(\"orgName\")\n              .setDescription(\"orgDesc\")\n              .build())\n            .setPaging(Common.Paging.newBuilder()\n              .setTotal(1)\n              .setPageSize(1)\n              .setPageIndex(1)\n              .build())\n            .build()))));\n    }\n\n    @SonarLintTest\n    void it_should_fail_if_client_certificate_not_provided(SonarLintTestHarness harness) {\n      var fakeClient = harness.newFakeClient().build();\n      var backend = harness.newBackend()\n        .withSonarQubeCloudEuRegionUri(sonarcloudMock.baseUrl())\n        .start(fakeClient);\n\n      when(fakeClient.checkServerTrusted(any(), any()))\n        .thenReturn(true);\n\n      var future = backend.getConnectionService().getOrganization(new GetOrganizationParams(Either.forLeft(new TokenDto(\"token\")), \"myOrg\", SonarCloudRegion.EU));\n\n      var thrown = assertThrows(CompletionException.class, future::join);\n      assertThat(thrown).hasRootCauseInstanceOf(ResponseErrorException.class).hasRootCauseMessage(\"org.sonarsource.sonarlint.core.serverapi.exception.NetworkException: Request failed\");\n      assertThat(future).isCompletedExceptionally();\n\n    }\n\n    @SonarLintTest\n    void it_should_succeed_if_client_certificate_provided(SonarLintTestHarness harness) {\n      var fakeClient = harness.newFakeClient().build();\n      var backend = harness.newBackend()\n        .withKeyStore(toPath(Objects.requireNonNull(SslMediumTests.class.getResource(\"/ssl/client.p12\"))), \"pwdClientCertP12\", null)\n        .withSonarQubeCloudEuRegionUri(sonarcloudMock.baseUrl())\n        .start(fakeClient);\n\n      when(fakeClient.checkServerTrusted(any(), any()))\n        .thenReturn(true);\n\n      var future = backend.getConnectionService().getOrganization(new GetOrganizationParams(Either.forLeft(new TokenDto(\"token\")), \"myOrg\", SonarCloudRegion.EU));\n\n      assertThat(future).succeedsWithin(1, TimeUnit.MINUTES);\n    }\n  }\n\n  private static Path toPath(URL url) {\n    try {\n      return Paths.get(url.toURI());\n    } catch (URISyntaxException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/http/TimeoutMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.http;\n\nimport com.github.tomakehurst.wiremock.junit5.WireMockExtension;\nimport java.time.Duration;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.GetOrganizationParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarcloud.ws.Organizations;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.get;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.sonarsource.sonarlint.core.test.utils.ProtobufUtils.protobufBody;\n\nclass TimeoutMediumTests {\n\n  @RegisterExtension\n  static WireMockExtension sonarcloudMock = WireMockExtension.newInstance()\n    .options(wireMockConfig().dynamicPort())\n    .build();\n\n  @AfterEach\n  void tearDown() {\n    System.clearProperty(\"sonarlint.http.responseTimeout\");\n  }\n\n  @SonarLintTest\n  void it_should_timeout_on_long_response(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient()\n      .build();\n    var backend = harness.newBackend()\n      .withHttpResponseTimeout(Duration.ofSeconds(1))\n      .withSonarQubeCloudEuRegionUri(sonarcloudMock.baseUrl())\n      .start(fakeClient);\n    sonarcloudMock.stubFor(get(\"/api/organizations/search.protobuf?organizations=myOrg&ps=500&p=1\")\n      .willReturn(aResponse().withStatus(200)\n        .withFixedDelay(2000)\n        .withResponseBody(protobufBody(Organizations.SearchWsResponse.newBuilder()\n          .addOrganizations(Organizations.Organization.newBuilder()\n            .setKey(\"myCustom\")\n            .setName(\"orgName\")\n            .setDescription(\"orgDesc\")\n            .build())\n          .build()))));\n\n    var future = backend.getConnectionService().getOrganization(new GetOrganizationParams(Either.forLeft(new TokenDto(\"token\")), \"myOrg\", SonarCloudRegion.EU));\n\n    assertThat(future)\n      .failsWithin(3, TimeUnit.SECONDS)\n      .withThrowableOfType(ExecutionException.class)\n      .withCauseExactlyInstanceOf(ResponseErrorException.class);\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/issues/AnalyzeFileListMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.issues;\n\nimport com.google.gson.Gson;\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.List;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.embedded.server.AnalyzeFileListRequestHandler;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.EMBEDDED_SERVER;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JAVA;\nimport static org.sonarsource.sonarlint.core.test.utils.storage.ServerTaintIssueFixtures.aServerTaintIssue;\nimport static utils.AnalysisUtils.createFile;\n\nclass AnalyzeFileListMediumTests {\n\n  @RegisterExtension\n  SonarLintLogTester logTester = new SonarLintLogTester(true);\n\n  private static final String FILE1_PATH = \"Foo.java\";\n  private static final String FILE2_PATH = \"Bar.java\";\n  private static final String CONFIG_SCOPE_ID = \"configScopeId\";\n  \n  private final Gson gson = new Gson();\n\n  @SonarLintTest\n  void it_should_analyze_single_file_and_return_issues_and_taints(SonarLintTestHarness harness, @TempDir Path baseDir) throws Exception {\n    var inputFile1 = prepareJavaInputFile(baseDir);\n\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile1.toUri(), baseDir.relativize(inputFile1), CONFIG_SCOPE_ID, false, null, inputFile1, null,\n          Language.JAVA, true)\n      ))\n      .build();\n\n    var server = harness.newFakeSonarQubeServer()\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile\n        .withLanguage(\"java\")\n        .withActiveRule(\"java:S1220\", activeRule -> activeRule\n          .withSeverity(IssueSeverity.MINOR))\n        .withActiveRule(\"java:S2094\", activeRule -> activeRule\n          .withSeverity(IssueSeverity.MINOR)))\n      .withProject(\"projectKey\",\n        project -> project\n          .withQualityProfile(\"qpKey\")\n          .withMainBranch(\"main\"))\n      .withPlugin(TestPlugin.JAVA)\n      .start();\n\n    var backend = harness.newBackend()\n      .withBackendCapability(FULL_SYNCHRONIZATION, EMBEDDED_SERVER)\n      .withSonarQubeConnection(\"connectionId\", server,\n        storage -> storage.withProject(\"projectKey\",\n          project -> project.withMainBranch(\"main\",\n            branch -> branch.withTaintIssue(aServerTaintIssue(\"key\")\n              .withFilePath(inputFile1.toAbsolutePath().toString())\n              .withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\"))))))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, \"connectionId\", \"projectKey\")\n      .withExtraEnabledLanguagesInConnectedMode(JAVA)\n      .withClientName(\"client\")\n      .start(fakeClient);\n\n    fakeClient.waitForSynchronization();\n\n    var requestBody = gson.toJson(new AnalyzeFileListRequestHandler.AnalyzeFileListRequest(\n      List.of(inputFile1.toAbsolutePath().normalize().toString()\n      )));\n\n    var response = executeAnalyzeRequest(backend, server.baseUrl(), requestBody);\n    assertThat(response.statusCode()).isEqualTo(200);\n\n    var analysisResult = gson.fromJson(response.body(), AnalyzeFileListRequestHandler.AnalyzeFileListResult.class);\n    assertThat(analysisResult).isNotNull();\n    assertThat(analysisResult.findings()).hasSize(3);\n    assertThat(analysisResult.findings()).extracting(AnalyzeFileListRequestHandler.RawFindingResponse::ruleKey,\n        AnalyzeFileListRequestHandler.RawFindingResponse::message,\n        AnalyzeFileListRequestHandler.RawFindingResponse::severity,\n        AnalyzeFileListRequestHandler.RawFindingResponse::textRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsExactlyInAnyOrder(\n        tuple(\"java:S1220\", \"Move this file to a named package.\", \"MINOR\", null),\n        tuple(\"java:S2094\", \"Remove this empty class, write its code or make it an \\\"interface\\\".\", \"MINOR\",\n          new TextRange(1, 13, 1, 16)),\n        tuple(\"ruleKey\", \"message\", \"MEDIUM\", new TextRange(1, 2, 3, 4))\n      );\n  }\n\n  @SonarLintTest\n  void it_should_analyze_multiple_files_and_return_issues(SonarLintTestHarness harness, @TempDir Path baseDir) throws Exception {\n    var inputFile1 = prepareJavaInputFile(baseDir);\n    var inputFile2 = prepareJavaInputFile2(baseDir);\n\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile1.toUri(), baseDir.relativize(inputFile1), CONFIG_SCOPE_ID, false, null, inputFile1, null,\n          Language.JAVA, true),\n        new ClientFileDto(inputFile2.toUri(), baseDir.relativize(inputFile2), CONFIG_SCOPE_ID, false, null, inputFile2, null,\n          Language.JAVA, true)\n      ))\n      .build();\n\n    var server = harness.newFakeSonarQubeServer()\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile\n        .withLanguage(\"java\")\n        .withActiveRule(\"java:S1220\", activeRule -> activeRule\n          .withSeverity(IssueSeverity.MINOR))\n        .withActiveRule(\"java:S2094\", activeRule -> activeRule\n          .withSeverity(IssueSeverity.MINOR)))\n      .withProject(\"projectKey\",\n        project -> project\n          .withQualityProfile(\"qpKey\")\n          .withMainBranch(\"main\"))\n      .withPlugin(TestPlugin.JAVA)\n      .start();\n\n    var backend = harness.newBackend()\n      .withBackendCapability(FULL_SYNCHRONIZATION, EMBEDDED_SERVER)\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBoundConfigScope(CONFIG_SCOPE_ID, \"connectionId\", \"projectKey\")\n      .withExtraEnabledLanguagesInConnectedMode(JAVA)\n      .withClientName(\"client\")\n      .start(fakeClient);\n\n    fakeClient.waitForSynchronization();\n\n    var requestBody = gson.toJson(new AnalyzeFileListRequestHandler.AnalyzeFileListRequest(\n      List.of(inputFile1.toAbsolutePath().normalize().toString(),\n        inputFile2.toAbsolutePath().normalize().toString()\n      )));\n\n    var response = executeAnalyzeRequest(backend, server.baseUrl(), requestBody);\n    assertThat(response.statusCode()).isEqualTo(200);\n\n    var analysisResult = gson.fromJson(response.body(), AnalyzeFileListRequestHandler.AnalyzeFileListResult.class);\n    assertThat(analysisResult).isNotNull();\n    assertThat(analysisResult.findings()).hasSize(4);\n    assertThat(analysisResult.findings()).extracting(AnalyzeFileListRequestHandler.RawFindingResponse::ruleKey,\n        AnalyzeFileListRequestHandler.RawFindingResponse::message,\n        AnalyzeFileListRequestHandler.RawFindingResponse::severity,\n        AnalyzeFileListRequestHandler.RawFindingResponse::textRange)\n      .usingRecursiveFieldByFieldElementComparator()\n      .containsOnly(\n        tuple(\"java:S1220\", \"Move this file to a named package.\", \"MINOR\", null),\n        tuple(\"java:S2094\", \"Remove this empty class, write its code or make it an \\\"interface\\\".\", \"MINOR\",\n          new TextRange(1, 13, 1, 16)),\n        tuple(\"java:S1220\", \"Move this file to a named package.\", \"MINOR\", null),\n        tuple(\"java:S2094\", \"Remove this empty class, write its code or make it an \\\"interface\\\".\", \"MINOR\",\n          new TextRange(1, 13, 1, 16))\n      );\n  }\n\n  @SonarLintTest\n  void it_should_return_error_when_no_files_found_to_be_indexed(SonarLintTestHarness harness, @TempDir Path baseDir) throws Exception {\n    var inputFile1 = prepareJavaInputFile(baseDir);\n\n    var fakeClient = harness.newFakeClient().build();\n\n    var server = harness.newFakeSonarQubeServer()\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile\n        .withLanguage(\"java\")\n        .withActiveRule(\"java:S1220\", activeRule -> activeRule\n          .withSeverity(IssueSeverity.MINOR))\n        .withActiveRule(\"java:S2094\", activeRule -> activeRule\n          .withSeverity(IssueSeverity.MINOR)))\n      .withProject(\"projectKey\",\n        project -> project\n          .withQualityProfile(\"qpKey\")\n          .withMainBranch(\"main\"))\n      .withPlugin(TestPlugin.JAVA)\n      .start();\n\n    var backend = harness.newBackend()\n      .withBackendCapability(FULL_SYNCHRONIZATION, EMBEDDED_SERVER)\n      .withSonarQubeConnection(\"connectionId\", server,\n        storage -> storage.withProject(\"projectKey\",\n          project -> project.withMainBranch(\"main\",\n            branch -> branch.withTaintIssue(aServerTaintIssue(\"key\")\n              .withFilePath(inputFile1.toAbsolutePath().toString())\n              .withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\"))))))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, \"connectionId\", \"projectKey\")\n      .withExtraEnabledLanguagesInConnectedMode(JAVA)\n      .withClientName(\"client\")\n      .start(fakeClient);\n\n    fakeClient.waitForSynchronization();\n\n    var requestBody = gson.toJson(new AnalyzeFileListRequestHandler.AnalyzeFileListRequest(\n      List.of(inputFile1.toAbsolutePath().normalize().toString())\n    ));\n\n    var response = executeAnalyzeRequest(backend, server.baseUrl(), requestBody);\n    assertThat(response.statusCode()).isEqualTo(500);\n    assertThat(response.body()).isEqualTo(\"Failed to analyze files, reason: No files were found to be indexed by SonarQube for IDE\");\n  }\n\n  @SonarLintTest\n  void it_should_return_bad_request_for_empty_file_list(SonarLintTestHarness harness) throws Exception {\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start();\n\n    var requestBody = gson.toJson(new AnalyzeFileListRequestHandler.AnalyzeFileListRequest(Collections.emptyList()));\n\n    var response = executeAnalyzeRequest(backend, \"\", requestBody);\n    assertThat(response.statusCode()).isEqualTo(400);\n  }\n\n  @SonarLintTest\n  void it_should_return_bad_request_for_invalid_json(SonarLintTestHarness harness) throws Exception {\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start();\n\n    var response = executeAnalyzeRequest(backend, \"\", \"invalid json\");\n    assertThat(response.statusCode()).isEqualTo(400);\n  }\n\n  @SonarLintTest\n  void it_should_return_bad_request_for_get_request(SonarLintTestHarness harness) throws Exception {\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start();\n\n    var request = HttpRequest.newBuilder()\n      .uri(URI.create(\"http://localhost:\" + backend.getEmbeddedServerPort() + \"/sonarlint/api/analysis/files\"))\n      .header(\"Origin\", \"http://localhost\")\n      .header(\"Content-Type\", \"application/json; charset=utf-8\")\n      .GET()\n      .build();\n\n    var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n    assertThat(response.statusCode()).isEqualTo(400);\n  }\n\n  private HttpResponse<String> executeAnalyzeRequest(SonarLintTestRpcServer backend, String baseUrl, String requestBody) throws IOException,\n    InterruptedException {\n    var request = HttpRequest.newBuilder()\n      .uri(URI.create(\"http://localhost:\" + backend.getEmbeddedServerPort() + \"/sonarlint/api/analysis/files\"))\n      .header(\"Origin\", baseUrl)\n      .header(\"Content-Type\", \"application/json; charset=utf-8\")\n      .POST(HttpRequest.BodyPublishers.ofString(requestBody))\n      .build();\n\n    // Print request sent\n    System.out.println(\"Sending request: \" + request);\n    System.out.println(\"Sending request to: \" + request.uri());\n    System.out.println(\"Request body sent: \" + requestBody);\n    System.out.println(\"Using Origin: \" + baseUrl);\n\n    return HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n  }\n\n  private static Path prepareJavaInputFile(Path baseDir) {\n    return createFile(baseDir, FILE1_PATH, \"public class Foo {\\n}\");\n  }\n\n  private static Path prepareJavaInputFile2(Path baseDir) {\n    return createFile(baseDir, FILE2_PATH, \"public class Bar {\\n}\");\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/issues/CheckAnticipatedStatusChangeSupportedMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.issues;\n\nimport java.time.Duration;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutionException;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.CheckAnticipatedStatusChangeSupportedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.CheckAnticipatedStatusChangeSupportedResponse;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.MockWebServerExtensionWithProtobuf;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass CheckAnticipatedStatusChangeSupportedMediumTests {\n\n  @RegisterExtension\n  public final MockWebServerExtensionWithProtobuf mockWebServerExtension = new MockWebServerExtensionWithProtobuf();\n\n  @AfterEach\n  void tearDown() {\n    mockWebServerExtension.shutdown();\n  }\n\n  @SonarLintTest\n  void it_should_fail_when_the_connection_is_unknown(SonarLintTestHarness harness) {\n    var backend = harness.newBackend().start();\n\n    assertThat(checkAnticipatedStatusChangeSupported(backend, \"configScopeId\"))\n      .failsWithin(Duration.ofSeconds(2))\n      .withThrowableOfType(ExecutionException.class)\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class)\n      .withMessage(\"No binding for config scope 'configScopeId'\");\n  }\n\n  @SonarLintTest\n  void it_should_not_be_available_for_sonarcloud(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(mockWebServerExtension.endpointParams().getBaseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"orgKey\")\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .start();\n\n    assertThat(checkAnticipatedStatusChangeSupported(backend, \"configScopeId\"))\n      .succeedsWithin(Duration.ofSeconds(2))\n      .extracting(CheckAnticipatedStatusChangeSupportedResponse::isSupported)\n      .isEqualTo(false);\n  }\n\n  @SonarLintTest\n  void it_should_not_be_available_for_sonarqube_prior_to_10_2(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", mockWebServerExtension.endpointParams().getBaseUrl(), storage -> storage.withServerVersion(\"10.1\"))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .start();\n\n    assertThat(checkAnticipatedStatusChangeSupported(backend, \"configScopeId\"))\n      .succeedsWithin(Duration.ofSeconds(2))\n      .extracting(CheckAnticipatedStatusChangeSupportedResponse::isSupported)\n      .isEqualTo(false);\n  }\n\n  @SonarLintTest\n  void it_should_be_available_for_sonarqube_10_2_plus(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", mockWebServerExtension.endpointParams().getBaseUrl(),  storage -> storage.withServerVersion(\"10.2\"))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .start();\n\n    assertThat(checkAnticipatedStatusChangeSupported(backend, \"configScopeId\"))\n      .succeedsWithin(Duration.ofSeconds(2))\n      .extracting(CheckAnticipatedStatusChangeSupportedResponse::isSupported)\n      .isEqualTo(true);\n  }\n\n  private CompletableFuture<CheckAnticipatedStatusChangeSupportedResponse> checkAnticipatedStatusChangeSupported(SonarLintTestRpcServer backend, String configScopeId) {\n    return backend.getIssueService().checkAnticipatedStatusChangeSupported(new CheckAnticipatedStatusChangeSupportedParams(configScopeId));\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/issues/CheckResolutionStatusChangePermittedMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.issues;\n\nimport com.google.protobuf.Message;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutionException;\nimport javax.annotation.Nullable;\nimport mockwebserver3.MockResponse;\nimport org.assertj.core.api.InstanceOfAssertFactories;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonar.scanner.protocol.Constants;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.CheckStatusChangePermittedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.CheckStatusChangePermittedResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ResolutionStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.MockWebServerExtensionWithProtobuf;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.when;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static utils.AnalysisUtils.createFile;\nimport static utils.AnalysisUtils.waitForAnalysisReady;\nimport static utils.AnalysisUtils.waitForRaisedIssues;\n\nclass CheckResolutionStatusChangePermittedMediumTests {\n\n  private static final String CONFIG_SCOPE_ID = \"configScopeId\";\n  private static final String CONNECTION_ID = \"connectionId\";\n\n  @RegisterExtension\n  public final MockWebServerExtensionWithProtobuf mockWebServerExtension = new MockWebServerExtensionWithProtobuf();\n\n  @AfterEach\n  void tearDown() {\n    mockWebServerExtension.shutdown();\n  }\n\n  @SonarLintTest\n  void it_should_fail_when_the_connection_is_unknown(SonarLintTestHarness harness) {\n    var backend = harness.newBackend().start();\n\n    var response = checkStatusChangePermitted(backend, CONNECTION_ID, \"issueKey\");\n\n    assertThat(response)\n      .failsWithin(Duration.ofSeconds(2))\n      .withThrowableOfType(ExecutionException.class)\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class)\n      .withMessage(\"Connection 'connectionId' is not valid\");\n  }\n\n  @SonarLintTest\n  void it_should_allow_2_statuses_when_user_has_permission_for_sonarqube_103(SonarLintTestHarness harness) {\n    fakeServerWithIssue(\"issueKey\", List.of(\"wontfix\", \"falsepositive\"));\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, mockWebServerExtension.endpointParams().getBaseUrl(), storage -> storage.withServerVersion(\"10.3\"))\n      .start();\n\n    var response = checkStatusChangePermitted(backend, CONNECTION_ID, \"issueKey\");\n\n    assertThat(response)\n      .succeedsWithin(Duration.ofSeconds(2))\n      .extracting(CheckStatusChangePermittedResponse::getAllowedStatuses)\n      .asInstanceOf(InstanceOfAssertFactories.list(ResolutionStatus.class))\n      .containsExactly(ResolutionStatus.WONT_FIX, ResolutionStatus.FALSE_POSITIVE);\n  }\n\n  @SonarLintTest\n  void it_should_allow_2_statuses_when_user_has_permission_for_sonarqube_104(SonarLintTestHarness harness) {\n    fakeServerWithIssue(\"issueKey\", List.of(\"accept\", \"falsepositive\"));\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, mockWebServerExtension.endpointParams().getBaseUrl(), storage -> storage.withServerVersion(\"10.4\"))\n      .start();\n\n    var response = checkStatusChangePermitted(backend, CONNECTION_ID, \"issueKey\");\n\n    assertThat(response)\n      .succeedsWithin(Duration.ofSeconds(2))\n      .extracting(CheckStatusChangePermittedResponse::getAllowedStatuses)\n      .asInstanceOf(InstanceOfAssertFactories.list(ResolutionStatus.class))\n      .containsExactly(ResolutionStatus.ACCEPT, ResolutionStatus.FALSE_POSITIVE);\n  }\n\n  @SonarLintTest\n  void it_should_allow_2_statuses_when_user_has_permission_for_sonarcloud(SonarLintTestHarness harness) {\n    fakeServerWithIssue(\"issueKey\", \"orgKey\", List.of(\"wontfix\", \"falsepositive\"));\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(mockWebServerExtension.endpointParams().getBaseUrl())\n      .withSonarCloudConnection(CONNECTION_ID, \"orgKey\")\n      .start();\n\n    var response = checkStatusChangePermitted(backend, CONNECTION_ID, \"issueKey\");\n\n    assertThat(response)\n      .succeedsWithin(Duration.ofSeconds(2))\n      .extracting(CheckStatusChangePermittedResponse::getAllowedStatuses)\n      .asInstanceOf(InstanceOfAssertFactories.list(ResolutionStatus.class))\n      .containsExactly(ResolutionStatus.WONT_FIX, ResolutionStatus.FALSE_POSITIVE);\n  }\n\n  @SonarLintTest\n  void it_should_fallback_to_server_check_if_the_issue_uuid_is_not_found_in_local_only_issues(SonarLintTestHarness harness) {\n    var issueKey = UUID.randomUUID().toString();\n    fakeServerWithIssue(issueKey, List.of(\"accept\", \"falsepositive\"));\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, mockWebServerExtension.endpointParams().getBaseUrl(), storage -> storage.withServerVersion(\"10.4\"))\n      .start();\n\n    var response = checkStatusChangePermitted(backend, CONNECTION_ID, issueKey);\n\n    assertThat(response)\n      .succeedsWithin(Duration.ofSeconds(2))\n      .extracting(CheckStatusChangePermittedResponse::getAllowedStatuses)\n      .asInstanceOf(InstanceOfAssertFactories.list(ResolutionStatus.class))\n      .containsExactly(ResolutionStatus.ACCEPT, ResolutionStatus.FALSE_POSITIVE);\n  }\n\n  @SonarLintTest\n  void it_should_not_permit_status_change_when_issue_misses_required_transitions(SonarLintTestHarness harness) {\n    fakeServerWithIssue(\"issueKey\", List.of(\"confirm\"));\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, mockWebServerExtension.endpointParams().getBaseUrl(), storage -> storage.withServerVersion(\"10.3\"))\n      .start();\n\n    var response = checkStatusChangePermitted(backend, CONNECTION_ID, \"issueKey\");\n\n    assertThat(response)\n      .succeedsWithin(Duration.ofSeconds(2))\n      .extracting(CheckStatusChangePermittedResponse::isPermitted, CheckStatusChangePermittedResponse::getNotPermittedReason,\n        CheckStatusChangePermittedResponse::getAllowedStatuses)\n      .containsExactly(false, \"Marking an issue as resolved requires the 'Administer Issues' permission\", List.of());\n  }\n\n  @SonarLintTest\n  void it_should_fail_if_no_issue_is_returned_by_web_api(SonarLintTestHarness harness) {\n    fakeServerWithResponse(\"issueKey\", null, Issues.SearchWsResponse.newBuilder().build());\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, mockWebServerExtension.endpointParams().getBaseUrl(), storage -> storage.withServerVersion(\"10.3\"))\n      .start();\n\n    var response = checkStatusChangePermitted(backend, CONNECTION_ID, \"issueKey\");\n\n    assertThat(response)\n      .failsWithin(Duration.ofSeconds(2))\n      .withThrowableOfType(ExecutionException.class)\n      .havingCause()\n      .isInstanceOfSatisfying(ResponseErrorException.class, ex -> {\n        assertThat(ex.getResponseError().getData().toString()).contains(\"No issue found with key 'issueKey'\");\n      });\n  }\n\n  @SonarLintTest\n  void it_should_fail_if_web_api_returns_an_error(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, mockWebServerExtension.endpointParams().getBaseUrl(), storage -> storage.withServerVersion(\"10.3\"))\n      .start();\n\n    var response = checkStatusChangePermitted(backend, CONNECTION_ID, \"issueKey\");\n\n    assertThat(response)\n      .failsWithin(Duration.ofSeconds(2))\n      .withThrowableOfType(ExecutionException.class)\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class);\n  }\n\n  @SonarLintTest\n  void it_should_fail_if_web_api_returns_unexpected_body(SonarLintTestHarness harness) {\n    fakeServerWithWrongBody(\"issueKey\");\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, mockWebServerExtension.endpointParams().getBaseUrl(), storage -> storage.withServerVersion(\"10.3\"))\n      .start();\n\n    var response = checkStatusChangePermitted(backend, CONNECTION_ID, \"issueKey\");\n\n    assertThat(response)\n      .failsWithin(Duration.ofSeconds(2))\n      .withThrowableOfType(ExecutionException.class)\n      .havingCause()\n      .isInstanceOfSatisfying(ResponseErrorException.class, ex -> {\n        assertThat(ex.getResponseError().getData().toString()).contains(\"Unexpected body received\");\n      });\n  }\n\n  @Disabled(\"SC is difficult to setup for this test\")\n  @SonarLintTest\n  void it_should_not_permit_status_change_on_local_only_issues_for_sonarcloud(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", \"\"\"\n      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <project>\n        <modelVersion>4.0.0</modelVersion>\n        <groupId>com.foo</groupId>\n        <artifactId>bar</artifactId>\n        <version>${pom.version}</version>\n      </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var branchName = \"main\";\n    var projectKey = \"projectKey\";\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(\n        new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"org\", organization -> organization\n        .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile\n          .withLanguage(\"xml\").withActiveRule(\"xml:S3421\", activeRule -> activeRule\n            .withSeverity(IssueSeverity.BLOCKER)))\n        .withProject(projectKey,\n          project -> project\n            .withQualityProfile(\"qpKey\")\n            .withBranch(branchName)))\n      .withPlugin(TestPlugin.XML)\n      .start();\n    var backend = harness.newBackend()\n      .withSonarCloudConnection(CONNECTION_ID, server.baseUrl())\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, projectKey)\n      .withExtraEnabledLanguagesInConnectedMode(Language.XML)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n    client.waitForSynchronization();\n    waitForAnalysisReady(client, CONFIG_SCOPE_ID);\n\n    backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(),\n      List.of(fileUri), Map.of(), false, 0)).join();\n\n    waitForRaisedIssues(client, CONFIG_SCOPE_ID);\n    var localOnlyIssue = client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID).get(0);\n    var response = checkStatusChangePermitted(backend, CONNECTION_ID, localOnlyIssue.getId().toString());\n\n    assertThat(response)\n      .succeedsWithin(Duration.ofSeconds(2))\n      .extracting(CheckStatusChangePermittedResponse::isPermitted, CheckStatusChangePermittedResponse::getNotPermittedReason,\n        CheckStatusChangePermittedResponse::getAllowedStatuses)\n      .containsExactly(false, \"Marking a local-only issue as resolved requires SonarQube Server 10.2+\", List.of());\n  }\n\n  @Disabled(\"SLCORE-966\")\n  @SonarLintTest\n  void it_should_not_permit_status_change_on_local_only_issues_for_sonarqube_prior_to_10_2(SonarLintTestHarness harness, @TempDir Path testDir) throws IOException {\n    var baseDir = testDir.resolve(\"it_should_not_permit_status_change_on_local_only_issues_for_sonarqube_prior_to_10_2\");\n    Files.createDirectory(baseDir);\n    var filePath = createFile(baseDir, \"pom.xml\", \"\"\"\n      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <project>\n        <modelVersion>4.0.0</modelVersion>\n        <groupId>com.foo</groupId>\n        <artifactId>bar</artifactId>\n        <version>${pom.version}</version>\n      </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var branchName = \"main\";\n    var projectKey = \"projectKey\";\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(\n        new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var server = harness.newFakeSonarQubeServer(\"10.1\")\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile\n        .withLanguage(\"xml\").withActiveRule(\"xml:S3421\", activeRule -> activeRule\n          .withSeverity(IssueSeverity.BLOCKER)))\n      .withProject(projectKey,\n        project -> project\n          .withQualityProfile(\"qpKey\")\n          .withBranch(branchName))\n      .withPlugin(TestPlugin.XML)\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server)\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, projectKey)\n      .withExtraEnabledLanguagesInConnectedMode(Language.XML)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n    client.waitForSynchronization();\n    waitForAnalysisReady(client, CONFIG_SCOPE_ID);\n\n    backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(),\n      List.of(fileUri), Map.of(), false, 0)).join();\n\n    waitForRaisedIssues(client, CONFIG_SCOPE_ID);\n    var localOnlyIssue = client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID).get(0);\n    assertThat(localOnlyIssue.getSeverityMode().isLeft()).isTrue();\n    assertThat(localOnlyIssue.getSeverityMode().getLeft().getSeverity()).isEqualTo(IssueSeverity.BLOCKER);\n    assertThat(localOnlyIssue.getRuleKey()).isEqualTo(\"xml:S3421\");\n\n    var response = checkStatusChangePermitted(backend, CONNECTION_ID, localOnlyIssue.getId().toString());\n\n    assertThat(response)\n      .succeedsWithin(Duration.ofSeconds(2))\n      .extracting(CheckStatusChangePermittedResponse::isPermitted, CheckStatusChangePermittedResponse::getNotPermittedReason,\n        CheckStatusChangePermittedResponse::getAllowedStatuses)\n      .containsExactly(false, \"Marking a local-only issue as resolved requires SonarQube Server 10.2+\", List.of());\n  }\n\n  @Disabled(\"SLCORE-966\")\n  @SonarLintTest\n  void it_should_permit_status_change_on_local_only_issues_for_sonarqube_10_2_plus(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", \"\"\"\n      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <project>\n        <modelVersion>4.0.0</modelVersion>\n        <groupId>com.foo</groupId>\n        <artifactId>bar</artifactId>\n        <version>${pom.version}</version>\n      </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var branchName = \"branchName\";\n    var projectKey = \"projectKey\";\n    var serverIssueKey = \"myIssueKey\";\n    var introductionDate = Instant.now().truncatedTo(ChronoUnit.SECONDS);\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(\n        new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    when(client.matchSonarProjectBranch(eq(CONFIG_SCOPE_ID), eq(\"main\"), eq(Set.of(\"main\", branchName)), any()))\n      .thenReturn(branchName);\n    var server = harness.newFakeSonarQubeServer(\"10.2\")\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile\n        .withLanguage(\"xml\").withActiveRule(\"xml:S3421\", activeRule -> activeRule\n          .withSeverity(IssueSeverity.MAJOR)))\n      .withProject(projectKey,\n        project -> project\n          .withQualityProfile(\"qpKey\")\n          .withBranch(branchName,\n            branch -> branch.withIssue(serverIssueKey, \"xml:S3421\", \"message\",\n              \"author\", baseDir.relativize(filePath).toString(), \"1356c67d7ad1638d816bfb822dd2c25d\", Constants.Severity.MAJOR, RuleType.CODE_SMELL,\n              \"OPEN\", null, introductionDate, new TextRange(1, 1, 1, 1))))\n      .withPlugin(TestPlugin.XML)\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server)\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, projectKey)\n      .withExtraEnabledLanguagesInConnectedMode(Language.XML)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n    client.waitForSynchronization();\n\n    backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(),\n      List.of(fileUri), Map.of(), false, 0)).join();\n\n    waitForRaisedIssues(client, CONFIG_SCOPE_ID);\n    var localOnlyIssue = client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID).get(0);\n\n    var response = checkStatusChangePermitted(backend, CONNECTION_ID, localOnlyIssue.getId().toString());\n\n    assertThat(response)\n      .succeedsWithin(Duration.ofSeconds(2))\n      .extracting(CheckStatusChangePermittedResponse::isPermitted, CheckStatusChangePermittedResponse::getNotPermittedReason,\n        CheckStatusChangePermittedResponse::getAllowedStatuses)\n      .containsExactly(true, null, List.of(ResolutionStatus.WONT_FIX, ResolutionStatus.FALSE_POSITIVE));\n  }\n\n  @Disabled(\"SLCORE-966\")\n  @SonarLintTest\n  void it_should_permit_status_change_on_local_only_issues_for_sonarqube_10_4_plus(SonarLintTestHarness harness, @TempDir Path testDir) throws IOException {\n    var baseDir = testDir.resolve(\"it_should_permit_status_change_on_local_only_issues_for_sonarqube_10_4_plus\");\n    Files.createDirectory(baseDir);\n    var filePath = createFile(baseDir, \"pom.xml\", \"\"\"\n      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <project>\n        <modelVersion>4.0.0</modelVersion>\n        <groupId>com.foo</groupId>\n        <artifactId>bar</artifactId>\n        <version>${pom.version}</version>\n      </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var branchName = \"main\";\n    var projectKey = \"projectKey\";\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(\n        new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var server = harness.newFakeSonarQubeServer(\"10.4\")\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile\n        .withLanguage(\"xml\").withActiveRule(\"xml:S3421\", activeRule -> activeRule\n          .withSeverity(IssueSeverity.MAJOR)))\n      .withProject(projectKey,\n        project -> project\n          .withQualityProfile(\"qpKey\")\n          .withBranch(branchName))\n      .withPlugin(TestPlugin.XML)\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server)\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, projectKey)\n      .withExtraEnabledLanguagesInConnectedMode(Language.XML)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n    client.waitForSynchronization();\n    waitForAnalysisReady(client, CONFIG_SCOPE_ID);\n\n    backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(),\n      List.of(fileUri), Map.of(), false, 0)).join();\n\n    waitForRaisedIssues(client, CONFIG_SCOPE_ID);\n    var localOnlyIssue = client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID).get(0);\n    var response = checkStatusChangePermitted(backend, CONNECTION_ID, localOnlyIssue.getId().toString());\n\n    assertThat(response)\n      .succeedsWithin(Duration.ofSeconds(2))\n      .extracting(CheckStatusChangePermittedResponse::isPermitted, CheckStatusChangePermittedResponse::getNotPermittedReason,\n        CheckStatusChangePermittedResponse::getAllowedStatuses)\n      .containsExactly(true, null, List.of(ResolutionStatus.ACCEPT, ResolutionStatus.FALSE_POSITIVE));\n  }\n\n  private void fakeServerWithIssue(String issueKey, List<String> transitions) {\n    fakeServerWithIssue(issueKey, null, transitions);\n  }\n\n  private void fakeServerWithIssue(String issueKey, @Nullable String orgKey, List<String> transitions) {\n    var pbTransitions = Issues.Transitions.newBuilder().addAllTransitions(transitions);\n    fakeServerWithResponse(issueKey, orgKey,\n      Issues.SearchWsResponse.newBuilder().addIssues(Issues.Issue.newBuilder().setKey(issueKey).setTransitions(pbTransitions.build()).build()).build());\n  }\n\n  private void fakeServerWithResponse(String issueKey, @Nullable String orgKey, Message response) {\n    mockWebServerExtension.addProtobufResponse(apiIssueSearchPath(issueKey, orgKey), response);\n  }\n\n  private void fakeServerWithWrongBody(String issueKey) {\n    mockWebServerExtension.addResponse(apiIssueSearchPath(issueKey, null), new MockResponse.Builder().code(200).body(\"wrong body\").build());\n  }\n\n  private static String apiIssueSearchPath(String issueKey, @Nullable String orgKey) {\n    var orgParam = orgKey == null ? \"\" : \"&organization=\" + orgKey;\n    return \"/api/issues/search.protobuf?issues=\" + issueKey + \"&additionalFields=transitions\" + orgParam + \"&ps=1&p=1\";\n  }\n\n  private CompletableFuture<CheckStatusChangePermittedResponse> checkStatusChangePermitted(SonarLintTestRpcServer backend, String connectionId, String issueKey) {\n    return backend.getIssueService().checkStatusChangePermitted(new CheckStatusChangePermittedParams(connectionId, issueKey));\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/issues/IssueEventsMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.issues;\n\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonar.scanner.protocol.Constants;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.ImpactDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerIssue;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.when;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SERVER_SENT_EVENTS;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JS;\nimport static org.sonarsource.sonarlint.core.test.utils.storage.ServerIssueFixtures.aServerIssue;\nimport static utils.AnalysisUtils.analyzeFileAndGetIssue;\nimport static utils.AnalysisUtils.createFile;\n\nclass IssueEventsMediumTests {\n\n  @RegisterExtension\n  static SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @Nested\n  class WhenReceivingIssueChangedEvent {\n    private static final String CONFIG_SCOPE_ID = \"CONFIG_SCOPE_ID\";\n\n    @SonarLintTest\n    void it_should_update_issue_in_storage_with_new_resolution(SonarLintTestHarness harness) {\n      var projectKey = \"projectKey\";\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(projectKey,\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var backend = harness.newBackend()\n        .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server,\n          storage -> storage.withProject(projectKey, project -> project.withMainBranch(\"branchName\", branch -> branch.withIssue(aServerIssue(\"key1\").open()))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", projectKey)\n        .start();\n\n      server.pushEvent(\"\"\"\n        event: IssueChanged\n        data: {\\\n        \"projectKey\": \"projectKey\",\\\n        \"issues\": [{\\\n          \"issueKey\": \"key1\",\\\n          \"branchName\": \"branchName\"\\\n        }],\\\n        \"resolved\": true\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(readIssues(backend, \"connectionId\", projectKey, \"branchName\", \"file/path\"))\n        .extracting(ServerIssue::getKey, ServerIssue::isResolved)\n        .containsOnly(tuple(\"key1\", true)));\n    }\n\n    @SonarLintTest\n    void it_should_update_issue_in_storage_with_new_impacts(SonarLintTestHarness harness) {\n      var projectKey = \"projectKey\";\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(projectKey,\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var backend = harness.newBackend()\n        .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server,\n          storage -> storage.withProject(projectKey,\n            project -> project.withMainBranch(\"branchName\",\n              branch -> branch.withIssue(aServerIssue(\"key1\").withImpacts(Map.of(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.HIGH))))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", projectKey)\n        .start();\n\n      server.pushEvent(\"\"\"\n        event: IssueChanged\n        data: {\\\n        \"projectKey\": \"projectKey\",\\\n        \"issues\": [{\\\n          \"issueKey\": \"key1\",\\\n          \"branchName\": \"branchName\",\\\n          \"impacts\": [ { \"softwareQuality\": \"MAINTAINABILITY\", \"severity\": \"BLOCKER\" } ]\\\n        }],\\\n        \"resolved\": true\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(readIssues(backend, \"connectionId\", projectKey, \"branchName\", \"file/path\"))\n        .extracting(ServerIssue::getKey, ServerIssue::isResolved, ServerIssue::getImpacts)\n        .containsOnly(tuple(\"key1\", true, Map.of(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.BLOCKER))));\n    }\n\n    @SonarLintTest\n    void it_should_update_issue_in_storage_with_new_impacts_when_it_does_not_exist_in_storage(SonarLintTestHarness harness) {\n      var projectKey = \"projectKey\";\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(projectKey,\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var backend = harness.newBackend()\n        .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server,\n          storage -> storage.withProject(projectKey, project -> project.withMainBranch(\"branchName\", branch -> branch.withIssue(aServerIssue(\"key1\")))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", projectKey)\n        .start();\n\n      server.pushEvent(\"\"\"\n        event: IssueChanged\n        data: {\\\n        \"projectKey\": \"projectKey\",\\\n        \"issues\": [{\\\n          \"issueKey\": \"key1\",\\\n          \"branchName\": \"branchName\",\\\n          \"impacts\": [ { \"softwareQuality\": \"SECURITY\", \"severity\": \"BLOCKER\" } ]\\\n        }],\\\n        \"resolved\": true\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(4)).untilAsserted(() -> assertThat(readIssues(backend, \"connectionId\", projectKey, \"branchName\", \"file/path\"))\n        .extracting(ServerIssue::getKey, ServerIssue::isResolved, ServerIssue::getImpacts)\n        .containsOnly(tuple(\"key1\", true, Map.of(SoftwareQuality.SECURITY, ImpactSeverity.BLOCKER))));\n    }\n\n    @SonarLintTest\n    void it_should_update_issue_in_storage_with_new_impacts_on_different_software_quality(SonarLintTestHarness harness) {\n      var projectKey = \"projectKey\";\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(projectKey,\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var backend = harness.newBackend()\n        .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server,\n          storage -> storage.withProject(projectKey,\n            project -> project.withMainBranch(\"branchName\",\n              branch -> branch.withIssue(aServerIssue(\"key1\").withImpacts(Map.of(SoftwareQuality.SECURITY, ImpactSeverity.BLOCKER))))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", projectKey)\n        .start();\n\n      server.pushEvent(\"\"\"\n        event: IssueChanged\n        data: {\\\n        \"projectKey\": \"projectKey\",\\\n        \"issues\": [{\\\n          \"issueKey\": \"key1\",\\\n          \"branchName\": \"branchName\",\\\n          \"impacts\": [ { \"softwareQuality\": \"MAINTAINABILITY\", \"severity\": \"HIGH\" } ]\\\n        }],\\\n        \"resolved\": true\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(readIssues(backend, \"connectionId\", projectKey, \"branchName\", \"file/path\"))\n        .extracting(ServerIssue::getKey, ServerIssue::isResolved, ServerIssue::getImpacts)\n        .containsOnly(tuple(\"key1\", true, Map.of(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.HIGH, SoftwareQuality.SECURITY, ImpactSeverity.BLOCKER))));\n    }\n\n    @SonarLintTest\n    void it_should_update_issue_in_storage_with_new_severity(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var backend = harness.newBackend()\n        .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server,\n          storage -> storage.withProject(\"projectKey\",\n            project -> project.withMainBranch(\"branchName\", branch -> branch.withIssue(aServerIssue(\"key1\").withSeverity(IssueSeverity.INFO)))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n\n      server.pushEvent(\"\"\"\n        event: IssueChanged\n        data: {\\\n        \"projectKey\": \"projectKey\",\\\n        \"issues\": [{\\\n          \"issueKey\": \"key1\",\\\n          \"branchName\": \"branchName\"\\\n        }],\\\n        \"userSeverity\": \"CRITICAL\"\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(readIssues(backend, \"connectionId\", \"projectKey\", \"branchName\", \"file/path\"))\n        .extracting(ServerIssue::getKey, ServerIssue::getUserSeverity)\n        .containsOnly(tuple(\"key1\", IssueSeverity.CRITICAL)));\n    }\n\n    @SonarLintTest\n    void it_should_update_issue_in_storage_with_new_type(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var backend = harness.newBackend()\n        .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server,\n          storage -> storage.withProject(\"projectKey\",\n            project -> project.withMainBranch(\"branchName\", branch -> branch.withIssue(aServerIssue(\"key1\").withType(RuleType.VULNERABILITY)))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n\n      server.pushEvent(\"\"\"\n        event: IssueChanged\n        data: {\\\n        \"projectKey\": \"projectKey\",\\\n        \"issues\": [{\\\n          \"issueKey\": \"key1\",\\\n          \"branchName\": \"branchName\"\\\n        }],\\\n        \"userType\": \"BUG\"\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(readIssues(backend, \"connectionId\", \"projectKey\", \"branchName\", \"file/path\"))\n        .extracting(ServerIssue::getKey, ServerIssue::getType)\n        .containsOnly(tuple(\"key1\", RuleType.BUG)));\n    }\n\n    @SonarLintTest\n    void should_raise_issue_with_changed_rule_type(SonarLintTestHarness harness, @TempDir Path baseDir) {\n      var filePath = createFile(baseDir, \"Foo.java\",\n        \"public class Foo {\\n}\");\n      var fileUri = filePath.toUri();\n      var connectionId = \"connectionId\";\n      var branchName = \"branchName\";\n      var projectKey = \"projectKey\";\n      var serverIssueKey = \"myIssueKey\";\n      var client = harness.newFakeClient()\n        .withToken(connectionId, \"token\")\n        .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n        .build();\n      when(client.matchSonarProjectBranch(eq(CONFIG_SCOPE_ID), eq(\"main\"), eq(Set.of(\"main\", branchName)), any())).thenReturn(branchName);\n      var introductionDate = Instant.now().truncatedTo(ChronoUnit.SECONDS);\n      var serverWithIssues = harness.newFakeSonarQubeServer(\"10.4\")\n        .withServerSentEventsEnabled()\n        .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile.withLanguage(\"java\").withActiveRule(\"java:S2094\", activeRule -> activeRule\n          .withSeverity(org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity.MAJOR)))\n        .withProject(projectKey,\n          project -> project\n            .withQualityProfile(\"qpKey\")\n            .withBranch(branchName,\n              branch -> branch.withIssue(serverIssueKey, \"java:S2094\", \"Remove this empty class, write its code or make it an \\\"interface\\\".\",\n                \"author\", baseDir.relativize(filePath).toString(), \"1356c67d7ad1638d816bfb822dd2c25d\", Constants.Severity.MAJOR, RuleType.CODE_SMELL,\n                \"OPEN\", null, introductionDate, new TextRange(1, 13, 1, 16))))\n        .start();\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS, FULL_SYNCHRONIZATION)\n        .withSonarQubeConnection(connectionId, serverWithIssues)\n        .withBoundConfigScope(CONFIG_SCOPE_ID, connectionId, projectKey)\n        .start(client);\n      await().atMost(Duration.ofMinutes(2)).untilAsserted(() -> assertThat(client.getSynchronizedConfigScopeIds()).contains(CONFIG_SCOPE_ID));\n      analyzeFileAndGetIssue(fileUri, client, backend, CONFIG_SCOPE_ID);\n      client.cleanRaisedIssues();\n\n      serverWithIssues.pushEvent(\"\"\"\n        event: IssueChanged\n        data: {\\\n        \"projectKey\": \"projectKey\",\\\n        \"issues\": [{\\\n          \"issueKey\": \"myIssueKey\",\\\n          \"branchName\": \"branchName\"\\\n        }],\\\n        \"userType\": \"BUG\"\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofMinutes(1)).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).isNotEmpty());\n      var raisedIssues = client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID).get(fileUri);\n      assertThat(raisedIssues).isNotEmpty();\n      var raisedIssueDto = raisedIssues.get(0);\n      assertThat(raisedIssueDto.getServerKey()).isEqualTo(serverIssueKey);\n      assertThat(raisedIssueDto.getSeverityMode().isRight()).isTrue();\n      assertThat(raisedIssueDto.getSeverityMode().getRight().getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CLEAR);\n      assertThat(raisedIssueDto.getSeverityMode().getRight().getImpacts())\n        .extracting(ImpactDto::getSoftwareQuality, ImpactDto::getImpactSeverity)\n        .containsExactly(\n          tuple(org.sonarsource.sonarlint.core.rpc.protocol.common.SoftwareQuality.MAINTAINABILITY, org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity.LOW));\n    }\n\n    @SonarLintTest\n    void should_raise_issue_with_changed_resolution(SonarLintTestHarness harness, @TempDir Path baseDir) {\n      var filePath = createFile(baseDir, \"Foo.java\",\n        \"public class Foo {\\n}\");\n      var fileUri = filePath.toUri();\n      var connectionId = \"connectionId\";\n      var branchName = \"branchName\";\n      var projectKey = \"projectKey\";\n      var serverIssueKey = \"myIssueKey\";\n      var client = harness.newFakeClient()\n        .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n        .build();\n      when(client.matchSonarProjectBranch(eq(CONFIG_SCOPE_ID), eq(\"main\"), eq(Set.of(\"main\", branchName)), any())).thenReturn(branchName);\n      var introductionDate = Instant.now().truncatedTo(ChronoUnit.SECONDS);\n      var serverWithIssues = harness.newFakeSonarQubeServer(\"10.4\")\n        .withServerSentEventsEnabled()\n        .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile.withLanguage(\"java\").withActiveRule(\"java:S2094\", activeRule -> activeRule\n          .withSeverity(org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity.MAJOR)))\n        .withProject(projectKey,\n          project -> project\n            .withQualityProfile(\"qpKey\")\n            .withBranch(branchName,\n              branch -> branch.withIssue(serverIssueKey, \"java:S2094\", \"Remove this empty class, write its code or make it an \\\"interface\\\".\",\n                \"author\", baseDir.relativize(filePath).toString(), \"1356c67d7ad1638d816bfb822dd2c25d\", Constants.Severity.MAJOR, RuleType.CODE_SMELL,\n                \"OPEN\", null, introductionDate, new TextRange(1, 13, 1, 16))))\n        .start();\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS, FULL_SYNCHRONIZATION)\n        .withSonarQubeConnection(connectionId, serverWithIssues)\n        .withBoundConfigScope(CONFIG_SCOPE_ID, connectionId, projectKey)\n        .start(client);\n      await().atMost(Duration.ofMinutes(2)).untilAsserted(() -> assertThat(client.getSynchronizedConfigScopeIds()).contains(CONFIG_SCOPE_ID));\n      analyzeFileAndGetIssue(fileUri, client, backend, CONFIG_SCOPE_ID);\n      client.cleanRaisedIssues();\n\n      serverWithIssues.pushEvent(\"\"\"\n        event: IssueChanged\n        data: {\\\n        \"projectKey\": \"projectKey\",\\\n        \"issues\": [{\\\n          \"issueKey\": \"myIssueKey\",\\\n          \"branchName\": \"branchName\"\\\n        }],\\\n        \"resolved\": \"true\"\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofMinutes(1)).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).isNotEmpty());\n      var raisedIssues = client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID).get(fileUri);\n      assertThat(raisedIssues).isNotEmpty();\n      var raisedIssueDto = raisedIssues.get(0);\n      assertThat(raisedIssueDto.getServerKey()).isEqualTo(serverIssueKey);\n      assertThat(raisedIssueDto.isResolved()).isTrue();\n      assertThat(raisedIssueDto.getSeverityMode().isRight()).isTrue();\n    }\n\n    @SonarLintTest\n    void should_raise_issue_with_changed_impacts(SonarLintTestHarness harness, @TempDir Path baseDir) {\n      var filePath = createFile(baseDir, \"Foo.java\",\n        \"public class Foo {\\n}\");\n      var fileUri = filePath.toUri();\n      var connectionId = \"connectionId\";\n      var branchName = \"branchName\";\n      var projectKey = \"projectKey\";\n      var serverIssueKey = \"myIssueKey\";\n      var client = harness.newFakeClient()\n        .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n        .build();\n      when(client.matchSonarProjectBranch(eq(CONFIG_SCOPE_ID), eq(\"main\"), eq(Set.of(\"main\", branchName)), any())).thenReturn(branchName);\n      var introductionDate = Instant.now().truncatedTo(ChronoUnit.SECONDS);\n      var serverWithIssues = harness.newFakeSonarQubeServer(\"10.4\")\n        .withServerSentEventsEnabled()\n        .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile.withLanguage(\"java\").withActiveRule(\"java:S2094\", activeRule -> activeRule\n          .withSeverity(org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity.MAJOR)))\n        .withProject(projectKey,\n          project -> project\n            .withQualityProfile(\"qpKey\")\n            .withBranch(branchName,\n              branch -> branch.withIssue(serverIssueKey, \"java:S2094\", \"Remove this empty class, write its code or make it an \\\"interface\\\".\",\n                \"author\", baseDir.relativize(filePath).toString(), \"1356c67d7ad1638d816bfb822dd2c25d\", Constants.Severity.MAJOR, RuleType.CODE_SMELL,\n                \"OPEN\", null, introductionDate, new TextRange(1, 13, 1, 16), Map.of(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.LOW))))\n        .start();\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS, FULL_SYNCHRONIZATION)\n        .withSonarQubeConnection(connectionId, serverWithIssues)\n        .withBoundConfigScope(CONFIG_SCOPE_ID, connectionId, projectKey)\n        .start(client);\n      await().atMost(Duration.ofMinutes(2)).untilAsserted(() -> assertThat(client.getSynchronizedConfigScopeIds()).contains(CONFIG_SCOPE_ID));\n      analyzeFileAndGetIssue(fileUri, client, backend, CONFIG_SCOPE_ID);\n      client.cleanRaisedIssues();\n\n      serverWithIssues.pushEvent(\"\"\"\n        event: IssueChanged\n        data: {\\\n        \"projectKey\": \"projectKey\",\\\n        \"issues\": [{\\\n          \"issueKey\": \"myIssueKey\",\\\n          \"branchName\": \"branchName\",\\\n          \"impacts\": [ { \"softwareQuality\": \"MAINTAINABILITY\", \"severity\": \"BLOCKER\" } ]\\\n        }],\\\n        \"resolved\": \"true\"\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofMinutes(1)).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).isNotEmpty());\n      var raisedIssues = client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID).get(fileUri);\n      assertThat(raisedIssues).isNotEmpty();\n      var raisedIssueDto = raisedIssues.get(0);\n      assertThat(raisedIssueDto.getServerKey()).isEqualTo(serverIssueKey);\n      assertThat(raisedIssueDto.getSeverityMode().isRight()).isTrue();\n      assertThat(raisedIssueDto.getSeverityMode().getRight().getImpacts().get(0))\n        .extracting(\"softwareQuality\", \"impactSeverity\")\n        .containsOnly(\n          org.sonarsource.sonarlint.core.rpc.protocol.common.SoftwareQuality.MAINTAINABILITY,\n          org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity.BLOCKER);\n    }\n\n    @SonarLintTest\n    void should_raise_issue_with_changed_severity(SonarLintTestHarness harness, @TempDir Path baseDir) {\n      var filePath = createFile(baseDir, \"Foo.java\",\n        \"public class Foo {\\n}\");\n      var fileUri = filePath.toUri();\n      var connectionId = \"connectionId\";\n      var branchName = \"branchName\";\n      var projectKey = \"projectKey\";\n      var serverIssueKey = \"myIssueKey\";\n      var client = harness.newFakeClient()\n        .withToken(connectionId, \"token\")\n        .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n        .build();\n      when(client.matchSonarProjectBranch(eq(CONFIG_SCOPE_ID), eq(\"main\"), eq(Set.of(\"main\", branchName)), any())).thenReturn(branchName);\n      var introductionDate = Instant.now().truncatedTo(ChronoUnit.SECONDS);\n      var serverWithIssues = harness.newFakeSonarQubeServer(\"10.4\")\n        .withServerSentEventsEnabled()\n        .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile.withLanguage(\"java\").withActiveRule(\"java:S2094\", activeRule -> activeRule\n          .withSeverity(org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity.MAJOR)))\n        .withProject(projectKey,\n          project -> project\n            .withQualityProfile(\"qpKey\")\n            .withBranch(branchName,\n              branch -> branch.withIssue(serverIssueKey, \"java:S2094\", \"Remove this empty class, write its code or make it an \\\"interface\\\".\",\n                \"author\", baseDir.relativize(filePath).toString(), \"1356c67d7ad1638d816bfb822dd2c25d\", Constants.Severity.MAJOR, RuleType.CODE_SMELL,\n                \"OPEN\", null, introductionDate, new TextRange(1, 13, 1, 16))))\n        .start();\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS, FULL_SYNCHRONIZATION)\n        .withSonarQubeConnection(connectionId, serverWithIssues)\n        .withBoundConfigScope(CONFIG_SCOPE_ID, connectionId, projectKey)\n        .start(client);\n      await().atMost(Duration.ofMinutes(2)).untilAsserted(() -> assertThat(client.getSynchronizedConfigScopeIds()).contains(CONFIG_SCOPE_ID));\n      analyzeFileAndGetIssue(fileUri, client, backend, CONFIG_SCOPE_ID);\n      client.cleanRaisedIssues();\n\n      serverWithIssues.pushEvent(\"\"\"\n        event: IssueChanged\n        data: {\\\n        \"projectKey\": \"projectKey\",\\\n        \"issues\": [{\\\n          \"issueKey\": \"myIssueKey\",\\\n          \"branchName\": \"branchName\"\\\n        }],\\\n        \"userSeverity\": \"MINOR\"\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofMinutes(1)).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).isNotEmpty());\n      var raisedIssues = client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID).get(fileUri);\n      assertThat(raisedIssues).isNotEmpty();\n      var raisedIssueDto = raisedIssues.get(0);\n      assertThat(raisedIssueDto.getServerKey()).isEqualTo(serverIssueKey);\n      assertThat(raisedIssueDto.getSeverityMode().isRight()).isTrue();\n      assertThat(raisedIssueDto.getSeverityMode().getRight().getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CLEAR);\n      assertThat(raisedIssueDto.getSeverityMode().getRight().getImpacts())\n        .extracting(ImpactDto::getSoftwareQuality, ImpactDto::getImpactSeverity)\n        .containsExactly(\n          tuple(org.sonarsource.sonarlint.core.rpc.protocol.common.SoftwareQuality.MAINTAINABILITY, org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity.LOW));\n    }\n\n  }\n\n  private List<ServerIssue<?>> readIssues(SonarLintTestRpcServer backend, String connectionId, String projectKey, String branchName, String filePath) {\n    return backend.getIssueStorageService().connection(connectionId).project(projectKey).findings().load(branchName, Path.of(filePath));\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/issues/IssuesStatusChangeMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.issues;\n\nimport com.github.tomakehurst.wiremock.client.WireMock;\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.LineWithHash;\nimport org.sonarsource.sonarlint.core.commons.LocalOnlyIssue;\nimport org.sonarsource.sonarlint.core.commons.LocalOnlyIssueResolution;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.AddIssueCommentParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ChangeIssueStatusParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ReopenAllIssuesForFileParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ReopenIssueParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ResolutionStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.equalTo;\nimport static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static mediumtest.fixtures.LocalOnlyIssueFixtures.aLocalOnlyIssueResolved;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.waitAtMost;\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.test.utils.server.ServerFixture.ServerStatus.DOWN;\nimport static org.sonarsource.sonarlint.core.test.utils.storage.ServerIssueFixtures.aServerIssue;\nimport static utils.AnalysisUtils.createFile;\nimport static utils.AnalysisUtils.waitForRaisedIssues;\n\nclass IssuesStatusChangeMediumTests {\n\n  private static final String CONFIGURATION_SCOPE_ID = \"configScopeId\";\n  private static final String CONNECTION_ID = \"connectionId\";\n\n  @SonarLintTest\n  void it_should_update_the_status_on_sonarqube_when_changing_the_status_on_a_server_matched_issue(SonarLintTestHarness harness) {\n    var serverIssue = aServerIssue(\"myIssueKey\").withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\")).withIntroductionDate(Instant.EPOCH.plusSeconds(1)).withType(RuleType.BUG);\n    var server = harness.newFakeSonarQubeServer().start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(\"projectKey\", project -> project.withMainBranch(\"main\", branch -> branch.withIssue(serverIssue)))\n        .withServerVersion(\"9.8\"))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .start();\n\n    var response = backend.getIssueService().changeStatus(new ChangeIssueStatusParams(CONFIGURATION_SCOPE_ID, \"myIssueKey\",\n      ResolutionStatus.WONT_FIX, false));\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    waitAtMost(2, SECONDS).untilAsserted(() -> {\n      server.getMockServer()\n        .verify(WireMock.postRequestedFor(urlEqualTo(\"/api/issues/do_transition\"))\n          .withHeader(\"Content-Type\", equalTo(\"application/x-www-form-urlencoded\"))\n          .withRequestBody(equalTo(\"issue=myIssueKey&transition=wontfix\")));\n    });\n  }\n\n  @SonarLintTest\n  void it_should_throw_on_update_the_status_on_sonarcloud_if_issue_dont_exist_on_server_and_is_not_synchronized(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"myOrg\", organization -> organization\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"main\")))\n      .withResponseCodes(responseCodes -> responseCodes.withIssueTransitionStatusCode(404))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarCloudConnection(CONNECTION_ID, \"myOrg\", true, storageBuilder -> storageBuilder\n        .withProject(\"projectKey\", projectStorageBuilder -> projectStorageBuilder.withMainBranch(\"main\")))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .start(client);\n\n    var issueService = backend.getIssueService();\n    var params = new ChangeIssueStatusParams(CONFIGURATION_SCOPE_ID, \"myIssueKey\", ResolutionStatus.WONT_FIX, false);\n\n    var changeStatusFuture = issueService.changeStatus(params);\n    assertThrows(ExecutionException.class, changeStatusFuture::get);\n  }\n\n  @SonarLintTest\n  void it_should_update_the_status_on_sonarcloud_if_issue_exist_on_server_but_is_not_synchronized(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"myOrg\", organization -> organization\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"main\")))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarCloudConnection(CONNECTION_ID, \"myOrg\", true, storageBuilder -> storageBuilder\n        .withProject(\"projectKey\", projectStorageBuilder -> projectStorageBuilder.withMainBranch(\"main\")))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .start(client);\n\n    var issueService = backend.getIssueService();\n    var params = new ChangeIssueStatusParams(CONFIGURATION_SCOPE_ID, \"myIssueKey\", ResolutionStatus.WONT_FIX, false);\n\n    var changeStatusFuture = issueService.changeStatus(params);\n    assertDoesNotThrow(() -> changeStatusFuture.get());\n  }\n\n  @SonarLintTest\n  void it_should_throw_on_add_issue_comment_on_sonarcloud_if_issue_dont_exist_on_server_and_is_not_synchronized(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"myOrg\", organization -> organization\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"main\")))\n      .withResponseCodes(responseCodes -> responseCodes.withAddCommentStatusCode(404))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarCloudConnection(CONNECTION_ID, \"myOrg\", true, storageBuilder -> storageBuilder\n        .withProject(\"projectKey\", projectStorageBuilder -> projectStorageBuilder.withMainBranch(\"main\")))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .start(client);\n\n    var issueService = backend.getIssueService();\n    var params = new AddIssueCommentParams(CONFIGURATION_SCOPE_ID, \"myIssueKey\", \"comment\");\n    var addCommentFuture = issueService.addComment(params);\n    assertThrows(ExecutionException.class, addCommentFuture::get);\n  }\n\n  @SonarLintTest\n  void it_should_add_issue_comment_on_sonarcloud_if_issue_exist_on_server_but_is_not_synchronized(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"myOrg\", organization -> organization\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"main\")))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarCloudConnection(CONNECTION_ID, \"myOrg\", true, storageBuilder -> storageBuilder\n        .withProject(\"projectKey\", projectStorageBuilder -> projectStorageBuilder.withMainBranch(\"main\")))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .start(client);\n\n    var issueService = backend.getIssueService();\n    var params = new AddIssueCommentParams(CONFIGURATION_SCOPE_ID, \"myIssueKey\", \"comment\");\n\n    var addCommentFuture = issueService.addComment(params);\n    assertDoesNotThrow(() -> addCommentFuture.get());\n  }\n\n  @SonarLintTest\n  void it_should_update_the_telemetry_when_changing_the_status_on_a_server_matched_issue(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer().start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(\"projectKey\",\n          project -> project.withMainBranch(\"main\",\n            branch -> branch.withIssue(\n              aServerIssue(\"myIssueKey\")\n                .withRuleKey(\"rule:key\")\n                .withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\"))\n                .withIntroductionDate(Instant.EPOCH.plusSeconds(1))\n                .withType(RuleType.BUG))))\n        .withServerVersion(\"9.8\"))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .withTelemetryEnabled()\n      .start();\n\n    var response = backend.getIssueService().changeStatus(new ChangeIssueStatusParams(CONFIGURATION_SCOPE_ID, \"myIssueKey\",\n      ResolutionStatus.WONT_FIX, false));\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    assertThat(backend.telemetryFileContent().issueStatusChangedRuleKeys()).isEqualTo(Set.of(\"rule:key\"));\n  }\n\n  @SonarLintTest\n  void it_should_fail_the_future_when_the_server_returns_an_error(SonarLintTestHarness harness) {\n    var serverIssue = aServerIssue(\"myIssueKey\").withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\")).withIntroductionDate(Instant.EPOCH.plusSeconds(1)).withType(RuleType.BUG);\n    var server = harness.newFakeSonarQubeServer().withStatus(DOWN).start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(\"projectKey\", project -> project.withMainBranch(\"main\", branch -> branch.withIssue(serverIssue)))\n        .withServerVersion(\"9.8\"))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .start();\n\n    var response = backend.getIssueService().changeStatus(new ChangeIssueStatusParams(CONFIGURATION_SCOPE_ID, \"myIssueKey\",\n      ResolutionStatus.WONT_FIX, false));\n\n    assertThat(response)\n      .failsWithin(Duration.ofSeconds(2))\n      .withThrowableOfType(ExecutionException.class)\n      .havingCause()\n      .isInstanceOfSatisfying(ResponseErrorException.class, ex -> {\n        assertThat(ex.getResponseError().getData().toString()).contains(\"Error 404 on\", \"/api/issues/do_transition\");\n      });\n  }\n\n  @SonarLintTest\n  void it_should_update_local_only_storage_when_the_issue_exists_locally(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", \"\"\"\n      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <project>\n        <modelVersion>4.0.0</modelVersion>\n        <groupId>com.foo</groupId>\n        <artifactId>bar</artifactId>\n        <version>${pom.version}</version>\n      </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarQubeServer()\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile\n        .withLanguage(\"xml\").withActiveRule(\"xml:S3421\", activeRule -> activeRule.withSeverity(IssueSeverity.BLOCKER)))\n      .withProject(\"projectKey\",\n        project -> project.withQualityProfile(\"qpKey\"))\n      .start();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIGURATION_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server)\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n    client.waitForSynchronization();\n\n    backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIGURATION_SCOPE_ID, UUID.randomUUID(),\n      List.of(fileUri), Map.of(), false, 0)).join();\n\n    waitForRaisedIssues(client, CONFIGURATION_SCOPE_ID);\n    var localOnlyIssue = client.getRaisedIssuesForScopeId(CONFIGURATION_SCOPE_ID).get(fileUri).get(0);\n\n    var response = backend.getIssueService().changeStatus(new ChangeIssueStatusParams(CONFIGURATION_SCOPE_ID, localOnlyIssue.getId().toString(),\n      ResolutionStatus.WONT_FIX, false));\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    var issueLoaded = loadIssuesForFile(backend, baseDir.relativize(filePath));\n    assertThat(issueLoaded).hasSize(1);\n    assertThat(issueLoaded.get(0).getId()).isEqualTo(localOnlyIssue.getId());\n    assertThat(issueLoaded.get(0).getResolution().getStatus()).isEqualTo(org.sonarsource.sonarlint.core.commons.IssueStatus.WONT_FIX);\n  }\n\n  @SonarLintTest\n  void it_should_sync_anticipated_transitions_with_sonarqube_when_the_issue_exists_locally(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", \"\"\"\n      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <project>\n        <modelVersion>4.0.0</modelVersion>\n        <groupId>com.foo</groupId>\n        <artifactId>bar</artifactId>\n        <version>${pom.version}</version>\n      </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarQubeServer()\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile\n        .withLanguage(\"xml\").withActiveRule(\"xml:S3421\", activeRule -> activeRule.withSeverity(IssueSeverity.BLOCKER)))\n      .withProject(\"projectKey\",\n        project -> project.withQualityProfile(\"qpKey\"))\n      .start();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIGURATION_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server)\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n    client.waitForSynchronization();\n\n    backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIGURATION_SCOPE_ID, UUID.randomUUID(),\n      List.of(fileUri), Map.of(), true, 0)).join();\n\n    waitForRaisedIssues(client, CONFIGURATION_SCOPE_ID);\n    var localOnlyIssue = client.getRaisedIssuesForScopeId(CONFIGURATION_SCOPE_ID).get(fileUri).get(0);\n\n    var response = backend.getIssueService().changeStatus(new ChangeIssueStatusParams(CONFIGURATION_SCOPE_ID, localOnlyIssue.getId().toString(),\n      ResolutionStatus.WONT_FIX, false));\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    waitAtMost(2, SECONDS).untilAsserted(() -> {\n      server.getMockServer()\n        .verify(WireMock.postRequestedFor(urlEqualTo(\"/api/issues/anticipated_transitions?projectKey=projectKey\"))\n          .withHeader(\"Content-Type\", equalTo(\"application/json; charset=UTF-8\"))\n          .withRequestBody(\n            equalToJson(\n              \"[{\\\"filePath\\\":\\\"pom.xml\\\",\\\"line\\\":6,\\\"hash\\\":\\\"07bac3d9d23dc1b0d7156598e01d40b0\\\",\\\"ruleKey\\\":\\\"xml:S3421\\\",\\\"issueMessage\\\":\\\"Replace \\\\\\\"pom.version\\\\\\\" with \\\\\\\"project.version\\\\\\\".\\\",\\\"transition\\\":\\\"wontfix\\\"}]\")));\n    });\n  }\n\n  @SonarLintTest\n  void it_should_update_telemetry_when_changing_status_of_a_local_only_issue(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", \"\"\"\n      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <project>\n        <modelVersion>4.0.0</modelVersion>\n        <groupId>com.foo</groupId>\n        <artifactId>bar</artifactId>\n        <version>${pom.version}</version>\n      </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarQubeServer()\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile\n        .withLanguage(\"xml\").withActiveRule(\"xml:S3421\", activeRule -> activeRule.withSeverity(IssueSeverity.BLOCKER)))\n      .withProject(\"projectKey\",\n        project -> project.withQualityProfile(\"qpKey\"))\n      .start();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIGURATION_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server)\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .withTelemetryEnabled()\n      .start(client);\n    client.waitForSynchronization();\n\n    backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIGURATION_SCOPE_ID, UUID.randomUUID(),\n      List.of(fileUri), Map.of(), false, 0)).join();\n\n    waitForRaisedIssues(client, CONFIGURATION_SCOPE_ID);\n    var localOnlyIssue = client.getRaisedIssuesForScopeId(CONFIGURATION_SCOPE_ID).get(fileUri).get(0);\n\n    var response = backend.getIssueService().changeStatus(new ChangeIssueStatusParams(CONFIGURATION_SCOPE_ID, localOnlyIssue.getId().toString(),\n      ResolutionStatus.WONT_FIX, false));\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    assertThat(backend.telemetryFileContent().issueStatusChangedRuleKeys()).isEqualTo(Set.of(\"xml:S3421\"));\n  }\n\n  @SonarLintTest\n  void it_should_fail_when_the_issue_does_not_exists(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer().withStatus(DOWN).start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server)\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .start();\n\n    var params = new ChangeIssueStatusParams(CONFIGURATION_SCOPE_ID, \"myIssueKey\", ResolutionStatus.WONT_FIX, false);\n    var issueService = backend.getIssueService();\n\n    assertThat(issueService.changeStatus(params))\n      .failsWithin(2, TimeUnit.SECONDS)\n      .withThrowableOfType(ExecutionException.class)\n      .withCauseExactlyInstanceOf(ResponseErrorException.class)\n      .withMessageContaining(\"Issue key myIssueKey was not found\");\n  }\n\n  @SonarLintTest\n  void it_should_add_new_comment_to_server_issue(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer().start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server,\n        storage -> storage.withProject(\"projectKey\", project -> project.withMainBranch(\"main\", branch -> branch.withIssue(aServerIssue(\"myIssueKey\")))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .start();\n\n    var response = backend.getIssueService().addComment(new AddIssueCommentParams(CONFIGURATION_SCOPE_ID, \"myIssueKey\", \"That's \" +\n      \"serious issue\"));\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    waitAtMost(2, SECONDS).untilAsserted(() -> {\n      server.getMockServer()\n        .verify(WireMock.postRequestedFor(urlEqualTo(\"/api/issues/add_comment\"))\n          .withHeader(\"Content-Type\", equalTo(\"application/x-www-form-urlencoded\"))\n          .withRequestBody(equalTo(\"issue=myIssueKey&text=That%27s+serious+issue\")));\n    });\n  }\n\n  @SonarLintTest\n  void it_should_add_new_comment_to_server_issue_with_uuid_key(SonarLintTestHarness harness) {\n    var issueKey = UUID.randomUUID().toString();\n    var server = harness.newFakeSonarQubeServer().start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server,\n        storage -> storage.withProject(\"projectKey\", project -> project.withMainBranch(\"main\", branch -> branch.withIssue(aServerIssue(issueKey)))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .start();\n\n    var response = backend.getIssueService().addComment(new AddIssueCommentParams(CONFIGURATION_SCOPE_ID, issueKey, \"That's \" +\n      \"serious issue\"));\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    waitAtMost(2, SECONDS).untilAsserted(() -> {\n      server.getMockServer()\n        .verify(WireMock.postRequestedFor(urlEqualTo(\"/api/issues/add_comment\"))\n          .withHeader(\"Content-Type\", equalTo(\"application/x-www-form-urlencoded\"))\n          .withRequestBody(equalTo(\"issue=\" + issueKey + \"&text=That%27s+serious+issue\")));\n    });\n  }\n\n  @SonarLintTest\n  void it_should_add_new_comment_to_resolved_local_only_issue(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer().start();\n    var issueId = UUID.randomUUID();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server)\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\", storage -> storage.withLocalOnlyIssue(aLocalOnlyIssueResolved(issueId)))\n      .start();\n\n    var response = backend.getIssueService().addComment(new AddIssueCommentParams(CONFIGURATION_SCOPE_ID, issueId.toString(), \"That's \" +\n      \"serious issue\"));\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    var storedIssues = loadIssuesForFile(backend, Path.of(\"file/path\"));\n    assertThat(storedIssues)\n      .extracting(LocalOnlyIssue::getResolution)\n      .extracting(LocalOnlyIssueResolution::getComment)\n      .containsOnly(\"That's serious issue\");\n  }\n\n  @SonarLintTest\n  void it_should_throw_if_server_response_is_not_OK_during_add_new_comment_to_issue(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer().withStatus(DOWN).start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server,\n        storage -> storage.withProject(\"projectKey\", project -> project.withMainBranch(\"main\", branch -> branch.withIssue(aServerIssue(\"myIssueKey\")))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .start();\n\n    var response = backend.getIssueService().addComment(new AddIssueCommentParams(CONFIGURATION_SCOPE_ID, \"myIssueKey\", \"That's \" +\n      \"serious issue\"));\n\n    assertThat(response)\n      .failsWithin(Duration.ofSeconds(2))\n      .withThrowableOfType(ExecutionException.class)\n      .havingCause()\n      .isInstanceOfSatisfying(ResponseErrorException.class, ex -> {\n        assertThat(ex.getResponseError().getData().toString()).contains(\"Error 404 on\", \"/api/issues/add_comment\");\n      });\n  }\n\n  @SonarLintTest\n  void it_should_throw_if_issue_is_unknown_when_adding_a_comment(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer()\n      .withResponseCodes(responseCodes -> responseCodes.withAddCommentStatusCode(404))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server)\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .start();\n\n    var response = backend.getIssueService().addComment(new AddIssueCommentParams(CONFIGURATION_SCOPE_ID, \"myIssueKey\", \"That's \" +\n      \"serious issue\"));\n\n    assertThat(response)\n      .failsWithin(Duration.ofSeconds(2))\n      .withThrowableOfType(ExecutionException.class)\n      .havingCause()\n      .isInstanceOfSatisfying(ResponseErrorException.class, ex -> {\n        assertThat(ex.getResponseError().getData().toString()).contains(\"myIssueKey\");\n      });\n  }\n\n  @SonarLintTest\n  void it_should_throw_if_issue_with_uuid_key_is_unknown_when_adding_a_comment(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer().start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server)\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .start();\n    var issueKey = UUID.randomUUID().toString();\n\n    var response = backend.getIssueService().addComment(new AddIssueCommentParams(CONFIGURATION_SCOPE_ID, issueKey, \"That's \" +\n      \"serious issue\"));\n\n    assertThat(response)\n      .failsWithin(Duration.ofSeconds(2))\n      .withThrowableOfType(ExecutionException.class)\n      .havingCause()\n      .isInstanceOfSatisfying(ResponseErrorException.class, ex -> {\n        assertThat(ex.getResponseError().getData().toString()).contains(issueKey);\n      });\n  }\n\n  @SonarLintTest\n  void it_should_reopen_issue_by_id(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    var server = harness.newFakeSonarQubeServer().start();\n    var issueId = UUID.randomUUID();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server)\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\", storage -> storage.withLocalOnlyIssue(aLocalOnlyIssueResolved(issueId)))\n      .start();\n    var storedIssues = loadIssues(backend);\n    assertThat(storedIssues).extracting(LocalOnlyIssue::getId).containsOnly(issueId);\n\n    var response = backend.getIssueService().reopenIssue(new ReopenIssueParams(CONFIGURATION_SCOPE_ID, issueId.toString(), false));\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    assertThat(response.get().isSuccess()).isTrue();\n    storedIssues = loadIssues(backend);\n    assertThat(storedIssues).isEmpty();\n  }\n\n  @SonarLintTest\n  void it_should_load_issues(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer().start();\n    var issueId1 = UUID.randomUUID();\n    var issueId2 = UUID.randomUUID();\n    var otherFileIssueId = UUID.randomUUID();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server)\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\",\n        storage -> storage\n          .withLocalOnlyIssue(new LocalOnlyIssue(\n            otherFileIssueId,\n            Path.of(\"file/path1\"),\n            new TextRangeWithHash(1, 2, 3, 4, \"ab12\"),\n            new LineWithHash(1, \"linehash\"),\n            \"ruleKey\",\n            \"message\",\n            new LocalOnlyIssueResolution(org.sonarsource.sonarlint.core.commons.IssueStatus.WONT_FIX, Instant.now().truncatedTo(ChronoUnit.MILLIS), \"comment\")))\n          .withLocalOnlyIssue(aLocalOnlyIssueResolved(issueId1))\n          .withLocalOnlyIssue(aLocalOnlyIssueResolved(issueId2)))\n      .start();\n\n    var issuesForFile = loadIssuesForFile(backend, Path.of(\"file/path\"));\n    var issuesForOtherFile = loadIssuesForFile(backend, Path.of(\"file/path1\"));\n    var allIssues = loadIssues(backend);\n    assertThat(issuesForFile).extracting(LocalOnlyIssue::getId).containsOnly(issueId1, issueId2);\n    assertThat(issuesForOtherFile).extracting(LocalOnlyIssue::getId).containsOnly(otherFileIssueId);\n    assertThat(allIssues).extracting(LocalOnlyIssue::getId).containsOnly(issueId1, issueId2, otherFileIssueId);\n  }\n\n  @SonarLintTest\n  void it_should_reopen_all_issues_for_file(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    var server = harness.newFakeSonarQubeServer().start();\n    var issueId1 = UUID.randomUUID();\n    var issueId2 = UUID.randomUUID();\n    var otherFileIssueId = UUID.randomUUID();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server)\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\",\n        storage -> storage\n          .withLocalOnlyIssue(aLocalOnlyIssueResolved(issueId1))\n          .withLocalOnlyIssue(aLocalOnlyIssueResolved(issueId2))\n          .withLocalOnlyIssue(new LocalOnlyIssue(\n            otherFileIssueId,\n            Path.of(\"file/path1\"),\n            new TextRangeWithHash(1, 2, 3, 4, \"ab12\"),\n            new LineWithHash(1, \"linehash\"),\n            \"ruleKey\",\n            \"message\",\n            new LocalOnlyIssueResolution(org.sonarsource.sonarlint.core.commons.IssueStatus.WONT_FIX, Instant.now().truncatedTo(ChronoUnit.MILLIS), \"comment\"))))\n      .start();\n    var storedIssues = loadIssues(backend);\n    assertThat(storedIssues).extracting(LocalOnlyIssue::getId).containsOnly(issueId1, issueId2, otherFileIssueId);\n\n    var response = backend.getIssueService()\n      .reopenAllIssuesForFile(new ReopenAllIssuesForFileParams(CONFIGURATION_SCOPE_ID, Path.of(\"file/path\")));\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    assertThat(response.get().isSuccess()).isTrue();\n    storedIssues = loadIssues(backend);\n    assertThat(storedIssues).extracting(LocalOnlyIssue::getId).containsOnly(otherFileIssueId);\n  }\n\n  @SonarLintTest\n  void it_should_return_false_on_reopen_issue_with_invalid_id(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    var server = harness.newFakeSonarQubeServer().start();\n    var issueId = UUID.randomUUID();\n    var invalidIssueId = \"invalid-id\";\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server)\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\", storage -> storage.withLocalOnlyIssue(aLocalOnlyIssueResolved(issueId)))\n      .start();\n\n    var response = backend.getIssueService().reopenIssue(new ReopenIssueParams(CONFIGURATION_SCOPE_ID, invalidIssueId, false));\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    assertThat(response.get().isSuccess()).isFalse();\n    var storedIssues = loadIssuesForFile(backend, Path.of(\"file/path\"));\n    assertThat(storedIssues).extracting(LocalOnlyIssue::getId).containsOnly(issueId);\n  }\n\n  @SonarLintTest\n  void it_should_return_false_on_reopen_non_existing_issue(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    var server = harness.newFakeSonarQubeServer().start();\n    var issueId = UUID.randomUUID();\n    var nonExistingIssueId = UUID.randomUUID();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server)\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\", storage -> storage.withLocalOnlyIssue(aLocalOnlyIssueResolved(issueId)))\n      .start();\n\n    var response = backend.getIssueService().reopenIssue(new ReopenIssueParams(CONFIGURATION_SCOPE_ID, nonExistingIssueId.toString(), false));\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    assertThat(response.get().isSuccess()).isFalse();\n    var storedIssues = loadIssuesForFile(backend, Path.of(\"file/path\"));\n    assertThat(storedIssues)\n      .extracting(LocalOnlyIssue::getId)\n      .containsOnly(issueId);\n  }\n\n  @SonarLintTest\n  void it_should_return_true_on_reopening_server_issue(SonarLintTestHarness harness) throws ExecutionException, InterruptedException {\n    var serverIssue = aServerIssue(\"myIssueKey\").withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\")).withIntroductionDate(Instant.EPOCH.plusSeconds(1)).withType(RuleType.BUG)\n      .resolved(IssueStatus.ACCEPT);\n    var server = harness.newFakeSonarQubeServer().start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(\"projectKey\", project -> project.withMainBranch(\"main\", branch -> branch.withIssue(serverIssue)))\n        .withServerVersion(\"9.8\"))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .start();\n\n    var reopenResponse = backend.getIssueService().reopenIssue(new ReopenIssueParams(CONFIGURATION_SCOPE_ID, \"myIssueKey\", false));\n\n    assertThat(reopenResponse).succeedsWithin(Duration.ofSeconds(2));\n    assertThat(reopenResponse.get().isSuccess()).isTrue();\n  }\n\n  private static List<LocalOnlyIssue> loadIssuesForFile(SonarLintTestRpcServer backend, Path path) {\n    return backend.getLocalOnlyIssuesRepository().loadForFile(CONFIGURATION_SCOPE_ID, path);\n  }\n\n  private static List<LocalOnlyIssue> loadIssues(SonarLintTestRpcServer backend) {\n    return backend.getLocalOnlyIssuesRepository().loadAll(CONFIGURATION_SCOPE_ID);\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/issues/LocalOnlyResolvedIssuesStorageMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.issues;\n\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport uk.org.webcompere.systemstubs.environment.EnvironmentVariables;\nimport uk.org.webcompere.systemstubs.jupiter.SystemStub;\nimport uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension;\n\nimport static mediumtest.fixtures.LocalOnlyIssueFixtures.aLocalOnlyIssueResolved;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.sonarsource.sonarlint.core.commons.dogfood.DogfoodEnvironmentDetectionService.SONARSOURCE_DOGFOODING_ENV_VAR_KEY;\nimport static org.sonarsource.sonarlint.core.test.utils.storage.ServerIssueFixtures.aServerIssue;\n\n@ExtendWith(SystemStubsExtension.class)\nclass LocalOnlyResolvedIssuesStorageMediumTests {\n\n  @SystemStub\n  EnvironmentVariables environmentVariables;\n\n  @BeforeEach\n  void prepare() {\n    environmentVariables.remove(SONARSOURCE_DOGFOODING_ENV_VAR_KEY);\n  }\n\n  @SonarLintTest\n  void it_should_purge_local_only_stored_issues_resolved_more_than_one_week_ago_at_startup(SonarLintTestHarness harness) {\n    var serverIssue = aServerIssue(\"myIssueKey\").withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\")).withIntroductionDate(Instant.EPOCH.plusSeconds(1)).withType(RuleType.BUG);\n    var server = harness.newFakeSonarQubeServer().start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server.baseUrl(), storage -> storage\n        .withProject(\"projectKey\", project -> project.withMainBranch(\"main\", branch -> branch.withIssue(serverIssue)))\n        .withServerVersion(\"9.8\"))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\",\n        storage -> storage.withLocalOnlyIssue(aLocalOnlyIssueResolved(Instant.now().minus(1, ChronoUnit.MINUTES).minus(7, ChronoUnit.DAYS))))\n      .start();\n\n    var storedIssues = backend.getLocalOnlyIssuesRepository().loadAll(\"configScopeId\");\n\n    assertThat(storedIssues).isEmpty();\n  }\n\n  @SonarLintTest\n  void it_should_migrate_the_local_only_issues_from_xodus_to_the_new_h2_database(SonarLintTestHarness harness) {\n    environmentVariables.set(SONARSOURCE_DOGFOODING_ENV_VAR_KEY, \"1\");\n    var serverIssue = aServerIssue(\"myIssueKey\").withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\")).withIntroductionDate(Instant.EPOCH.plusSeconds(1)).withType(RuleType.BUG);\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", harness.newFakeSonarQubeServer().start(), storage -> storage\n        .withProject(\"projectKey\", project -> project.withMainBranch(\"main\", branch -> branch.withIssue(serverIssue)))\n        .withServerVersion(\"9.8\"))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\",\n        storage -> storage\n          .usingXodus()\n          .withLocalOnlyIssue(aLocalOnlyIssueResolved()))\n      .start();\n\n    var issues = backend.getLocalOnlyIssuesRepository().loadForFile(\"configScopeId\", Paths.get(\"file/path\"));\n\n    assertThat(issues).hasSize(1);\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/issues/OpenFixSuggestionInIdeMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.issues;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.stubbing.Answer;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.file.FilePathTranslation;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidUpdateConnectionsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarCloudConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.AssistBindingResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fix.FixSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.MessageType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AiSuggestionSource;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryFixSuggestionReceivedCounter;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintBackendFixture;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport org.sonarsource.sonarlint.core.test.utils.server.ServerFixture;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.after;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.verify;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.EMBEDDED_SERVER;\nimport static utils.AnalysisUtils.createFile;\n\nclass OpenFixSuggestionInIdeMediumTests {\n\n  public static final String PROJECT_KEY = \"projectKey\";\n  @RegisterExtension\n  SonarLintLogTester logTester = new SonarLintLogTester(true);\n\n  private static final String ISSUE_KEY = \"myIssueKey\";\n  private static final String CONNECTION_ID = \"connectionId\";\n  private static final String CONFIG_SCOPE_ID = \"configScopeId\";\n  private static final String BRANCH_NAME = \"branchName\";\n  private static final String ORG_KEY = \"orgKey\";\n  private static final String FIX_PAYLOAD = \"\"\"\n    {\n    \"fileEdit\": {\n    \"path\": \"Main.java\",\n    \"changes\": [{\n    \"beforeLineRange\": {\n    \"startLine\": 0,\n    \"endLine\": 1\n    },\n    \"before\": \"\",\n    \"after\": \"var fix = 1;\"\n    }]\n    },\n    \"suggestionId\": \"eb93b2b4-f7b0-4b5c-9460-50893968c264\",\n    \"explanation\": \"Modifying the variable name is good\"\n    }\n    \"\"\";\n\n  @SonarLintTest\n  void it_should_update_the_telemetry_on_show_issue(SonarLintTestHarness harness, @TempDir Path baseDir) throws Exception {\n    var inputFile = createFile(baseDir, \"Main.java\", \"\");\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, List.of(new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIG_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var scServer = buildSonarCloudServer(harness).start();\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(scServer.baseUrl())\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .withBackendCapability(EMBEDDED_SERVER)\n      .withTelemetryEnabled()\n      .start(fakeClient);\n\n    assertThat(backend.telemetryFileContent().getFixSuggestionReceivedCounter()).isEmpty();\n\n    var statusCode = executeOpenFixSuggestionRequestWithoutToken(backend, scServer, FIX_PAYLOAD, ISSUE_KEY, PROJECT_KEY, BRANCH_NAME, ORG_KEY);\n\n    assertThat(statusCode).isEqualTo(200);\n    await().atMost(2, TimeUnit.SECONDS)\n      .untilAsserted(() -> assertThat(backend.telemetryFileContent().getFixSuggestionReceivedCounter())\n        .isEqualTo(Map.of(\"eb93b2b4-f7b0-4b5c-9460-50893968c264\", new TelemetryFixSuggestionReceivedCounter(AiSuggestionSource.SONARCLOUD, 1, false))));\n  }\n\n  @SonarLintTest\n  void it_should_open_a_fix_suggestion_in_ide(SonarLintTestHarness harness, @TempDir Path baseDir) throws Exception {\n    var inputFile = createFile(baseDir, \"Main.java\", \"\");\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, List.of(new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIG_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var scServer = buildSonarCloudServer(harness).start();\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(scServer.baseUrl())\n      .withSonarCloudConnection(CONNECTION_ID, ORG_KEY)\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start(fakeClient);\n\n    var statusCode = executeOpenFixSuggestionRequestWithoutToken(backend, scServer, FIX_PAYLOAD, ISSUE_KEY, PROJECT_KEY, BRANCH_NAME, ORG_KEY);\n    assertThat(statusCode).isEqualTo(200);\n\n    ArgumentCaptor<FixSuggestionDto> captor = ArgumentCaptor.captor();\n    verify(fakeClient, timeout(2000)).showFixSuggestion(eq(CONFIG_SCOPE_ID), eq(ISSUE_KEY), captor.capture());\n\n    var pathTranslation = new FilePathTranslation(Path.of(\"ide\"), Path.of(\"home\"));\n\n    var fixSuggestion = captor.getValue();\n    assertThat(fixSuggestion).isNotNull();\n    assertThat(fixSuggestion.suggestionId()).isEqualTo(\"eb93b2b4-f7b0-4b5c-9460-50893968c264\");\n    assertThat(fixSuggestion.explanation()).isEqualTo(\"Modifying the variable name is good\");\n    assertThat(fixSuggestion.fileEdit().idePath().toString()).contains(pathTranslation.serverToIdePath(Paths.get(\"Main.java\")).toString());\n    assertThat(fixSuggestion.fileEdit().changes()).hasSize(1);\n    var change = fixSuggestion.fileEdit().changes().get(0);\n    assertThat(change.before()).isEmpty();\n    assertThat(change.after()).isEqualTo(\"var fix = 1;\");\n    assertThat(change.beforeLineRange().getStartLine()).isZero();\n    assertThat(change.beforeLineRange().getEndLine()).isEqualTo(1);\n  }\n\n  @SonarLintTest\n  void it_should_assist_creating_the_binding_if_scope_not_bound(SonarLintTestHarness harness, @TempDir Path baseDir) throws Exception {\n    var inputFile = createFile(baseDir, \"Main.java\", \"\");\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, List.of(new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIG_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n\n    var scServer = buildSonarCloudServer(harness).start();\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(scServer.baseUrl())\n      .withUnboundConfigScope(CONFIG_SCOPE_ID, PROJECT_KEY)\n      .withBackendCapability(EMBEDDED_SERVER)\n      .beforeInitialize(createdBackend -> {\n        mockAssistCreatingConnection(createdBackend, fakeClient, CONNECTION_ID);\n        mockAssistBinding(createdBackend, fakeClient, CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY);\n      })\n      .start(fakeClient);\n\n    var statusCode = executeOpenFixSuggestionRequestWithoutToken(backend, scServer, FIX_PAYLOAD, ISSUE_KEY, PROJECT_KEY, BRANCH_NAME, ORG_KEY);\n    assertThat(statusCode).isEqualTo(200);\n\n    verify(fakeClient, timeout(2000)).showFixSuggestion(eq(CONFIG_SCOPE_ID), eq(ISSUE_KEY), any());\n    verify(fakeClient, never()).showMessage(any(), any());\n  }\n\n  @SonarLintTest\n  void it_should_not_assist_binding_if_multiple_suggestions(SonarLintTestHarness harness, @TempDir Path baseDir) throws Exception {\n    var inputFile = createFile(baseDir, \"Main.java\", \"\");\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, List.of(new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIG_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var scServer = buildSonarCloudServer(harness).start();\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(scServer.baseUrl())\n      .withUnboundConfigScope(\"configScopeA\", PROJECT_KEY + \" 1\")\n      .withUnboundConfigScope(\"configScopeB\", PROJECT_KEY + \" 2\")\n      .withBackendCapability(EMBEDDED_SERVER)\n      .beforeInitialize(createdBackend -> {\n        mockAssistCreatingConnection(createdBackend, fakeClient, CONNECTION_ID);\n        mockAssistBinding(createdBackend, fakeClient, CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY);\n      })\n      .start(fakeClient);\n\n    var statusCode = executeOpenFixSuggestionRequestWithoutToken(backend, scServer, FIX_PAYLOAD, ISSUE_KEY, PROJECT_KEY, BRANCH_NAME, ORG_KEY);\n\n    assertThat(statusCode).isEqualTo(200);\n    // Since noBindingSuggestionFound now has a NoBindingSuggestionFoundParams parameter, we can just check for any!\n    verify(fakeClient, timeout(1000)).noBindingSuggestionFound(any());\n    verify(fakeClient, never()).showIssue(any(), any());\n  }\n\n  @SonarLintTest\n  void it_should_assist_binding_if_multiple_suggestions_but_scopes_are_parent_and_child(SonarLintTestHarness harness, @TempDir Path baseDir) throws Exception {\n    var inputFile = createFile(baseDir, \"Main.java\", \"\");\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScopeParent\",\n        List.of(new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), \"configScopeParent\", false, null, inputFile, null, null, true)))\n      .withInitialFs(\"configScopeChild\", List.of())\n      .build();\n    var scServer = buildSonarCloudServer(harness).start();\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(scServer.baseUrl())\n      .withUnboundConfigScope(\"configScopeParent\", PROJECT_KEY)\n      .withUnboundConfigScope(\"configScopeChild\", PROJECT_KEY, \"configScopeParent\")\n      .withBackendCapability(EMBEDDED_SERVER)\n      .beforeInitialize(createdBackend -> {\n        mockAssistCreatingConnection(createdBackend, fakeClient, CONNECTION_ID);\n        mockAssistBinding(createdBackend, fakeClient, \"configScopeParent\", CONNECTION_ID, PROJECT_KEY);\n      })\n      .start(fakeClient);\n\n    var statusCode = executeOpenFixSuggestionRequestWithoutToken(backend, scServer, FIX_PAYLOAD, ISSUE_KEY, PROJECT_KEY, BRANCH_NAME, ORG_KEY);\n\n    assertThat(statusCode).isEqualTo(200);\n    verify(fakeClient, timeout(2000)).showFixSuggestion(any(), any(), any());\n    verify(fakeClient, never()).showMessage(any(), any());\n  }\n\n  @SonarLintTest\n  void it_should_assist_creating_the_connection_when_no_sc_connection(SonarLintTestHarness harness, @TempDir Path baseDir) throws Exception {\n    var inputFile = createFile(baseDir, \"Main.java\", \"\");\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, List.of(new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIG_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n\n    var scServer = buildSonarCloudServer(harness).start();\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(scServer.baseUrl())\n      .withUnboundConfigScope(CONFIG_SCOPE_ID, PROJECT_KEY)\n      .withBackendCapability(EMBEDDED_SERVER)\n      .beforeInitialize(createdBackend -> {\n        mockAssistCreatingConnection(createdBackend, fakeClient, CONNECTION_ID);\n        mockAssistBinding(createdBackend, fakeClient, CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY);\n      })\n      .start(fakeClient);\n\n    var statusCode = executeOpenFixSuggestionRequestWithToken(backend, scServer, FIX_PAYLOAD, ISSUE_KEY, PROJECT_KEY, BRANCH_NAME, ORG_KEY, \"token-name\", \"token-value\");\n    assertThat(statusCode).isEqualTo(200);\n\n    verify(fakeClient, timeout(2000)).showFixSuggestion(eq(CONFIG_SCOPE_ID), eq(ISSUE_KEY), any());\n    verify(fakeClient, never()).showMessage(any(), any());\n\n    ArgumentCaptor<AssistCreatingConnectionParams> captor = ArgumentCaptor.captor();\n    verify(fakeClient, timeout(1000)).assistCreatingConnection(captor.capture(), any());\n    assertThat(captor.getAllValues())\n      .extracting(connectionParams -> connectionParams.getConnectionParams().getRight().getOrganizationKey(),\n        AssistCreatingConnectionParams::getTokenName,\n        AssistCreatingConnectionParams::getTokenValue)\n      .containsExactly(tuple(ORG_KEY, \"token-name\", \"token-value\"));\n  }\n\n  @SonarLintTest\n  void it_should_fail_request_when_issue_parameter_missing(SonarLintTestHarness harness) throws Exception {\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start();\n    var scServer = buildSonarCloudServer(harness).start();\n\n    var statusCode = executeOpenFixSuggestionRequestWithoutToken(backend, scServer, FIX_PAYLOAD, \"\", PROJECT_KEY, BRANCH_NAME, ORG_KEY);\n\n    assertThat(statusCode).isEqualTo(400);\n  }\n\n  @SonarLintTest\n  void it_should_fail_request_when_project_parameter_missing(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start();\n    var scServer = buildSonarCloudServer(harness).start();\n\n    var statusCode = executeOpenFixSuggestionRequestWithoutToken(backend, scServer, ISSUE_KEY, \"\", \"\", \"\", \"\");\n\n    assertThat(statusCode).isEqualTo(400);\n  }\n\n  @SonarLintTest\n  void it_should_fail_when_origin_is_missing(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var fakeClient = harness.newFakeClient().build();\n    var scServer = buildSonarCloudServer(harness).start();\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(scServer.baseUrl())\n      .withSonarCloudConnection(CONNECTION_ID, ORG_KEY)\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start(fakeClient);\n    HttpRequest request = HttpRequest.newBuilder()\n      .uri(URI.create(\n        \"http://localhost:\" + backend.getEmbeddedServerPort() + \"/sonarlint/api/fix/show?server=\" + scServer.baseUrl() + \"&issue=\" + ISSUE_KEY +\n          \"&project=\" + PROJECT_KEY + \"&branch=\" + BRANCH_NAME + \"&organizationKey=\" + ORG_KEY))\n      .POST(HttpRequest.BodyPublishers.ofString(FIX_PAYLOAD)).build();\n    var response = java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n\n    var statusCode = response.statusCode();\n\n    assertThat(statusCode).isEqualTo(400);\n  }\n\n  @SonarLintTest\n  void it_should_fail_when_origin_does_not_match(SonarLintTestHarness harness, @TempDir Path baseDir) throws Exception {\n    var inputFile = createFile(baseDir, \"Main.java\", \"\");\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, List.of(new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIG_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n    var scServer = buildSonarCloudServer(harness).start();\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(scServer.baseUrl())\n      .withSonarCloudConnection(CONNECTION_ID, ORG_KEY)\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start(fakeClient);\n    HttpRequest request = HttpRequest.newBuilder()\n      .uri(URI.create(\n        \"http://localhost:\" + backend.getEmbeddedServerPort() + \"/sonarlint/api/fix/show?server=\" + scServer.baseUrl() + \"&issue=\" + ISSUE_KEY +\n          \"&project=\" + PROJECT_KEY + \"&branch=\" + BRANCH_NAME + \"&organizationKey=\" + ORG_KEY))\n      .header(\"Origin\", \"malicious\")\n      .POST(HttpRequest.BodyPublishers.ofString(FIX_PAYLOAD)).build();\n    var response = java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n\n    var statusCode = response.statusCode();\n\n    assertThat(statusCode).isEqualTo(200);\n\n    ArgumentCaptor<LogParams> captor = ArgumentCaptor.captor();\n    verify(fakeClient, after(500).atLeastOnce()).log(captor.capture());\n    assertThat(captor.getAllValues())\n      .extracting(LogParams::getMessage)\n      .containsAnyOf(\"The origin 'malicious' is not trusted, this could be a malicious request\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_request_when_server_points_to_sonarcloud(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start(client);\n    HttpRequest request = openFixSuggestionRequest(backend,\n      \"\",\n      org.sonarsource.sonarlint.core.SonarCloudRegion.EU.getProductionUri().toString(),\n      \"someOrigin\");\n    var response = java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n\n    assertThat(response.statusCode()).isEqualTo(400);\n    verify(client).showMessage(MessageType.ERROR,\n      \"Invalid request to SonarQube backend. The 'server' parameter should not be SonarQube Cloud URL, use it only to specify URL of a SonarQube Server.\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_request_when_server_points_to_sonarcloud_us(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start(client);\n    HttpRequest request = openFixSuggestionRequest(backend,\n      \"\",\n      org.sonarsource.sonarlint.core.SonarCloudRegion.US.getProductionUri().toString(),\n      \"someOrigin\");\n\n    var response = java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n\n    assertThat(response.statusCode()).isEqualTo(400);\n    verify(client).showMessage(MessageType.ERROR,\n      \"Invalid request to SonarQube backend. The 'server' parameter should not be SonarQube Cloud URL, use it only to specify URL of a SonarQube Server.\");\n  }\n\n  private Object executeOpenFixSuggestionRequestWithToken(SonarLintTestRpcServer backend, ServerFixture.Server scServer, String payload, String issueKey, String projectKey,\n    String branchName, String orgKey,\n    String tokenName, String tokenValue) throws IOException, InterruptedException {\n    HttpRequest request = openFixSuggestionRequest(backend, scServer, payload, \"&issue=\" + issueKey, \"&project=\" + projectKey, \"&branch=\" + branchName,\n      \"&organizationKey=\" + orgKey, \"&tokenName=\" + tokenName, \"&tokenValue=\" + tokenValue);\n    var response = java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n    return response.statusCode();\n  }\n\n  private Object executeOpenFixSuggestionRequestWithoutToken(SonarLintTestRpcServer backend, ServerFixture.Server scServer, String payload, String issueKey, String projectKey,\n    String branchName, String orgKey) throws IOException, InterruptedException {\n    HttpRequest request = openFixSuggestionRequest(backend, scServer, payload, \"&issue=\" + issueKey, \"&project=\" + projectKey, \"&branch=\" + branchName,\n      \"&organizationKey=\" + orgKey);\n    var response = java.net.http.HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n    return response.statusCode();\n  }\n\n  private HttpRequest openFixSuggestionRequest(SonarLintTestRpcServer backend, ServerFixture.Server scServer, String payload, String... params) {\n    return openFixSuggestionRequest(backend, payload, scServer.baseUrl(), scServer.baseUrl(), params);\n  }\n\n  private static HttpRequest openFixSuggestionRequest(SonarLintTestRpcServer backend, String payload, String server, String origin, String... params) {\n    return HttpRequest.newBuilder()\n      .uri(URI.create(\n        \"http://localhost:\" + backend.getEmbeddedServerPort() + \"/sonarlint/api/fix/show?server=\" + server + String.join(\"\", params)))\n      .POST(HttpRequest.BodyPublishers.ofString(payload))\n      .header(\"Origin\", origin)\n      .build();\n  }\n\n  private void mockAssistBinding(SonarLintTestRpcServer backend, SonarLintBackendFixture.FakeSonarLintRpcClient fakeClient, String configScopeId, String connectionId,\n    String sonarProjectKey) {\n    doAnswer((Answer<AssistBindingResponse>) invocation -> {\n      backend.getConfigurationService().didUpdateBinding(new DidUpdateBindingParams(configScopeId,\n        new BindingConfigurationDto(connectionId, sonarProjectKey, false)));\n      return new AssistBindingResponse(configScopeId);\n    }).when(fakeClient).assistBinding(any(), any());\n  }\n\n  private void mockAssistCreatingConnection(SonarLintTestRpcServer backend, SonarLintBackendFixture.FakeSonarLintRpcClient fakeClient, String connectionId) {\n    doAnswer((Answer<AssistCreatingConnectionResponse>) invocation -> {\n      backend.getConnectionService().didUpdateConnections(\n        new DidUpdateConnectionsParams(Collections.emptyList(),\n          List.of(new SonarCloudConnectionConfigurationDto(connectionId, ORG_KEY, SonarCloudRegion.EU, true))));\n      return new AssistCreatingConnectionResponse(connectionId);\n    }).when(fakeClient).assistCreatingConnection(any(), any());\n  }\n\n  private static ServerFixture.SonarQubeCloudBuilder buildSonarCloudServer(SonarLintTestHarness harness) {\n    return harness.newFakeSonarCloudServer()\n      .withOrganization(ORG_KEY, organization -> organization.withProject(PROJECT_KEY, project -> project.withBranch(BRANCH_NAME)));\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/issues/OpenIssueInIdeMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.issues;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Instant;\nimport java.time.LocalDateTime;\nimport java.time.ZoneOffset;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.stubbing.Answer;\nimport org.sonarsource.sonarlint.core.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidUpdateConnectionsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarQubeConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.AssistBindingResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.IssueDetailsDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.MessageType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintBackendFixture;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport org.sonarsource.sonarlint.core.test.utils.server.ServerFixture;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.verify;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.EMBEDDED_SERVER;\n\nclass OpenIssueInIdeMediumTests {\n\n  public static final String PROJECT_KEY = \"projectKey\";\n  public static final String SONAR_PROJECT_NAME = \"SonarLint IntelliJ\";\n  @RegisterExtension\n  SonarLintLogTester logTester = new SonarLintLogTester(true);\n\n  private static final String ISSUE_KEY = \"myIssueKey\";\n  private static final String PR_ISSUE_KEY = \"PRIssueKey\";\n  private static final String FILE_LEVEL_ISSUE_KEY = \"fileLevelIssueKey\";\n  private static final String CONNECTION_ID = \"connectionId\";\n  private static final String CONFIG_SCOPE_ID = \"configScopeId\";\n  public static final String RULE_KEY = \"ruleKey\";\n  private static final String BRANCH_NAME = \"branchName\";\n  private static final Instant ISSUE_INTRODUCTION_DATE = LocalDateTime.of(2023, 12, 25, 12, 30, 35).toInstant(ZoneOffset.UTC);\n\n  @SonarLintTest\n  void it_should_update_the_telemetry_on_show_issue(SonarLintTestHarness harness) throws Exception {\n    var fakeClient = harness.newFakeClient().build();\n    var fakeServerWithIssue = fakeServerWithIssue(harness).start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, fakeServerWithIssue)\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .withBackendCapability(EMBEDDED_SERVER)\n      .withTelemetryEnabled()\n      .start(fakeClient);\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getShowIssueRequestsCount()).isZero());\n\n    var statusCode = executeOpenIssueRequest(backend, fakeServerWithIssue, ISSUE_KEY, PROJECT_KEY, BRANCH_NAME);\n\n    assertThat(statusCode).isEqualTo(200);\n    await().atMost(2, TimeUnit.SECONDS)\n      .untilAsserted(() -> assertThat(backend.telemetryFileContent().getShowIssueRequestsCount()).isOne());\n  }\n\n  @SonarLintTest\n  void it_should_open_an_issue_in_ide(SonarLintTestHarness harness) throws Exception {\n    var issueKey = \"myIssueKey\";\n    var projectKey = PROJECT_KEY;\n    var connectionId = \"connectionId\";\n    var configScopeId = \"configScopeId\";\n\n    var fakeClient = harness.newFakeClient().build();\n    var fakeServerWithIssue = fakeServerWithIssue(harness).start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(connectionId, fakeServerWithIssue)\n      .withBoundConfigScope(configScopeId, connectionId, projectKey)\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start(fakeClient);\n\n    var statusCode = executeOpenIssueRequest(backend, fakeServerWithIssue, ISSUE_KEY, PROJECT_KEY, BRANCH_NAME);\n    assertThat(statusCode).isEqualTo(200);\n\n    ArgumentCaptor<IssueDetailsDto> captor = ArgumentCaptor.captor();\n    verify(fakeClient, timeout(2000)).showIssue(eq(configScopeId), captor.capture());\n\n    var issues = captor.getAllValues();\n    assertThat(issues).hasSize(1);\n    var issueDetails = issues.get(0);\n    assertThat(issueDetails.getIssueKey()).isEqualTo(issueKey);\n    assertThat(issueDetails.isTaint()).isFalse();\n    assertThat(issueDetails.getMessage()).isEqualTo(\"msg\");\n    assertThat(issueDetails.getRuleKey()).isEqualTo(\"ruleKey\");\n    assertThat(issueDetails.getCreationDate()).isEqualTo(\"2023-12-25T12:30:35+0000\");\n    assertThat(issueDetails.getTextRange()).extracting(TextRangeDto::getStartLine, TextRangeDto::getStartLineOffset,\n      TextRangeDto::getEndLine, TextRangeDto::getEndLineOffset)\n      .contains(1, 0, 3, 4);\n    assertThat(issueDetails.getCodeSnippet()).isEqualTo(\"source\\ncode\\nfile\");\n  }\n\n  @SonarLintTest\n  void it_should_open_pr_issue_in_ide(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var projectKey = PROJECT_KEY;\n    var connectionId = \"connectionId\";\n    var configScopeId = \"configScopeId\";\n\n    var fakeClient = harness.newFakeClient().build();\n    var fakeServerWithIssue = fakeServerWithIssue(harness).start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(connectionId, fakeServerWithIssue)\n      .withBoundConfigScope(configScopeId, connectionId, projectKey)\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start(fakeClient);\n\n    var statusCode = executeOpenIssueRequest(backend, fakeServerWithIssue, PR_ISSUE_KEY, PROJECT_KEY, BRANCH_NAME, \"1234\");\n    assertThat(statusCode).isEqualTo(200);\n\n    ArgumentCaptor<IssueDetailsDto> captor = ArgumentCaptor.captor();\n    verify(fakeClient, timeout(2000)).showIssue(eq(configScopeId), captor.capture());\n\n    var issues = captor.getAllValues();\n    assertThat(issues).hasSize(1);\n    var issueDetails = issues.get(0);\n    assertThat(issueDetails.getIssueKey()).isEqualTo(PR_ISSUE_KEY);\n    assertThat(issueDetails.isTaint()).isFalse();\n    assertThat(issueDetails.getMessage()).isEqualTo(\"msg\");\n    assertThat(issueDetails.getRuleKey()).isEqualTo(\"ruleKey\");\n    assertThat(issueDetails.getCreationDate()).isEqualTo(\"2023-12-25T12:30:35+0000\");\n    assertThat(issueDetails.getTextRange()).extracting(TextRangeDto::getStartLine, TextRangeDto::getStartLineOffset,\n      TextRangeDto::getEndLine, TextRangeDto::getEndLineOffset)\n      .contains(1, 0, 3, 4);\n    assertThat(issueDetails.getCodeSnippet()).isEqualTo(\"source\\ncode\\nfile\");\n  }\n\n  @SonarLintTest\n  void it_should_open_a_file_level_issue_in_ide(SonarLintTestHarness harness) throws Exception {\n    var issueKey = FILE_LEVEL_ISSUE_KEY;\n    var projectKey = PROJECT_KEY;\n    var connectionId = \"connectionId\";\n    var configScopeId = \"configScopeId\";\n\n    var fakeClient = harness.newFakeClient().build();\n    var fakeServerWithIssue = fakeServerWithIssue(harness).start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(connectionId, fakeServerWithIssue)\n      .withBoundConfigScope(configScopeId, connectionId, projectKey)\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start(fakeClient);\n\n    var statusCode = executeOpenIssueRequest(backend, fakeServerWithIssue, FILE_LEVEL_ISSUE_KEY, PROJECT_KEY, BRANCH_NAME);\n    assertThat(statusCode).isEqualTo(200);\n\n    ArgumentCaptor<IssueDetailsDto> captor = ArgumentCaptor.captor();\n    verify(fakeClient, timeout(2000)).showIssue(eq(configScopeId), captor.capture());\n\n    var issues = captor.getAllValues();\n    assertThat(issues).hasSize(1);\n    var issueDetails = issues.get(0);\n    assertThat(issueDetails.getIssueKey()).isEqualTo(issueKey);\n    assertThat(issueDetails.isTaint()).isFalse();\n    assertThat(issueDetails.getMessage()).isEqualTo(\"msg\");\n    assertThat(issueDetails.getRuleKey()).isEqualTo(\"ruleKey\");\n    assertThat(issueDetails.getCreationDate()).isEqualTo(\"2023-12-25T12:30:35+0000\");\n    assertThat(issueDetails.getTextRange()).extracting(TextRangeDto::getStartLine, TextRangeDto::getStartLineOffset,\n      TextRangeDto::getEndLine, TextRangeDto::getEndLineOffset)\n      .contains(0, 0, 0, 0);\n    assertThat(issueDetails.getCodeSnippet()).isEqualTo(\"source\\ncode\\nfile\\nfive\\nlines\");\n  }\n\n  @SonarLintTest\n  void it_should_assist_creating_the_binding_if_scope_not_bound(SonarLintTestHarness harness) throws Exception {\n    var fakeClient = harness.newFakeClient().build();\n\n    var fakeServerWithIssue = fakeServerWithIssue(harness).start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, fakeServerWithIssue)\n      .withUnboundConfigScope(CONFIG_SCOPE_ID, SONAR_PROJECT_NAME)\n      .withBackendCapability(EMBEDDED_SERVER)\n      .beforeInitialize(createdBackend -> {\n        mockAssistCreatingConnection(createdBackend, fakeClient, fakeServerWithIssue, CONNECTION_ID);\n        mockAssistBinding(createdBackend, fakeClient, CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY);\n      })\n      .start(fakeClient);\n\n    var statusCode = executeOpenIssueRequest(backend, fakeServerWithIssue, ISSUE_KEY, PROJECT_KEY, BRANCH_NAME);\n    assertThat(statusCode).isEqualTo(200);\n\n    verify(fakeClient, timeout(2000)).showIssue(eq(CONFIG_SCOPE_ID), any());\n    verify(fakeClient, never()).showMessage(any(), any());\n  }\n\n  @SonarLintTest\n  void it_should_not_assist_binding_if_multiple_suggestions(SonarLintTestHarness harness) throws Exception {\n    var fakeClient = harness.newFakeClient().build();\n    var fakeServerWithIssue = fakeServerWithIssue(harness).start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, fakeServerWithIssue)\n      // Both config scopes will match the Sonar project name\n      .withUnboundConfigScope(\"configScopeA\", SONAR_PROJECT_NAME + \" 1\")\n      .withUnboundConfigScope(\"configScopeB\", SONAR_PROJECT_NAME + \" 2\")\n      .withBackendCapability(EMBEDDED_SERVER)\n      .beforeInitialize(createdBackend -> {\n        mockAssistCreatingConnection(createdBackend, fakeClient, fakeServerWithIssue, CONNECTION_ID);\n        mockAssistBinding(createdBackend, fakeClient, CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY);\n      })\n      .start(fakeClient);\n\n    var statusCode = executeOpenIssueRequest(backend, fakeServerWithIssue, ISSUE_KEY, PROJECT_KEY, BRANCH_NAME);\n\n    assertThat(statusCode).isEqualTo(200);\n    // Since noBindingSuggestionFound now has a NoBindingSuggestionFoundParams parameter, we can just check for any!\n    verify(fakeClient, timeout(1000)).noBindingSuggestionFound(any());\n    verify(fakeClient, never()).showIssue(any(), any());\n  }\n\n  @SonarLintTest\n  void it_should_assist_binding_if_multiple_suggestions_but_scopes_are_parent_and_child(SonarLintTestHarness harness) throws Exception {\n    var fakeClient = harness.newFakeClient().build();\n    var fakeServerWithIssue = fakeServerWithIssue(harness).start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, fakeServerWithIssue)\n      // Both config scopes will match the Sonar project name\n      .withUnboundConfigScope(\"configScopeParent\", SONAR_PROJECT_NAME)\n      .withUnboundConfigScope(\"configScopeChild\", SONAR_PROJECT_NAME, \"configScopeParent\")\n      .withBackendCapability(EMBEDDED_SERVER)\n      .beforeInitialize(createdBackend -> {\n        mockAssistCreatingConnection(createdBackend, fakeClient, fakeServerWithIssue, CONNECTION_ID);\n        mockAssistBinding(createdBackend, fakeClient, \"configScopeParent\", CONNECTION_ID, PROJECT_KEY);\n      })\n      .start(fakeClient);\n\n    var statusCode = executeOpenIssueRequest(backend, fakeServerWithIssue, ISSUE_KEY, PROJECT_KEY, BRANCH_NAME);\n\n    assertThat(statusCode).isEqualTo(200);\n    verify(fakeClient, timeout(2000)).showIssue(eq(\"configScopeParent\"), any());\n    verify(fakeClient, never()).showMessage(any(), any());\n  }\n\n  @SonarLintTest\n  void it_should_assist_creating_the_connection_when_server_url_unknown(SonarLintTestHarness harness) throws Exception {\n    var fakeClient = harness.newFakeClient().build();\n    var fakeServerWithIssue = fakeServerWithIssue(harness).start();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID, SONAR_PROJECT_NAME)\n      .withBackendCapability(EMBEDDED_SERVER)\n      .beforeInitialize(createdBackend -> {\n        mockAssistCreatingConnection(createdBackend, fakeClient, fakeServerWithIssue, CONNECTION_ID);\n        mockAssistBinding(createdBackend, fakeClient, CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY);\n      })\n      .start(fakeClient);\n\n    var statusCode = executeOpenIssueRequest(backend, fakeServerWithIssue, ISSUE_KEY, PROJECT_KEY, BRANCH_NAME);\n    assertThat(statusCode).isEqualTo(200);\n\n    verify(fakeClient, timeout(2000)).showIssue(eq(CONFIG_SCOPE_ID), any());\n    verify(fakeClient, never()).showMessage(any(), any());\n\n    ArgumentCaptor<AssistCreatingConnectionParams> captor = ArgumentCaptor.captor();\n    verify(fakeClient, timeout(1000)).assistCreatingConnection(captor.capture(), any());\n    assertThat(captor.getAllValues())\n      .extracting(connectionParams -> connectionParams.getConnectionParams().getLeft().getServerUrl(),\n        connectionParams -> connectionParams.getConnectionParams().getLeft() != null,\n        AssistCreatingConnectionParams::getTokenName,\n        AssistCreatingConnectionParams::getTokenValue)\n      .containsExactly(tuple(fakeServerWithIssue.baseUrl(), true, null, null));\n  }\n\n  @SonarLintTest\n  void it_should_assist_creating_the_connection_when_no_sc_connection(SonarLintTestHarness harness) throws Exception {\n    var fakeClient = harness.newFakeClient().build();\n    var fakeServerWithIssue = fakeServerWithIssue(harness).start();\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(\"https://sonar.my\")\n      .withUnboundConfigScope(CONFIG_SCOPE_ID, SONAR_PROJECT_NAME)\n      .withBackendCapability(EMBEDDED_SERVER)\n      .beforeInitialize(createdBackend -> {\n        mockAssistCreatingConnection(createdBackend, fakeClient, fakeServerWithIssue, CONNECTION_ID);\n        mockAssistBinding(createdBackend, fakeClient, CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY);\n      })\n      .start(fakeClient);\n\n    var statusCode = executeOpenSCIssueRequest(backend, ISSUE_KEY, PROJECT_KEY, BRANCH_NAME, \"orgKey\");\n    assertThat(statusCode).isEqualTo(200);\n\n    verify(fakeClient, timeout(2000)).showIssue(eq(CONFIG_SCOPE_ID), any());\n    verify(fakeClient, never()).showMessage(any(), any());\n\n    ArgumentCaptor<AssistCreatingConnectionParams> captor = ArgumentCaptor.captor();\n    verify(fakeClient, timeout(1000)).assistCreatingConnection(captor.capture(), any());\n    assertThat(captor.getAllValues())\n      .extracting(connectionParams -> connectionParams.getConnectionParams().getRight().getOrganizationKey(),\n        AssistCreatingConnectionParams::getTokenName,\n        AssistCreatingConnectionParams::getTokenValue)\n      .containsExactly(tuple(\"orgKey\", null, null));\n  }\n\n  @SonarLintTest\n  void it_should_fail_request_when_issue_parameter_missing(SonarLintTestHarness harness) throws Exception {\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start();\n    var fakeServerWithIssue = fakeServerWithIssue(harness).start();\n\n    var statusCode = executeOpenIssueRequest(backend, fakeServerWithIssue, \"\", PROJECT_KEY, BRANCH_NAME);\n\n    assertThat(statusCode).isEqualTo(400);\n  }\n\n  @SonarLintTest\n  void it_should_fail_request_when_project_parameter_missing(SonarLintTestHarness harness) throws Exception {\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start();\n    var fakeServerWithIssue = fakeServerWithIssue(harness).start();\n\n    var statusCode = executeOpenIssueRequest(backend, fakeServerWithIssue, ISSUE_KEY, \"\", \"\", \"\");\n\n    assertThat(statusCode).isEqualTo(400);\n  }\n\n  @SonarLintTest\n  void it_should_fail_request_when_server_points_to_sonarcloud_from_sqs(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start(client);\n    var request = openIssueRequestWithOrigin(backend,\n      SonarCloudRegion.EU.getProductionUri().toString(),\n      \"http://fake.sonar\");\n\n    var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n\n    assertThat(response.statusCode()).isEqualTo(400);\n    verify(client).showMessage(MessageType.ERROR,\n      \"Invalid request to SonarQube backend. The 'server' parameter should not be SonarQube Cloud URL, use it only to specify URL of a SonarQube Server.\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_request_when_server_points_to_sonarcloud_us_from_sqs(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start(client);\n    var request = openIssueRequestWithOrigin(backend,\n      SonarCloudRegion.US.getProductionUri().toString(),\n      \"http://fake.sonar\");\n\n    var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n\n    assertThat(response.statusCode()).isEqualTo(400);\n    verify(client).showMessage(MessageType.ERROR,\n      \"Invalid request to SonarQube backend. The 'server' parameter should not be SonarQube Cloud URL, use it only to specify URL of a SonarQube Server.\");\n  }\n\n  @SonarLintTest\n  void it_should_not_fail_request_when_server_points_to_sonarcloud_from_sonarcloud(SonarLintTestHarness harness) throws IOException, InterruptedException {\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withBackendCapability(EMBEDDED_SERVER)\n      .start(client);\n    var request = openIssueRequestWithOrigin(backend,\n      SonarCloudRegion.EU.getProductionUri().toString(),\n      SonarCloudRegion.EU.getProductionUri().toString(),\n      \"&issue=\" + ISSUE_KEY,\n      \"&project=\" + PROJECT_KEY,\n      \"&branch=\" + BRANCH_NAME,\n      \"&organizationKey=orgKey\");\n\n    var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n\n    assertThat(response.statusCode()).isEqualTo(200);\n  }\n\n  private int executeOpenIssueRequest(SonarLintTestRpcServer backend, ServerFixture.Server server, String issueKey, String projectKey, String branch)\n    throws IOException, InterruptedException {\n    HttpRequest request = openIssueRequest(backend, server.baseUrl(), \"&issue=\" + issueKey, \"&project=\" + projectKey, \"&branch=\" + branch);\n    var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n    return response.statusCode();\n  }\n\n  private int executeOpenSCIssueRequest(SonarLintTestRpcServer backend, String issueKey, String projectKey, String branch, String organizationKey)\n    throws IOException, InterruptedException {\n    HttpRequest request = this.openIssueRequest(backend, \"https://sonar.my\", \"&issue=\" + issueKey, \"&project=\" + projectKey, \"&branch=\" + branch,\n      \"&organizationKey=\" + organizationKey);\n    var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n    return response.statusCode();\n  }\n\n  private int executeOpenIssueRequest(SonarLintTestRpcServer backend, ServerFixture.Server server, String issueKey, String projectKey, String branch, String pullRequest)\n    throws IOException, InterruptedException {\n    HttpRequest request = openIssueRequest(backend, server.baseUrl(), \"&issue=\" + issueKey, \"&project=\" + projectKey, \"&branch=\" + branch, \"&pullRequest=\" + pullRequest);\n    var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n    return response.statusCode();\n  }\n\n  private HttpRequest openIssueRequest(SonarLintTestRpcServer backend, String baseUrl, String... params) {\n    return HttpRequest.newBuilder()\n      .uri(URI.create(\n        \"http://localhost:\" + backend.getEmbeddedServerPort() + \"/sonarlint/api/issues/show?server=\" + baseUrl + String.join(\"\", params)))\n      .header(\"Origin\", baseUrl)\n      .GET().build();\n  }\n\n  private HttpRequest openIssueRequestWithOrigin(SonarLintTestRpcServer backend, String baseUrl, String origin, String... params) {\n    return HttpRequest.newBuilder()\n      .uri(URI.create(\n        \"http://localhost:\" + backend.getEmbeddedServerPort() + \"/sonarlint/api/issues/show?server=\" + baseUrl + String.join(\"\", params)))\n      .header(\"Origin\", origin)\n      .GET().build();\n  }\n\n  private void mockAssistBinding(SonarLintTestRpcServer backend, SonarLintBackendFixture.FakeSonarLintRpcClient fakeClient, String configScopeId, String connectionId,\n    String sonarProjectKey) {\n    doAnswer((Answer<AssistBindingResponse>) invocation -> {\n      backend.getConfigurationService().didUpdateBinding(new DidUpdateBindingParams(configScopeId, new BindingConfigurationDto(connectionId, sonarProjectKey, false)));\n      return new AssistBindingResponse(configScopeId);\n    }).when(fakeClient).assistBinding(any(), any());\n  }\n\n  private void mockAssistCreatingConnection(SonarLintTestRpcServer backend, SonarLintBackendFixture.FakeSonarLintRpcClient fakeClient, ServerFixture.Server server,\n    String connectionId) {\n    doAnswer((Answer<AssistCreatingConnectionResponse>) invocation -> {\n      backend.getConnectionService().didUpdateConnections(\n        new DidUpdateConnectionsParams(List.of(new SonarQubeConnectionConfigurationDto(connectionId, server.baseUrl(), true)), Collections.emptyList()));\n      return new AssistCreatingConnectionResponse(connectionId);\n    }).when(fakeClient).assistCreatingConnection(any(), any());\n  }\n\n  private static ServerFixture.AbstractServerBuilder fakeServerWithIssue(SonarLintTestHarness harness) {\n    return harness.newFakeSonarQubeServer(\"10.2\")\n      .withProject(PROJECT_KEY,\n        project -> {\n          project.withProjectName(SONAR_PROJECT_NAME).withPullRequest(\"1234\",\n            pullRequest -> (ServerFixture.AbstractServerBuilder.ServerProjectBuilder.ServerProjectPullRequestBuilder) pullRequest\n              .withIssue(PR_ISSUE_KEY, RULE_KEY, \"msg\", \"author\", \"file/path\", \"OPEN\", \"\", ISSUE_INTRODUCTION_DATE,\n                new TextRange(1, 0, 3, 4))\n              .withSourceFile(\"projectKey:file/path\", sourceFile -> sourceFile.withCode(\"source\\ncode\\nfile\\nfive\\nlines\")));\n          return project.withBranch(\"branchName\",\n            branch -> {\n              branch.withIssue(ISSUE_KEY, RULE_KEY, \"msg\", \"author\", \"file/path\", \"OPEN\", \"\", ISSUE_INTRODUCTION_DATE,\n                new TextRange(1, 0, 3, 4));\n              branch.withIssue(FILE_LEVEL_ISSUE_KEY, RULE_KEY, \"msg\", \"author\", \"file/path\", \"OPEN\", \"\", ISSUE_INTRODUCTION_DATE,\n                new TextRange(0, 0, 0, 0));\n              return branch.withSourceFile(\"projectKey:file/path\", sourceFile -> sourceFile.withCode(\"source\\ncode\\nfile\\nfive\\nlines\"));\n            });\n        });\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/labs/IdeLabsMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.labs;\n\nimport com.github.tomakehurst.wiremock.junit5.WireMockExtension;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.labs.JoinIdeLabsProgramParams;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.equalTo;\nimport static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;\nimport static com.github.tomakehurst.wiremock.client.WireMock.okJson;\nimport static com.github.tomakehurst.wiremock.client.WireMock.post;\nimport static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThat;\nimport static org.sonarsource.sonarlint.core.labs.IdeLabsSpringConfig.PROPERTY_IDE_LABS_SUBSCRIPTION_URL;\n\npublic class IdeLabsMediumTests {\n  @RegisterExtension\n  static WireMockExtension marketingCloudMock = WireMockExtension.newInstance()\n    .options(wireMockConfig().dynamicPort())\n    .build();\n\n  @AfterAll\n  static void tearDown() {\n    System.clearProperty(PROPERTY_IDE_LABS_SUBSCRIPTION_URL);\n  }\n\n  @SonarLintTest\n  void it_should_join_labs_successfully(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withTelemetryEnabled()\n      .withIdeLabsSubscriptionUrl(marketingCloudMock.baseUrl())\n      .start();\n    marketingCloudMock.stubFor(post(\"/\")\n      .willReturn(okJson(\"{ \\\"valid_email\\\": true }\")));\n    var sampleEmail = \"example@example.com\";\n    var ideName = \"VSCode\";\n\n\n    var response = backend.getIdeLabsService().joinIdeLabsProgram(new JoinIdeLabsProgramParams(sampleEmail, ideName));\n\n    assertThat(response).succeedsWithin(2, TimeUnit.SECONDS);\n    assertMarketingCloudEndpointCalled(sampleEmail, ideName);\n    assertThat(response.join().isSuccess()).isTrue();\n  }\n\n  @SonarLintTest\n  void it_should_fail_to_join_labs_with_invalid_email(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withIdeLabsSubscriptionUrl(marketingCloudMock.baseUrl())\n      .start();\n    marketingCloudMock.stubFor(post(\"/\")\n      .willReturn(okJson(\"{ \\\"valid_email\\\": false }\")));\n    var sampleEmail = \"invalid-email\";\n    var ideName = \"VSCode\";\n\n    var response = backend.getIdeLabsService().joinIdeLabsProgram(new JoinIdeLabsProgramParams(sampleEmail, ideName));\n\n    assertThat(response).succeedsWithin(2, TimeUnit.SECONDS);\n    assertMarketingCloudEndpointCalled(sampleEmail, ideName);\n    assertThat(response.join().isSuccess()).isFalse();\n    assertThat(response.join().getMessage()).contains(\"The provided email address is not valid. Please enter a valid email address.\");\n  }\n\n  @SonarLintTest\n  void it_should_handle_server_error_when_joining_labs(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withIdeLabsSubscriptionUrl(marketingCloudMock.baseUrl())\n      .start();\n    marketingCloudMock.stubFor(post(\"/\")\n      .willReturn(aResponse().withStatus(500)));\n    var sampleEmail = \"example@example.com\";\n    var ideName = \"VSCode\";\n\n    var response = backend.getIdeLabsService().joinIdeLabsProgram(new JoinIdeLabsProgramParams(sampleEmail, ideName));\n\n    assertThat(response).succeedsWithin(2, TimeUnit.SECONDS);\n    assertMarketingCloudEndpointCalled(sampleEmail, ideName);\n    assertThat(response.join().isSuccess()).isFalse();\n    assertThat(response.join().getMessage()).contains(\"An unexpected error occurred. Server responded with status code: 500\");\n  }\n\n  void assertMarketingCloudEndpointCalled(String email, String source) {\n    var expectedRequestBody = String.format(\"\"\"\n      {\n        \"email\": \"%s\",\n        \"source\": \"%s\"\n      }\n      \"\"\", email, source);\n\n    marketingCloudMock.verify(postRequestedFor(urlEqualTo(\"/\"))\n      .withHeader(\"Content-Type\", equalTo(\"application/json\"))\n      .withRequestBody(equalToJson(expectedRequestBody)));\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/log/LoggingMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.log;\n\nimport java.time.Duration;\nimport java.util.List;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.log.LogLevel;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.log.SetLogLevelParams;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\n\npublic class LoggingMediumTests {\n\n  @SonarLintTest\n  void it_should_print_a_debug_log_when_level_allows(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withLogLevel(LogLevel.TRACE)\n      .start(fakeClient);\n\n    backend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(new ConfigurationScopeDto(\"id\", null, true, \"name\", null))));\n\n    await().untilAsserted(() -> assertThat(fakeClient.getLogMessages()).contains(\"Added configuration scope 'id'\"));\n  }\n\n  @SonarLintTest\n  void it_should_not_print_a_log_when_level_does_not_allow(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withLogLevel(LogLevel.OFF)\n      .start(fakeClient);\n\n    backend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(new ConfigurationScopeDto(\"id\", null, true, \"name\", null))));\n\n    await().during(Duration.ofSeconds(1)).untilAsserted(() -> assertThat(fakeClient.getLogMessages()).isEmpty());\n  }\n\n  @SonarLintTest\n  void it_should_adjust_the_logging_after_initialization(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withLogLevel(LogLevel.DEBUG)\n      .start(fakeClient);\n    backend.getLogService().setLogLevel(new SetLogLevelParams(LogLevel.OFF));\n    fakeClient.clearLogs();\n\n    backend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(new ConfigurationScopeDto(\"id\", null, true, \"name\", null))));\n\n    await().during(Duration.ofSeconds(1)).untilAsserted(() -> assertThat(fakeClient.getLogMessages()).isEmpty());\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/monitoring/MonitoringMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.monitoring;\n\nimport com.github.tomakehurst.wiremock.WireMockServer;\nimport io.sentry.Sentry;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.dogfood.DogfoodEnvironmentDetectionService;\nimport org.sonarsource.sonarlint.core.monitoring.MonitoringService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidUpdateFileSystemParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport uk.org.webcompere.systemstubs.environment.EnvironmentVariables;\nimport uk.org.webcompere.systemstubs.jupiter.SystemStub;\nimport uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension;\nimport utils.TestPlugin;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.post;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.MONITORING;\nimport static org.sonarsource.sonarlint.core.test.utils.plugins.SonarPluginBuilder.newSonarPlugin;\nimport static utils.AnalysisUtils.analyzeFileAndGetIssues;\nimport static utils.AnalysisUtils.createFile;\n\n@ExtendWith(SystemStubsExtension.class)\nclass MonitoringMediumTests {\n  private static final String CONFIGURATION_SCOPE_ID = \"configScopeId\";\n  private WireMockServer sentryServer;\n\n  @SystemStub\n  private EnvironmentVariables environmentVariables;\n\n  @BeforeEach\n  void setup() {\n    sentryServer = new WireMockServer(wireMockConfig().dynamicPort());\n    sentryServer.start();\n    System.setProperty(MonitoringService.DSN_PROPERTY, createValidSentryDsn(sentryServer));\n    System.setProperty(MonitoringService.TRACES_SAMPLE_RATE_PROPERTY, \"1\");\n    environmentVariables.set(DogfoodEnvironmentDetectionService.SONARSOURCE_DOGFOODING_ENV_VAR_KEY, \"1\");\n    setupSentryStubs();\n  }\n\n  @AfterEach\n  void tearDown() {\n    Sentry.close();\n    sentryServer.stop();\n  }\n\n  private String createValidSentryDsn(WireMockServer server) {\n    return \"http://fake-public-key@localhost:\" + server.port() + \"/12345\";\n  }\n\n  private void setupSentryStubs() {\n    // Stub the Sentry project endpoint (where events are sent)\n    sentryServer.stubFor(post(urlPathMatching(\"/api/\\\\d+/store/\"))\n      .willReturn(aResponse()\n        .withStatus(200)\n        .withHeader(\"Content-Type\", \"application/json\")\n        .withBody(\"{\\\"id\\\": \\\"event-id-12345\\\"}\")));\n\n    // Stub the Sentry envelope endpoint (used for transactions, etc.)\n    sentryServer.stubFor(post(urlPathMatching(\"/api/\\\\d+/envelope/\"))\n      .willReturn(aResponse()\n        .withStatus(200)));\n  }\n\n  @SonarLintTest\n  void simple_php_with_monitoring(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, \"foo.php\", \"\"\"\n      <?php\n      function writeMsg($fname) {\n          $i = 0; // NOSONAR\n          echo \"Hello world!\";\n      }\n      ?>\n      \"\"\");\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PHP)\n      .withBackendCapability(MONITORING)\n      .start(client);\n\n    var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    assertThat(issues).extracting(RaisedIssueDto::getRuleKey, i -> i.getTextRange().getStartLine()).contains(tuple(\"php:S1172\", 2));\n\n    // The mock Sentry server receives 1 event for the analysis trace\n    await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> assertThat(sentryServer.getAllServeEvents()).hasSize(1));\n  }\n\n  @SonarLintTest\n  void analysis_errors_with_tracing(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var content = \"\"\"\n      <?php\n      function writeMsg($fname) {\n          echo \"Hello world!;\n      }\n      ?>\"\"\";\n    var inputFile = createFile(baseDir, \"foo.php\", content);\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n\n    var throwingPluginPath = newSonarPlugin(\"php\")\n      .withSensor(ThrowingPhpSensor.class)\n      .generate(baseDir);\n\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPlugin(throwingPluginPath)\n      .withEnabledLanguageInStandaloneMode(Language.PHP)\n      .withBackendCapability(MONITORING)\n      .start(client);\n\n    var analysisId = UUID.randomUUID();\n    var analysisResult = backend.getAnalysisService().analyzeFilesAndTrack(\n      new AnalyzeFilesAndTrackParams(CONFIGURATION_SCOPE_ID, analysisId, List.of(inputFile.toUri()), Map.of(), true, System.currentTimeMillis()))\n      .join();\n    assertThat(analysisResult.getFailedAnalysisFiles()).isEmpty();\n    await().during(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIGURATION_SCOPE_ID)).isEmpty());\n    // The mock Sentry server receives 1 event: one for the trace\n    await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> assertThat(sentryServer.getAllServeEvents()).hasSize(1));\n    assertThat(sentryServer.getAllServeEvents())\n      .extracting(e -> e.getRequest().getBodyAsString())\n      // Server name should be removed from events\n      .noneMatch(m -> m.contains(\"server_name\"));\n  }\n\n  @SonarLintTest\n  void uncaught_exception_should_be_reported_to_sentry(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withBackendCapability(MONITORING)\n      .start(client);\n\n    var futureResponse = backend.getConnectionService().validateConnection(null);\n\n    try {\n      futureResponse.join();\n    } catch (Exception e) {\n    }\n\n    await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(sentryServer.getAllServeEvents()).isNotEmpty());\n\n    var exceptionEvent = sentryServer.getAllServeEvents().stream()\n      .filter(e -> e.getRequest().getBodyAsString().contains(\"NullPointerException\"))\n      .findFirst();\n\n    assertThat(exceptionEvent).isPresent();\n    var eventBody = exceptionEvent.get().getRequest().getBodyAsString();\n    assertThat(eventBody)\n      .contains(\"NullPointerException\")\n      .contains(\"stacktrace.txt\");\n  }\n\n  @SonarLintTest\n  void should_not_capture_silenced_exception(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var content = \"\"\"\n      [3, 1, 4, 1, 5, 9]\n      result = set(sorted(data))\n\n      result = set(sordata))\n      \"\"\";\n    var newContent = \"\"\"\n      [3, 1, 4, 1, 5, 9]\n      result = set(sorted(data))\n\n      result = set(sordata))\n      \"\"\";\n    var filePath = createFile(baseDir, \"invalid.py\", content);\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, baseDir,\n        List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIGURATION_SCOPE_ID, false, null, filePath, content, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .withBackendCapability(MONITORING)\n      .start(client);\n    // analyze once to start the analysis scheduler\n    backend.getAnalysisService().analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(CONFIGURATION_SCOPE_ID, UUID.randomUUID(), List.of(fileUri), Map.of(), false)).join();\n\n    var updatedFile = new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIGURATION_SCOPE_ID, false, null, filePath, newContent, Language.PYTHON, true);\n    backend.getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(List.of(), List.of(updatedFile), List.of()));\n\n    await().untilAsserted(() -> assertThat(client.getLogMessages()).contains(\"Error processing file event\"));\n    // only a single event for the analysis\n    await().atLeast(100, TimeUnit.MILLISECONDS).untilAsserted(() -> assertThat(sentryServer.getAllServeEvents()).hasSize(1));\n  }\n\n  @SonarLintTest\n  void should_configure_dogfood_environment(SonarLintTestHarness harness) {\n    startMonitoringBackend(harness);\n\n    assertThat(Sentry.getCurrentScopes().getOptions().getEnvironment()).isEqualTo(\"dogfood\");\n  }\n\n  @SonarLintTest\n  void should_configure_production_environment_when_dogfood_disabled(SonarLintTestHarness harness) {\n    environmentVariables.set(DogfoodEnvironmentDetectionService.SONARSOURCE_DOGFOODING_ENV_VAR_KEY, null);\n\n    var client = harness.newFakeClient().build();\n    harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withBackendCapability(MONITORING)\n      .withProductKey(\"idea\")\n      .withTelemetryEnabled()\n      .start(client);\n\n    assertThat(Sentry.getCurrentScopes().getOptions().getEnvironment()).isEqualTo(\"production\");\n  }\n\n  @SonarLintTest\n  void should_configure_production_environment_when_product_is_not_intellij(SonarLintTestHarness harness) {\n    environmentVariables.set(DogfoodEnvironmentDetectionService.SONARSOURCE_DOGFOODING_ENV_VAR_KEY, null);\n\n    var client = harness.newFakeClient().build();\n    harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withBackendCapability(MONITORING)\n      .withProductKey(\"vscode\")\n      .withTelemetryEnabled()\n      .start(client);\n\n    assertThat(Sentry.isEnabled()).isTrue();\n  }\n\n  @SonarLintTest\n  void should_configure_production_environment_when_product_is_not_intellij_and_telemetry_enabled_event_happens(SonarLintTestHarness harness) {\n    environmentVariables.set(DogfoodEnvironmentDetectionService.SONARSOURCE_DOGFOODING_ENV_VAR_KEY, null);\n\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withBackendCapability(MONITORING)\n      .withProductKey(\"vscode\")\n      .withTelemetryEnabled()\n      .start(client);\n\n    assertThat(Sentry.isEnabled()).isTrue();\n\n    backend.getTelemetryService().disableTelemetry();\n    await().atMost(2, TimeUnit.SECONDS)\n      .untilAsserted(() -> assertThat(backend.getTelemetryService().getStatus().get(2, TimeUnit.SECONDS).isEnabled()).isFalse());\n\n    backend.getTelemetryService().enableTelemetry();\n    await().atMost(2, TimeUnit.SECONDS)\n      .untilAsserted(() -> assertThat(backend.getTelemetryService().getStatus().get(2, TimeUnit.SECONDS).isEnabled()).isTrue());\n\n    assertThat(Sentry.isEnabled()).isTrue();\n  }\n\n  @SonarLintTest\n  void should_configure_production_environment_when_product_is_intellij_and_adapt_to_telemetry_event(SonarLintTestHarness harness) {\n    environmentVariables.set(DogfoodEnvironmentDetectionService.SONARSOURCE_DOGFOODING_ENV_VAR_KEY, null);\n\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withBackendCapability(MONITORING)\n      .withProductKey(\"idea\")\n      .withTelemetryEnabled()\n      .start(client);\n\n    assertThat(Sentry.isEnabled()).isTrue();\n\n    backend.getTelemetryService().disableTelemetry();\n    await().atMost(2, TimeUnit.SECONDS)\n      .untilAsserted(() -> assertThat(backend.getTelemetryService().getStatus().get(2, TimeUnit.SECONDS).isEnabled()).isFalse());\n\n    assertThat(Sentry.isEnabled()).isFalse();\n\n    backend.getTelemetryService().enableTelemetry();\n    await().atMost(2, TimeUnit.SECONDS)\n      .untilAsserted(() -> assertThat(backend.getTelemetryService().getStatus().get(2, TimeUnit.SECONDS).isEnabled()).isTrue());\n\n    assertThat(Sentry.isEnabled()).isTrue();\n  }\n\n  @SonarLintTest\n  void should_use_sample_rate_from_system_property(SonarLintTestHarness harness) {\n    withSampleRateProperty(\"0.42\", () -> {\n      startMonitoringBackend(harness);\n\n      assertThat(Sentry.getCurrentScopes().getOptions().getTracesSampleRate()).isEqualTo(0.42);\n    });\n  }\n\n  @SonarLintTest\n  void should_default_sample_rate_to_zero_when_property_invalid_and_not_dogfood(SonarLintTestHarness harness) {\n    environmentVariables.set(DogfoodEnvironmentDetectionService.SONARSOURCE_DOGFOODING_ENV_VAR_KEY, null);\n\n    withSampleRateProperty(\"invalid\", () -> {\n      var client = harness.newFakeClient().build();\n      harness.newBackend()\n        .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n        .withBackendCapability(MONITORING)\n        .withProductKey(\"idea\")\n        .withTelemetryEnabled()\n        .start(client);\n\n      assertThat(Sentry.getCurrentScopes().getOptions().getTracesSampleRate()).isZero();\n    });\n  }\n\n  @SonarLintTest\n  void should_default_sample_rate_to_dogfood_value_when_property_invalid(SonarLintTestHarness harness) {\n    withSampleRateProperty(\"invalid\", () -> {\n      startMonitoringBackend(harness);\n\n      assertThat(Sentry.getCurrentScopes().getOptions().getTracesSampleRate()).isEqualTo(0.01);\n    });\n  }\n\n  private void withSampleRateProperty(String value, Runnable action) {\n    var previousValue = System.getProperty(MonitoringService.TRACES_SAMPLE_RATE_PROPERTY);\n    try {\n      System.setProperty(MonitoringService.TRACES_SAMPLE_RATE_PROPERTY, value);\n      action.run();\n    } finally {\n      if (previousValue == null) {\n        System.clearProperty(MonitoringService.TRACES_SAMPLE_RATE_PROPERTY);\n      } else {\n        System.setProperty(MonitoringService.TRACES_SAMPLE_RATE_PROPERTY, previousValue);\n      }\n    }\n  }\n\n  private void startMonitoringBackend(SonarLintTestHarness harness, BackendCapability... extraCapabilities) {\n    var client = harness.newFakeClient().build();\n    var backendBuilder = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withBackendCapability(MONITORING);\n    for (var capability : extraCapabilities) {\n      backendBuilder = backendBuilder.withBackendCapability(capability);\n    }\n    backendBuilder.start(client);\n  }\n\n  @SonarLintTest\n  void should_not_initialize_sentry_when_monitoring_capability_not_enabled(SonarLintTestHarness harness) {\n    environmentVariables.set(DogfoodEnvironmentDetectionService.SONARSOURCE_DOGFOODING_ENV_VAR_KEY, null);\n\n    var client = harness.newFakeClient().build();\n    harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .start(client);\n\n    assertThat(Sentry.isEnabled()).isFalse();\n  }\n\n  @SonarLintTest\n  void should_verify_sentry_tags_are_set_correctly(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, \"test.php\", \"\"\"\n      <?php\n      function test($unused) {\n          echo \"Hello\";\n      }\n      ?>\n      \"\"\");\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PHP)\n      .withBackendCapability(MONITORING)\n      .start(client);\n\n    analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(sentryServer.getAllServeEvents()).isNotEmpty());\n\n    var eventBody = sentryServer.getAllServeEvents().get(0).getRequest().getBodyAsString();\n    assertThat(eventBody)\n      .contains(\"productKey\")\n      .contains(\"sonarQubeForIDEVersion\")\n      .contains(\"ideVersion\")\n      .contains(\"platform\")\n      .contains(\"architecture\");\n  }\n\n  @SonarLintTest\n  void should_not_enable_sentry_logs(SonarLintTestHarness harness) {\n    startMonitoringBackend(harness);\n\n    assertThat(Sentry.getCurrentScopes().getOptions().getLogs().isEnabled()).isFalse();\n  }\n\n  @SonarLintTest\n  void should_close_sentry_when_telemetry_is_disabled(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withBackendCapability(MONITORING)\n      .withTelemetryEnabled()\n      .start(client);\n\n    assertThat(Sentry.isEnabled()).isTrue();\n\n    backend.getTelemetryService().disableTelemetry();\n\n    await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(Sentry.isEnabled()).isFalse());\n  }\n\n  @SonarLintTest\n  void should_verify_dsn_is_configurable_via_system_property(SonarLintTestHarness harness) {\n    startMonitoringBackend(harness);\n\n    var options = Sentry.getCurrentScopes().getOptions();\n    assertThat(options.getDsn()).contains(\"localhost:\" + sentryServer.port());\n  }\n\n  @SonarLintTest\n  void should_create_analysis_trace_with_system_metrics(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, \"test.php\", \"\"\"\n      <?php\n      echo \"test\";\n      ?>\n      \"\"\");\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PHP)\n      .withBackendCapability(MONITORING)\n      .start(client);\n\n    analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(sentryServer.getAllServeEvents()).isNotEmpty());\n\n    var eventBody = sentryServer.getAllServeEvents().stream()\n      .map(e -> e.getRequest().getBodyAsString())\n      .filter(body -> body.contains(\"AnalysisService\"))\n      .findFirst();\n\n    assertThat(eventBody).isPresent();\n  }\n\n  @SonarLintTest\n  void sentry_should_be_enabled_in_dogfood_environment(SonarLintTestHarness harness) {\n    startMonitoringBackend(harness);\n\n    assertThat(Sentry.isEnabled()).isTrue();\n  }\n\n  @SonarLintTest\n  void should_handle_multiple_analyses_with_tracing(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile1 = createFile(baseDir, \"file1.php\", \"\"\"\n      <?php\n      function test1($unused) { echo \"1\"; }\n      ?>\n      \"\"\");\n    var inputFile2 = createFile(baseDir, \"file2.php\", \"\"\"\n      <?php\n      function test2($unused) { echo \"2\"; }\n      ?>\n      \"\"\");\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile1.toUri(), baseDir.relativize(inputFile1), CONFIGURATION_SCOPE_ID, false, null, inputFile1, null, null, true),\n        new ClientFileDto(inputFile2.toUri(), baseDir.relativize(inputFile2), CONFIGURATION_SCOPE_ID, false, null, inputFile2, null, null, true)))\n      .build();\n\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PHP)\n      .withBackendCapability(MONITORING)\n      .start(client);\n\n    analyzeFileAndGetIssues(inputFile1.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n    analyzeFileAndGetIssues(inputFile2.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(sentryServer.getAllServeEvents()).hasSize(2));\n  }\n\n  @SonarLintTest\n  void should_reinitialize_sentry_when_telemetry_is_enabled_after_being_disabled(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withBackendCapability(MONITORING)\n      .withTelemetryEnabled()\n      .start(client);\n\n    assertThat(Sentry.isEnabled()).isTrue();\n\n    backend.getTelemetryService().disableTelemetry();\n\n    await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(Sentry.isEnabled()).isFalse());\n\n    backend.getTelemetryService().enableTelemetry();\n\n    await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(Sentry.isEnabled()).isTrue());\n  }\n\n  @SonarLintTest\n  void should_not_send_events_when_sentry_is_closed(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var inputFile = createFile(baseDir, \"test.php\", \"\"\"\n      <?php\n      function test($unused) { echo \"test\"; }\n      ?>\n      \"\"\");\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIGURATION_SCOPE_ID, List.of(\n        new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true)))\n      .build();\n\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIGURATION_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PHP)\n      .withBackendCapability(MONITORING)\n      .withTelemetryEnabled()\n      .start(client);\n\n    // Disable telemetry to close Sentry\n    backend.getTelemetryService().disableTelemetry();\n    await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(Sentry.isEnabled()).isFalse());\n\n    // Clear any existing events\n    sentryServer.resetAll();\n    setupSentryStubs();\n\n    analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID);\n\n    await().during(2000, TimeUnit.MILLISECONDS).untilAsserted(() -> assertThat(sentryServer.getAllServeEvents()).isEmpty());\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/monitoring/ThrowingPhpSensor.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.monitoring;\n\nimport org.sonar.api.batch.sensor.Sensor;\nimport org.sonar.api.batch.sensor.SensorContext;\nimport org.sonar.api.batch.sensor.SensorDescriptor;\n\npublic class ThrowingPhpSensor implements Sensor {\n\n  @Override\n  public void describe(SensorDescriptor sensorDescriptor) {\n    sensorDescriptor.name(\"Throwing PHP Sensor\")\n      .onlyOnLanguage(\"php\");\n  }\n\n  @Override\n  public void execute(SensorContext sensorContext) {\n    throw new IllegalStateException(\"This should finish a span and trace exceptionally\");\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/newcode/NewCodeTelemetryMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.newcode;\n\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryLocalStorage;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\n\nclass NewCodeTelemetryMediumTests {\n\n  @SonarLintTest\n  void it_should_save_initial_value_when_focus_on_overall_code(SonarLintTestHarness harness) {\n    var backend = harness.newBackend().withTelemetryEnabled().start();\n\n    assertThat(backend.telemetryFileContent())\n      .extracting(TelemetryLocalStorage::isFocusOnNewCode, TelemetryLocalStorage::getCodeFocusChangedCount)\n      .containsExactly(false, 0);\n  }\n\n  @SonarLintTest\n  void it_should_save_initial_value_when_focus_on_new_code(SonarLintTestHarness harness) {\n    var backend = harness.newBackend().withTelemetryEnabled().withFocusOnNewCode().start();\n\n    assertThat(backend.telemetryFileContent())\n      .extracting(TelemetryLocalStorage::isFocusOnNewCode, TelemetryLocalStorage::getCodeFocusChangedCount)\n      .containsExactly(true, 0);\n  }\n\n  @SonarLintTest\n  void it_should_save_new_focus_and_increment_count_when_focusing_on_new_code(SonarLintTestHarness harness) {\n    var backend = harness.newBackend().withTelemetryEnabled().start();\n\n    backend.getNewCodeService().didToggleFocus();\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent())\n      .extracting(TelemetryLocalStorage::isFocusOnNewCode, TelemetryLocalStorage::getCodeFocusChangedCount)\n      .containsExactly(true, 1));\n  }\n\n  @SonarLintTest\n  void it_should_save_new_focus_and_increment_count_when_focusing_on_overall_code(SonarLintTestHarness harness) {\n    var backend = harness.newBackend().withTelemetryEnabled().withFocusOnNewCode().start();\n\n    backend.getNewCodeService().didToggleFocus();\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent())\n      .extracting(TelemetryLocalStorage::isFocusOnNewCode, TelemetryLocalStorage::getCodeFocusChangedCount)\n      .containsExactly(false, 1));\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/plugin/OnDemandAnalyzersMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.plugin;\n\nimport java.util.concurrent.TimeUnit;\nimport mockwebserver3.MockWebServer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.sonarsource.sonarlint.core.plugin.source.binaries.BinariesArtifact;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.GetPluginStatusesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.PluginStateDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport uk.org.webcompere.systemstubs.jupiter.SystemStub;\nimport uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension;\nimport uk.org.webcompere.systemstubs.properties.SystemProperties;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\n\n@ExtendWith(SystemStubsExtension.class)\nclass OnDemandAnalyzersMediumTests {\n\n  @SystemStub\n  SystemProperties systemProperties;\n\n  private MockWebServer mockWebServer;\n\n  @BeforeEach\n  void setUp() throws Exception {\n    mockWebServer = new MockWebServer();\n    mockWebServer.start();\n  }\n\n  @AfterEach\n  void tearDown() throws Exception {\n    mockWebServer.shutdown();\n  }\n\n  @SonarLintTest\n  void should_download_and_cache_cfamily_in_standalone_mode(SonarLintTestHarness harness) throws Exception {\n    byte[] dummyJarBody;\n    try (var is = OnDemandAnalyzersMediumTests.class.getResourceAsStream(\"/ondemand/sonar-cpp-plugin-test.jar\")) {\n      assertThat(is).as(\"Test resource /ondemand/sonar-cpp-plugin-test.jar must be on the classpath\").isNotNull();\n      dummyJarBody = is.readAllBytes();\n    }\n    for (int i = 0; i < 5; i++) {\n      var b = new okio.Buffer();\n      b.write(dummyJarBody);\n      mockWebServer.enqueue(new mockwebserver3.MockResponse.Builder().body(b).code(200).build());\n    }\n\n    var serverUrl = mockWebServer.url(\"\").toString().replaceAll(\"/$\", \"\");\n    systemProperties.set(BinariesArtifact.PROPERTY_URL_PATTERN, serverUrl);\n\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.CPP)\n      .start(client);\n\n    await().atMost(15, TimeUnit.SECONDS).untilAsserted(() -> {\n      var statuses = backend.getPluginService().getPluginStatuses(new GetPluginStatusesParams(null)).get().getPluginStatuses();\n      var cfamilyStatusOpt = statuses.stream().filter(s -> \"C/C++/Objective-C\".equals(s.getPluginName()) || s.getPluginName().contains(\"C++\")).findFirst();\n      assertThat(cfamilyStatusOpt).isPresent();\n      assertThat(cfamilyStatusOpt.get().getState()).isEqualTo(PluginStateDto.ACTIVE);\n    });\n  }\n\n  @SonarLintTest\n  void should_download_server_plugins_in_connected_mode(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().withToken(\"connId\", \"token\").build();\n    var server = harness.newFakeSonarQubeServer()\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile.withLanguage(\"java\"))\n      .withProject(\"projectKey\", project -> project.withQualityProfile(\"qpKey\").withBranch(\"main\"))\n      .withPlugin(TestPlugin.JAVA)\n      .start();\n\n    // Start backend WITH JAVA enabled in standalone mode (to avoid PREMIUM state), without embedded artifact to force download.\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connId\", server)\n      .withBoundConfigScope(\"scopeId\", \"connId\", \"projectKey\")\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withExtraEnabledLanguagesInConnectedMode(Language.JAVA)\n      .withBackendCapability(BackendCapability.FULL_SYNCHRONIZATION)\n      .start(client);\n\n    client.waitForSynchronization();\n    \n    await().atMost(15, TimeUnit.SECONDS).untilAsserted(() -> {\n      var statuses = backend.getPluginService().getPluginStatuses(new GetPluginStatusesParams(\"scopeId\")).get().getPluginStatuses();\n      var javaStatusOpt = statuses.stream().filter(s -> \"Java\".equals(s.getPluginName())).findFirst();\n      assertThat(javaStatusOpt).isPresent();\n      assertThat(javaStatusOpt.get().getState()).isEqualTo(PluginStateDto.SYNCED);\n    });\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/plugin/PluginRpcServiceMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.plugin;\n\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.ArtifactSourceDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.GetPluginStatusesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.PluginStateDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.PluginStatusDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass PluginRpcServiceMediumTests {\n\n  @SonarLintTest\n  void should_return_active_status_for_embedded_plugin(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start();\n\n    var response = backend.getPluginService()\n      .getPluginStatuses(new GetPluginStatusesParams(null))\n      .join();\n\n    assertThat(response.getPluginStatuses())\n      .filteredOn(s -> \"Python\".equals(s.getPluginName()))\n      .singleElement()\n      .satisfies(status -> {\n        assertThat(status.getState()).isEqualTo(PluginStateDto.ACTIVE);\n        assertThat(status.getSource()).isEqualTo(ArtifactSourceDto.EMBEDDED);\n        assertThat(status.getActualVersion()).isNotNull();\n        assertThat(status.getOverriddenVersion()).isNull();\n      });\n  }\n\n  @SonarLintTest\n  void should_return_unsupported_status_for_languages_with_no_plugin(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.PYTHON)\n      .start();\n\n    var response = backend.getPluginService()\n      .getPluginStatuses(new GetPluginStatusesParams(null))\n      .join();\n\n    assertThat(response.getPluginStatuses())\n      .filteredOn(s -> \"Python\".equals(s.getPluginName()))\n      .singleElement()\n      .satisfies(status -> {\n        assertThat(status.getState()).isEqualTo(PluginStateDto.UNSUPPORTED);\n        assertThat(status.getSource()).isNull();\n        assertThat(status.getActualVersion()).isNull();\n        assertThat(status.getOverriddenVersion()).isNull();\n      });\n  }\n\n  @SonarLintTest\n  void should_return_one_entry_per_known_language(SonarLintTestHarness harness) {\n    var backend = harness.newBackend().start();\n\n    var response = backend.getPluginService()\n      .getPluginStatuses(new GetPluginStatusesParams(null))\n      .join();\n\n    assertThat(response.getPluginStatuses()).hasSize(SonarLanguage.values().length);\n  }\n\n  @SonarLintTest\n  void should_return_premium_for_languages_only_available_in_connected_mode(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withExtraEnabledLanguagesInConnectedMode(Language.ABAP)\n      .start();\n\n    var response = backend.getPluginService()\n      .getPluginStatuses(new GetPluginStatusesParams(null))\n      .join();\n\n    assertThat(response.getPluginStatuses())\n      .filteredOn(s -> \"Abap\".equals(s.getPluginName()))\n      .singleElement()\n      .extracting(PluginStatusDto::getState)\n      .isEqualTo(PluginStateDto.PREMIUM);\n  }\n\n  @SonarLintTest\n  void should_return_synced_status_for_plugin_from_sonarqube_server_connection(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer()\n      .withPlugin(TestPlugin.PYTHON)\n      .start();\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.PYTHON)\n      .withSonarQubeConnection(\"connectionId\", server, storage -> storage\n        .withPlugin(TestPlugin.PYTHON))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .start();\n\n    var response = backend.getPluginService()\n      .getPluginStatuses(new GetPluginStatusesParams(\"configScopeId\"))\n      .join();\n\n    assertThat(response.getPluginStatuses())\n      .filteredOn(s -> \"Python\".equals(s.getPluginName()))\n      .singleElement()\n      .satisfies(status -> {\n        assertThat(status.getState()).isEqualTo(PluginStateDto.SYNCED);\n        assertThat(status.getSource()).isEqualTo(ArtifactSourceDto.SONARQUBE_SERVER);\n      });\n  }\n\n  @SonarLintTest\n  void should_return_standalone_statuses_when_config_scope_has_no_binding(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .withUnboundConfigScope(\"configScopeId\")\n      .start();\n\n    var response = backend.getPluginService()\n      .getPluginStatuses(new GetPluginStatusesParams(\"configScopeId\"))\n      .join();\n\n    assertThat(response.getPluginStatuses())\n      .filteredOn(s -> \"Python\".equals(s.getPluginName()))\n      .singleElement()\n      .extracting(PluginStatusDto::getState)\n      .isEqualTo(PluginStateDto.ACTIVE);\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/promotion/CampaignMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.promotion;\n\nimport java.net.MalformedURLException;\nimport java.net.URI;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.LocalDate;\nimport java.time.OffsetDateTime;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\nimport org.sonarsource.sonarlint.core.commons.storage.local.FileStorageManager;\nimport org.sonarsource.sonarlint.core.promotion.campaign.CampaignConstants;\nimport org.sonarsource.sonarlint.core.promotion.campaign.storage.CampaignsLocalStorage;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.MessageActionItem;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.MessageType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowMessageRequestResponse;\nimport org.sonarsource.sonarlint.core.serverconnection.FileUtils;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryLocalStorage;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryLocalStorageManager;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintBackendFixture;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport uk.org.webcompere.systemstubs.jupiter.SystemStub;\nimport uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension;\nimport uk.org.webcompere.systemstubs.properties.SystemProperties;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\n@ExtendWith(SystemStubsExtension.class)\nclass CampaignMediumTests {\n\n  private static final int MORE_THAN_TWO_WEEKS_AGO = 16;\n  private static final String DEFAULT_KEY = \"mediumTests\";\n\n  @SystemStub\n  SystemProperties propertiesStubs;\n\n  private Path userHome;\n\n  @BeforeEach\n  void setUp(@TempDir Path temp) {\n    propertiesStubs.set(\"sonarlint.internal.promotion.initialDelay\", \"0\");\n    userHome = temp.resolve(\"sonarHome\");\n    FileUtils.deleteRecursively(userHome);\n  }\n\n  @SonarLintTest\n  void it_should_initialize_campaigns_file(SonarLintTestHarness harness) {\n    saveTelemetryInstallTime(DEFAULT_KEY, MORE_THAN_TWO_WEEKS_AGO);\n    var client = harness.newFakeClient().build();\n\n    baseBackend(harness)\n      .start(client);\n\n    var campaignsFile = getCampaignsPath(DEFAULT_KEY);\n    await().until(() -> Files.exists(campaignsFile));\n    assertThat(getCampaigns(campaignsFile))\n      .hasSize(1)\n      .contains(Map.entry(\n        \"feedback_2026_01\",\n        new CampaignsLocalStorage.Campaign(\"feedback_2026_01\", LocalDate.now(), \"IGNORE\")));\n  }\n\n  @SonarLintTest\n  void it_should_only_update_campaign_file_on_returning_null(SonarLintTestHarness harness) {\n    saveTelemetryInstallTime(DEFAULT_KEY, MORE_THAN_TWO_WEEKS_AGO);\n    var client = harness.newFakeClient().build();\n\n    baseBackend(harness)\n      .start(client);\n\n    var campaignsFile = getCampaignsPath(DEFAULT_KEY);\n    await().until(() -> Files.exists(campaignsFile));\n    assertThat(getCampaigns(campaignsFile))\n      .hasSize(1)\n      .contains(Map.entry(\n        \"feedback_2026_01\",\n        new CampaignsLocalStorage.Campaign(\"feedback_2026_01\", LocalDate.now(), \"IGNORE\")));\n    verify(client, never()).openUrlInBrowser(any());\n  }\n\n  @SonarLintTest\n  void it_should_only_update_campaign_file_on_ignore(SonarLintTestHarness harness) {\n    saveTelemetryInstallTime(DEFAULT_KEY, MORE_THAN_TWO_WEEKS_AGO);\n    var client = harness.newFakeClient().build();\n    when(client.showMessageRequest(any(), any(), any()))\n      .thenAnswer(new Answer<>() {\n        @Override\n        public Object answer(InvocationOnMock invocationOnMock) throws Throwable {\n          wait();\n          return null;\n        }\n      });\n\n    baseBackend(harness)\n      .start(client);\n\n    var campaignsFile = getCampaignsPath(DEFAULT_KEY);\n    await().until(() -> Files.exists(campaignsFile));\n    assertThat(getCampaigns(campaignsFile))\n      .hasSize(1)\n      .contains(Map.entry(\n        \"feedback_2026_01\",\n        new CampaignsLocalStorage.Campaign(\"feedback_2026_01\", LocalDate.now(), \"IGNORE\")));\n    verify(client, never()).openUrlInBrowser(any());\n  }\n\n  @SonarLintTest\n  void it_should_update_campaign_file_and_telemetry_on_close(SonarLintTestHarness harness) {\n    propertiesStubs.set(\"sonarlint.internal.promotion.initialDelay\", \"1\");\n    saveTelemetryInstallTime(DEFAULT_KEY, MORE_THAN_TWO_WEEKS_AGO);\n    var client = harness.newFakeClient().build();\n    when(client.showMessageRequest(any(), any(), any()))\n      .thenReturn(new ShowMessageRequestResponse(null, true));\n\n    var backend = baseBackend(harness)\n      .withProductKey(DEFAULT_KEY)\n      .withTelemetryEnabled()\n      .start(client);\n\n    var campaignsFile = getCampaignsPath(DEFAULT_KEY);\n    await().untilAsserted(\n      () -> assertThat(getCampaigns(campaignsFile))\n        .hasSize(1)\n        .contains(Map.entry(\n          CampaignConstants.FEEDBACK_2026_01_CAMPAIGN,\n          new CampaignsLocalStorage.Campaign(CampaignConstants.FEEDBACK_2026_01_CAMPAIGN, LocalDate.now(), \"CLOSED\")))\n    );\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent())\n      .extracting(TelemetryLocalStorage::getCampaignsResolutions)\n      .extracting(campaigns -> campaigns.get(CampaignConstants.FEEDBACK_2026_01_CAMPAIGN))\n      .isEqualTo(\"CLOSED\"));\n  }\n\n  @SonarLintTest\n  void it_should_update_campaigns_file_and_open_idea_url_on_love_it(SonarLintTestHarness harness) throws MalformedURLException {\n    verifyOpenUrlOnResponse(harness,\n      \"LOVE_IT\", \"idea\", \"https://plugins.jetbrains.com/plugin/7973-sonarqube-for-ide/reviews\");\n  }\n\n  @SonarLintTest\n  void it_should_update_campaigns_file_and_open_vs_url_on_love_it(SonarLintTestHarness harness) throws MalformedURLException {\n    verifyOpenUrlOnResponse(harness,\n      \"LOVE_IT\", \"visualstudio\", \"https://marketplace.visualstudio.com/items?itemName=SonarSource.SonarLintforVisualStudio2022&ssr=false#review-details\");\n  }\n\n  @SonarLintTest\n  void it_should_update_campaigns_file_and_open_vscode_url_on_love_it(SonarLintTestHarness harness) throws MalformedURLException {\n    verifyOpenUrlOnResponse(harness,\n      \"LOVE_IT\", \"vscode\", \"https://marketplace.visualstudio.com/items?itemName=SonarSource.sonarlint-vscode&ssr=false#review-details\");\n  }\n\n  @SonarLintTest\n  void it_should_update_campaigns_file_and_open_vsx_url_on_love_it(SonarLintTestHarness harness) throws MalformedURLException {\n    verifyOpenUrlOnResponse(harness,\n      \"LOVE_IT\", \"windsurf\", \"https://open-vsx.org/extension/SonarSource/sonarlint-vscode/reviews\");\n  }\n\n  @SonarLintTest\n  void it_should_update_campaigns_file_and_open_idea_google_form_url_on_share_feedback(SonarLintTestHarness harness) throws MalformedURLException {\n    verifyOpenUrlOnResponse(harness,\n      \"SHARE_FEEDBACK\", \"idea\", \"https://forms.gle/kDyQ7sDyBfpPEBsy6\");\n  }\n\n  @SonarLintTest\n  void it_should_update_campaigns_file_and_open_visualstudio_google_form_url_on_share_feedback(SonarLintTestHarness harness) throws MalformedURLException {\n    verifyOpenUrlOnResponse(harness,\n      \"SHARE_FEEDBACK\", \"visualstudio\", \"https://forms.gle/LjKGKWECDdJw1PmU7\");\n  }\n\n  @SonarLintTest\n  void it_should_update_campaigns_file_and_open_vscode_google_form_url_on_share_feedback(SonarLintTestHarness harness) throws MalformedURLException {\n    verifyOpenUrlOnResponse(harness,\n      \"SHARE_FEEDBACK\", \"vscode\", \"https://forms.gle/TncKAVK4EWM7z4RV6\");\n  }\n\n  @SonarLintTest\n  void it_should_update_campaigns_file_and_open_vscode_google_form_url_on_share_feedback_for_forks(SonarLintTestHarness harness) throws MalformedURLException {\n    verifyOpenUrlOnResponse(harness,\n      \"SHARE_FEEDBACK\", \"windsurf\", \"https://forms.gle/TncKAVK4EWM7z4RV6\");\n  }\n\n  @SonarLintTest\n  void it_should_only_update_campaign_file_on_maybe_later(SonarLintTestHarness harness) {\n    saveTelemetryInstallTime(DEFAULT_KEY, MORE_THAN_TWO_WEEKS_AGO);\n    var client = harness.newFakeClient().build();\n    when(client.showMessageRequest(any(), any(), any()))\n      .thenReturn(new ShowMessageRequestResponse(\"MAYBE_LATER\", false));\n\n    baseBackend(harness)\n      .start(client);\n\n    var campaignsFile = getCampaignsPath(DEFAULT_KEY);\n    await().until(() -> Files.exists(campaignsFile));\n    assertThat(getCampaigns(campaignsFile))\n      .hasSize(1)\n      .contains(Map.entry(\n        \"feedback_2026_01\",\n        new CampaignsLocalStorage.Campaign(\"feedback_2026_01\", LocalDate.now(), \"MAYBE_LATER\")));\n    verify(client, never()).openUrlInBrowser(any());\n  }\n\n  @SonarLintTest\n  void it_should_redirect_to_community_on_invalid_productKey(SonarLintTestHarness harness) {\n    saveTelemetryInstallTime(DEFAULT_KEY, MORE_THAN_TWO_WEEKS_AGO);\n    var client = harness.newFakeClient().build();\n    when(client.showMessageRequest(any(), eq(\"Enjoying SonarQube for IDE? We'd love to hear what you think.\"), any()))\n      .thenReturn(new ShowMessageRequestResponse(\"LOVE_IT\", false));\n    when(client.showMessageRequest(any(), eq(\"Could not find feedback link for mediumTests. Please consider sharing your feedback directly on our community forum\"), any()))\n      .thenReturn(new ShowMessageRequestResponse(\"OPEN_COMMUNITY\", false));\n\n    baseBackend(harness)\n      .start(client);\n\n    var campaignsFile = getCampaignsPath(DEFAULT_KEY);\n    await().until(() -> Files.exists(campaignsFile));\n    assertThat(getCampaigns(campaignsFile))\n      .hasSize(1)\n      .contains(Map.entry(\n        \"feedback_2026_01\",\n        new CampaignsLocalStorage.Campaign(\"feedback_2026_01\", LocalDate.now(), \"LOVE_IT\")));\n\n    ArgumentCaptor<List<MessageActionItem>> actionsArgumentCaptor = ArgumentCaptor.forClass(List.class);\n    var messageTypeCaptor = ArgumentCaptor.forClass(MessageType.class);\n    var messageCaptor = ArgumentCaptor.forClass(String.class);\n    var openInBrowserCaptor = ArgumentCaptor.forClass(URL.class);\n\n    verify(client, times(2)).showMessageRequest(messageTypeCaptor.capture(),\n      messageCaptor.capture(),\n      actionsArgumentCaptor.capture());\n    // showMessageRequest will be called twice - once for the feedback, and second time for the community fallback\n    assertThat(messageTypeCaptor.getAllValues().get(1)).isEqualTo(MessageType.INFO);\n    assertThat(messageCaptor.getAllValues().get(1))\n      .isEqualTo(\"Could not find feedback link for mediumTests. Please consider sharing your feedback directly on our community forum\");\n    assertThat(actionsArgumentCaptor.getAllValues().get(1)).hasSize(1);\n    assertThat(actionsArgumentCaptor.getAllValues().get(1).get(0)).extracting(MessageActionItem::getKey, MessageActionItem::getDisplayText, MessageActionItem::isPrimaryAction)\n      .containsExactly(\"OPEN_COMMUNITY\", \"Open Community Forum\", true);\n    verify(client, times(1)).openUrlInBrowser(openInBrowserCaptor.capture());\n    assertThat(openInBrowserCaptor.getValue()).hasToString(\"https://community.sonarsource.com/c/sl/11\");\n  }\n\n  @SonarLintTest\n  void it_should_delay_the_notification_configured_amount_of_time(SonarLintTestHarness harness) {\n    saveTelemetryInstallTime(DEFAULT_KEY, MORE_THAN_TWO_WEEKS_AGO);\n    propertiesStubs.set(\"sonarlint.internal.promotion.initialDelay\", \"1\");\n    var client = harness.newFakeClient().build();\n    when(client.showMessageRequest(any(), any(), any()))\n      .thenReturn(new ShowMessageRequestResponse(\"LOVE_IT\", false));\n\n    baseBackend(harness)\n      .start(client);\n\n    verify(client, timeout(1500)).showMessageRequest(\n      eq(MessageType.INFO),\n      eq(\"Enjoying SonarQube for IDE? We'd love to hear what you think.\"),\n      any());\n  }\n\n  @SonarLintTest\n  void it_should_not_trigger_notification_for_recent_installation(SonarLintTestHarness harness) {\n    saveTelemetryInstallTime(DEFAULT_KEY, 5);\n    var client = harness.newFakeClient().build();\n\n    baseBackend(harness)\n      .start(client);\n\n    verify(client, never()).showMessageRequest(any(), any(), any());\n  }\n\n  @SonarLintTest\n  void it_should_trigger_notification_again_after_week_for_maybe_later(SonarLintTestHarness harness) {\n    saveFeedbackCampaign(LocalDate.now().minusDays(8), \"MAYBE_LATER\");\n    var client = harness.newFakeClient().build();\n\n    baseBackend(harness)\n      .start(client);\n\n    verify(client, timeout(500)).showMessageRequest(\n      eq(MessageType.INFO),\n      eq(\"Enjoying SonarQube for IDE? We'd love to hear what you think.\"),\n      any());\n  }\n\n  @SonarLintTest\n  void it_should_not_trigger_notification_again_after_less_then_week_for_maybe_later(SonarLintTestHarness harness) {\n    saveFeedbackCampaign(LocalDate.now().minusDays(6), \"MAYBE_LATER\");\n    var client = harness.newFakeClient().build();\n\n    baseBackend(harness)\n      .start(client);\n\n    verify(client, never()).showMessageRequest(any(), any(), any());\n  }\n\n  @SonarLintTest\n  void it_should_trigger_notification_again_after_6_months_for_ignore(SonarLintTestHarness harness) {\n    saveFeedbackCampaign(LocalDate.now().minusMonths(6).minusDays(1), \"IGNORE\");\n    var client = harness.newFakeClient().build();\n\n    baseBackend(harness)\n      .start(client);\n\n    verify(client, timeout(500)).showMessageRequest(\n      eq(MessageType.INFO),\n      eq(\"Enjoying SonarQube for IDE? We'd love to hear what you think.\"),\n      any());\n  }\n\n  @SonarLintTest\n  void it_should_not_trigger_notification_again_after_less_then_6_months_for_ignore(SonarLintTestHarness harness) {\n    saveFeedbackCampaign(LocalDate.now().minusMonths(6).plusDays(1), \"IGNORE\");\n    var client = harness.newFakeClient().build();\n\n    baseBackend(harness)\n      .start(client);\n\n    verify(client, never()).showMessageRequest(any(), any(), any());\n  }\n\n  @SonarLintTest\n  void it_should_record_notification_shown_in_telemetry(SonarLintTestHarness harness) {\n    // make a little delay as telemetry event listeners take some time to be seen by the context\n    propertiesStubs.set(\"sonarlint.internal.promotion.initialDelay\", \"1\");\n    saveTelemetryInstallTime(DEFAULT_KEY, MORE_THAN_TWO_WEEKS_AGO);\n    var backend = baseBackend(harness)\n      .withTelemetryEnabled()\n      .start();\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent())\n      .extracting(TelemetryLocalStorage::getCampaignsShown)\n      .extracting(campaigns -> campaigns.get(CampaignConstants.FEEDBACK_2026_01_CAMPAIGN))\n      .isEqualTo(1));\n  }\n\n  @SonarLintTest\n  void it_should_record_notification_responded_in_telemetry(SonarLintTestHarness harness) {\n    // make a little delay as telemetry event listeners take some time to be seen by the context\n    propertiesStubs.set(\"sonarlint.internal.promotion.initialDelay\", \"1\");\n    saveTelemetryInstallTime(DEFAULT_KEY, MORE_THAN_TWO_WEEKS_AGO);\n    var client = harness.newFakeClient().build();\n    when(client.showMessageRequest(any(), any(), any()))\n      .thenReturn(new ShowMessageRequestResponse(\"LOVE_IT\", false));\n\n    var backend = baseBackend(harness)\n      .withTelemetryEnabled()\n      .start(client);\n\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent())\n      .extracting(TelemetryLocalStorage::getCampaignsResolutions)\n      .extracting(campaigns -> campaigns.get(CampaignConstants.FEEDBACK_2026_01_CAMPAIGN))\n      .isEqualTo(\"LOVE_IT\"));\n  }\n\n  @SonarLintTest\n  void it_should_not_trigger_notification_with_no_backend_capability(SonarLintTestHarness harness) {\n    saveTelemetryInstallTime(DEFAULT_KEY, MORE_THAN_TWO_WEEKS_AGO);\n    var client = harness.newFakeClient().build();\n\n    harness.newBackend()\n      .withUserHome(userHome)\n      .start(client);\n\n    verify(client, never()).showMessageRequest(any(), any(), any());\n  }\n\n  @SonarLintTest\n  void it_should_not_show_notification_multiple_times_when_multiple_backends_start_before_delay(SonarLintTestHarness harness) {\n    saveTelemetryInstallTime(DEFAULT_KEY, MORE_THAN_TWO_WEEKS_AGO);\n    propertiesStubs.set(\"sonarlint.internal.promotion.initialDelay\", \"3\");\n    var client = harness.newFakeClient().build();\n\n    baseBackend(harness).start(client);\n    baseBackend(harness).start(client);\n\n    await().atMost(5, TimeUnit.SECONDS)\n      .untilAsserted(() -> {\n        var campaignsFile = getCampaignsPath(DEFAULT_KEY);\n        assertThat(Files.exists(campaignsFile)).isTrue();\n      });\n\n    verify(client, timeout(5000)).showMessageRequest(\n      eq(MessageType.INFO),\n      eq(\"Enjoying SonarQube for IDE? We'd love to hear what you think.\"),\n      any());\n\n    var campaignsFile = getCampaignsPath(DEFAULT_KEY);\n    assertThat(getCampaigns(campaignsFile))\n      .hasSize(1)\n      .containsKey(CampaignConstants.FEEDBACK_2026_01_CAMPAIGN);\n  }\n\n  private void verifyOpenUrlOnResponse(SonarLintTestHarness harness, String response, String productKey, String expectedUrl) throws MalformedURLException {\n    saveTelemetryInstallTime(productKey, MORE_THAN_TWO_WEEKS_AGO);\n    var client = harness.newFakeClient()\n      .build();\n    when(client.showMessageRequest(any(), any(), any()))\n      .thenReturn(new ShowMessageRequestResponse(response, false));\n\n    baseBackend(harness)\n      .withProductKey(productKey)\n      .start(client);\n\n    var campaignsFile = getCampaignsPath(productKey);\n    await().untilAsserted(\n      () -> assertThat(getCampaigns(campaignsFile))\n        .hasSize(1)\n        .contains(Map.entry(\n          CampaignConstants.FEEDBACK_2026_01_CAMPAIGN,\n          new CampaignsLocalStorage.Campaign(CampaignConstants.FEEDBACK_2026_01_CAMPAIGN, LocalDate.now(), response))));\n    verify(client, timeout(500)).openUrlInBrowser(URI.create(expectedUrl).toURL());\n  }\n\n  private void saveFeedbackCampaign(LocalDate lastShown, String lastResponse) {\n    var campaignsStorage = new FileStorageManager<>(getCampaignsPath(CampaignMediumTests.DEFAULT_KEY), CampaignsLocalStorage::new, CampaignsLocalStorage.class);\n    campaignsStorage.tryUpdateAtomically(data -> data.campaigns()\n      .put(CampaignConstants.FEEDBACK_2026_01_CAMPAIGN,\n        new CampaignsLocalStorage.Campaign(\n          CampaignConstants.FEEDBACK_2026_01_CAMPAIGN,\n          lastShown,\n          lastResponse)));\n  }\n\n  private static Map<String, CampaignsLocalStorage.Campaign> getCampaigns(Path campaignsFile) {\n    var campaignsStorage = new FileStorageManager<>(campaignsFile, CampaignsLocalStorage::new, CampaignsLocalStorage.class);\n    return campaignsStorage.getStorage().campaigns();\n  }\n\n  private void saveTelemetryInstallTime(String productKey, int daysAgo) {\n    var telemetryPath = getTelemetryPath(productKey);\n    TelemetryLocalStorageManager telemetryStorageManager = new TelemetryLocalStorageManager(telemetryPath, mock(InitializeParams.class));\n    telemetryStorageManager.tryUpdateAtomically(data -> data.setInstallTime(OffsetDateTime.now().minusDays(daysAgo)));\n  }\n\n  private Path getCampaignsPath(String productKey) {\n    return userHome.resolve(\"campaigns\").resolve(productKey).resolve(\"campaigns\");\n  }\n\n  private Path getTelemetryPath(String productKey) {\n    return userHome.resolve(\"telemetry\").resolve(productKey).resolve(\"usage\");\n  }\n\n  private SonarLintBackendFixture.SonarLintBackendBuilder baseBackend(SonarLintTestHarness harness) {\n    return harness.newBackend()\n      .withUserHome(userHome)\n      .withBackendCapability(BackendCapability.PROMOTIONAL_CAMPAIGNS);\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/promotion/ExtraEnabledLanguagesInConnectedModePromotionMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.promotion;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.UUID;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogLevel;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static org.mockito.ArgumentMatchers.anySet;\nimport static org.mockito.ArgumentMatchers.argThat;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.after;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.EMBEDDED_SERVER;\n\nclass ExtraEnabledLanguagesInConnectedModePromotionMediumTests {\n  @RegisterExtension\n  SonarLintLogTester logTester = new SonarLintLogTester(true);\n\n  @SonarLintTest\n  void it_should_notify_clients_for_a_detected_language_that_is_enabled_only_in_connected_mode(SonarLintTestHarness harness, @TempDir Path tempDir) throws IOException {\n    var abapFile = tempDir.resolve(\"file.abap\");\n    Files.createFile(abapFile);\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScopeId\", tempDir, List.of(new ClientFileDto(abapFile.toUri(), tempDir.relativize(abapFile), \"configScopeId\", false, null, abapFile, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withExtraEnabledLanguagesInConnectedMode(Language.ABAP)\n      .withUnboundConfigScope(\"configScopeId\")\n      .withBackendCapability(EMBEDDED_SERVER)\n      .withTelemetryEnabled()\n      .start(fakeClient);\n\n    backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(\"configScopeId\", UUID.randomUUID(),\n        List.of(abapFile.toUri()), Map.of(), false)).join();\n\n    verify(fakeClient).promoteExtraEnabledLanguagesInConnectedMode(\"configScopeId\", Set.of(Language.ABAP));\n  }\n\n  @SonarLintTest\n  void it_should_not_notify_clients_when_already_in_connected_mode(SonarLintTestHarness harness, @TempDir Path tempDir) throws IOException {\n    var abapFile = tempDir.resolve(\"file.abap\");\n    Files.createFile(abapFile);\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScopeId\", tempDir, List.of(new ClientFileDto(abapFile.toUri(), tempDir.relativize(abapFile), \"configScopeId\", false, null, abapFile, null, null, true)))\n      .build();\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(\"projectKey\")\n      .start();\n    var backend = harness.newBackend()\n      .withExtraEnabledLanguagesInConnectedMode(Language.ABAP)\n      .withSonarQubeConnection(\"connectionId\", server, storage -> storage.withProject(\"projectKey\", project -> project.withRuleSet(\"abap\", ruleSet -> ruleSet.withActiveRule(\"abap:S100\", \"MAJOR\")).withMainBranch(\"main\")))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(EMBEDDED_SERVER)\n      .withTelemetryEnabled()\n      .start(fakeClient);\n\n    backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(\"configScopeId\", UUID.randomUUID(),\n        List.of(abapFile.toUri()), Map.of(), false)).join();\n\n    verify(fakeClient, after(200).never()).promoteExtraEnabledLanguagesInConnectedMode(\"configScopeId\", Set.of(Language.ABAP));\n  }\n\n  @SonarLintTest\n  void it_should_not_notify_clients_when_detected_language_is_not_an_extra_language(SonarLintTestHarness harness, @TempDir Path tempDir) throws IOException {\n    var abapFile = tempDir.resolve(\"file.abap\");\n    Files.createFile(abapFile);\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScopeId\", tempDir, List.of(new ClientFileDto(abapFile.toUri(), tempDir.relativize(abapFile), \"configScopeId\", false, null, abapFile, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.ABAP)\n      .withUnboundConfigScope(\"configScopeId\")\n      .withBackendCapability(EMBEDDED_SERVER)\n      .withTelemetryEnabled()\n      .start(fakeClient);\n\n    backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(\"configScopeId\", UUID.randomUUID(),\n        List.of(abapFile.toUri()), Map.of(), false)).join();\n\n    verify(fakeClient, after(200).never()).promoteExtraEnabledLanguagesInConnectedMode(\"configScopeId\", Set.of(Language.ABAP));\n  }\n\n  @SonarLintTest\n  void it_should_not_notify_clients_when_no_language_was_detected_during_analysis(SonarLintTestHarness harness, @TempDir Path tempDir) throws IOException {\n    var randomFile = tempDir.resolve(\"file.abc\");\n    Files.createFile(randomFile);\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScopeId\", tempDir,\n        List.of(new ClientFileDto(randomFile.toUri(), tempDir.relativize(randomFile), \"configScopeId\", false, null, randomFile, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(\"configScopeId\")\n      .withBackendCapability(EMBEDDED_SERVER)\n      .withTelemetryEnabled()\n      .start(fakeClient);\n\n    backend.getAnalysisService()\n      .analyzeFilesAndTrack(new AnalyzeFilesAndTrackParams(\"configScopeId\", UUID.randomUUID(),\n        List.of(randomFile.toUri()), Map.of(), false)).join();\n\n    verify(fakeClient, after(200).never()).promoteExtraEnabledLanguagesInConnectedMode(eq(\"configScopeId\"), anySet());\n    verify(fakeClient, never()).log(argThat(logParams -> logParams.getLevel().equals(LogLevel.ERROR)));\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/remediation/aicodefix/AiCodeFixMediumTest.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.remediation.aicodefix;\n\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.UUID;\nimport org.assertj.core.api.InstanceOfAssertFactories;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseError;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.serverconnection.aicodefix.AiCodeFix;\nimport org.sonarsource.sonarlint.core.serverconnection.aicodefix.AiCodeFixRepository;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidUpdateFileSystemParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.remediation.aicodefix.SuggestFixChangeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.remediation.aicodefix.SuggestFixParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.ListAllParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AiSuggestionSource;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryFixSuggestionReceivedCounter;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode.InvalidParams;\nimport static org.sonarsource.sonarlint.core.serverconnection.aicodefix.AiCodeFix.Enablement.ENABLED_FOR_SOME_PROJECTS;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode.CONFIG_SCOPE_NOT_BOUND;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode.CONNECTION_NOT_FOUND;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode.FILE_NOT_FOUND;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode.ISSUE_NOT_FOUND;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode.TOO_MANY_REQUESTS;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.serverapi.ServerApiHelper.HTTP_TOO_MANY_REQUESTS;\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ProjectStoragePaths.encodeForFs;\nimport static org.sonarsource.sonarlint.core.test.utils.storage.ServerTaintIssueFixtures.aServerTaintIssue;\nimport static utils.AnalysisUtils.analyzeFileAndGetIssue;\nimport static utils.AnalysisUtils.createFile;\n\npublic class AiCodeFixMediumTest {\n  public static final String XML_SOURCE_CODE_WITH_ISSUE = \"\"\"\n    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n    <project>\n      <modelVersion>4.0.0</modelVersion>\n      <groupId>com.foo</groupId>\n      <artifactId>bar</artifactId>\n      <version>${pom.version}</version>\n    </project>\"\"\";\n\n  @SonarLintTest\n  void it_should_fail_if_the_configuration_scope_is_not_bound(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(\"configScope\")\n      .start();\n\n    var future = backend.getAiCodeFixRpcService().suggestFix(new SuggestFixParams(\"configScope\", UUID.randomUUID()));\n\n    assertThat(future).failsWithin(Duration.of(1, ChronoUnit.SECONDS))\n      .withThrowableThat()\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class)\n      .asInstanceOf(InstanceOfAssertFactories.type(ResponseErrorException.class))\n      .extracting(ResponseErrorException::getResponseError)\n      .extracting(ResponseError::getCode, ResponseError::getMessage)\n      .containsExactly(CONFIG_SCOPE_NOT_BOUND, \"The provided configuration scope is not bound\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_if_the_configuration_scope_is_bound_to_an_unknown_connection(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .start();\n\n    var future = backend.getAiCodeFixRpcService().suggestFix(new SuggestFixParams(\"configScope\", UUID.randomUUID()));\n\n    assertThat(future).failsWithin(Duration.of(1, ChronoUnit.SECONDS))\n      .withThrowableThat()\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class)\n      .asInstanceOf(InstanceOfAssertFactories.type(ResponseErrorException.class))\n      .extracting(ResponseErrorException::getResponseError)\n      .extracting(ResponseError::getCode, ResponseError::getMessage)\n      .containsExactly(CONNECTION_NOT_FOUND, \"The provided configuration scope is bound to an unknown connection\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_if_the_issue_is_unknown(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarCloudServer().start();\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"organizationKey\", true, storage -> storage\n        .withProject(\"projectKey\"))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .start();\n    var issueId = UUID.randomUUID();\n\n    var future = backend.getAiCodeFixRpcService().suggestFix(new SuggestFixParams(\"configScope\", issueId));\n\n    assertThat(future).failsWithin(Duration.of(2, ChronoUnit.SECONDS))\n      .withThrowableThat()\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class)\n      .asInstanceOf(InstanceOfAssertFactories.type(ResponseErrorException.class))\n      .extracting(ResponseErrorException::getResponseError)\n      .extracting(ResponseError::getCode, ResponseError::getMessage)\n      .containsExactly(ISSUE_NOT_FOUND, \"The provided issue or taint does not exist\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_if_the_file_is_unknown(SonarLintTestHarness harness, @TempDir Path baseDir) throws InterruptedException {\n    var filePath = createFile(baseDir, \"pom.xml\", XML_SOURCE_CODE_WITH_ISSUE);\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"organizationKey\", organization -> organization\n        .withProject(\"projectKey\", project -> project\n          .withBranch(\"branchName\")\n          .withAiCodeFixSuggestion(suggestion -> suggestion\n            .withId(UUID.fromString(\"e51b7bbd-72bc-4008-a4f1-d75583f3dc98\"))\n            .withExplanation(\"This is the explanation\")\n            .withChange(0, 0, \"This is the new code\"))))\n      .start();\n\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"organizationKey\", true, storage -> storage\n        .withProject(\"projectKey\", project -> project.withMainBranch(\"main\").withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MAJOR\")))\n        .withAiCodeFixSettings(aiCodeFix -> aiCodeFix\n          .withSupportedRules(Set.of(\"xml:S3421\"))\n          .organizationEligible(true).enabledForAllProjects()))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .start(fakeClient);\n    var issue = analyzeFileAndGetIssue(fileUri, fakeClient, backend, \"configScope\");\n    backend.getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(List.of(), List.of(), List.of(fileUri)));\n    // leave time for the notification to be received by the backend\n    Thread.sleep(300);\n\n    var future = backend.getAiCodeFixRpcService().suggestFix(new SuggestFixParams(\"configScope\", issue.getId()));\n\n    assertThat(future).failsWithin(Duration.of(1, ChronoUnit.SECONDS))\n      .withThrowableThat()\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class)\n      .asInstanceOf(InstanceOfAssertFactories.type(ResponseErrorException.class))\n      .extracting(ResponseErrorException::getResponseError)\n      .extracting(ResponseError::getCode, ResponseError::getMessage)\n      .containsExactly(FILE_NOT_FOUND, \"The provided issue ID corresponds to an unknown file\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_if_the_issue_is_not_fixable_because_at_file_level(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var sourceCode = \"public interface Fubar\\n\" +\n      \"{}\";\n    var filePath = createFile(baseDir, \"Fubar.java\", sourceCode);\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"organizationKey\", organization -> organization\n        .withProject(\"projectKey\", project -> project\n          .withBranch(\"branchName\")\n          .withAiCodeFixSuggestion(suggestion -> suggestion\n            .withId(UUID.fromString(\"e51b7bbd-72bc-4008-a4f1-d75583f3dc98\"))\n            .withExplanation(\"This is the explanation\")\n            .withChange(0, 0, \"This is the new code\"))))\n      .start();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"organizationKey\", true, storage -> storage\n        .withProject(\"projectKey\", project -> project.withMainBranch(\"main\").withRuleSet(\"java\", ruleSet -> ruleSet.withActiveRule(\"java:S1220\", \"MAJOR\")))\n        .withAiCodeFixSettings(settings -> settings.organizationEligible(true).enabledForAllProjects()))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .start(fakeClient);\n    var issue = analyzeFileAndGetIssue(fileUri, fakeClient, backend, \"configScope\");\n    assertThat(issue.isAiCodeFixable()).isFalse();\n\n    var future = backend.getAiCodeFixRpcService().suggestFix(new SuggestFixParams(\"configScope\", issue.getId()));\n\n    assertThat(future).failsWithin(Duration.of(1, ChronoUnit.SECONDS))\n      .withThrowableThat()\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class)\n      .asInstanceOf(InstanceOfAssertFactories.type(ResponseErrorException.class))\n      .extracting(ResponseErrorException::getResponseError)\n      .extracting(ResponseError::getCode, ResponseError::getMessage)\n      .containsExactly(InvalidParams.getValue(), \"The provided issue cannot be fixed\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_if_the_issue_is_not_fixable_because_the_organization_is_not_eligible(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var sourceCode = \"public interface Fubar\\n\" +\n      \"{}\";\n    var filePath = createFile(baseDir, \"Fubar.java\", sourceCode);\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"organizationKey\", organization -> organization\n        .withProject(\"projectKey\", project -> project\n          .withBranch(\"branchName\")\n          .withAiCodeFixSuggestion(suggestion -> suggestion\n            .withId(UUID.fromString(\"e51b7bbd-72bc-4008-a4f1-d75583f3dc98\"))\n            .withExplanation(\"This is the explanation\")\n            .withChange(0, 0, \"This is the new code\"))))\n      .start();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"organizationKey\", true, storage -> storage\n        .withProject(\"projectKey\", project -> project.withMainBranch(\"main\").withRuleSet(\"java\", ruleSet -> ruleSet.withActiveRule(\"java:S1220\", \"MAJOR\")))\n        .withAiCodeFixSettings(settings -> settings.organizationEligible(false)))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .start(fakeClient);\n    var issue = analyzeFileAndGetIssue(fileUri, fakeClient, backend, \"configScope\");\n    assertThat(issue.isAiCodeFixable()).isFalse();\n\n    var future = backend.getAiCodeFixRpcService().suggestFix(new SuggestFixParams(\"configScope\", issue.getId()));\n\n    assertThat(future).failsWithin(Duration.of(1, ChronoUnit.SECONDS))\n      .withThrowableThat()\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class)\n      .asInstanceOf(InstanceOfAssertFactories.type(ResponseErrorException.class))\n      .extracting(ResponseErrorException::getResponseError)\n      .extracting(ResponseError::getCode, ResponseError::getMessage)\n      .containsExactly(InvalidParams.getValue(), \"The provided issue cannot be fixed\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_if_the_issue_is_not_fixable_because_the_feature_is_globally_disabled(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var sourceCode = \"public interface Fubar\\n\" +\n      \"{}\";\n    var filePath = createFile(baseDir, \"Fubar.java\", sourceCode);\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"organizationKey\", organization -> organization\n        .withProject(\"projectKey\",\n          project -> project\n            .withBranch(\"branchName\")\n            .withAiCodeFixSuggestion(suggestion -> suggestion\n              .withId(UUID.fromString(\"e51b7bbd-72bc-4008-a4f1-d75583f3dc98\"))\n              .withExplanation(\"This is the explanation\")\n              .withChange(0, 0, \"This is the new code\"))))\n      .start();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"organizationKey\", true, storage -> storage\n        .withProject(\"projectKey\", project -> project.withMainBranch(\"main\").withRuleSet(\"java\", ruleSet -> ruleSet.withActiveRule(\"java:S1220\", \"MAJOR\")))\n        .withAiCodeFixSettings(settings -> settings.organizationEligible(true).disabled()))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .start(fakeClient);\n    var issue = analyzeFileAndGetIssue(fileUri, fakeClient, backend, \"configScope\");\n    assertThat(issue.isAiCodeFixable()).isFalse();\n\n    var future = backend.getAiCodeFixRpcService().suggestFix(new SuggestFixParams(\"configScope\", issue.getId()));\n\n    assertThat(future).failsWithin(Duration.of(1, ChronoUnit.SECONDS))\n      .withThrowableThat()\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class)\n      .asInstanceOf(InstanceOfAssertFactories.type(ResponseErrorException.class))\n      .extracting(ResponseErrorException::getResponseError)\n      .extracting(ResponseError::getCode, ResponseError::getMessage)\n      .containsExactly(InvalidParams.getValue(), \"The provided issue cannot be fixed\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_if_the_issue_is_not_fixable_because_the_feature_is_disabled_for_the_project(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var sourceCode = \"public interface Fubar\\n\" +\n      \"{}\";\n    var filePath = createFile(baseDir, \"Fubar.java\", sourceCode);\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"organizationKey\", organization -> organization\n        .withProject(\"projectKey\",\n          project -> project\n            .withBranch(\"branchName\")\n            .withAiCodeFixSuggestion(suggestion -> suggestion\n              .withId(UUID.fromString(\"e51b7bbd-72bc-4008-a4f1-d75583f3dc98\"))\n              .withExplanation(\"This is the explanation\")\n              .withChange(0, 0, \"This is the new code\"))))\n      .start();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"organizationKey\", true, storage -> storage\n        .withProject(\"projectKey\", project -> project.withMainBranch(\"main\").withRuleSet(\"java\", ruleSet -> ruleSet.withActiveRule(\"java:S1220\", \"MAJOR\")))\n        .withAiCodeFixSettings(settings -> settings.organizationEligible(true).enabledForProjects(\"otherProjectKey\")))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .start(fakeClient);\n    var issue = analyzeFileAndGetIssue(fileUri, fakeClient, backend, \"configScope\");\n    assertThat(issue.isAiCodeFixable()).isFalse();\n\n    var future = backend.getAiCodeFixRpcService().suggestFix(new SuggestFixParams(\"configScope\", issue.getId()));\n\n    assertThat(future).failsWithin(Duration.of(1, ChronoUnit.SECONDS))\n      .withThrowableThat()\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class)\n      .asInstanceOf(InstanceOfAssertFactories.type(ResponseErrorException.class))\n      .extracting(ResponseErrorException::getResponseError)\n      .extracting(ResponseError::getCode, ResponseError::getMessage)\n      .containsExactly(InvalidParams.getValue(), \"The provided issue cannot be fixed\");\n  }\n\n  @SonarLintTest\n  void it_should_mark_the_issue_as_not_fixable_if_not_bound(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", XML_SOURCE_CODE_WITH_ISSUE);\n    var fileUri = filePath.toUri();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withUnboundConfigScope(\"configScope\")\n      .start(fakeClient);\n\n    var issue = analyzeFileAndGetIssue(fileUri, fakeClient, backend, \"configScope\");\n\n    assertThat(issue.isAiCodeFixable()).isFalse();\n  }\n\n  @SonarLintTest\n  void it_should_mark_the_issue_as_not_fixable_if_bound_to_sonarqube_server(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", XML_SOURCE_CODE_WITH_ISSUE);\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(\"projectKey\", project -> project.withBranch(\"branchName\"))\n      .start();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withSonarQubeConnection(\"connectionId\", server, storage -> storage\n        .withProject(\"projectKey\", project -> project.withMainBranch(\"main\").withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MAJOR\"))))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .start(fakeClient);\n\n    var issue = analyzeFileAndGetIssue(fileUri, fakeClient, backend, \"configScope\");\n\n    assertThat(issue.isAiCodeFixable()).isFalse();\n  }\n\n  @SonarLintTest\n  void it_should_mark_the_issue_as_not_fixable_if_rule_not_supported(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", XML_SOURCE_CODE_WITH_ISSUE);\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"organizationKey\", organization -> organization\n        .withProject(\"projectKey\",\n          project -> project\n            .withBranch(\"branchName\")\n            .withAiCodeFixSuggestion(suggestion -> suggestion\n              .withId(UUID.fromString(\"e51b7bbd-72bc-4008-a4f1-d75583f3dc98\"))\n              .withExplanation(\"This is the explanation\")\n              .withChange(0, 0, \"This is the new code\"))))\n      .start();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"organizationKey\", true, storage -> storage\n        .withProject(\"projectKey\", project -> project.withMainBranch(\"main\").withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MAJOR\")))\n        .withAiCodeFixSettings(aiCodeFix -> aiCodeFix\n          .withSupportedRules(Set.of(\"xml:S0000\"))\n          .organizationEligible(true)\n          .enabledForProjects(\"projectKey\")))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .start(fakeClient);\n\n    var issue = analyzeFileAndGetIssue(fileUri, fakeClient, backend, \"configScope\");\n\n    assertThat(issue.isAiCodeFixable()).isFalse();\n  }\n\n  @SonarLintTest\n  void it_should_mark_the_issue_as_fixable_if_supported(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", XML_SOURCE_CODE_WITH_ISSUE);\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"organizationKey\", organization -> organization\n        .withProject(\"projectKey\",\n          project -> project\n            .withBranch(\"branchName\")\n            .withAiCodeFixSuggestion(suggestion -> suggestion\n              .withId(UUID.fromString(\"e51b7bbd-72bc-4008-a4f1-d75583f3dc98\"))\n              .withExplanation(\"This is the explanation\")\n              .withChange(0, 0, \"This is the new code\"))))\n      .start();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"organizationKey\", true, storage -> storage\n        .withProject(\"projectKey\", project -> project.withMainBranch(\"main\").withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MAJOR\")))\n        .withAiCodeFixSettings(aiCodeFix -> aiCodeFix\n          .withSupportedRules(Set.of(\"xml:S3421\"))\n          .organizationEligible(true)\n          .enabledForProjects(\"projectKey\")))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .start(fakeClient);\n\n    var issue = analyzeFileAndGetIssue(fileUri, fakeClient, backend, \"configScope\");\n\n    assertThat(issue.isAiCodeFixable()).isTrue();\n  }\n\n  @SonarLintTest\n  void it_should_throw_too_many_requests_when_fixing_issue(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", XML_SOURCE_CODE_WITH_ISSUE);\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"organizationKey\", organization -> organization\n        .withProject(\"projectKey\",\n          project -> project\n            .withBranch(\"branchName\")\n            .withAiCodeFixSuggestion(suggestion -> suggestion\n              .withId(UUID.fromString(\"e51b7bbd-72bc-4008-a4f1-d75583f3dc98\"))\n              .withExplanation(\"This is the explanation\")\n              .withChange(0, 0, \"This is the new code\"))))\n      .withResponseCodes(codes -> codes.withStatusCode(HTTP_TOO_MANY_REQUESTS))\n      .start();\n\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarQubeCloudEuRegionApiUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"organizationKey\", true, storage -> storage\n        .withProject(\"projectKey\", project -> project.withMainBranch(\"main\").withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MAJOR\")))\n        .withAiCodeFixSettings(aiCodeFix -> aiCodeFix\n          .withSupportedRules(Set.of(\"xml:S3421\"))\n          .organizationEligible(true)\n          .enabledForProjects(\"projectKey\")))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .start(fakeClient);\n    var issue = analyzeFileAndGetIssue(fileUri, fakeClient, backend, \"configScope\");\n\n    var future = backend.getAiCodeFixRpcService().suggestFix(new SuggestFixParams(\"configScope\", issue.getId()));\n\n    assertThat(future).failsWithin(Duration.ofSeconds(3))\n      .withThrowableThat()\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class)\n      .asInstanceOf(InstanceOfAssertFactories.type(ResponseErrorException.class))\n      .extracting(ResponseErrorException::getResponseError)\n      .extracting(ResponseError::getCode, ResponseError::getMessage)\n      .containsExactly(TOO_MANY_REQUESTS,\n        \"AI CodeFix usage has been capped. Too many requests have been made.\");\n  }\n\n  @SonarLintTest\n  void it_should_throw_too_many_requests_when_fixing_taint_issue(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"File.java\", XML_SOURCE_CODE_WITH_ISSUE);\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"organizationKey\", organization -> organization\n        .withProject(\"projectKey\",\n          project -> project\n            .withBranch(\"branchName\")\n            .withAiCodeFixSuggestion(suggestion -> suggestion\n              .withId(UUID.fromString(\"e51b7bbd-72bc-4008-a4f1-d75583f3dc98\"))\n              .withExplanation(\"This is the explanation\")\n              .withChange(0, 0, \"This is the new code\"))))\n      .withResponseCodes(codes -> codes.withStatusCode(HTTP_TOO_MANY_REQUESTS))\n      .start();\n\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarQubeCloudEuRegionApiUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"organizationKey\", true, storage -> storage\n        .withProject(\"projectKey\", project -> project\n          .withMainBranch(\"branchName\", branch -> branch.withTaintIssue(aServerTaintIssue(\"key\")\n            .withRuleKey(\"javasecurity:S2076\")\n            .withFilePath(\"File.java\")\n            .withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\")))))\n        .withAiCodeFixSettings(aiCodeFix -> aiCodeFix\n          .withSupportedRules(Set.of(\"javasecurity:S2076\"))\n          .organizationEligible(true)\n          .enabledForProjects(\"projectKey\")))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .start(fakeClient);\n    var listAllResponse = backend.getTaintVulnerabilityTrackingService().listAll(new ListAllParams(\"configScope\")).join();\n    var taintVulnerabilityDto = listAllResponse.getTaintVulnerabilities().get(0);\n\n    var future = backend.getAiCodeFixRpcService().suggestFix(new SuggestFixParams(\"configScope\", taintVulnerabilityDto.getId()));\n\n    assertThat(future).failsWithin(Duration.ofSeconds(3))\n      .withThrowableThat()\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class)\n      .asInstanceOf(InstanceOfAssertFactories.type(ResponseErrorException.class))\n      .extracting(ResponseErrorException::getResponseError)\n      .extracting(ResponseError::getCode, ResponseError::getMessage)\n      .containsExactly(TOO_MANY_REQUESTS,\n        \"AI CodeFix usage has been capped. Too many requests have been made.\");\n  }\n\n  @SonarLintTest\n  void it_should_return_the_suggestion_from_sonarqube_cloud_for_an_issue(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", XML_SOURCE_CODE_WITH_ISSUE);\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"organizationKey\", organization -> organization\n        .withProject(\"projectKey\",\n          project -> project\n            .withBranch(\"branchName\")\n            .withAiCodeFixSuggestion(suggestion -> suggestion\n              .withId(UUID.fromString(\"e51b7bbd-72bc-4008-a4f1-d75583f3dc98\"))\n              .withExplanation(\"This is the explanation\")\n              .withChange(0, 0, \"This is the new code\"))))\n      .start();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarQubeCloudEuRegionApiUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"organizationKey\", true, storage -> storage\n        .withProject(\"projectKey\", project -> project.withMainBranch(\"main\").withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MAJOR\")))\n        .withAiCodeFixSettings(aiCodeFix -> aiCodeFix\n          .withSupportedRules(Set.of(\"xml:S3421\"))\n          .organizationEligible(true)\n          .enabledForProjects(\"projectKey\")))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .start(fakeClient);\n    var issue = analyzeFileAndGetIssue(fileUri, fakeClient, backend, \"configScope\");\n\n    var fixSuggestion = backend.getAiCodeFixRpcService().suggestFix(new SuggestFixParams(\"configScope\", issue.getId())).join();\n\n    assertThat(fixSuggestion.getId()).isEqualTo(UUID.fromString(\"e51b7bbd-72bc-4008-a4f1-d75583f3dc98\"));\n    assertThat(fixSuggestion.getExplanation()).isEqualTo(\"This is the explanation\");\n    assertThat(fixSuggestion.getChanges())\n      .extracting(SuggestFixChangeDto::getStartLine, SuggestFixChangeDto::getEndLine, SuggestFixChangeDto::getNewCode)\n      .containsExactly(tuple(0, 0, \"This is the new code\"));\n    assertThat(server.getMockServer().getAllServeEvents().get(0).getRequest().getBodyAsString())\n      .isEqualTo(\n        \"\"\"\n          {\"organizationKey\":\"organizationKey\",\"projectKey\":\"projectKey\",\"issue\":{\"message\":\"Replace \\\\\"pom.version\\\\\" with \\\\\"project.version\\\\\".\",\"startLine\":6,\"endLine\":6,\"ruleKey\":\"xml:S3421\",\"sourceCode\":\"%s\"}}\"\"\"\n          .formatted(XML_SOURCE_CODE_WITH_ISSUE.replace(\"\\\\\", \"\\\\\\\\\").replace(\"\\n\", \"\\\\n\").replace(\"\\\"\", \"\\\\\\\"\")));\n  }\n\n  @SonarLintTest\n  void it_should_return_the_suggestion_from_sonarqube_cloud_for_a_taint_issue(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"File.java\", \"source\");\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"organizationKey\", organization -> organization\n        .withProject(\"projectKey\",\n          project -> project\n            .withBranch(\"branchName\")\n            .withAiCodeFixSuggestion(suggestion -> suggestion\n              .withId(UUID.fromString(\"e51b7bbd-72bc-4008-a4f1-d75583f3dc98\"))\n              .withExplanation(\"This is the explanation\")\n              .withChange(0, 0, \"This is the new code\"))))\n      .start();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarQubeCloudEuRegionApiUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"organizationKey\", true, storage -> storage\n        .withProject(\"projectKey\", project -> project\n          .withMainBranch(\"main\", branch -> branch.withTaintIssue(aServerTaintIssue(\"key\")\n            .withRuleKey(\"javasecurity:S2076\")\n            .withFilePath(\"File.java\")\n            .withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\")))))\n        .withAiCodeFixSettings(aiCodeFix -> aiCodeFix\n          .withSupportedRules(Set.of(\"javasecurity:S2076\"))\n          .organizationEligible(true)\n          .enabledForProjects(\"projectKey\")))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .start(fakeClient);\n    var listAllResponse = backend.getTaintVulnerabilityTrackingService().listAll(new ListAllParams(\"configScope\")).join();\n    var taintVulnerabilityDto = listAllResponse.getTaintVulnerabilities().get(0);\n\n    var fixSuggestion = backend.getAiCodeFixRpcService().suggestFix(new SuggestFixParams(\"configScope\", taintVulnerabilityDto.getId())).join();\n\n    assertThat(fixSuggestion.getId()).isEqualTo(UUID.fromString(\"e51b7bbd-72bc-4008-a4f1-d75583f3dc98\"));\n    assertThat(fixSuggestion.getExplanation()).isEqualTo(\"This is the explanation\");\n    assertThat(fixSuggestion.getChanges())\n      .extracting(SuggestFixChangeDto::getStartLine, SuggestFixChangeDto::getEndLine, SuggestFixChangeDto::getNewCode)\n      .containsExactly(tuple(0, 0, \"This is the new code\"));\n    assertThat(server.getMockServer().getAllServeEvents().get(0).getRequest().getBodyAsString())\n      .isEqualTo(\n        \"\"\"\n          {\"organizationKey\":\"organizationKey\",\"projectKey\":\"projectKey\",\"issue\":{\"message\":\"message\",\"startLine\":1,\"endLine\":3,\"ruleKey\":\"javasecurity:S2076\",\"sourceCode\":\"source\"}}\"\"\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_if_the_taint_issue_is_not_fixable_because_rule_is_not_supported(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"File.java\", \"source\");\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"organizationKey\", organization -> organization\n        .withProject(\"projectKey\"))\n      .start();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarQubeCloudEuRegionApiUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"organizationKey\", true, storage -> storage\n        .withProject(\"projectKey\", project -> project\n          .withMainBranch(\"main\", branch -> branch.withTaintIssue(aServerTaintIssue(\"key\")\n            .withRuleKey(\"javasecurity:S2076\")\n            .withFilePath(\"File.java\")\n            .withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\")))))\n        .withAiCodeFixSettings(aiCodeFix -> aiCodeFix\n          .withSupportedRules(Set.of(\"javasecurity:SXXXX\"))\n          .organizationEligible(true)\n          .enabledForProjects(\"projectKey\")))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .start(fakeClient);\n    var listAllResponse = backend.getTaintVulnerabilityTrackingService().listAll(new ListAllParams(\"configScope\")).join();\n    var taintVulnerabilityDto = listAllResponse.getTaintVulnerabilities().get(0);\n\n    var future = backend.getAiCodeFixRpcService().suggestFix(new SuggestFixParams(\"configScope\", taintVulnerabilityDto.getId()));\n\n    assertThat(future).failsWithin(Duration.of(1, ChronoUnit.SECONDS))\n      .withThrowableThat()\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class)\n      .asInstanceOf(InstanceOfAssertFactories.type(ResponseErrorException.class))\n      .extracting(ResponseErrorException::getResponseError)\n      .extracting(ResponseError::getCode, ResponseError::getMessage)\n      .containsExactly(InvalidParams.getValue(), \"The provided taint cannot be fixed\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_if_the_taint_issue_is_not_fixable_because_file_was_removed(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"File.java\", \"source\");\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"organizationKey\", organization -> organization\n        .withProject(\"projectKey\"))\n      .start();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarQubeCloudEuRegionApiUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"organizationKey\", true, storage -> storage\n        .withProject(\"projectKey\", project -> project\n          .withMainBranch(\"main\", branch -> branch.withTaintIssue(aServerTaintIssue(\"key\")\n            .withRuleKey(\"javasecurity:S2076\")\n            .withFilePath(\"OtherFile.java\")\n            .withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\")))))\n        .withAiCodeFixSettings(aiCodeFix -> aiCodeFix\n          .withSupportedRules(Set.of(\"javasecurity:S2076\"))\n          .organizationEligible(true)\n          .enabledForProjects(\"projectKey\")))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .start(fakeClient);\n    var listAllResponse = backend.getTaintVulnerabilityTrackingService().listAll(new ListAllParams(\"configScope\")).join();\n    var taintVulnerabilityDto = listAllResponse.getTaintVulnerabilities().get(0);\n\n    var future = backend.getAiCodeFixRpcService().suggestFix(new SuggestFixParams(\"configScope\", taintVulnerabilityDto.getId()));\n\n    assertThat(future).failsWithin(Duration.of(1, ChronoUnit.SECONDS))\n      .withThrowableThat()\n      .havingCause()\n      .isInstanceOf(ResponseErrorException.class)\n      .asInstanceOf(InstanceOfAssertFactories.type(ResponseErrorException.class))\n      .extracting(ResponseErrorException::getResponseError)\n      .extracting(ResponseError::getCode, ResponseError::getMessage)\n      .containsExactly(FILE_NOT_FOUND, \"The provided taint ID corresponds to an unknown file\");\n  }\n\n  @SonarLintTest\n  void it_should_synchronize_the_ai_codefix_settings_from_sq_cloud_when_disabled(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", XML_SOURCE_CODE_WITH_ISSUE);\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarCloudServer()\n      .withAiCodeFixSupportedRules(Set.of(\"xml:S3421\"))\n      .withOrganization(\"organizationKey\", organization -> organization\n        .withAiCodeFixFeature(feature -> feature\n          .organizationEligible(true)\n          .disabled())\n        .withProject(\"projectKey\"))\n      .start();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarQubeCloudEuRegionApiUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"organizationKey\", true, storage -> storage\n        .withProject(\"projectKey\", project -> project.withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MAJOR\"))))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(fakeClient);\n\n    await().untilAsserted(() -> assertThat(readAiCodeFixSettings(backend, \"connectionId\"))\n      .contains(new AiCodeFix(\"connectionId\", Set.of(\"xml:S3421\"),\n        true, AiCodeFix.Enablement.DISABLED, Set.of())));\n  }\n\n  @SonarLintTest\n  void it_should_synchronize_the_ai_codefix_settings_from_sq_cloud_when_enabled_for_some_projects(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", XML_SOURCE_CODE_WITH_ISSUE);\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarCloudServer()\n      .withAiCodeFixSupportedRules(Set.of(\"xml:S3421\"))\n      .withOrganization(\"organizationKey\", organization -> organization\n        .withAiCodeFixFeature(feature -> feature\n          .organizationEligible(true)\n          .enabledForProjects(\"projectKey\"))\n        .withProject(\"projectKey\"))\n      .start();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarQubeCloudEuRegionApiUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"organizationKey\", true, storage -> storage\n        .withProject(\"projectKey\", project -> project.withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MAJOR\"))))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(fakeClient);\n\n    await().untilAsserted(() -> assertThat(readAiCodeFixSettings(backend, \"connectionId\"))\n      .contains(new AiCodeFix(\"connectionId\", Set.of(\"xml:S3421\"),\n        true, AiCodeFix.Enablement.ENABLED_FOR_SOME_PROJECTS, Set.of(\"projectKey\"))));\n  }\n\n  @SonarLintTest\n  void it_should_synchronize_the_ai_codefix_settings_from_sq_cloud_when_enabled_for_all_projects(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", XML_SOURCE_CODE_WITH_ISSUE);\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarCloudServer()\n      .withAiCodeFixSupportedRules(Set.of(\"xml:S3421\"))\n      .withOrganization(\"organizationKey\", organization -> organization\n        .withAiCodeFixFeature(feature -> feature\n          .organizationEligible(true)\n          .enabledForAllProjects())\n        .withProject(\"projectKey\"))\n      .start();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarQubeCloudEuRegionApiUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"organizationKey\", true, storage -> storage\n        .withProject(\"projectKey\", project -> project.withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MAJOR\"))))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(fakeClient);\n\n    await().untilAsserted(() -> assertThat(readAiCodeFixSettings(backend, \"connectionId\"))\n      .contains(new AiCodeFix(\"connectionId\", Set.of(\"xml:S3421\"),\n        true, AiCodeFix.Enablement.ENABLED_FOR_ALL_PROJECTS, Set.of())));\n  }\n\n  @SonarLintTest\n  void it_should_not_synchronize_the_ai_codefix_settings_for_sq_server_older_then_2025_3(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", XML_SOURCE_CODE_WITH_ISSUE);\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarQubeServer(\"2025.2\")\n      .withFeature(\"fix-suggestions\")\n      .withProject(\"projectKey\",\n        project -> project\n          .withBranch(\"branchName\"))\n      .start();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withSonarQubeConnection(\"connectionId\", server, storage -> storage\n        .withProject(\"projectKey\", project -> project.withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MAJOR\"))))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(fakeClient);\n    fakeClient.waitForSynchronization();\n\n    await().untilAsserted(() -> assertThat(getAiCodeFixStorageFilePath(backend, \"connectionId\"))\n      .doesNotExist());\n  }\n\n  @SonarLintTest\n  void it_should_not_synchronize_the_ai_codefix_settings_for_sq_server_without_feature(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", XML_SOURCE_CODE_WITH_ISSUE);\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarQubeServer(\"2025.3\")\n      .withProject(\"projectKey\",\n        project -> project\n          .withBranch(\"branchName\"))\n      .start();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withSonarQubeConnection(\"connectionId\", server, storage -> storage\n        .withProject(\"projectKey\", project -> project.withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MAJOR\"))))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(fakeClient);\n    fakeClient.waitForSynchronization();\n\n    await().untilAsserted(() -> assertThat(getAiCodeFixStorageFilePath(backend, \"connectionId\"))\n      .doesNotExist());\n  }\n\n  @SonarLintTest\n  void it_should_synchronize_the_ai_codefix_settings_for_sq_server(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", XML_SOURCE_CODE_WITH_ISSUE);\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarQubeServer(\"2025.3\")\n      .withAiCodeFixSupportedRules(Set.of(\"xml:S3421\"))\n      .withFeature(\"fix-suggestions\")\n      .withProject(\"projectKey\", project -> project\n        .withAiCodeFixEnabled(true))\n      .start();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withSonarQubeConnection(\"connectionId\", server, storage -> storage\n        .withProject(\"projectKey\", project -> project\n          .withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MAJOR\"))))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(fakeClient);\n    fakeClient.waitForSynchronization();\n\n    await().untilAsserted(() -> assertThat(readAiCodeFixSettings(backend, \"connectionId\"))\n      .contains(new AiCodeFix(\"connectionId\", Set.of(\"xml:S3421\"), true,\n        ENABLED_FOR_SOME_PROJECTS, Set.of(\"projectKey\"))));\n  }\n\n  @SonarLintTest\n  void it_should_return_the_suggestion_from_sonarqube_server_for_an_issue(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", XML_SOURCE_CODE_WITH_ISSUE);\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(\"projectKey\",\n        project -> project\n          .withBranch(\"branchName\")\n          .withAiCodeFixSuggestion(suggestion -> suggestion\n            .withId(UUID.fromString(\"e51b7bbd-72bc-4008-a4f1-d75583f3dc98\"))\n            .withExplanation(\"This is the explanation\")\n            .withChange(0, 0, \"This is the new code\")))\n      .start();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withSonarQubeConnection(\"connectionId\", server, storage -> storage\n        .withProject(\"projectKey\", project -> project.withMainBranch(\"main\").withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MAJOR\")))\n        .withAiCodeFixSettings(aiCodeFix -> aiCodeFix\n          .withSupportedRules(Set.of(\"xml:S3421\"))\n          .organizationEligible(true)\n          .enabledForProjects(\"projectKey\")))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .start(fakeClient);\n    var issue = analyzeFileAndGetIssue(fileUri, fakeClient, backend, \"configScope\");\n\n    var fixSuggestion = backend.getAiCodeFixRpcService().suggestFix(new SuggestFixParams(\"configScope\", issue.getId())).join();\n\n    assertThat(fixSuggestion.getId()).isEqualTo(UUID.fromString(\"e51b7bbd-72bc-4008-a4f1-d75583f3dc98\"));\n    assertThat(fixSuggestion.getExplanation()).isEqualTo(\"This is the explanation\");\n    assertThat(fixSuggestion.getChanges())\n      .extracting(SuggestFixChangeDto::getStartLine, SuggestFixChangeDto::getEndLine, SuggestFixChangeDto::getNewCode)\n      .containsExactly(tuple(0, 0, \"This is the new code\"));\n    assertThat(server.getMockServer().getAllServeEvents().get(0).getRequest().getBodyAsString())\n      .isEqualTo(\n        \"\"\"\n          {\"projectKey\":\"projectKey\",\"issue\":{\"message\":\"Replace \\\\\"pom.version\\\\\" with \\\\\"project.version\\\\\".\",\"startLine\":6,\"endLine\":6,\"ruleKey\":\"xml:S3421\",\"sourceCode\":\"%s\"}}\"\"\"\n          .formatted(XML_SOURCE_CODE_WITH_ISSUE.replace(\"\\\\\", \"\\\\\\\\\").replace(\"\\n\", \"\\\\n\").replace(\"\\\"\", \"\\\\\\\"\")));\n  }\n\n  @SonarLintTest\n  void it_should_register_telemetry_for_sonarqube_cloud(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", XML_SOURCE_CODE_WITH_ISSUE);\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"organizationKey\", organization -> organization\n        .withProject(\"projectKey\",\n          project -> project\n            .withBranch(\"branchName\")\n            .withAiCodeFixSuggestion(suggestion -> suggestion\n              .withId(UUID.fromString(\"e51b7bbd-72bc-4008-a4f1-d75583f3dc98\"))\n              .withExplanation(\"This is the explanation\")\n              .withChange(0, 0, \"This is the new code\"))))\n      .start();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarQubeCloudEuRegionApiUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"organizationKey\", true, storage -> storage\n        .withProject(\"projectKey\", project -> project.withMainBranch(\"main\").withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MAJOR\")))\n        .withAiCodeFixSettings(aiCodeFix -> aiCodeFix\n          .withSupportedRules(Set.of(\"xml:S3421\"))\n          .organizationEligible(true)\n          .enabledForProjects(\"projectKey\")))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .withTelemetryEnabled()\n      .start(fakeClient);\n    var issue = analyzeFileAndGetIssue(fileUri, fakeClient, backend, \"configScope\");\n\n    backend.getAiCodeFixRpcService().suggestFix(new SuggestFixParams(\"configScope\", issue.getId())).join();\n\n    assertThat(backend.telemetryFileContent().getFixSuggestionReceivedCounter())\n      .isEqualTo(Map.of(\"e51b7bbd-72bc-4008-a4f1-d75583f3dc98\", new TelemetryFixSuggestionReceivedCounter(AiSuggestionSource.SONARCLOUD, 1, true)));\n  }\n\n  @SonarLintTest\n  void it_should_register_telemetry_for_sonarqube_server(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", XML_SOURCE_CODE_WITH_ISSUE);\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(\"projectKey\",\n        project -> project\n          .withBranch(\"branchName\")\n          .withAiCodeFixSuggestion(suggestion -> suggestion\n            .withId(UUID.fromString(\"e51b7bbd-72bc-4008-a4f1-d75583f3dc98\"))\n            .withExplanation(\"This is the explanation\")\n            .withChange(0, 0, \"This is the new code\")))\n      .start();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withSonarQubeConnection(\"connectionId\", server, storage -> storage\n        .withProject(\"projectKey\", project -> project.withMainBranch(\"main\").withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MAJOR\")))\n        .withAiCodeFixSettings(aiCodeFix -> aiCodeFix\n          .withSupportedRules(Set.of(\"xml:S3421\"))\n          .organizationEligible(true)\n          .enabledForProjects(\"projectKey\")))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .withTelemetryEnabled()\n      .start(fakeClient);\n    var issue = analyzeFileAndGetIssue(fileUri, fakeClient, backend, \"configScope\");\n\n    backend.getAiCodeFixRpcService().suggestFix(new SuggestFixParams(\"configScope\", issue.getId())).join();\n\n    assertThat(backend.telemetryFileContent().getFixSuggestionReceivedCounter())\n      .isEqualTo(Map.of(\"e51b7bbd-72bc-4008-a4f1-d75583f3dc98\", new TelemetryFixSuggestionReceivedCounter(AiSuggestionSource.SONARQUBE, 1, true)));\n  }\n\n  @SonarLintTest\n  void it_should_skip_synchronization_if_user_not_a_member_of_the_organization(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"pom.xml\", XML_SOURCE_CODE_WITH_ISSUE);\n    var fileUri = filePath.toUri();\n    var server = harness.newFakeSonarCloudServer()\n      .withAiCodeFixSupportedRules(Set.of(\"xml:S3421\"))\n      .withOrganization(\"organizationKey\", organization -> organization\n        .withCurrentUserMember(false)\n        .withAiCodeFixFeature(feature -> feature\n          .organizationEligible(true)\n          .enabledForAllProjects())\n        .withProject(\"projectKey\"))\n      .start();\n    var fakeClient = harness.newFakeClient()\n      .withInitialFs(\"configScope\", baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), \"configScope\", false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarQubeCloudEuRegionApiUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"organizationKey\", true, storage -> storage\n        .withProject(\"projectKey\", project -> project.withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MAJOR\"))))\n      .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(fakeClient);\n    fakeClient.waitForSynchronization();\n\n    await().untilAsserted(() -> assertThat(getAiCodeFixStorageFilePath(backend, \"connectionId\"))\n      .doesNotExist());\n  }\n\n  private Optional<AiCodeFix> readAiCodeFixSettings(SonarLintTestRpcServer backend, String connectionId) {\n    var sonarLintDatabase = backend.getSonarLintDatabase();\n    var aiCodeFixRepository = new AiCodeFixRepository(sonarLintDatabase.dsl());\n    return aiCodeFixRepository.get(connectionId);\n  }\n\n  private static Path getAiCodeFixStorageFilePath(SonarLintTestRpcServer backend, String connectionId) {\n    return backend.getStorageRoot().resolve(encodeForFs(connectionId)).resolve(\"ai_codefix.pb\");\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/rules/OkRulesDefinition.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.rules;\n\nimport org.sonar.api.server.rule.RulesDefinition;\n\nclass OkRulesDefinition implements RulesDefinition {\n  @Override\n  public void define(Context context) {\n    var repo = context.createRepository(\"ok-rules\", \"php\");\n    repo.createRule(\"S001\")\n      .setName(\"This rule is OK\")\n      .setHtmlDescription(\"This rule is OK\");\n    repo.done();\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/rules/RuleEventsMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.rules;\n\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidOpenFileParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedFindingDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.serverconnection.proto.Sonarlint;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ProtobufFileUtil;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.when;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SECURITY_HOTSPOTS;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SERVER_SENT_EVENTS;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JAVA;\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ProjectStoragePaths.encodeForFs;\nimport static utils.AnalysisUtils.analyzeFileAndGetIssues;\nimport static utils.AnalysisUtils.createFile;\n\nclass RuleEventsMediumTests {\n\n  private static final String CONFIG_SCOPE_ID = \"CONFIG_SCOPE_ID\";\n\n  @Nested\n  class WhenReceivingRuleSetChangedEvent {\n    // write a test just like this one without the impacts\n    @SonarLintTest\n    void it_should_create_the_ruleset_storage_if_does_not_exist_without_impacts(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var client = harness.newFakeClient().build();\n      when(client.matchSonarProjectBranch(eq(\"configScope\"), any(), any(), any())).thenReturn(\"branchName\");\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n\n      server.pushEvent(\"\"\"\n        event: RuleSetChanged\n        data: {\\\n          \"projects\": [\"projectKey\", \"projectKey2\"],\\\n          \"deactivatedRules\": [],\\\n          \"activatedRules\": [{\\\n            \"key\": \"java:S0000\",\\\n            \"language\": \"java\",\\\n            \"severity\": \"MAJOR\",\\\n            \"params\": [{\\\n              \"key\": \"key1\",\\\n              \"value\": \"value1\"\\\n            }]\\\n          }],\\\n          \"deactivatedRules\": [\"java:S4321\"]\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(readRuleSets(backend, \"connectionId\", \"projectKey\"))\n        .extractingByKey(\"java\")\n        .isEqualTo(Sonarlint.RuleSet.newBuilder()\n          .addRule(Sonarlint.RuleSet.ActiveRule.newBuilder().setRuleKey(\"java:S0000\").setSeverity(\"MAJOR\").putParams(\"key1\", \"value1\").build()).build()));\n    }\n\n    @SonarLintTest\n    void it_should_create_the_ruleset_storage_if_does_not_exist(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var client = harness.newFakeClient().build();\n      when(client.matchSonarProjectBranch(eq(\"configScope\"), any(), any(), any())).thenReturn(\"branchName\");\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n\n      server.pushEvent(\"\"\"\n        event: RuleSetChanged\n        data: {\\\n          \"projects\": [\"projectKey\", \"projectKey2\"],\\\n          \"deactivatedRules\": [],\\\n          \"activatedRules\": [{\\\n            \"key\": \"java:S0000\",\\\n            \"language\": \"java\",\\\n            \"severity\": \"MAJOR\",\\\n            \"params\": [{\\\n              \"key\": \"key1\",\\\n              \"value\": \"value1\"\\\n            }],\\\n            \"impacts\": [{\\\n              \"softwareQuality\": \"SECURITY\",\\\n              \"severity\": \"HIGH\"\\\n            }]\\\n          }]\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(readRuleSets(backend, \"connectionId\", \"projectKey\"))\n        .extractingByKey(\"java\")\n        .isEqualTo(Sonarlint.RuleSet.newBuilder()\n          .addRule(Sonarlint.RuleSet.ActiveRule.newBuilder()\n            .setRuleKey(\"java:S0000\")\n            .setSeverity(\"MAJOR\")\n            .putParams(\"key1\", \"value1\")\n            .addOverriddenImpacts(Sonarlint.RuleSet.ActiveRule.newBuilder().addOverriddenImpactsBuilder()\n              .setSoftwareQuality(\"SECURITY\")\n              .setSeverity(\"HIGH\")\n              .build())\n            .build())\n          .build()));\n    }\n\n    @SonarLintTest\n    void it_should_update_existing_rule_in_storage(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var client = harness.newFakeClient().build();\n      when(client.matchSonarProjectBranch(eq(\"configScope\"), any(), any(), any())).thenReturn(\"branchName\");\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server, storage -> storage.withProject(\"projectKey\",\n          project -> project.withRuleSet(\"java\",\n            ruleSet -> ruleSet.withActiveRule(\"java:S0000\", \"INFO\"))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n\n      server.pushEvent(\"\"\"\n        event: RuleSetChanged\n        data: {\\\n          \"projects\": [\"projectKey\", \"projectKey2\"],\\\n          \"deactivatedRules\": [],\\\n          \"activatedRules\": [{\\\n            \"key\": \"java:S0000\",\\\n            \"language\": \"java\",\\\n            \"severity\": \"MAJOR\",\\\n            \"params\": [{\\\n              \"key\": \"key1\",\\\n              \"value\": \"value1\"\\\n            }]\\\n          }]\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(readRuleSets(backend, \"connectionId\", \"projectKey\"))\n        .extractingByKey(\"java\")\n        .isEqualTo(Sonarlint.RuleSet.newBuilder()\n          .addRule(Sonarlint.RuleSet.ActiveRule.newBuilder().setRuleKey(\"java:S0000\").setSeverity(\"MAJOR\").putParams(\"key1\", \"value1\").build()).build()));\n    }\n\n    @SonarLintTest\n    void it_should_add_rule_to_existing_ruleset_in_storage(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var client = harness.newFakeClient().build();\n      when(client.matchSonarProjectBranch(eq(\"configScope\"), any(), any(), any())).thenReturn(\"branchName\");\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server, storage -> storage.withProject(\"projectKey\",\n          project -> project.withRuleSet(\"java\",\n            ruleSet -> ruleSet.withActiveRule(\"java:S0000\", \"INFO\"))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n\n      server.pushEvent(\"\"\"\n        event: RuleSetChanged\n        data: {\\\n          \"projects\": [\"projectKey\", \"projectKey2\"],\\\n          \"deactivatedRules\": [],\\\n          \"activatedRules\": [{\\\n            \"key\": \"java:S0001\",\\\n            \"language\": \"java\",\\\n            \"severity\": \"MAJOR\",\\\n            \"params\": [{\\\n              \"key\": \"key1\",\\\n              \"value\": \"value1\"\\\n            }]\\\n          }]\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(readRuleSets(backend, \"connectionId\", \"projectKey\"))\n        .extractingByKey(\"java\")\n        .isEqualTo(Sonarlint.RuleSet.newBuilder()\n          .addRule(Sonarlint.RuleSet.ActiveRule.newBuilder().setRuleKey(\"java:S0000\").setSeverity(\"INFO\").build())\n          .addRule(Sonarlint.RuleSet.ActiveRule.newBuilder().setRuleKey(\"java:S0001\").setSeverity(\"MAJOR\").putParams(\"key1\", \"value1\").build()).build()));\n    }\n\n    @SonarLintTest\n    void it_should_reanalyze_open_files_on_new_rules_enabled(SonarLintTestHarness harness, @TempDir Path baseDir) {\n      var filePath = createFile(baseDir, \"Foo.java\",\n        \"\"\"\n          public class Foo {\n            void foo() {\n              // TODO foo\n              int i = 0;\n            }\n          }\n          \"\"\");\n      var fileUri = filePath.toUri();\n      var connectionId = \"connectionId\";\n      var branchName = \"branchName\";\n      var projectKey = \"projectKey\";\n      var client = harness.newFakeClient()\n        .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n        .build();\n      when(client.matchSonarProjectBranch(eq(CONFIG_SCOPE_ID), eq(\"main\"), eq(Set.of(\"main\", branchName)), any())).thenReturn(branchName);\n      var server = harness.newFakeSonarQubeServer()\n        .withServerSentEventsEnabled()\n        .withProject(projectKey)\n        .start();\n      var backend = harness.newBackend()\n        .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(connectionId, server, storage -> storage\n          .withServerVersion(\"99.9\")\n          .withProject(projectKey, project -> project\n            .withMainBranch(\"main\")\n            .withRuleSet(\"java\", ruleSet -> ruleSet\n              .withActiveRule(\"java:S1481\", \"MAJOR\"))))\n        .withBoundConfigScope(CONFIG_SCOPE_ID, connectionId, projectKey)\n        .start(client);\n\n      backend.getFileService().didOpenFile(new DidOpenFileParams(CONFIG_SCOPE_ID, fileUri));\n      var raisedIssues = analyzeFileAndGetIssues(fileUri, client, backend, CONFIG_SCOPE_ID);\n      assertThat(raisedIssues).hasSize(1);\n      client.cleanRaisedIssues();\n\n      server.pushEvent(\"\"\"\n        event: RuleSetChanged\n        data: {\\\n          \"projects\": [\"projectKey\"],\\\n          \"deactivatedRules\": [],\\\n          \"activatedRules\": [{\\\n            \"key\": \"java:S1135\",\\\n            \"language\": \"java\",\\\n            \"severity\": \"MAJOR\",\\\n            \"params\": []\\\n          }]\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(15)).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(CONFIG_SCOPE_ID)).hasSize(2));\n      raisedIssues = client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID).get(fileUri);\n      assertThat(raisedIssues)\n        .extracting(RaisedFindingDto::getRuleKey)\n        .containsExactlyInAnyOrder(\"java:S1135\", \"java:S1481\");\n    }\n\n    @SonarLintTest\n    void it_should_add_rule_to_new_ruleset_in_existing_storage(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var client = harness.newFakeClient().build();\n      when(client.matchSonarProjectBranch(eq(\"configScope\"), any(), any(), any())).thenReturn(\"branchName\");\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server, storage -> storage.withProject(\"projectKey\",\n          project -> project.withRuleSet(\"java\",\n            ruleSet -> ruleSet.withActiveRule(\"java:S0000\", \"INFO\"))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n\n      server.pushEvent(\"\"\"\n        event: RuleSetChanged\n        data: {\\\n          \"projects\": [\"projectKey\", \"projectKey2\"],\\\n          \"deactivatedRules\": [],\\\n          \"activatedRules\": [{\\\n            \"key\": \"cs:S0000\",\\\n            \"language\": \"cs\",\\\n            \"severity\": \"MAJOR\",\\\n            \"params\": [{\\\n              \"key\": \"key1\",\\\n              \"value\": \"value1\"\\\n            }]\\\n          }]\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(readRuleSets(backend, \"connectionId\", \"projectKey\"))\n        .extractingByKey(\"cs\")\n        .isEqualTo(Sonarlint.RuleSet.newBuilder()\n          .addRule(Sonarlint.RuleSet.ActiveRule.newBuilder().setRuleKey(\"cs:S0000\").setSeverity(\"MAJOR\").putParams(\"key1\", \"value1\").build()).build()));\n    }\n\n    @SonarLintTest\n    void it_should_remove_deactivated_rule_from_existing_storage(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var client = harness.newFakeClient().build();\n      when(client.matchSonarProjectBranch(eq(\"configScope\"), any(), any(), any())).thenReturn(\"branchName\");\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server, storage -> storage.withProject(\"projectKey\",\n          project -> project.withRuleSet(\"java\",\n            ruleSet -> ruleSet.withActiveRule(\"java:S0000\", \"INFO\").withActiveRule(\"java:S0001\", \"INFO\"))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n\n      server.pushEvent(\"\"\"\n        event: RuleSetChanged\n        data: {\\\n          \"projects\": [\"projectKey\", \"projectKey2\"],\\\n          \"activatedRules\": [],\\\n          \"deactivatedRules\": [\"java:S0000\"]\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(readRuleSets(backend, \"connectionId\", \"projectKey\"))\n        .extractingByKey(\"java\")\n        .isEqualTo(Sonarlint.RuleSet.newBuilder().addRule(Sonarlint.RuleSet.ActiveRule.newBuilder().setRuleKey(\"java:S0001\").setSeverity(\"INFO\").build()).build()));\n    }\n\n    @SonarLintTest\n    void it_should_remove_ruleset_from_storage_when_deactivating_last_rule(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var client = harness.newFakeClient().build();\n      when(client.matchSonarProjectBranch(eq(\"configScope\"), any(), any(), any())).thenReturn(\"branchName\");\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server, storage -> storage.withProject(\"projectKey\",\n          project -> project.withRuleSet(\"java\",\n            ruleSet -> ruleSet.withActiveRule(\"java:S0000\", \"INFO\"))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n\n      server.pushEvent(\"\"\"\n        event: RuleSetChanged\n        data: {\\\n          \"projects\": [\"projectKey\", \"projectKey2\"],\\\n          \"activatedRules\": [],\\\n          \"deactivatedRules\": [\"java:S0000\"]\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(readRuleSets(backend, \"connectionId\", \"projectKey\"))\n        .isEmpty());\n    }\n\n    @SonarLintTest\n    void it_should_re_raise_issues_without_deactivated_rules(SonarLintTestHarness harness, @TempDir Path baseDir) {\n      var filePath = createFile(baseDir, \"Foo.java\",\n        \"\"\"\n          public class Foo {\n            void foo() {\n              // TODO foo\n              int i = 0;\n              System.out.println(\"Foo\");\n              String ip = \"192.168.12.42\";\n            }\n          }\n          \"\"\");\n      var fileUri = filePath.toUri();\n      var connectionId = \"connectionId\";\n      var branchName = \"branchName\";\n      var projectKey = \"projectKey\";\n      var client = harness.newFakeClient()\n        .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n        .build();\n      when(client.matchSonarProjectBranch(eq(CONFIG_SCOPE_ID), eq(\"main\"), eq(Set.of(\"main\", branchName)), any())).thenReturn(branchName);\n      var server = harness.newFakeSonarQubeServer()\n        .withServerSentEventsEnabled()\n        .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile.withLanguage(\"java\")\n          .withActiveRule(\"java:S1481\", activeRule -> activeRule.withSeverity(IssueSeverity.MAJOR))\n          .withActiveRule(\"java:S1135\", activeRule -> activeRule.withSeverity(IssueSeverity.MAJOR))\n          .withActiveRule(\"java:S1313\", activeRule -> activeRule.withSeverity(IssueSeverity.MAJOR))\n          .withActiveRule(\"java:S106\", activeRule -> activeRule.withSeverity(IssueSeverity.MAJOR)))\n        .withProject(projectKey,\n          project -> project\n            .withQualityProfile(\"qpKey\"))\n        .withPlugin(TestPlugin.JAVA)\n        .start();\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS, SECURITY_HOTSPOTS, FULL_SYNCHRONIZATION)\n        .withSonarQubeConnection(connectionId, server)\n        .withBoundConfigScope(CONFIG_SCOPE_ID, connectionId, projectKey)\n        .start(client);\n      await().atMost(Duration.ofSeconds(20)).untilAsserted(() -> assertThat(client.getSynchronizedConfigScopeIds()).contains(CONFIG_SCOPE_ID));\n      var raisedIssues = analyzeFileAndGetIssues(fileUri, client, backend, CONFIG_SCOPE_ID);\n      assertThat(raisedIssues).hasSize(4);\n      client.cleanRaisedIssues();\n      client.cleanRaisedHotspots();\n\n      server.pushEvent(\"\"\"\n        event: RuleSetChanged\n        data: {\\\n          \"projects\": [\"projectKey\"],\\\n          \"activatedRules\": [],\\\n          \"deactivatedRules\": [\"java:S1481\", \"java:S1313\"]\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(15)).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).isNotEmpty());\n      raisedIssues = client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID).get(fileUri);\n      var raisedHotspots = client.getRaisedHotspotsForScopeId(CONFIG_SCOPE_ID).getOrDefault(fileUri, List.of());\n      assertThat(raisedIssues)\n        .extracting(RaisedIssueDto::getRuleKey)\n        .containsExactlyInAnyOrder(\"java:S1135\", \"java:S106\");\n      assertThat(raisedHotspots).isEmpty();\n    }\n  }\n\n  private Map<String, Sonarlint.RuleSet> readRuleSets(SonarLintTestRpcServer backend, String connectionId, String projectKey) {\n    var path = backend.getStorageRoot().resolve(encodeForFs(connectionId)).resolve(\"projects\").resolve(encodeForFs(projectKey)).resolve(\"analyzer_config.pb\");\n    if (path.toFile().exists()) {\n      return ProtobufFileUtil.readFile(path, Sonarlint.AnalyzerConfiguration.parser()).getRuleSetsByLanguageKeyMap();\n    }\n    return Map.of();\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/rules/RuleExtractionMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.rules;\n\nimport java.nio.file.Path;\nimport java.util.concurrent.ExecutionException;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.sonarsource.sonarlint.core.test.utils.plugins.SonarPluginBuilder.newSonarPlugin;\n\npublic class RuleExtractionMediumTests {\n\n  @SonarLintTest\n  void should_gracefully_skip_plugin_when_rules_definitions_fail(SonarLintTestHarness harness, @TempDir Path baseDir) throws ExecutionException, InterruptedException {\n    var client = harness.newFakeClient()\n      .build();\n\n    // First plugin actually declares a PHP rule\n    var okPluginDir = baseDir.resolve(\"ok-plugin\");\n    okPluginDir.toFile().mkdirs();\n    var okPluginPath = newSonarPlugin(\"ok-plugin\")\n      .withRulesDefinition(OkRulesDefinition.class)\n      .generate(okPluginDir);\n\n    // Second plugin throws an IllegalStateException when defining rules\n    var throwingPluginDir = baseDir.resolve(\"throwing-plugin\");\n    throwingPluginDir.toFile().mkdirs();\n    var throwingPluginPath = newSonarPlugin(\"throwing-plugin\")\n      .withRulesDefinition(ThrowingRulesDefinition.class)\n      .generate(throwingPluginDir);\n\n    var backend = harness.newBackend()\n      .withStandaloneEmbeddedPlugin(okPluginPath)\n      .withStandaloneEmbeddedPlugin(throwingPluginPath)\n      .withEnabledLanguageInStandaloneMode(Language.PHP)\n      .start(client);\n\n    var allRules = backend.getRulesService().listAllStandaloneRulesDefinitions().get();\n\n    assertThat(allRules.getRulesByKey()).containsOnlyKeys(\"ok-rules:S001\");\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/rules/ThrowingRulesDefinition.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.rules;\n\nimport org.sonar.api.server.rule.RulesDefinition;\n\nclass ThrowingRulesDefinition implements RulesDefinition {\n  @Override\n  public void define(Context ignored) {\n    throw new IllegalStateException(\"Nope, not gonna happen\");\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/sca/CheckDependencyRisksSupportedMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.sca;\n\nimport java.util.concurrent.CompletionException;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.CheckDependencyRiskSupportedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.CheckDependencyRiskSupportedResponse;\nimport org.sonarsource.sonarlint.core.serverapi.features.Feature;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\n\nclass CheckDependencyRisksSupportedMediumTests {\n\n  private static final String CONFIG_SCOPE_ID = \"configScopeId\";\n  private static final String CONNECTION_ID = \"connectionId\";\n  private static final String PROJECT_KEY = \"projectKey\";\n\n  @SonarLintTest\n  void it_should_fail_when_config_scope_not_found() {\n    var harness = new SonarLintTestHarness();\n    var backend = harness.newBackend().start();\n\n    var throwable = catchThrowable(() -> checkSupported(backend, \"unknown-scope\"));\n\n    assertThat(throwable)\n      .isInstanceOf(CompletionException.class)\n      .hasCauseInstanceOf(ResponseErrorException.class);\n    var responseErrorException = (ResponseErrorException) throwable.getCause();\n    assertThat(responseErrorException.getResponseError().getCode()).isEqualTo(SonarLintRpcErrorCode.CONFIG_SCOPE_NOT_FOUND);\n    assertThat(responseErrorException.getResponseError().getMessage()).contains(\"does not exist: unknown-scope\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_when_config_scope_not_bound() {\n    var harness = new SonarLintTestHarness();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .start();\n\n    var response = checkSupported(backend, CONFIG_SCOPE_ID);\n\n    assertThat(response.isSupported()).isFalse();\n    assertThat(response.getReason()).contains(\"not bound\").contains(\"2025.4\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_when_connection_not_found() {\n    var harness = new SonarLintTestHarness();\n    var backend = harness.newBackend()\n      .withBoundConfigScope(CONFIG_SCOPE_ID, \"missing-connection\", PROJECT_KEY)\n      .start();\n\n    var throwable = catchThrowable(() -> checkSupported(backend, CONFIG_SCOPE_ID));\n\n    assertThat(throwable)\n      .isInstanceOf(CompletionException.class)\n      .hasCauseInstanceOf(ResponseErrorException.class);\n    var responseErrorException = (ResponseErrorException) throwable.getCause();\n    assertThat(responseErrorException.getResponseError().getCode()).isEqualTo(SonarLintRpcErrorCode.CONNECTION_NOT_FOUND);\n    assertThat(responseErrorException.getResponseError().getMessage()).contains(\"unknown connection\");\n  }\n\n  @SonarLintTest\n  void it_should_succeed_on_sonarcloud() {\n    var harness = new SonarLintTestHarness();\n    var backend = harness.newBackend()\n      .withSonarCloudConnection(CONNECTION_ID, storage -> storage\n        .withServerFeature(Feature.SCA))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var response = checkSupported(backend, CONFIG_SCOPE_ID);\n\n    assertThat(response.isSupported()).isTrue();\n    assertThat(response.getReason()).isNull();\n  }\n\n  @SonarLintTest\n  void it_should_fail_when_server_version_too_old() {\n    var harness = new SonarLintTestHarness();\n    var server = harness.newFakeSonarQubeServer().start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server,\n        storage -> storage\n          .withServerFeature(Feature.SCA)\n          .withServerVersion(\"2025.3\"))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var response = checkSupported(backend, CONFIG_SCOPE_ID);\n\n    assertThat(response.isSupported()).isFalse();\n    assertThat(response.getReason()).contains(\"lower than the minimum supported version 2025.4\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_when_sca_disabled() {\n    var harness = new SonarLintTestHarness();\n    var server = harness.newFakeSonarQubeServer().start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server,\n        storage -> storage\n          .withServerVersion(\"2025.4\"))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var response = checkSupported(backend, CONFIG_SCOPE_ID);\n\n    assertThat(response.isSupported()).isFalse();\n    assertThat(response.getReason()).contains(\"does not have Advanced Security enabled\");\n  }\n\n  @SonarLintTest\n  void it_should_fail_when_server_info_missing() {\n    var harness = new SonarLintTestHarness();\n    var server = harness.newFakeSonarQubeServer().start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server)\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var throwable = catchThrowable(() -> checkSupported(backend, CONFIG_SCOPE_ID));\n\n    assertThat(throwable)\n      .isInstanceOf(CompletionException.class)\n      .hasCauseInstanceOf(ResponseErrorException.class);\n    var responseErrorException = (ResponseErrorException) throwable.getCause();\n    assertThat(responseErrorException.getResponseError().getCode()).isEqualTo(SonarLintRpcErrorCode.CONNECTION_NOT_FOUND);\n    assertThat(responseErrorException.getResponseError().getMessage()).contains(\"Could not retrieve server information\");\n  }\n\n  @SonarLintTest\n  void it_should_succeed_when_all_conditions_met() {\n    var harness = new SonarLintTestHarness();\n    var server = harness.newFakeSonarQubeServer().start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server,\n        storage -> storage\n          .withServerFeature(Feature.SCA)\n          .withServerVersion(\"2025.4\"))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var response = checkSupported(backend, CONFIG_SCOPE_ID);\n\n    assertThat(response.isSupported()).isTrue();\n    assertThat(response.getReason()).isNull();\n  }\n\n  @SonarLintTest\n  void it_should_succeed_with_newer_server_version() {\n    var harness = new SonarLintTestHarness();\n    var server = harness.newFakeSonarQubeServer().start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server,\n        storage -> storage\n          .withServerFeature(Feature.SCA)\n          .withServerVersion(\"2025.5\"))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var response = checkSupported(backend, CONFIG_SCOPE_ID);\n\n    assertThat(response.isSupported()).isTrue();\n    assertThat(response.getReason()).isNull();\n  }\n\n  private CheckDependencyRiskSupportedResponse checkSupported(SonarLintTestRpcServer backend, String configScopeId) {\n    return backend.getDependencyRiskService().checkSupported(new CheckDependencyRiskSupportedParams(configScopeId)).join();\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/sca/DependencyRiskStatusChangeMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.sca;\n\nimport com.github.tomakehurst.wiremock.client.WireMock;\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport org.assertj.core.api.InstanceOfAssertFactories;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.ChangeDependencyRiskStatusParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.DependencyRiskTransition;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.DependencyRiskDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.sca.DidChangeDependencyRisksParams;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerDependencyRisk;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.equalTo;\nimport static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.waitAtMost;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.verify;\nimport static org.sonarsource.sonarlint.core.test.utils.server.ServerFixture.ServerStatus.DOWN;\nimport static org.sonarsource.sonarlint.core.test.utils.storage.ServerDependencyRiskFixtures.aServerDependencyRisk;\n\nclass DependencyRiskStatusChangeMediumTests {\n\n  private static final String CONFIGURATION_SCOPE_ID = \"configScopeId\";\n  private static final String CONNECTION_ID = \"connectionId\";\n  private static final String PROJECT_KEY = \"projectKey\";\n  private static final String BRANCH_NAME = \"main\";\n\n  @SonarLintTest\n  void it_should_update_the_status_on_sonarqube_server_when_changing_the_status_on_a_server_matched_dependency_risk(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.randomUUID();\n    var dependencyRisk = aServerDependencyRisk()\n      .withKey(dependencyRiskKey)\n      .withType(ServerDependencyRisk.Type.VULNERABILITY)\n      .withSeverity(ServerDependencyRisk.Severity.HIGH)\n      .withStatus(ServerDependencyRisk.Status.OPEN)\n      .withQuality(ServerDependencyRisk.SoftwareQuality.SECURITY)\n      .withPackageName(\"com.example.vulnerable\")\n      .withPackageVersion(\"1.0.0\")\n      .withVulnerabilityId(\"CVE-1234\")\n      .withCvssScore(\"7.5\")\n      .withTransitions(List.of(\n        ServerDependencyRisk.Transition.CONFIRM,\n        ServerDependencyRisk.Transition.REOPEN\n      ));\n\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY, project -> project.withBranch(BRANCH_NAME))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME, branch -> branch.withDependencyRisk(dependencyRisk))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var comment = \"I confirm this is a risk\";\n    var response = backend.getDependencyRiskService().changeStatus(new ChangeDependencyRiskStatusParams(CONFIGURATION_SCOPE_ID, dependencyRiskKey,\n      DependencyRiskTransition.CONFIRM, comment));\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    var expectedJson = String.format(\"\"\"\n        {\n          \"issueReleaseKey\":\"%s\",\n          \"transitionKey\":\"CONFIRM\",\n          \"comment\":\"%s\"\n        }\n      \"\"\", dependencyRiskKey, comment);\n    waitAtMost(2, SECONDS).untilAsserted(() -> {\n      server.getMockServer()\n        .verify(WireMock.postRequestedFor(urlEqualTo(\"/api/v2/sca/issues-releases/change-status\"))\n          .withHeader(\"Content-Type\", equalTo(\"application/json; charset=UTF-8\"))\n          .withRequestBody(equalToJson(expectedJson)));\n    });\n  }\n\n  @SonarLintTest\n  void it_should_update_the_status_on_sonarqube_cloud_when_changing_the_status_on_a_server_matched_dependency_risk(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.randomUUID();\n    var dependencyRisk = aServerDependencyRisk()\n      .withKey(dependencyRiskKey)\n      .withType(ServerDependencyRisk.Type.VULNERABILITY)\n      .withSeverity(ServerDependencyRisk.Severity.HIGH)\n      .withStatus(ServerDependencyRisk.Status.OPEN)\n      .withQuality(ServerDependencyRisk.SoftwareQuality.SECURITY)\n      .withPackageName(\"com.example.vulnerable\")\n      .withPackageVersion(\"1.0.0\")\n      .withVulnerabilityId(\"CVE-1234\")\n      .withCvssScore(\"7.5\")\n      .withTransitions(List.of(\n        ServerDependencyRisk.Transition.CONFIRM,\n        ServerDependencyRisk.Transition.REOPEN\n      ));\n\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"org\", org ->\n        org.withProject(PROJECT_KEY, project -> project.withBranch(BRANCH_NAME)))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarQubeCloudEuRegionApiUri(server.baseUrl())\n      .withSonarCloudConnection(CONNECTION_ID, \"org\", true, storage -> storage\n        .withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME, branch -> branch.withDependencyRisk(dependencyRisk))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var comment = \"I confirm this is a risk\";\n    var response = backend.getDependencyRiskService().changeStatus(new ChangeDependencyRiskStatusParams(CONFIGURATION_SCOPE_ID, dependencyRiskKey,\n      DependencyRiskTransition.CONFIRM, comment));\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    var expectedJson = String.format(\"\"\"\n        {\n          \"issueReleaseKey\":\"%s\",\n          \"transitionKey\":\"CONFIRM\",\n          \"comment\":\"%s\"\n        }\n      \"\"\", dependencyRiskKey, comment);\n    waitAtMost(2, SECONDS).untilAsserted(() -> {\n      server.getMockServer()\n        .verify(WireMock.postRequestedFor(urlEqualTo(\"/sca/issues-releases/change-status\"))\n          .withHeader(\"Content-Type\", equalTo(\"application/json; charset=UTF-8\"))\n          .withRequestBody(equalToJson(expectedJson)));\n    });\n  }\n\n  @SonarLintTest\n  void it_should_update_the_status_and_transitions_in_the_local_storage_when_changing_the_status_on_a_server_matched_dependency_risk(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.randomUUID();\n    var dependencyRisk = aServerDependencyRisk()\n      .withKey(dependencyRiskKey)\n      .withType(ServerDependencyRisk.Type.VULNERABILITY)\n      .withSeverity(ServerDependencyRisk.Severity.HIGH)\n      .withStatus(ServerDependencyRisk.Status.OPEN)\n      .withQuality(ServerDependencyRisk.SoftwareQuality.SECURITY)\n      .withPackageName(\"com.example.vulnerable\")\n      .withPackageVersion(\"1.0.0\")\n      .withVulnerabilityId(\"CVE-1234\")\n      .withCvssScore(\"7.5\")\n      .withTransitions(List.of(\n        ServerDependencyRisk.Transition.CONFIRM,\n        ServerDependencyRisk.Transition.SAFE,\n        ServerDependencyRisk.Transition.ACCEPT\n      ));\n\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY, project -> project.withBranch(BRANCH_NAME))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME, branch -> branch.withDependencyRisk(dependencyRisk))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var comment = \"I confirm this is a risk\";\n    var response = backend.getDependencyRiskService().changeStatus(new ChangeDependencyRiskStatusParams(CONFIGURATION_SCOPE_ID, dependencyRiskKey,\n      DependencyRiskTransition.CONFIRM, comment));\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    var issueStorage = backend.getIssueStorageService().connection(CONNECTION_ID).project(PROJECT_KEY).findings();\n    var storedDependencyRisk = issueStorage.loadDependencyRisks(BRANCH_NAME).stream().filter(risk -> risk.key().equals(dependencyRiskKey)).findFirst();\n    assertThat(storedDependencyRisk)\n      .map(ServerDependencyRisk::status)\n      .contains(ServerDependencyRisk.Status.CONFIRM);\n    assertThat(storedDependencyRisk)\n      .map(ServerDependencyRisk::transitions)\n      .get()\n      .asInstanceOf(InstanceOfAssertFactories.list(ServerDependencyRisk.Transition.class))\n      .containsExactlyInAnyOrder(\n        ServerDependencyRisk.Transition.SAFE,\n        ServerDependencyRisk.Transition.REOPEN,\n        ServerDependencyRisk.Transition.ACCEPT\n      );\n  }\n\n  @SonarLintTest\n  void it_should_notify_the_client_with_updated_dependency_risk_when_changing_the_status_on_a_server_matched_dependency_risk(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.randomUUID();\n    var dependencyRisk = aServerDependencyRisk()\n      .withKey(dependencyRiskKey)\n      .withType(ServerDependencyRisk.Type.VULNERABILITY)\n      .withSeverity(ServerDependencyRisk.Severity.HIGH)\n      .withStatus(ServerDependencyRisk.Status.OPEN)\n      .withQuality(ServerDependencyRisk.SoftwareQuality.SECURITY)\n      .withPackageName(\"com.example.vulnerable\")\n      .withPackageVersion(\"1.0.0\")\n      .withVulnerabilityId(\"CVE-1234\")\n      .withCvssScore(\"7.5\")\n      .withTransitions(List.of(\n        ServerDependencyRisk.Transition.CONFIRM,\n        ServerDependencyRisk.Transition.REOPEN));\n\n    var fakeClient = harness.newFakeClient().build();\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY, project -> project.withBranch(BRANCH_NAME))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME, branch -> branch.withDependencyRisk(dependencyRisk))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start(fakeClient);\n\n    var comment = \"I confirm this is a risk\";\n    var response = backend.getDependencyRiskService().changeStatus(new ChangeDependencyRiskStatusParams(CONFIGURATION_SCOPE_ID, dependencyRiskKey,\n      DependencyRiskTransition.CONFIRM, comment));\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    verify(fakeClient, timeout(2000).times(1)).didChangeDependencyRisks(any(), any(), any(), any());\n    assertThat(fakeClient.getDependencyRiskChanges())\n      .extracting(DidChangeDependencyRisksParams::getAddedDependencyRisks, DidChangeDependencyRisksParams::getClosedDependencyRiskIds)\n      .containsExactly(tuple(List.of(), Set.of()));\n    assertThat(fakeClient.getDependencyRiskChanges().get(0).getUpdatedDependencyRisks())\n      .extracting(DependencyRiskDto::getId, DependencyRiskDto::getStatus, DependencyRiskDto::getTransitions)\n      .containsExactly(tuple(dependencyRiskKey, DependencyRiskDto.Status.CONFIRM, List.of(DependencyRiskDto.Transition.REOPEN, DependencyRiskDto.Transition.SAFE, DependencyRiskDto.Transition.ACCEPT)));\n  }\n\n  @SonarLintTest\n  void it_should_notify_the_client_with_updated_dependency_risk_when_reopening_a_server_matched_dependency_risk(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.randomUUID();\n    var dependencyRisk = aServerDependencyRisk()\n      .withKey(dependencyRiskKey)\n      .withType(ServerDependencyRisk.Type.VULNERABILITY)\n      .withSeverity(ServerDependencyRisk.Severity.HIGH)\n      .withStatus(ServerDependencyRisk.Status.ACCEPT)\n      .withQuality(ServerDependencyRisk.SoftwareQuality.SECURITY)\n      .withPackageName(\"com.example.vulnerable\")\n      .withPackageVersion(\"1.0.0\")\n      .withVulnerabilityId(\"CVE-1234\")\n      .withCvssScore(\"7.5\")\n      .withTransitions(List.of(\n        ServerDependencyRisk.Transition.REOPEN));\n\n    var fakeClient = harness.newFakeClient().build();\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY, project -> project.withBranch(BRANCH_NAME))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME, branch -> branch.withDependencyRisk(dependencyRisk))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start(fakeClient);\n\n    var comment = \"I confirm this is a risk\";\n    var response = backend.getDependencyRiskService().changeStatus(new ChangeDependencyRiskStatusParams(CONFIGURATION_SCOPE_ID, dependencyRiskKey,\n      DependencyRiskTransition.REOPEN, comment));\n\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    verify(fakeClient, timeout(2000).times(1)).didChangeDependencyRisks(any(), any(), any(), any());\n    assertThat(fakeClient.getDependencyRiskChanges())\n      .extracting(DidChangeDependencyRisksParams::getAddedDependencyRisks, DidChangeDependencyRisksParams::getClosedDependencyRiskIds)\n      .containsExactly(tuple(List.of(), Set.of()));\n    assertThat(fakeClient.getDependencyRiskChanges().get(0).getUpdatedDependencyRisks())\n      .extracting(DependencyRiskDto::getId, DependencyRiskDto::getStatus, DependencyRiskDto::getTransitions)\n      .containsExactly(tuple(dependencyRiskKey, DependencyRiskDto.Status.OPEN,\n        List.of(DependencyRiskDto.Transition.CONFIRM, DependencyRiskDto.Transition.SAFE, DependencyRiskDto.Transition.ACCEPT)));\n  }\n\n  @SonarLintTest\n  void it_should_throw_when_dependency_risk_does_not_exist(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY, project -> project.withBranch(BRANCH_NAME))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME)))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var nonExistentKey = UUID.randomUUID();\n    var params = new ChangeDependencyRiskStatusParams(CONFIGURATION_SCOPE_ID, nonExistentKey, DependencyRiskTransition.CONFIRM, null);\n    var scaService = backend.getDependencyRiskService();\n\n    assertThat(scaService.changeStatus(params))\n      .failsWithin(2, TimeUnit.SECONDS)\n      .withThrowableOfType(ExecutionException.class)\n      .withCauseExactlyInstanceOf(ResponseErrorException.class)\n      .withMessageContaining(\"Dependency Risk with key \" + nonExistentKey + \" was not found\");\n  }\n\n  @SonarLintTest\n  void it_should_throw_when_transition_is_not_allowed(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.randomUUID();\n    var dependencyRisk = aServerDependencyRisk()\n      .withKey(dependencyRiskKey)\n      .withType(ServerDependencyRisk.Type.VULNERABILITY)\n      .withSeverity(ServerDependencyRisk.Severity.HIGH)\n      .withStatus(ServerDependencyRisk.Status.OPEN)\n      .withQuality(ServerDependencyRisk.SoftwareQuality.MAINTAINABILITY)\n      .withPackageName(\"com.example.vulnerable\")\n      .withPackageVersion(\"1.0.0\")\n      .withVulnerabilityId(\"CVE-1234\")\n      .withCvssScore(\"7.5\")\n      .withTransitions(List.of(\n        ServerDependencyRisk.Transition.CONFIRM\n        // ACCEPT is not in the allowed transitions\n      ));\n\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY, project -> project.withBranch(BRANCH_NAME))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME, branch -> branch.withDependencyRisk(dependencyRisk))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var params = new ChangeDependencyRiskStatusParams(CONFIGURATION_SCOPE_ID, dependencyRiskKey, DependencyRiskTransition.ACCEPT, \"comment\");\n    var scaService = backend.getDependencyRiskService();\n\n    assertThat(scaService.changeStatus(params))\n      .failsWithin(2, TimeUnit.SECONDS)\n      .withThrowableOfType(ExecutionException.class)\n      .withCauseExactlyInstanceOf(ResponseErrorException.class)\n      .withMessageContaining(\"Transition ACCEPT is not allowed for this dependency risk\");\n  }\n\n  @SonarLintTest\n  void it_should_throw_when_accept_transition_has_no_comment(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.randomUUID();\n    var dependencyRisk = aServerDependencyRisk()\n      .withKey(dependencyRiskKey)\n      .withType(ServerDependencyRisk.Type.VULNERABILITY)\n      .withSeverity(ServerDependencyRisk.Severity.HIGH)\n      .withStatus(ServerDependencyRisk.Status.OPEN)\n      .withQuality(ServerDependencyRisk.SoftwareQuality.RELIABILITY)\n      .withPackageName(\"com.example.vulnerable\")\n      .withPackageVersion(\"1.0.0\")\n      .withVulnerabilityId(\"CVE-1234\")\n      .withCvssScore(\"7.5\")\n      .withTransitions(List.of(\n        ServerDependencyRisk.Transition.ACCEPT\n      ));\n\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY, project -> project.withBranch(BRANCH_NAME))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME, branch -> branch.withDependencyRisk(dependencyRisk))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var params = new ChangeDependencyRiskStatusParams(CONFIGURATION_SCOPE_ID, dependencyRiskKey, DependencyRiskTransition.ACCEPT, null);\n    var scaService = backend.getDependencyRiskService();\n\n    assertThat(scaService.changeStatus(params))\n      .failsWithin(2, TimeUnit.SECONDS)\n      .withThrowableOfType(ExecutionException.class)\n      .withCauseExactlyInstanceOf(ResponseErrorException.class)\n      .withMessageContaining(\"Comment is required for ACCEPT and SAFE transitions\");\n  }\n\n  @SonarLintTest\n  void it_should_throw_when_safe_transition_has_no_comment(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.randomUUID();\n    var dependencyRisk = aServerDependencyRisk()\n      .withKey(dependencyRiskKey)\n      .withType(ServerDependencyRisk.Type.VULNERABILITY)\n      .withSeverity(ServerDependencyRisk.Severity.HIGH)\n      .withStatus(ServerDependencyRisk.Status.OPEN)\n      .withQuality(ServerDependencyRisk.SoftwareQuality.SECURITY)\n      .withPackageName(\"com.example.vulnerable\")\n      .withPackageVersion(\"1.0.0\")\n      .withVulnerabilityId(\"CVE-1234\")\n      .withCvssScore(\"7.5\")\n      .withTransitions(List.of(\n        ServerDependencyRisk.Transition.SAFE\n      ));\n\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY, project -> project.withBranch(BRANCH_NAME))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME, branch -> branch.withDependencyRisk(dependencyRisk))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var params = new ChangeDependencyRiskStatusParams(CONFIGURATION_SCOPE_ID, dependencyRiskKey, DependencyRiskTransition.SAFE, null);\n    var scaService = backend.getDependencyRiskService();\n\n    assertThat(scaService.changeStatus(params))\n      .failsWithin(2, TimeUnit.SECONDS)\n      .withThrowableOfType(ExecutionException.class)\n      .withCauseExactlyInstanceOf(ResponseErrorException.class)\n      .withMessageContaining(\"Comment is required for ACCEPT and SAFE transitions\");\n  }\n\n  @SonarLintTest\n  void it_should_succeed_when_accept_transition_has_comment(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.randomUUID();\n    var dependencyRisk = aServerDependencyRisk()\n      .withKey(dependencyRiskKey)\n      .withType(ServerDependencyRisk.Type.VULNERABILITY)\n      .withSeverity(ServerDependencyRisk.Severity.HIGH)\n      .withStatus(ServerDependencyRisk.Status.OPEN)\n      .withQuality(ServerDependencyRisk.SoftwareQuality.SECURITY)\n      .withPackageName(\"com.example.vulnerable\")\n      .withPackageVersion(\"1.0.0\")\n      .withVulnerabilityId(\"CVE-1234\")\n      .withCvssScore(\"7.5\")\n      .withTransitions(List.of(\n        ServerDependencyRisk.Transition.ACCEPT\n      ));\n\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY, project -> project.withBranch(BRANCH_NAME))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME, branch -> branch.withDependencyRisk(dependencyRisk))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var params = new ChangeDependencyRiskStatusParams(CONFIGURATION_SCOPE_ID, dependencyRiskKey, DependencyRiskTransition.ACCEPT, \"This is acceptable\");\n    var scaService = backend.getDependencyRiskService();\n\n    var response = scaService.changeStatus(params);\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    var expectedJson = String.format(\"\"\"\n        {\n          \"issueReleaseKey\":\"%s\",\n          \"transitionKey\":\"ACCEPT\",\n          \"comment\":\"This is acceptable\"\n        }\n      \"\"\", dependencyRiskKey);\n    waitAtMost(2, SECONDS).untilAsserted(() -> {\n      server.getMockServer()\n        .verify(WireMock.postRequestedFor(urlEqualTo(\"/api/v2/sca/issues-releases/change-status\"))\n          .withHeader(\"Content-Type\", equalTo(\"application/json; charset=UTF-8\"))\n          .withRequestBody(equalToJson(expectedJson)));\n    });\n  }\n\n  @SonarLintTest\n  void it_should_succeed_when_safe_transition_has_comment(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.randomUUID();\n    var dependencyRisk = aServerDependencyRisk()\n      .withKey(dependencyRiskKey)\n      .withType(ServerDependencyRisk.Type.VULNERABILITY)\n      .withSeverity(ServerDependencyRisk.Severity.HIGH)\n      .withStatus(ServerDependencyRisk.Status.OPEN)\n      .withQuality(ServerDependencyRisk.SoftwareQuality.SECURITY)\n      .withPackageName(\"com.example.vulnerable\")\n      .withPackageVersion(\"1.0.0\")\n      .withVulnerabilityId(\"CVE-1234\")\n      .withCvssScore(\"7.5\")\n      .withTransitions(List.of(\n        ServerDependencyRisk.Transition.SAFE\n      ));\n\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY, project -> project.withBranch(BRANCH_NAME))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME, branch -> branch.withDependencyRisk(dependencyRisk))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var params = new ChangeDependencyRiskStatusParams(CONFIGURATION_SCOPE_ID, dependencyRiskKey, DependencyRiskTransition.SAFE, \"This is safe\");\n    var scaService = backend.getDependencyRiskService();\n\n    var response = scaService.changeStatus(params);\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    var expectedJson = String.format(\"\"\"\n        {\n          \"issueReleaseKey\":\"%s\",\n          \"transitionKey\":\"SAFE\",\n          \"comment\":\"This is safe\"\n        }\n      \"\"\", dependencyRiskKey);\n    waitAtMost(2, SECONDS).untilAsserted(() -> {\n      server.getMockServer()\n        .verify(WireMock.postRequestedFor(urlEqualTo(\"/api/v2/sca/issues-releases/change-status\"))\n          .withHeader(\"Content-Type\", equalTo(\"application/json; charset=UTF-8\"))\n          .withRequestBody(equalToJson(expectedJson)));\n    });\n  }\n\n  @SonarLintTest\n  void it_should_fail_when_server_returns_error(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.randomUUID();\n    var dependencyRisk = aServerDependencyRisk()\n      .withKey(dependencyRiskKey)\n      .withType(ServerDependencyRisk.Type.VULNERABILITY)\n      .withSeverity(ServerDependencyRisk.Severity.HIGH)\n      .withStatus(ServerDependencyRisk.Status.OPEN)\n      .withQuality(ServerDependencyRisk.SoftwareQuality.SECURITY)\n      .withPackageName(\"com.example.vulnerable\")\n      .withPackageVersion(\"1.0.0\")\n      .withVulnerabilityId(\"CVE-1234\")\n      .withCvssScore(\"7.5\")\n      .withTransitions(List.of(\n        ServerDependencyRisk.Transition.CONFIRM\n      ));\n\n    var server = harness.newFakeSonarQubeServer().withStatus(DOWN).start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME, branch -> branch.withDependencyRisk(dependencyRisk))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var params = new ChangeDependencyRiskStatusParams(CONFIGURATION_SCOPE_ID, dependencyRiskKey, DependencyRiskTransition.CONFIRM, null);\n    var scaService = backend.getDependencyRiskService();\n\n    assertThat(scaService.changeStatus(params))\n      .failsWithin(2, TimeUnit.SECONDS)\n      .withThrowableOfType(ExecutionException.class)\n      .havingCause()\n      .isInstanceOfSatisfying(ResponseErrorException.class, ex -> {\n        assertThat(ex.getResponseError().getData().toString()).contains(\"Error 404 on\", \"/api/v2/sca/issues-releases/change-status\");\n      });\n  }\n\n  @SonarLintTest\n  void it_should_handle_multiple_dependency_risks_with_different_transitions(SonarLintTestHarness harness) {\n    var dependencyRiskKey1 = UUID.randomUUID();\n    var dependencyRiskKey2 = UUID.randomUUID();\n\n    var dependencyRisk1 = aServerDependencyRisk()\n      .withKey(dependencyRiskKey1)\n      .withType(ServerDependencyRisk.Type.VULNERABILITY)\n      .withSeverity(ServerDependencyRisk.Severity.HIGH)\n      .withStatus(ServerDependencyRisk.Status.OPEN)\n      .withQuality(ServerDependencyRisk.SoftwareQuality.SECURITY)\n      .withPackageName(\"com.example.vulnerable1\")\n      .withPackageVersion(\"1.0.0\")\n      .withVulnerabilityId(\"CVE-1234\")\n      .withCvssScore(\"7.5\")\n      .withTransitions(List.of(\n        ServerDependencyRisk.Transition.CONFIRM,\n        ServerDependencyRisk.Transition.REOPEN\n      ));\n\n    var dependencyRisk2 = aServerDependencyRisk()\n      .withKey(dependencyRiskKey2)\n      .withType(ServerDependencyRisk.Type.PROHIBITED_LICENSE)\n      .withSeverity(ServerDependencyRisk.Severity.BLOCKER)\n      .withStatus(ServerDependencyRisk.Status.OPEN)\n      .withPackageName(\"com.example.prohibited\")\n      .withPackageVersion(\"2.0.0\")\n      .withTransitions(List.of(\n        ServerDependencyRisk.Transition.ACCEPT\n      ));\n\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY, project -> project.withBranch(BRANCH_NAME))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME, branch -> branch\n          .withDependencyRisk(dependencyRisk1)\n          .withDependencyRisk(dependencyRisk2))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var scaService = backend.getDependencyRiskService();\n\n    // Test first issue with CONFIRM transition\n    var params1 = new ChangeDependencyRiskStatusParams(CONFIGURATION_SCOPE_ID, dependencyRiskKey1, DependencyRiskTransition.CONFIRM, null);\n    var response1 = scaService.changeStatus(params1);\n    assertThat(response1).succeedsWithin(Duration.ofSeconds(2));\n\n    // Test second issue with ACCEPT transition and comment\n    var params2 = new ChangeDependencyRiskStatusParams(CONFIGURATION_SCOPE_ID, dependencyRiskKey2, DependencyRiskTransition.ACCEPT, \"License is acceptable\");\n    var response2 = scaService.changeStatus(params2);\n    assertThat(response2).succeedsWithin(Duration.ofSeconds(2));\n\n    var expectedJson1 = String.format(\"\"\"\n        {\n          \"issueReleaseKey\":\"%s\",\n          \"transitionKey\":\"CONFIRM\"\n        }\n      \"\"\", dependencyRiskKey1);\n\n    var expectedJson2 = String.format(\"\"\"\n        {\n          \"issueReleaseKey\":\"%s\",\n          \"transitionKey\":\"ACCEPT\",\n          \"comment\":\"License is acceptable\"\n        }\n      \"\"\", dependencyRiskKey2);\n\n    waitAtMost(2, SECONDS).untilAsserted(() -> {\n      server.getMockServer()\n        .verify(WireMock.postRequestedFor(urlEqualTo(\"/api/v2/sca/issues-releases/change-status\"))\n          .withRequestBody(equalToJson(expectedJson1)));\n\n      server.getMockServer()\n        .verify(WireMock.postRequestedFor(urlEqualTo(\"/api/v2/sca/issues-releases/change-status\"))\n          .withRequestBody(equalToJson(expectedJson2)));\n    });\n  }\n\n  @SonarLintTest\n  void it_should_handle_empty_comment_for_accept_transition(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.randomUUID();\n    var dependencyRisk = aServerDependencyRisk()\n      .withKey(dependencyRiskKey)\n      .withType(ServerDependencyRisk.Type.VULNERABILITY)\n      .withSeverity(ServerDependencyRisk.Severity.HIGH)\n      .withStatus(ServerDependencyRisk.Status.OPEN)\n      .withQuality(ServerDependencyRisk.SoftwareQuality.SECURITY)\n      .withPackageName(\"com.example.vulnerable\")\n      .withPackageVersion(\"1.0.0\")\n      .withVulnerabilityId(\"CVE-1234\")\n      .withCvssScore(\"7.5\")\n      .withTransitions(List.of(\n        ServerDependencyRisk.Transition.ACCEPT\n      ));\n\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY, project -> project.withBranch(BRANCH_NAME))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME, branch -> branch.withDependencyRisk(dependencyRisk))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var params = new ChangeDependencyRiskStatusParams(CONFIGURATION_SCOPE_ID, dependencyRiskKey, DependencyRiskTransition.ACCEPT, \"\");\n    var scaService = backend.getDependencyRiskService();\n\n    assertThat(scaService.changeStatus(params))\n      .failsWithin(2, TimeUnit.SECONDS)\n      .withThrowableOfType(ExecutionException.class)\n      .withCauseExactlyInstanceOf(ResponseErrorException.class)\n      .withMessageContaining(\"Comment is required for ACCEPT and SAFE transitions\");\n  }\n\n  @SonarLintTest\n  void it_should_handle_whitespace_only_comment_for_accept_transition(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.randomUUID();\n    var dependencyRisk = aServerDependencyRisk()\n      .withKey(dependencyRiskKey)\n      .withType(ServerDependencyRisk.Type.VULNERABILITY)\n      .withSeverity(ServerDependencyRisk.Severity.HIGH)\n      .withStatus(ServerDependencyRisk.Status.OPEN)\n      .withQuality(ServerDependencyRisk.SoftwareQuality.SECURITY)\n      .withPackageName(\"com.example.vulnerable\")\n      .withPackageVersion(\"1.0.0\")\n      .withVulnerabilityId(\"CVE-1234\")\n      .withCvssScore(\"7.5\")\n      .withTransitions(List.of(\n        ServerDependencyRisk.Transition.ACCEPT\n      ));\n\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY, project -> project.withBranch(BRANCH_NAME))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME, branch -> branch.withDependencyRisk(dependencyRisk))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var params = new ChangeDependencyRiskStatusParams(CONFIGURATION_SCOPE_ID, dependencyRiskKey, DependencyRiskTransition.ACCEPT, \"   \");\n    var scaService = backend.getDependencyRiskService();\n\n    assertThat(scaService.changeStatus(params))\n      .failsWithin(2, TimeUnit.SECONDS)\n      .withThrowableOfType(ExecutionException.class)\n      .withCauseExactlyInstanceOf(ResponseErrorException.class)\n      .withMessageContaining(\"Comment is required for ACCEPT and SAFE transitions\");\n  }\n\n  @SonarLintTest\n  void it_should_handle_dependency_risk_with_different_severities(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.randomUUID();\n    var dependencyRisk = aServerDependencyRisk()\n      .withKey(dependencyRiskKey)\n      .withType(ServerDependencyRisk.Type.VULNERABILITY)\n      .withSeverity(ServerDependencyRisk.Severity.HIGH)\n      .withStatus(ServerDependencyRisk.Status.OPEN)\n      .withQuality(ServerDependencyRisk.SoftwareQuality.SECURITY)\n      .withPackageName(\"com.example.critical\")\n      .withPackageVersion(\"1.0.0\")\n      .withVulnerabilityId(\"CVE-1234\")\n      .withCvssScore(\"7.5\")\n      .withTransitions(List.of(\n        ServerDependencyRisk.Transition.CONFIRM,\n        ServerDependencyRisk.Transition.ACCEPT\n      ));\n\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY, project -> project.withBranch(BRANCH_NAME))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME, branch -> branch.withDependencyRisk(dependencyRisk))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var params = new ChangeDependencyRiskStatusParams(CONFIGURATION_SCOPE_ID, dependencyRiskKey, DependencyRiskTransition.ACCEPT, \"Critical issue accepted\");\n    var scaService = backend.getDependencyRiskService();\n\n    var response = scaService.changeStatus(params);\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    var expectedJson = String.format(\"\"\"\n        {\n          \"issueReleaseKey\":\"%s\",\n          \"transitionKey\":\"ACCEPT\",\n          \"comment\":\"Critical issue accepted\"\n        }\n      \"\"\", dependencyRiskKey);\n    waitAtMost(2, SECONDS).untilAsserted(() -> {\n      server.getMockServer()\n        .verify(WireMock.postRequestedFor(urlEqualTo(\"/api/v2/sca/issues-releases/change-status\"))\n          .withHeader(\"Content-Type\", equalTo(\"application/json; charset=UTF-8\"))\n          .withRequestBody(equalToJson(expectedJson)));\n    });\n  }\n\n  @SonarLintTest\n  void it_should_handle_dependency_risk_with_long_comment(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.randomUUID();\n    var longComment = \"This is a very long comment that exceeds the typical length of a normal comment. \" +\n      \"It contains multiple sentences and should be handled properly by the dependency risk status change functionality. \" +\n      \"The comment should be truncated or handled appropriately by the server.\";\n\n    var dependencyRisk = aServerDependencyRisk()\n      .withKey(dependencyRiskKey)\n      .withType(ServerDependencyRisk.Type.VULNERABILITY)\n      .withSeverity(ServerDependencyRisk.Severity.HIGH)\n      .withStatus(ServerDependencyRisk.Status.OPEN)\n      .withQuality(ServerDependencyRisk.SoftwareQuality.SECURITY)\n      .withPackageName(\"com.example.vulnerable\")\n      .withPackageVersion(\"1.0.0\")\n      .withVulnerabilityId(\"CVE-1234\")\n      .withCvssScore(\"7.5\")\n      .withTransitions(List.of(\n        ServerDependencyRisk.Transition.ACCEPT\n      ));\n\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY, project -> project.withBranch(BRANCH_NAME))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME, branch -> branch.withDependencyRisk(dependencyRisk))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var params = new ChangeDependencyRiskStatusParams(CONFIGURATION_SCOPE_ID, dependencyRiskKey, DependencyRiskTransition.ACCEPT, longComment);\n    var scaService = backend.getDependencyRiskService();\n\n    var response = scaService.changeStatus(params);\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    var expectedJson = String.format(\"\"\"\n        {\n          \"issueReleaseKey\":\"%s\",\n          \"transitionKey\":\"ACCEPT\",\n          \"comment\":\"%s\"\n        }\n      \"\"\", dependencyRiskKey, longComment);\n    waitAtMost(2, SECONDS).untilAsserted(() -> {\n      server.getMockServer()\n        .verify(WireMock.postRequestedFor(urlEqualTo(\"/api/v2/sca/issues-releases/change-status\"))\n          .withHeader(\"Content-Type\", equalTo(\"application/json; charset=UTF-8\"))\n          .withRequestBody(equalToJson(expectedJson)));\n    });\n  }\n\n  @SonarLintTest\n  void it_should_handle_dependency_risk_with_special_characters_in_comment(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.randomUUID();\n    var specialComment = \"Comment with special chars: \\\"quotes\\\", 'apostrophes', & < > symbols, and \\n newlines \\t tabs\";\n\n    var dependencyRisk = aServerDependencyRisk()\n      .withKey(dependencyRiskKey)\n      .withType(ServerDependencyRisk.Type.VULNERABILITY)\n      .withSeverity(ServerDependencyRisk.Severity.HIGH)\n      .withStatus(ServerDependencyRisk.Status.OPEN)\n      .withQuality(ServerDependencyRisk.SoftwareQuality.SECURITY)\n      .withPackageName(\"com.example.vulnerable\")\n      .withPackageVersion(\"1.0.0\")\n      .withVulnerabilityId(\"CVE-1234\")\n      .withCvssScore(\"7.5\")\n      .withTransitions(List.of(\n        ServerDependencyRisk.Transition.ACCEPT\n      ));\n\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY, project -> project.withBranch(BRANCH_NAME))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME, branch -> branch.withDependencyRisk(dependencyRisk))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var params = new ChangeDependencyRiskStatusParams(CONFIGURATION_SCOPE_ID, dependencyRiskKey, DependencyRiskTransition.ACCEPT, specialComment);\n    var scaService = backend.getDependencyRiskService();\n\n    var response = scaService.changeStatus(params);\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    var expectedJson = String.format(\"\"\"\n        {\n          \"issueReleaseKey\":\"%s\",\n          \"transitionKey\":\"ACCEPT\",\n          \"comment\":\"%s\"\n        }\n      \"\"\", dependencyRiskKey, specialComment\n      .replace(\"\\\"\", \"\\\\\\\"\")\n      .replace(\"\\n\", \"\\\\n\")\n      .replace(\"\\t\", \"\\\\t\"));\n    waitAtMost(2, SECONDS).untilAsserted(() -> {\n      server.getMockServer()\n        .verify(WireMock.postRequestedFor(urlEqualTo(\"/api/v2/sca/issues-releases/change-status\"))\n          .withHeader(\"Content-Type\", equalTo(\"application/json; charset=UTF-8\"))\n          .withRequestBody(equalToJson(expectedJson)));\n    });\n  }\n\n  @SonarLintTest\n  void it_should_handle_dependency_risk_with_unicode_characters_in_comment(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.randomUUID();\n    var unicodeComment = \"Comment with unicode: 🚀 emoji, éñtîôñs, 中文, русский, العربية\";\n\n    var dependencyRisk = aServerDependencyRisk()\n      .withKey(dependencyRiskKey)\n      .withType(ServerDependencyRisk.Type.VULNERABILITY)\n      .withSeverity(ServerDependencyRisk.Severity.HIGH)\n      .withStatus(ServerDependencyRisk.Status.OPEN)\n      .withQuality(ServerDependencyRisk.SoftwareQuality.SECURITY)\n      .withPackageName(\"com.example.vulnerable\")\n      .withPackageVersion(\"1.0.0\")\n      .withVulnerabilityId(\"CVE-1234\")\n      .withCvssScore(\"7.5\")\n      .withTransitions(List.of(\n        ServerDependencyRisk.Transition.ACCEPT\n      ));\n\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY, project -> project.withBranch(BRANCH_NAME))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME, branch -> branch.withDependencyRisk(dependencyRisk))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var params = new ChangeDependencyRiskStatusParams(CONFIGURATION_SCOPE_ID, dependencyRiskKey, DependencyRiskTransition.ACCEPT, unicodeComment);\n    var scaService = backend.getDependencyRiskService();\n\n    var response = scaService.changeStatus(params);\n    assertThat(response).succeedsWithin(Duration.ofSeconds(2));\n    var expectedJson = String.format(\"\"\"\n        {\n          \"issueReleaseKey\":\"%s\",\n          \"transitionKey\":\"ACCEPT\",\n          \"comment\":\"%s\"\n        }\n      \"\"\", dependencyRiskKey, unicodeComment);\n    waitAtMost(2, SECONDS).untilAsserted(() -> {\n      server.getMockServer()\n        .verify(WireMock.postRequestedFor(urlEqualTo(\"/api/v2/sca/issues-releases/change-status\"))\n          .withHeader(\"Content-Type\", equalTo(\"application/json; charset=UTF-8\"))\n          .withRequestBody(equalToJson(expectedJson)));\n    });\n  }\n\n  @SonarLintTest\n  void it_should_handle_dependency_risk_with_no_transitions_available(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.randomUUID();\n    var dependencyRisk = aServerDependencyRisk()\n      .withKey(dependencyRiskKey)\n      .withType(ServerDependencyRisk.Type.VULNERABILITY)\n      .withSeverity(ServerDependencyRisk.Severity.HIGH)\n      .withStatus(ServerDependencyRisk.Status.OPEN)\n      .withQuality(ServerDependencyRisk.SoftwareQuality.SECURITY)\n      .withPackageName(\"com.example.vulnerable\")\n      .withPackageVersion(\"1.0.0\")\n      .withVulnerabilityId(\"CVE-1234\")\n      .withCvssScore(\"7.5\")\n      .withTransitions(List.of()); // No transitions available\n\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY, project -> project.withBranch(BRANCH_NAME))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server.baseUrl(), storage -> storage\n        .withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME, branch -> branch.withDependencyRisk(dependencyRisk))))\n      .withBoundConfigScope(CONFIGURATION_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var params = new ChangeDependencyRiskStatusParams(CONFIGURATION_SCOPE_ID, dependencyRiskKey, DependencyRiskTransition.CONFIRM, null);\n    var scaService = backend.getDependencyRiskService();\n\n    assertThat(scaService.changeStatus(params))\n      .failsWithin(2, TimeUnit.SECONDS)\n      .withThrowableOfType(ExecutionException.class)\n      .withCauseExactlyInstanceOf(ResponseErrorException.class)\n      .withMessageContaining(\"Transition CONFIRM is not allowed for this dependency risk\");\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/sca/DependencyRisksMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.sca;\n\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.UUID;\nimport org.assertj.core.groups.Tuple;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.CheckDependencyRiskSupportedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.CheckDependencyRiskSupportedResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.DependencyRiskDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.ListAllParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.sca.DidChangeDependencyRisksParams;\nimport org.sonarsource.sonarlint.core.serverapi.features.Feature;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerDependencyRisk;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport org.sonarsource.sonarlint.core.test.utils.server.ServerFixture;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SCA_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.serverconnection.issues.ServerDependencyRisk.Severity;\nimport static org.sonarsource.sonarlint.core.serverconnection.issues.ServerDependencyRisk.Status;\nimport static org.sonarsource.sonarlint.core.serverconnection.issues.ServerDependencyRisk.Type;\nimport static org.sonarsource.sonarlint.core.test.utils.storage.ServerDependencyRiskFixtures.aServerDependencyRisk;\n\nclass DependencyRisksMediumTests {\n\n  private static final String CONFIG_SCOPE_ID = \"configScopeId\";\n  private static final String CONNECTION_ID = \"connectionId\";\n  private static final String PROJECT_KEY = \"projectKey\";\n\n  @SonarLintTest\n  void it_should_return_no_dependency_risks_if_the_scope_does_not_exist(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withBackendCapability(SCA_SYNCHRONIZATION)\n      .start();\n\n    var dependencyRisks = listAllDependencyRisks(backend, CONFIG_SCOPE_ID);\n\n    assertThat(dependencyRisks).isEmpty();\n  }\n\n  @SonarLintTest\n  void it_should_return_no_dependency_risks_if_the_scope_is_not_bound(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withBackendCapability(SCA_SYNCHRONIZATION)\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .start();\n\n    var dependencyRisks = listAllDependencyRisks(backend, CONFIG_SCOPE_ID);\n\n    assertThat(dependencyRisks).isEmpty();\n  }\n\n  @SonarLintTest\n  void it_should_return_no_dependency_risks_if_the_storage_is_empty(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withBackendCapability(SCA_SYNCHRONIZATION)\n      .withSonarQubeConnection(CONNECTION_ID)\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var dependencyRisks = listAllDependencyRisks(backend, CONFIG_SCOPE_ID);\n\n    assertThat(dependencyRisks).isEmpty();\n  }\n\n  @SonarLintTest\n  void it_should_return_the_stored_dependency_risks(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY, project -> project.withBranch(\"main\"))\n      .start();\n    var dependencyRiskKey = UUID.fromString(\"550e8400-e29b-41d4-a716-446655440000\");\n    var backend = harness.newBackend()\n      .withBackendCapability(SCA_SYNCHRONIZATION)\n      .withSonarQubeConnection(CONNECTION_ID, server,\n        storage -> storage.withProject(PROJECT_KEY,\n          project -> project.withMainBranch(\"main\",\n            branch -> branch.withDependencyRisk(aServerDependencyRisk()\n              .withKey(dependencyRiskKey)\n              .withPackageName(\"com.example.vulnerable\")\n              .withPackageVersion(\"2.1.0\")\n              .withVulnerabilityId(\"CVE-1234\")\n              .withCvssScore(\"7.5\")\n              .withType(Type.VULNERABILITY)\n              .withSeverity(Severity.HIGH)))))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var dependencyRisks = listAllDependencyRisks(backend, CONFIG_SCOPE_ID);\n\n    await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(dependencyRisks)\n      .extracting(DependencyRiskDto::getId)\n      .containsOnly(dependencyRiskKey));\n  }\n\n  @SonarLintTest\n  void it_should_return_the_stored_fixed_dependency_risks(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY, project -> project.withBranch(\"main\"))\n      .start();\n    var dependencyRiskKey = UUID.fromString(\"550e8400-e29b-41d4-a716-446655440000\");\n    var backend = harness.newBackend()\n      .withBackendCapability(SCA_SYNCHRONIZATION)\n      .withSonarQubeConnection(CONNECTION_ID, server,\n        storage -> storage.withProject(PROJECT_KEY,\n          project -> project.withMainBranch(\"main\",\n            branch -> branch.withDependencyRisk(aServerDependencyRisk()\n              .withKey(dependencyRiskKey)\n              .withStatus(Status.FIXED)))))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var dependencyRisks = listAllDependencyRisks(backend, CONFIG_SCOPE_ID);\n\n    await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(dependencyRisks)\n      .extracting(DependencyRiskDto::getId, DependencyRiskDto::getStatus)\n      .containsOnly(Tuple.tuple(dependencyRiskKey, DependencyRiskDto.Status.FIXED)));\n  }\n\n  @SonarLintTest\n  void it_should_refresh_dependency_risks_when_requested(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.fromString(\"550e8400-e29b-41d4-a716-446655440000\");\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY,\n        project -> project.withBranch(\"main\",\n          branch -> branch\n            .withDependencyRisk(\n              new ServerFixture.AbstractServerBuilder.ServerProjectBuilder.ServerDependencyRisk(dependencyRiskKey.toString(), \"PROHIBITED_LICENSE\",\n                \"HIGH\", \"MAINTAINABILITY\", \"OPEN\", \"com.example.vulnerable\", \"2.1.0\",\n                null, null, List.of(\"CONFIRM\")))))\n      .start();\n    var backend = harness.newBackend()\n      .withBackendCapability(SCA_SYNCHRONIZATION)\n      .withSonarQubeConnection(CONNECTION_ID, server,\n        storage -> storage\n          .withServerFeature(Feature.SCA)\n          .withProject(PROJECT_KEY, project -> project.withMainBranch(\"main\")))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var refreshedDependencyRisks = refreshAndListAllDependencyRisks(backend, CONFIG_SCOPE_ID);\n\n    assertThat(refreshedDependencyRisks)\n      .extracting(DependencyRiskDto::getId, DependencyRiskDto::getType, DependencyRiskDto::getSeverity, DependencyRiskDto::getQuality, DependencyRiskDto::getStatus,\n        DependencyRiskDto::getTransitions, DependencyRiskDto::getPackageName, DependencyRiskDto::getPackageVersion)\n      .containsExactly(\n        tuple(dependencyRiskKey, DependencyRiskDto.Type.PROHIBITED_LICENSE, DependencyRiskDto.Severity.HIGH, DependencyRiskDto.SoftwareQuality.MAINTAINABILITY,\n          DependencyRiskDto.Status.OPEN, List.of(DependencyRiskDto.Transition.CONFIRM), \"com.example.vulnerable\", \"2.1.0\"));\n  }\n\n  @SonarLintTest\n  void it_should_notify_client_when_new_dependency_risks_are_added(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.fromString(\"550e8400-e29b-41d4-a716-446655440000\");\n    var server = harness.newFakeSonarQubeServer()\n      .withFeature(\"sca\")\n      .withProject(PROJECT_KEY,\n        project -> project.withBranch(\"main\",\n          branch -> branch\n            .withDependencyRisk(\n              new ServerFixture.AbstractServerBuilder.ServerProjectBuilder.ServerDependencyRisk(dependencyRiskKey.toString(), \"VULNERABILITY\", \"HIGH\",\n                \"SECURITY\", \"OPEN\", \"com.example.vulnerable\", \"2.1.0\",\n                \"CVE-1234\", \"7.5\", List.of(\"CONFIRM\")))))\n      .start();\n    var client = harness.newFakeClient().build();\n    harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server)\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .withBackendCapability(FULL_SYNCHRONIZATION, SCA_SYNCHRONIZATION)\n      .start(client);\n    client.waitForSynchronization();\n\n    await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> {\n      var changes = client.getDependencyRiskChanges();\n      assertThat(changes).hasSize(1);\n      var change = changes.get(0);\n      assertThat(change.getConfigurationScopeId()).isEqualTo(CONFIG_SCOPE_ID);\n      assertThat(change.getClosedDependencyRiskIds()).isEmpty();\n      assertThat(change.getAddedDependencyRisks())\n        .hasSize(1)\n        .first()\n        .satisfies(dependencyRisk -> {\n          assertThat(dependencyRisk.getId()).isEqualTo(dependencyRiskKey);\n          assertThat(dependencyRisk.getPackageName()).isEqualTo(\"com.example.vulnerable\");\n          assertThat(dependencyRisk.getPackageVersion()).isEqualTo(\"2.1.0\");\n          assertThat(dependencyRisk.getVulnerabilityId()).isEqualTo(\"CVE-1234\");\n          assertThat(dependencyRisk.getCvssScore()).isEqualTo(\"7.5\");\n          assertThat(dependencyRisk.getType()).isEqualTo(DependencyRiskDto.Type.VULNERABILITY);\n          assertThat(dependencyRisk.getSeverity()).isEqualTo(DependencyRiskDto.Severity.HIGH);\n          assertThat(dependencyRisk.getQuality()).isEqualTo(DependencyRiskDto.SoftwareQuality.SECURITY);\n        });\n      assertThat(change.getUpdatedDependencyRisks()).isEmpty();\n    });\n  }\n\n  @SonarLintTest\n  void it_should_notify_client_when_dependency_risks_are_removed(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.fromString(\"550e8400-e29b-41d4-a716-446655440000\");\n    var server = harness.newFakeSonarQubeServer()\n      .withFeature(\"sca\")\n      .withProject(PROJECT_KEY, project -> project.withBranch(\"main\"))\n      .start();\n    var client = harness.newFakeClient().build();\n    harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server,\n        storage -> storage.withProject(PROJECT_KEY,\n          project -> project.withMainBranch(\"main\",\n            branch -> branch.withDependencyRisk(aServerDependencyRisk()\n              .withKey(dependencyRiskKey)\n              .withPackageName(\"com.example.vulnerable\")\n              .withPackageVersion(\"2.1.0\")\n              .withVulnerabilityId(\"CVE-1234\")\n              .withCvssScore(\"7.5\")\n              .withType(Type.VULNERABILITY)\n              .withSeverity(Severity.HIGH)))))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .withBackendCapability(FULL_SYNCHRONIZATION, SCA_SYNCHRONIZATION)\n      .start(client);\n    client.waitForSynchronization();\n\n    await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> {\n      var changes = client.getDependencyRiskChanges();\n      assertThat(changes)\n        .extracting(DidChangeDependencyRisksParams::getConfigurationScopeId, DidChangeDependencyRisksParams::getClosedDependencyRiskIds, DidChangeDependencyRisksParams::getAddedDependencyRisks,\n          DidChangeDependencyRisksParams::getUpdatedDependencyRisks)\n        .containsExactly(tuple(CONFIG_SCOPE_ID, Set.of(dependencyRiskKey), List.of(), List.of()));\n    });\n  }\n\n  @SonarLintTest\n  void it_should_notify_client_when_dependency_risks_are_updated(SonarLintTestHarness harness) {\n    var dependencyRiskKey = UUID.fromString(\"550e8400-e29b-41d4-a716-446655440000\");\n    var server = harness.newFakeSonarQubeServer()\n      .withFeature(\"sca\")\n      .withProject(PROJECT_KEY,\n        project -> project.withBranch(\"main\",\n          branch -> branch\n            .withDependencyRisk(\n              new ServerFixture.AbstractServerBuilder.ServerProjectBuilder.ServerDependencyRisk(dependencyRiskKey.toString(), \"VULNERABILITY\", \"LOW\",\n                \"RELIABILITY\", \"ACCEPT\", \"com.example.vulnerable\", \"2.1.0\", \"CVE-1234\",\n                \"7.5\", List.of(\"REOPEN\")))))\n      .start();\n    var client = harness.newFakeClient().build();\n    harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server,\n        storage -> storage.withProject(PROJECT_KEY,\n          project -> project.withMainBranch(\"main\",\n            branch -> branch.withDependencyRisk(aServerDependencyRisk()\n              .withKey(dependencyRiskKey)\n              .withPackageName(\"com.example.vulnerable2\")\n              .withPackageVersion(\"0.1.2\")\n              .withType(Type.PROHIBITED_LICENSE)\n              .withSeverity(Severity.HIGH)\n              .withQuality(ServerDependencyRisk.SoftwareQuality.RELIABILITY)\n              .withStatus(Status.OPEN)\n              .withTransitions(List.of(ServerDependencyRisk.Transition.REOPEN))))))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .withBackendCapability(FULL_SYNCHRONIZATION, SCA_SYNCHRONIZATION)\n      .start(client);\n    client.waitForSynchronization();\n\n    await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> {\n      var changes = client.getDependencyRiskChanges();\n      assertThat(changes).hasSize(1);\n      var change = changes.get(0);\n      assertThat(change.getConfigurationScopeId()).isEqualTo(CONFIG_SCOPE_ID);\n      assertThat(change.getClosedDependencyRiskIds()).isEmpty();\n      assertThat(change.getAddedDependencyRisks()).isEmpty();\n      assertThat(change.getUpdatedDependencyRisks())\n        .extracting(DependencyRiskDto::getId, DependencyRiskDto::getType, DependencyRiskDto::getSeverity, DependencyRiskDto::getStatus,\n          DependencyRiskDto::getTransitions, DependencyRiskDto::getPackageName, DependencyRiskDto::getPackageVersion,\n          DependencyRiskDto::getVulnerabilityId, DependencyRiskDto::getCvssScore)\n        .containsExactly(\n          tuple(dependencyRiskKey, DependencyRiskDto.Type.VULNERABILITY, DependencyRiskDto.Severity.LOW, DependencyRiskDto.Status.ACCEPT, List.of(DependencyRiskDto.Transition.REOPEN), \"com.example.vulnerable\",\n            \"2.1.0\", \"CVE-1234\", \"7.5\"));\n    });\n  }\n\n  @SonarLintTest\n  void it_should_check_for_supported_sca(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer()\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server,\n        storage -> storage\n          .withServerFeature(Feature.SCA)\n          .withServerVersion(\"2025.4\"))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var response = checkSupported(backend, CONFIG_SCOPE_ID);\n\n    assertThat(response.isSupported()).isTrue();\n    assertThat(response.getReason()).isNull();\n  }\n\n  @SonarLintTest\n  void it_should_not_be_supported_if_old_version(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer()\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server,\n        storage -> storage\n          .withServerFeature(Feature.SCA)\n          .withServerVersion(\"2025.3\"))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var response = checkSupported(backend, CONFIG_SCOPE_ID);\n\n    assertThat(response.isSupported()).isFalse();\n    assertThat(response.getReason()).isEqualTo(\"The connected SonarQube Server version is lower than the minimum supported version 2025.4\");\n  }\n\n  @SonarLintTest\n  void it_should_not_be_supported_if_sca_disabled(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer()\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server,\n        storage -> storage\n          .withServerVersion(\"2025.4\"))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .start();\n\n    var response = checkSupported(backend, CONFIG_SCOPE_ID);\n\n    assertThat(response.isSupported()).isFalse();\n    assertThat(response.getReason()).isEqualTo(\"The connected SonarQube Server does not have Advanced Security enabled (requires Enterprise edition or higher)\");\n  }\n\n  private List<DependencyRiskDto> listAllDependencyRisks(SonarLintTestRpcServer backend, String configScopeId) {\n    return backend.getDependencyRiskService().listAll(new ListAllParams(configScopeId)).join().getDependencyRisks();\n  }\n\n  private List<DependencyRiskDto> refreshAndListAllDependencyRisks(SonarLintTestRpcServer backend, String configScopeId) {\n    return backend.getDependencyRiskService().listAll(new ListAllParams(configScopeId, true)).join().getDependencyRisks();\n  }\n\n  private CheckDependencyRiskSupportedResponse checkSupported(SonarLintTestRpcServer backend, String configScopeId) {\n    return backend.getDependencyRiskService().checkSupported(new CheckDependencyRiskSupportedParams(configScopeId)).join();\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/sca/OpenDependencyRiskInBrowserMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.sca;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.URL;\nimport java.time.Duration;\nimport java.util.UUID;\nimport java.util.concurrent.ExecutionException;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.OpenDependencyRiskInBrowserParams;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.verify;\nimport static org.sonarsource.sonarlint.core.serverapi.UrlUtils.urlEncode;\n\nclass OpenDependencyRiskInBrowserMediumTests {\n  static final String CONNECTION_ID = \"connectionId\";\n  static final String SCOPE_ID = \"scopeId\";\n  static final String PROJECT_KEY = \"projectKey\";\n  static final UUID DEPENDENCY_KEY = UUID.randomUUID();\n  static final String BRANCH_NAME = \"master\";\n\n  @SonarLintTest\n  void it_should_open_dependency_risk_in_sonarqube(SonarLintTestHarness harness) throws IOException {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, \"http://localhost:12345\", storage -> storage.withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME)))\n      .withBoundConfigScope(SCOPE_ID, CONNECTION_ID, PROJECT_KEY)\n      .withTelemetryEnabled()\n      .start(fakeClient);\n\n    backend.getDependencyRiskService().openDependencyRiskInBrowser(new OpenDependencyRiskInBrowserParams(\n      SCOPE_ID, DEPENDENCY_KEY)).join();\n\n    var expectedUrl = String.format(\"http://localhost:12345/dependency-risks/%s/what?id=%s&branch=%s\",\n      urlEncode(DEPENDENCY_KEY.toString()), urlEncode(PROJECT_KEY), urlEncode(BRANCH_NAME));\n\n    verify(fakeClient, timeout(5000)).openUrlInBrowser(URI.create(expectedUrl).toURL());\n    await().untilAsserted(() -> assertThat(backend.telemetryFileContent().getDependencyRiskInvestigatedRemotelyCount()).isEqualTo(1));\n  }\n\n  @SonarLintTest\n  void it_should_not_open_dependency_risk_if_unbound(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(SCOPE_ID)\n      .start(fakeClient);\n\n    var result = backend.getDependencyRiskService().openDependencyRiskInBrowser(new OpenDependencyRiskInBrowserParams(\n      SCOPE_ID, DEPENDENCY_KEY));\n\n    assertThat(result).failsWithin(Duration.ofSeconds(2)).withThrowableOfType(ExecutionException.class)\n      .withMessage(\"org.eclipse.lsp4j.jsonrpc.ResponseErrorException: Configuration scope 'scopeId' is not bound properly, unable to open dependency risk\");\n    verify(fakeClient, timeout(5000).times(0)).openUrlInBrowser(any(URL.class));\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/server/events/ServerSentEventsMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.server.events;\n\nimport com.github.tomakehurst.wiremock.junit5.WireMockExtension;\nimport com.github.tomakehurst.wiremock.stubbing.ServeEvent;\nimport com.github.tomakehurst.wiremock.verification.LoggedRequest;\nimport java.nio.file.Paths;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.TimeUnit;\nimport java.util.regex.Pattern;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.mockito.ArgumentCaptor;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidRemoveConfigurationScopeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidChangeCredentialsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidUpdateConnectionsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarCloudConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarQubeConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TextRangeWithHashDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.StandardModeDetails;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.get;\nimport static com.github.tomakehurst.wiremock.client.WireMock.jsonResponse;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.awaitility.Awaitility.waitAtMost;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.reset;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SERVER_SENT_EVENTS;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JAVA;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JS;\n\nclass ServerSentEventsMediumTests {\n\n  @RegisterExtension\n  SonarLintLogTester logTester = new SonarLintLogTester(true);\n\n  @RegisterExtension\n  static WireMockExtension sonarServerMock = WireMockExtension.newInstance()\n    .options(wireMockConfig().dynamicPort())\n    .build();\n\n  @BeforeEach\n  void init() {\n    sonarServerMock.stubFor(get(\"/api/system/status\")\n      .willReturn(aResponse().withStatus(200).withBody(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"10.8\\\",\\\"status\\\": \" +\n        \"\\\"UP\\\"}\")));\n  }\n\n  @Nested\n  class WhenScopeBound {\n    @SonarLintTest\n    void should_subscribe_for_events_if_connected_to_sonarqube(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", sonarServerMock.baseUrl())\n        .withUnboundConfigScope(\"configScope\")\n        .start();\n\n      bind(backend, \"configScope\", \"connectionId\", \"projectKey\");\n\n      await().atMost(Duration.ofSeconds(2))\n        .untilAsserted(() -> assertThat(requestedPaths()).containsExactly(\"/api/push/sonarlint_events?projectKeys=projectKey&languages=java,js\"));\n    }\n\n    @SonarLintTest\n    void should_not_subscribe_for_events_if_sonarcloud_connection(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withSonarQubeCloudEuRegionUri(sonarServerMock.baseUrl())\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarCloudConnection(\"connectionId\")\n        .withUnboundConfigScope(\"configScope\")\n        .start();\n\n      bind(backend, \"configScope\", \"connectionId\", \"projectKey\");\n\n      await().during(Duration.ofMillis(300)).until(() -> requestedPaths().isEmpty());\n    }\n\n    @SonarLintTest\n    void should_not_resubscribe_for_events_if_sonarqube_connection_and_binding_is_the_same(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", sonarServerMock.baseUrl())\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(requestedPaths()).hasSize(1));\n\n      bind(backend, \"configScope\", \"connectionId\", \"projectKey\");\n\n      await().during(Duration.ofMillis(300)).until(() -> requestedPaths().size() == 1);\n    }\n\n    private void bind(SonarLintTestRpcServer backend, String configScopeId, String connectionId, String projectKey) {\n      backend.getConfigurationService().didUpdateBinding(new DidUpdateBindingParams(configScopeId, new BindingConfigurationDto(connectionId, projectKey, true)));\n    }\n  }\n\n  @Nested\n  class WhenUnbindingScope {\n    @SonarLintTest\n    void should_not_resubscribe_for_events_if_sonarqube_connection(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", sonarServerMock.baseUrl())\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(requestedPaths()).hasSize(1));\n\n      unbind(backend, \"configScope\");\n\n      await().during(Duration.ofMillis(300)).until(() -> requestedPaths().size() == 1);\n    }\n\n    @SonarLintTest\n    void should_unsubscribe_for_events_if_sonarqube_connection_and_other_projects_bound(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", sonarServerMock.baseUrl())\n        .withBoundConfigScope(\"configScope1\", \"connectionId\", \"projectKey1\")\n        .withBoundConfigScope(\"configScope2\", \"connectionId\", \"projectKey2\")\n        .start();\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(requestedPaths()).hasSize(1));\n\n      unbind(backend, \"configScope1\");\n\n      await().atMost(Duration.ofSeconds(2))\n        .untilAsserted(() -> assertThat(requestedPaths()).containsOnly(\n          \"/api/push/sonarlint_events?projectKeys=projectKey2,projectKey1&languages=java,js\",\n          \"/api/push/sonarlint_events?projectKeys=projectKey2&languages=java,js\"));\n    }\n\n    private void unbind(SonarLintTestRpcServer backend, String configScope) {\n      backend.getConfigurationService().didUpdateBinding(new DidUpdateBindingParams(configScope, new BindingConfigurationDto(null, null, true)));\n    }\n  }\n\n  @Nested\n  class WhenScopeAdded {\n    @SonarLintTest\n    void should_subscribe_if_bound_to_sonarqube(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", sonarServerMock.baseUrl())\n        .start();\n\n      addConfigurationScope(backend, \"configScope\", \"connectionId\", \"projectKey\");\n\n      await().atMost(Duration.ofSeconds(2))\n        .untilAsserted(() -> assertThat(requestedPaths()).containsExactly(\"/api/push/sonarlint_events?projectKeys=projectKey&languages=java,js\"));\n    }\n\n    @SonarLintTest\n    void should_log_subscription_errors(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient().build();\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", sonarServerMock.baseUrl())\n        .start(client);\n      var projectKey = \"projectKey\";\n\n      sonarServerMock.stubFor(get(\"/api/push/sonarlint_events?projectKeys=\" + projectKey + \"&languages=java,js\")\n        .willReturn(jsonResponse(\"{\\\"errors\\\":[{\\\"msg\\\":\\\"Some error from server\\\"}]}\", 400)));\n\n      addConfigurationScope(backend, \"configScope\", \"connectionId\", projectKey);\n\n      await().atMost(Duration.ofSeconds(2))\n        .untilAsserted(() -> assertThat(requestedPaths()).containsExactly(\"/api/push/sonarlint_events?projectKeys=projectKey&languages=java,js\"));\n\n      await().atMost(Duration.ofSeconds(1))\n        .untilAsserted(() -> assertThat(client.getLogMessages())\n          .contains(\n            \"Cannot connect to server event-stream (400), retrying in 60s\",\n            \"Received event-stream data while not connected: {\\\"errors\\\":[{\\\"msg\\\":\\\"Some error from server\\\"}]}\"));\n    }\n\n    @SonarLintTest\n    void should_not_subscribe_if_not_bound(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", sonarServerMock.baseUrl())\n        .start();\n\n      addConfigurationScope(backend, \"configScope\", null, null);\n\n      await().during(Duration.ofMillis(300)).until(() -> requestedPaths().isEmpty());\n    }\n\n    @SonarLintTest\n    void should_not_subscribe_if_bound_to_sonarcloud(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withSonarQubeCloudEuRegionUri(sonarServerMock.baseUrl())\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarCloudConnection(\"connectionId\")\n        .start();\n\n      addConfigurationScope(backend, \"configScope\", \"connectionId\", \"projectKey\");\n\n      await().during(Duration.ofMillis(300)).until(() -> requestedPaths().isEmpty());\n    }\n\n    private void addConfigurationScope(SonarLintTestRpcServer backend, String configScope, String connectionId, String projectKey) {\n      backend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(\n        List.of(new ConfigurationScopeDto(configScope, null, true, \"name\", new BindingConfigurationDto(connectionId, projectKey, true)))));\n    }\n  }\n\n  @Nested\n  class WhenScopeRemoved {\n\n    @SonarLintTest\n    void should_do_nothing_if_scope_was_not_bound(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", sonarServerMock.baseUrl())\n        .withUnboundConfigScope(\"configScope\")\n        .start();\n\n      removeScope(backend, \"configScope\");\n\n      await().during(Duration.ofMillis(300)).until(() -> requestedPaths().isEmpty());\n    }\n\n    @SonarLintTest\n    void should_do_nothing_if_scope_was_bound_to_sonarcloud(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withSonarQubeCloudEuRegionUri(sonarServerMock.baseUrl())\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarCloudConnection(\"connectionId\")\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n\n      removeScope(backend, \"configScope\");\n\n      await().during(Duration.ofMillis(300)).until(() -> requestedPaths().isEmpty());\n    }\n\n    @SonarLintTest\n    void should_close_connection_when_if_scope_was_bound_to_sonarcloud_and_no_other_project_interested(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", sonarServerMock.baseUrl())\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(requestedPaths()).hasSize(1));\n\n      removeScope(backend, \"configScope\");\n\n      await().during(Duration.ofMillis(300)).until(() -> requestedPaths().size() == 1);\n    }\n\n    @SonarLintTest\n    void should_keep_connection_if_scope_was_bound_to_sonarqube_and_another_scope_is_interested_in_the_same_project(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", sonarServerMock.baseUrl())\n        .withBoundConfigScope(\"configScope1\", \"connectionId\", \"projectKey\")\n        .withBoundConfigScope(\"configScope2\", \"connectionId\", \"projectKey\")\n        .start();\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(requestedPaths()).hasSize(1));\n\n      removeScope(backend, \"configScope1\");\n\n      await().atMost(Duration.ofSeconds(2))\n        .untilAsserted(() -> assertThat(requestedPaths()).containsOnly(\n          \"/api/push/sonarlint_events?projectKeys=projectKey&languages=java,js\"));\n    }\n\n    @SonarLintTest\n    void should_reopen_connection_if_scope_was_bound_to_sonarqube_and_another_scope_is_interested_in_a_different_project(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", sonarServerMock.baseUrl())\n        .withBoundConfigScope(\"configScope1\", \"connectionId\", \"projectKey1\")\n        .withBoundConfigScope(\"configScope2\", \"connectionId\", \"projectKey2\")\n        .start();\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(requestedPaths()).hasSize(1));\n\n      removeScope(backend, \"configScope1\");\n\n      await().atMost(Duration.ofSeconds(2))\n        .untilAsserted(() -> assertThat(requestedPaths()).containsOnly(\n          \"/api/push/sonarlint_events?projectKeys=projectKey2,projectKey1&languages=java,js\",\n          \"/api/push/sonarlint_events?projectKeys=projectKey2&languages=java,js\"));\n    }\n\n    private void removeScope(SonarLintTestRpcServer backend, String configScope) {\n      backend.getConfigurationService().didRemoveConfigurationScope(new DidRemoveConfigurationScopeParams(configScope));\n    }\n  }\n\n  @Nested\n  class WhenConnectionCredentialsChanged {\n\n    @SonarLintTest\n    void should_resubscribe_if_sonarqube_connection_was_open(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", sonarServerMock.baseUrl())\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(requestedPaths()).hasSize(1));\n\n      notifyCredentialsChanged(backend, \"connectionId\");\n\n      await().atMost(Duration.ofSeconds(2))\n        .untilAsserted(() -> assertThat(requestedPaths()).containsOnly(\n          \"/api/push/sonarlint_events?projectKeys=projectKey&languages=java,js\",\n          \"/api/push/sonarlint_events?projectKeys=projectKey&languages=java,js\"));\n    }\n\n    @SonarLintTest\n    void should_do_nothing_if_sonarcloud(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withSonarQubeCloudEuRegionUri(sonarServerMock.baseUrl())\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarCloudConnection(\"connectionId\")\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n\n      notifyCredentialsChanged(backend, \"connectionId\");\n\n      await().during(Duration.ofMillis(300)).until(() -> requestedPaths().isEmpty());\n    }\n\n    @SonarLintTest\n    void should_do_nothing_if_sonarqube_connection_was_not_open(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", sonarServerMock.baseUrl())\n        .start();\n\n      notifyCredentialsChanged(backend, \"connectionId\");\n\n      await().during(Duration.ofMillis(300)).until(() -> requestedPaths().isEmpty());\n    }\n\n    private void notifyCredentialsChanged(SonarLintTestRpcServer backend, String connectionId) {\n      backend.getConnectionService().didChangeCredentials(new DidChangeCredentialsParams(connectionId));\n    }\n  }\n\n  @Nested\n  class WhenConnectionAdded {\n\n    @SonarLintTest\n    void should_do_nothing_if_no_scope_is_bound(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withUnboundConfigScope(\"configScope\")\n        .start();\n\n      backend.getConnectionService()\n        .didUpdateConnections(new DidUpdateConnectionsParams(List.of(new SonarQubeConnectionConfigurationDto(\"connectionId\", \"url\", true)), Collections.emptyList()));\n\n      await().during(Duration.ofMillis(300)).until(() -> requestedPaths().isEmpty());\n    }\n\n    @SonarLintTest\n    void should_do_nothing_if_sonarcloud(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n\n      backend.getConnectionService()\n        .didUpdateConnections(\n          new DidUpdateConnectionsParams(Collections.emptyList(), List.of(new SonarCloudConnectionConfigurationDto(\"connectionId\", \"orgKey\", SonarCloudRegion.EU, true))));\n\n      await().during(Duration.ofMillis(300)).until(() -> requestedPaths().isEmpty());\n    }\n\n    @SonarLintTest\n    void should_open_connection_when_bound_scope_exists(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n\n      backend.getConnectionService().didUpdateConnections(\n        new DidUpdateConnectionsParams(List.of(new SonarQubeConnectionConfigurationDto(\"connectionId\", sonarServerMock.baseUrl(), true)), Collections.emptyList()));\n\n      await().atMost(Duration.ofSeconds(2))\n        .untilAsserted(() -> assertThat(requestedPaths()).containsOnly(\"/api/push/sonarlint_events?projectKeys=projectKey&languages=java,js\"));\n    }\n  }\n\n  @Nested\n  class WhenConnectionRemoved {\n\n    @SonarLintTest\n    void should_do_nothing_if_sonarcloud(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withSonarQubeCloudEuRegionUri(sonarServerMock.baseUrl())\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarCloudConnection(\"connectionId\")\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n\n      backend.getConnectionService().didUpdateConnections(new DidUpdateConnectionsParams(Collections.emptyList(), Collections.emptyList()));\n\n      await().during(Duration.ofMillis(300)).until(() -> requestedPaths().isEmpty());\n    }\n\n    @SonarLintTest\n    void should_close_active_connection_if_sonarqube(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", sonarServerMock.baseUrl())\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(requestedPaths()).hasSize(1));\n\n      backend.getConnectionService().didUpdateConnections(new DidUpdateConnectionsParams(Collections.emptyList(), Collections.emptyList()));\n\n      await().during(Duration.ofMillis(300)).until(() -> requestedPaths().size() == 1);\n    }\n  }\n\n  @Nested\n  class WhenConnectionUpdated {\n\n    @SonarLintTest\n    void should_resubscribe_if_sonarqube_connection_active(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", sonarServerMock.baseUrl())\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(requestedPaths()).hasSize(1));\n\n      backend.getConnectionService().didUpdateConnections(\n        new DidUpdateConnectionsParams(List.of(new SonarQubeConnectionConfigurationDto(\"connectionId\", sonarServerMock.baseUrl(), false)), Collections.emptyList()));\n\n      await().atMost(Duration.ofSeconds(2))\n        .untilAsserted(() -> assertThat(requestedPaths()).containsOnly(\n          \"/api/push/sonarlint_events?projectKeys=projectKey&languages=java,js\",\n          \"/api/push/sonarlint_events?projectKeys=projectKey&languages=java,js\"));\n    }\n\n    @SonarLintTest\n    void should_not_resubscribe_if_sonarcloud(SonarLintTestHarness harness) {\n      var backend = harness.newBackend()\n        .withSonarQubeCloudEuRegionUri(sonarServerMock.baseUrl())\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarCloudConnection(\"connectionId\")\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start();\n\n      backend.getConnectionService().didUpdateConnections(new DidUpdateConnectionsParams(List.of(), Collections.emptyList()));\n\n      await().during(Duration.ofMillis(300)).until(() -> requestedPaths().isEmpty());\n    }\n  }\n\n  @Nested\n  class WhenReceivingIssueChangedEvent {\n\n    @SonarLintTest\n    void should_forward_taint_events_to_client(SonarLintTestHarness harness) {\n      var fakeClient = harness.newFakeClient().build();\n      var branchName = \"branchName\";\n      when(fakeClient.matchSonarProjectBranch(eq(\"configScope\"), eq(\"main\"), eq(Set.of(\"main\", branchName)), any())).thenReturn(branchName);\n      var projectKey = \"projectKey\";\n      var introductionDate = Instant.now().truncatedTo(ChronoUnit.SECONDS);\n      var serverWithTaintIssues = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(projectKey,\n          project -> project.withBranch(branchName,\n            branch -> branch\n              .withTaintIssue(\"key1\", \"ruleKey\", \"msg\", \"author\", \"file/path\", \"REVIEWED\", \"WONTFIX\", introductionDate, new TextRange(1, 0, 3, 4), RuleType.VULNERABILITY)\n              .withSourceFile(\"projectKey:file/path\", sourceFile -> sourceFile.withCode(\"source\\ncode\\nfile\"))))\n        .start();\n      harness.newBackend()\n        .withEnabledLanguageInStandaloneMode(JS)\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS, FULL_SYNCHRONIZATION)\n        .withSonarQubeConnection(\"connectionId\", serverWithTaintIssues)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", projectKey)\n        .start(fakeClient);\n      fakeClient.waitForSynchronization();\n      ArgumentCaptor<List<TaintVulnerabilityDto>> captor = ArgumentCaptor.forClass(List.class);\n      verify(fakeClient, timeout(3000)).didChangeTaintVulnerabilities(eq(\"configScope\"), eq(Set.of()), captor.capture(), eq(List.of()));\n\n      serverWithTaintIssues.pushEvent(\"event: IssueChanged\\n\" +\n        \"data: {\" +\n        \"\\\"projectKey\\\": \\\"\" + projectKey + \"\\\",\" +\n        \"\\\"issues\\\": [{\" +\n        \"  \\\"issueKey\\\": \\\"key1\\\",\" +\n        \"  \\\"branchName\\\": \\\"\" + branchName + \"\\\"\" +\n        \"}],\" +\n        \"\\\"userType\\\": \\\"BUG\\\"\" +\n        \"}\\n\\n\");\n\n      // initial sync\n      assertThat(captor.getValue())\n        .usingRecursiveComparison()\n        .ignoringFields(\"id\")\n        .isEqualTo(List.of(new TaintVulnerabilityDto(UUID.randomUUID(), \"key1\", true, null, \"ruleKey\", \"msg\", Paths.get(\"file/path\"), introductionDate,\n          Either.forLeft(new StandardModeDetails(IssueSeverity.MAJOR, org.sonarsource.sonarlint.core.rpc.protocol.common.RuleType.BUG)), Collections.emptyList(),\n          new TextRangeWithHashDto(1, 0, 3, 4, \"hash\"), null, true, false)));\n\n      reset(fakeClient);\n      waitAtMost(20, TimeUnit.SECONDS).untilAsserted(() -> assertThat(fakeClient.getTaintVulnerabilityChanges()).isNotEmpty());\n\n      // server event\n      assertThat(captor.getValue())\n        .usingRecursiveComparison()\n        .ignoringFields(\"id\")\n        .isEqualTo(List.of(new TaintVulnerabilityDto(UUID.randomUUID(), \"key1\", true, null, \"ruleKey\", \"msg\", Paths.get(\"file/path\"), introductionDate,\n          Either.forLeft(new StandardModeDetails(IssueSeverity.MAJOR, org.sonarsource.sonarlint.core.rpc.protocol.common.RuleType.BUG)), Collections.emptyList(),\n          new TextRangeWithHashDto(1, 0, 3, 4, \"hash\"), null, true, false)));\n    }\n  }\n\n  private List<String> requestedPaths() {\n    var pattern = Pattern.compile(\"/api/push/sonarlint_events*\");\n    return sonarServerMock.getAllServeEvents()\n      .stream()\n      .map(ServeEvent::getRequest)\n      .map(LoggedRequest::getUrl)\n      .filter(pattern.asPredicate())\n      .toList();\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/sloop/JreLocator.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.sloop;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport org.jetbrains.annotations.NotNull;\n\nimport static java.util.Objects.requireNonNull;\n\npublic class JreLocator {\n  private static final String JRE_WINDOWS_PATH = \"target/jre-windows/\";\n  private static final String JRE_LINUX_PATH = \"target/jre-linux/\";\n\n  public static Path getWindowsJrePath() {\n    return getJrePath(JRE_WINDOWS_PATH);\n  }\n\n  public static Path getLinuxJrePath() {\n    return getJrePath(JRE_LINUX_PATH);\n  }\n\n  @NotNull\n  private static Path getJrePath(String jreLinuxPath) {\n    var jreDir = Paths.get(jreLinuxPath).toAbsolutePath().normalize().toFile();\n    return Arrays.stream(requireNonNull(jreDir.listFiles())).findFirst().get().toPath();\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/sloop/ProcessUtils.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.sloop;\n\nimport java.util.concurrent.TimeUnit;\n\npublic class ProcessUtils {\n\n  public static int waitFor(Process process) throws InterruptedException {\n    if (process.waitFor(1, TimeUnit.MINUTES)) {\n      return process.exitValue();\n    } else {\n      process.destroyForcibly();\n      return -1;\n    }\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/sloop/SloopDistLocator.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.sloop;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.io.filefilter.FalseFileFilter;\nimport org.apache.commons.io.filefilter.RegexFileFilter;\n\npublic class SloopDistLocator {\n\n  private static final String SLOOP_DIST_PATH = \"target\";\n  private static final String WINDOWS_DIST_REGEXP = \"^sonarlint-backend-cli-([0-9.]+)(-SNAPSHOT)*-windows_x64.zip$\";\n  private static final String LINUX_64_DIST_REGEXP = \"^sonarlint-backend-cli-([0-9.]+)(-SNAPSHOT)*-linux_x64.tar.gz$\";\n\n  public static Path getLinux64DistPath() {\n    return getSloopDistPath(LINUX_64_DIST_REGEXP);\n  }\n\n  public static Path getWindowsDistPath() {\n    return getSloopDistPath(WINDOWS_DIST_REGEXP);\n  }\n\n  private static Path getSloopDistPath(String regexp) {\n    var sloopDistDir = Paths.get(SLOOP_DIST_PATH).toAbsolutePath().normalize().toFile();\n    return FileUtils.listFiles(sloopDistDir, new RegexFileFilter(regexp), FalseFileFilter.FALSE).iterator().next().toPath();\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/sloop/SloopLauncherTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.sloop;\n\nimport java.net.URI;\nimport java.net.URL;\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Queue;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ConcurrentLinkedQueue;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.rpc.client.ConnectionNotFoundException;\nimport org.sonarsource.sonarlint.core.rpc.client.Sloop;\nimport org.sonarsource.sonarlint.core.rpc.client.SloopLauncher;\nimport org.sonarsource.sonarlint.core.rpc.client.SonarLintCancelChecker;\nimport org.sonarsource.sonarlint.core.rpc.client.SonarLintRpcClientDelegate;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.ClientConstantInfoDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.HttpConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.TelemetryClientConstantAttributesDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.GetEffectiveRuleDetailsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.AssistBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.AssistBindingResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.NoBindingSuggestionFoundParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.ConnectionSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.event.DidReceiveServerHotspotEvent;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fix.FixSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.HotspotDetailsDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.GetProxyPasswordAuthenticationResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.ProxyDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.X509CertificateDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.IssueDetailsDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.MessageType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowSoonUnsupportedMessageParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.progress.ReportProgressParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.progress.StartProgressParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.smartnotification.ShowSmartNotificationParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.TelemetryClientLiveAttributesResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\nimport utils.PluginLocator;\n\nimport static mediumtest.sloop.UnArchiveUtils.unarchiveDistribution;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.PHP;\n\nclass SloopLauncherTests {\n\n  @TempDir\n  private static Path sonarUserHome;\n\n  @TempDir\n  private static Path unarchiveTmpDir;\n\n  private static Sloop sloop;\n  private static SonarLintRpcServer server;\n  private static Path sloopOutDirPath;\n  private Integer exitValue;\n  private boolean shutdownRequested;\n\n  @BeforeAll\n  static void setup() {\n    var sloopDistPath = SystemUtils.IS_OS_WINDOWS ? SloopDistLocator.getWindowsDistPath() : SloopDistLocator.getLinux64DistPath();\n    sloopOutDirPath = unarchiveTmpDir.resolve(\"sloopDistOut\");\n    unarchiveDistribution(sloopDistPath.toString(), sloopOutDirPath);\n  }\n\n  @BeforeEach\n  void start() {\n    shutdownRequested = false;\n    exitValue = null;\n    var sloopLauncher = new SloopLauncher(new DummySonarLintRpcClient());\n    sloop = sloopLauncher.start(sloopOutDirPath.toAbsolutePath());\n    server = sloop.getRpcServer();\n  }\n\n  @AfterEach\n  void tearDown() {\n    if (!shutdownRequested) {\n      sloop.shutdown().join();\n    }\n  }\n\n  @Test\n  void test_all_rules_returns() throws Exception {\n    var telemetryInitDto = new TelemetryClientConstantAttributesDto(\"SonarLint ITs\", \"SonarLint ITs\",\n      \"1.2.3\", \"4.5.6\", Collections.emptyMap());\n    var clientInfo = new ClientConstantInfoDto(\"clientName\", \"integrationTests\");\n\n    server.initialize(new InitializeParams(clientInfo, telemetryInitDto, HttpConfigurationDto.defaultConfig(), null, Set.of(), sonarUserHome.resolve(\"storage\"), sonarUserHome.resolve(\"workDir\"),\n      Set.of(PluginLocator.getPhpPluginPath().toAbsolutePath()), Collections.emptyMap(), Set.of(PHP), Collections.emptySet(), Collections.emptySet(), Collections.emptyList(),\n      Collections.emptyList(), sonarUserHome.toString(), Map.of(), false, null, false, null)).get();\n\n    var result = server.getRulesService().listAllStandaloneRulesDefinitions().get();\n    assertThat(result.getRulesByKey()).hasSize(234);\n\n    server.getConfigurationService()\n      .didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(new ConfigurationScopeDto(\"myConfigScope\", null, true, \"My Config Scope\", null))));\n\n    var result2 = server.getRulesService().getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams(\"myConfigScope\", \"php:S100\", null)).join();\n    assertThat(result2.details().getName()).isEqualTo(\"Function names should comply with a naming convention\");\n  }\n\n  @Test\n  void it_should_complete_onExit_future_when_process_exits() {\n    var telemetryInitDto = new TelemetryClientConstantAttributesDto(\"SonarLint ITs\", \"SonarLint ITs\",\n      \"1.2.3\", \"4.5.6\", Collections.emptyMap());\n    var clientInfo = new ClientConstantInfoDto(\"clientName\", \"integrationTests\");\n    server.initialize(new InitializeParams(clientInfo, telemetryInitDto, HttpConfigurationDto.defaultConfig(), null, Set.of(), sonarUserHome.resolve(\"storage\"), sonarUserHome.resolve(\"workDir\"),\n      Set.of(PluginLocator.getPhpPluginPath().toAbsolutePath()), Collections.emptyMap(), Set.of(PHP), Collections.emptySet(), Collections.emptySet(), Collections.emptyList(),\n      Collections.emptyList(), sonarUserHome.toString(), Map.of(), false, null, false, null)).join();\n    sloop.onExit().thenAccept(processExitValue -> this.exitValue = processExitValue);\n\n    shutdownRequested = true;\n    sloop.shutdown().join();\n\n    // it can take some time for the process to finish\n    await().atMost(Duration.ofSeconds(30)).untilAsserted(() -> assertThat(exitValue).isZero());\n  }\n\n  static class DummySonarLintRpcClient implements SonarLintRpcClientDelegate {\n    final Queue<LogParams> logs = new ConcurrentLinkedQueue<>();\n\n    public Queue<LogParams> getLogs() {\n      return logs;\n    }\n\n    @Override\n    public void suggestBinding(Map<String, List<BindingSuggestionDto>> suggestionsByConfigScope) {\n\n    }\n\n    @Override\n    public void suggestConnection(Map<String, List<ConnectionSuggestionDto>> suggestionsByConfigScope) {\n\n    }\n\n    @Override\n    public void openUrlInBrowser(URL url) {\n\n    }\n\n    @Override\n    public void showMessage(MessageType type, String text) {\n\n    }\n\n    @Override\n    public void log(LogParams params) {\n      logs.add(params);\n      var log = new StringBuilder();\n      log.append(\"[\").append(params.getThreadName()).append(\"] \");\n      log.append(params.getLevel()).append(\" \").append(params.getMessage());\n      if (params.getConfigScopeId() != null) {\n        log.append(\" [\").append(params.getConfigScopeId()).append(\"]\");\n      }\n      System.out.println(log);\n    }\n\n    @Override\n    public void showSoonUnsupportedMessage(ShowSoonUnsupportedMessageParams params) {\n\n    }\n\n    @Override\n    public void showSmartNotification(ShowSmartNotificationParams params) {\n\n    }\n\n    @Override\n    public String getClientLiveDescription() {\n      return \"\";\n    }\n\n    @Override\n    public void showHotspot(String configurationScopeId, HotspotDetailsDto hotspotDetails) {\n\n    }\n\n    @Override\n    public void showIssue(String configurationScopeId, IssueDetailsDto issueDetails) {\n\n    }\n\n    @Override\n    public void showFixSuggestion(String configurationScopeId, String issueKey, FixSuggestionDto fixSuggestion) {\n\n    }\n\n    @Override\n    public AssistCreatingConnectionResponse assistCreatingConnection(AssistCreatingConnectionParams params, SonarLintCancelChecker cancelChecker) throws CancellationException {\n      throw new CancellationException();\n    }\n\n    @Override\n    public AssistBindingResponse assistBinding(AssistBindingParams params, SonarLintCancelChecker cancelChecker) throws CancellationException {\n      throw new CancellationException();\n    }\n\n    @Override\n    public void startProgress(StartProgressParams params) throws UnsupportedOperationException {\n\n    }\n\n    @Override\n    public void reportProgress(ReportProgressParams params) {\n\n    }\n\n    @Override\n    public void didSynchronizeConfigurationScopes(Set<String> configurationScopeIds) {\n\n    }\n\n    @Override\n    public Either<TokenDto, UsernamePasswordDto> getCredentials(String connectionId) throws ConnectionNotFoundException {\n      throw new ConnectionNotFoundException();\n    }\n\n    @Override\n    public List<ProxyDto> selectProxies(URI uri) {\n      return List.of(ProxyDto.NO_PROXY);\n    }\n\n    @Override\n    public GetProxyPasswordAuthenticationResponse getProxyPasswordAuthentication(String host, int port, String protocol, String prompt, String scheme, URL targetHost) {\n      return new GetProxyPasswordAuthenticationResponse(null, null);\n    }\n\n    @Override\n    public boolean checkServerTrusted(List<X509CertificateDto> chain, String authType) {\n      return false;\n    }\n\n    @Override\n    public void didReceiveServerHotspotEvent(DidReceiveServerHotspotEvent params) {\n\n    }\n\n    @Override\n    public String matchSonarProjectBranch(String configurationScopeId, String mainBranchName, Set<String> allBranchesNames, SonarLintCancelChecker cancelChecker) {\n      return null;\n    }\n\n    @Override\n    public void didChangeMatchedSonarProjectBranch(String configScopeId, String newMatchedBranchName) {\n\n    }\n\n    @Override\n    public TelemetryClientLiveAttributesResponse getTelemetryLiveAttributes() {\n      System.err.println(\"Telemetry should be disabled in tests\");\n      throw new CancellationException(\"Telemetry should be disabled in tests\");\n    }\n\n    @Override\n    public void didChangeTaintVulnerabilities(String configurationScopeId, Set<UUID> closedTaintVulnerabilityIds, List<TaintVulnerabilityDto> addedTaintVulnerabilities,\n      List<TaintVulnerabilityDto> updatedTaintVulnerabilities) {\n    }\n\n    @Override\n    public List<ClientFileDto> listFiles(String configScopeId) {\n      return List.of();\n    }\n\n    @Override\n    public void noBindingSuggestionFound(NoBindingSuggestionFoundParams params) {\n\n    }\n\n    @Override\n    public void didChangeAnalysisReadiness(Set<String> configurationScopeIds, boolean areReadyForAnalysis) {\n\n    }\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/sloop/SloopLauncherWithJreTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.sloop;\n\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ExecutionException;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.rpc.client.Sloop;\nimport org.sonarsource.sonarlint.core.rpc.client.SloopLauncher;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.ClientConstantInfoDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.HttpConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.TelemetryClientConstantAttributesDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\nimport utils.PluginLocator;\n\nimport static mediumtest.sloop.UnArchiveUtils.unarchiveDistribution;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.PHP;\n\nclass SloopLauncherWithJreTests {\n  @TempDir\n  private static Path sonarUserHome;\n\n  @TempDir\n  private static Path unarchiveTmpDir;\n\n  private static Sloop sloop;\n  private static SonarLintRpcServer server;\n\n  private static SloopLauncherTests.DummySonarLintRpcClient client;\n\n  @BeforeAll\n  static void setup() {\n    var sloopDistPath = SystemUtils.IS_OS_WINDOWS ? SloopDistLocator.getWindowsDistPath() : SloopDistLocator.getLinux64DistPath();\n    var jrePath = SystemUtils.IS_OS_WINDOWS ? JreLocator.getWindowsJrePath() : JreLocator.getLinuxJrePath();\n    var sloopOutDirPath = unarchiveSloop(sloopDistPath);\n    client = new SloopLauncherTests.DummySonarLintRpcClient();\n    var sloopLauncher = new SloopLauncher(client);\n    sloop = sloopLauncher.start(sloopOutDirPath.toAbsolutePath(), jrePath.toAbsolutePath());\n    server = sloop.getRpcServer();\n  }\n\n  @AfterAll\n  static void tearDown() throws ExecutionException, InterruptedException {\n    sloop.shutdown().get();\n    var exitCode = sloop.onExit().join();\n    assertThat(exitCode).isZero();\n  }\n\n  @Test\n  void test_all_rules_returns() {\n    var telemetryInitDto = new TelemetryClientConstantAttributesDto(\"SonarLint ITs\", \"SonarLint ITs\",\n      \"1.2.3\", \"4.5.6\", Collections.emptyMap());\n    var clientInfo = new ClientConstantInfoDto(\"clientName\", \"integrationTests\");\n\n    server.initialize(new InitializeParams(clientInfo, telemetryInitDto, HttpConfigurationDto.defaultConfig(), null, Set.of(), sonarUserHome.resolve(\"storage\"), sonarUserHome.resolve(\"workDir\"),\n    Set.of(PluginLocator.getPhpPluginPath().toAbsolutePath()), Collections.emptyMap(), Set.of(PHP), Collections.emptySet(), Collections.emptySet(), Collections.emptyList(),\n      Collections.emptyList(), sonarUserHome.toString(), Map.of(), false, null, false, null)).join();\n\n    var result = server.getRulesService().listAllStandaloneRulesDefinitions().join();\n    assertThat(result.getRulesByKey()).hasSize(234);\n    var expectedJreLog = \"Using JRE from \" + (SystemUtils.IS_OS_WINDOWS ? JreLocator.getWindowsJrePath() : JreLocator.getLinuxJrePath());\n    assertThat(client.getLogs()).extracting(LogParams::getMessage).contains(expectedJreLog);\n  }\n\n  @NotNull\n  private static Path unarchiveSloop(Path sloopDistPath) {\n    var sloopOutDirPath = unarchiveTmpDir.resolve(\"sloopDistOut\");\n    unarchiveDistribution(sloopDistPath.toString(), sloopOutDirPath);\n    return sloopOutDirPath;\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/sloop/UnArchiveUtils.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.sloop;\n\nimport java.nio.file.Path;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.codehaus.plexus.archiver.tar.TarGZipUnArchiver;\nimport org.codehaus.plexus.archiver.zip.ZipUnArchiver;\nimport org.codehaus.plexus.components.io.fileselectors.FileSelector;\n\npublic class UnArchiveUtils {\n\n  public static void unarchiveDistribution(String inputFilePath, Path destionationPath, FileSelector[] fileSelectors) {\n    var unArchiver = SystemUtils.IS_OS_WINDOWS ? new ZipUnArchiver() : new TarGZipUnArchiver();\n    var outputDirectory = destionationPath.toFile();\n    outputDirectory.mkdirs();\n    var inputFile = Path.of(inputFilePath).toFile();\n    unArchiver.setSourceFile(inputFile);\n    unArchiver.setFileSelectors(fileSelectors);\n    unArchiver.setDestDirectory(outputDirectory);\n    unArchiver.extract();\n  }\n\n  public static void unarchiveDistribution(String inputFilePath, Path destionationPath) {\n    unarchiveDistribution(inputFilePath, destionationPath, new FileSelector[] {});\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/smartnotifications/SmartNotificationsMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.smartnotifications;\n\nimport java.time.ZonedDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.List;\nimport java.util.Set;\nimport mockwebserver3.MockResponse;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidRemoveConfigurationScopeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.smartnotification.ShowSmartNotificationParams;\nimport org.sonarsource.sonarlint.core.serverapi.UrlUtils;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport org.sonarsource.sonarlint.core.test.utils.server.websockets.WebSocketServer;\nimport utils.MockWebServerExtensionWithProtobuf;\n\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SERVER_SENT_EVENTS;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SMART_NOTIFICATIONS;\nimport static org.sonarsource.sonarlint.core.test.utils.server.ServerFixture.newSonarQubeServer;\n\nclass SmartNotificationsMediumTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private static final ZonedDateTime STORED_DATE = ZonedDateTime.now().minusHours(1);\n  private static final String PROJECT_KEY = \"projectKey\";\n  private static final String PROJECT_KEY_2 = \"projectKey2\";\n  private static final String PROJECT_KEY_3 = \"projectKey3\";\n  private static final String PROJECT_KEY_4 = \"projectKey4\";\n  private static final String CONNECTION_ID = \"connectionId\";\n  private static final String CONNECTION_ID_2 = \"connectionId2\";\n  private static final String DATETIME_FORMAT = \"yyyy-MM-dd'T'HH:mm:ssZ\";\n  private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(DATETIME_FORMAT);\n  private static final String EVENT_PROJECT_1 = \"{\\\"events\\\": [\" +\n    \"{\\\"message\\\": \\\"msg1\\\",\" +\n    \"\\\"link\\\": \\\"lnk\\\",\" +\n    \"\\\"project\\\": \\\"\" + PROJECT_KEY + \"\\\",\" +\n    \"\\\"date\\\": \\\"2022-01-01T08:00:00+0000\\\",\" +\n    \"\\\"category\\\": \\\"category\\\"}]}\";\n  private static final String EVENT_PROJECT_P2 = \"{\\\"events\\\": [\" +\n    \"{\\\"message\\\": \\\"msg2\\\",\" +\n    \"\\\"link\\\": \\\"lnk\\\",\" +\n    \"\\\"project\\\": \\\"\" + PROJECT_KEY_2 + \"\\\",\" +\n    \"\\\"date\\\": \\\"2022-01-01T08:00:00+0000\\\",\" +\n    \"\\\"category\\\": \\\"category\\\"}]}\";\n  private static final String THREE_EVENTS_P1_P3_P4 = \"{\\\"events\\\": [\" +\n    \"{\\\"message\\\": \\\"msg1\\\",\" +\n    \"\\\"link\\\": \\\"lnk\\\",\" +\n    \"\\\"project\\\": \\\"\" + PROJECT_KEY + \"\\\",\" +\n    \"\\\"date\\\": \\\"2022-01-01T08:00:00+0000\\\",\" +\n    \"\\\"category\\\": \\\"category\\\"},\"\n    + \"{\\\"message\\\": \\\"msg3\\\",\" +\n    \"\\\"link\\\": \\\"lnk\\\",\" +\n    \"\\\"project\\\": \\\"\" + PROJECT_KEY_3 + \"\\\",\" +\n    \"\\\"date\\\": \\\"2022-01-01T08:00:00+0000\\\",\" +\n    \"\\\"category\\\": \\\"category\\\"},\"\n    + \"{\\\"message\\\": \\\"msg4\\\",\" +\n    \"\\\"link\\\": \\\"lnk\\\",\" +\n    \"\\\"project\\\": \\\"\" + PROJECT_KEY_4 + \"\\\",\" +\n    \"\\\"date\\\": \\\"2022-01-01T08:00:00+0000\\\",\" +\n    \"\\\"category\\\": \\\"category\\\"}\"\n    + \"]}\";\n  private static final String NEW_ISSUES_EVENT = \"{\\n\" +\n    \"  \\\"event\\\": \\\"MyNewIssues\\\", \\n\" +\n    \"  \\\"data\\\": {\\n\" +\n    \"    \\\"message\\\": \\\"You have 3 new issues on project u0027SonarLint Coreu0027 on pull request u0027657u0027\\\",\\n\" +\n    \"    \\\"link\\\": \\\"link\\\",\\n\" +\n    \"    \\\"project\\\": \\\"\" + PROJECT_KEY + \"\\\",\\n\" +\n    \"    \\\"date\\\": \\\"2023-07-19T15:08:01+0000\\\"\\n\" +\n    \"  }\\n\" +\n    \"}\";\n  @RegisterExtension\n  private final MockWebServerExtensionWithProtobuf mockWebServerExtension = new MockWebServerExtensionWithProtobuf();\n  @RegisterExtension\n  private final MockWebServerExtensionWithProtobuf mockWebServerExtension2 = new MockWebServerExtensionWithProtobuf();\n\n  private WebSocketServer webSocketServer;\n\n  @AfterEach\n  void tearDown() {\n    if (webSocketServer != null) {\n      webSocketServer.stop();\n    }\n  }\n\n  @SonarLintTest\n  void it_should_send_notification_for_two_config_scope_with_same_binding(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    mockWebServerExtension.addResponse(\"/api/developers/search_events?projects=&from=\", new MockResponse.Builder().code(200).build());\n    mockWebServerExtension.addStringResponse(\"/api/developers/search_events?projects=\" + PROJECT_KEY + \"&from=\" +\n      UrlUtils.urlEncode(STORED_DATE.format(TIME_FORMATTER)), EVENT_PROJECT_1);\n\n    harness.newBackend()\n      .withSonarQubeConnectionAndNotifications(CONNECTION_ID, mockWebServerExtension.endpointParams().getBaseUrl(),\n        storage -> storage.withProject(PROJECT_KEY, project -> project.withLastSmartNotificationPoll(STORED_DATE)))\n      .withSonarQubeConnectionAndNotifications(CONNECTION_ID_2, mockWebServerExtension.endpointParams().getBaseUrl())\n      .withBoundConfigScope(\"scopeId\", CONNECTION_ID, PROJECT_KEY)\n      .withBoundConfigScope(\"scopeId2\", CONNECTION_ID, PROJECT_KEY)\n      .withBackendCapability(SMART_NOTIFICATIONS)\n      .start(fakeClient);\n\n    await().atMost(3, SECONDS).until(() -> !fakeClient.getSmartNotificationsToShow().isEmpty());\n\n    var notificationsResult = fakeClient.getSmartNotificationsToShow();\n    assertThat(notificationsResult).hasSize(1);\n    assertThat(notificationsResult.element().getScopeIds()).hasSize(2).containsExactlyInAnyOrder(\"scopeId\", \"scopeId2\");\n  }\n\n  @SonarLintTest\n  void it_should_send_notification_for_two_config_scope_with_inherited_binding(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    mockWebServerExtension.addResponse(\"/api/developers/search_events?projects=&from=\", new MockResponse.Builder().code(200).build());\n    mockWebServerExtension.addStringResponse(\"/api/developers/search_events?projects=\" + PROJECT_KEY + \"&from=\" +\n      UrlUtils.urlEncode(STORED_DATE.format(TIME_FORMATTER)), EVENT_PROJECT_1);\n\n    harness.newBackend()\n      .withSonarQubeConnectionAndNotifications(CONNECTION_ID, mockWebServerExtension.endpointParams().getBaseUrl(),\n        storage -> storage.withProject(PROJECT_KEY, project -> project.withLastSmartNotificationPoll(STORED_DATE)))\n      .withSonarQubeConnectionAndNotifications(CONNECTION_ID_2, mockWebServerExtension.endpointParams().getBaseUrl())\n      .withBoundConfigScope(\"parentScopeId\", CONNECTION_ID, PROJECT_KEY)\n      .withChildConfigScope(\"childScopeId\", \"parentScopeId\")\n      .withBackendCapability(SMART_NOTIFICATIONS)\n      .start(fakeClient);\n\n    await().atMost(3, SECONDS).until(() -> !fakeClient.getSmartNotificationsToShow().isEmpty());\n\n    var notificationsResult = fakeClient.getSmartNotificationsToShow();\n    assertThat(notificationsResult).hasSize(1);\n    assertThat(notificationsResult.element().getScopeIds()).hasSize(2).containsExactlyInAnyOrder(\"parentScopeId\", \"childScopeId\");\n  }\n\n  @SonarLintTest\n  void it_should_send_notification_for_different_bindings(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    mockWebServerExtension.addResponse(\"/api/developers/search_events?projects=&from=\", new MockResponse.Builder().code(200).build());\n    mockWebServerExtension2.addResponse(\"/api/developers/search_events?projects=&from=\", new MockResponse.Builder().code(200).build());\n    var timestamp = UrlUtils.urlEncode(STORED_DATE.format(TIME_FORMATTER));\n    mockWebServerExtension.addStringResponse(\"/api/developers/search_events?projects=\" + PROJECT_KEY + \",\" + PROJECT_KEY_3 + \",\" + PROJECT_KEY_4 + \"&from=\" +\n      timestamp + \",\" + timestamp + \",\" + timestamp, THREE_EVENTS_P1_P3_P4);\n    mockWebServerExtension2.addStringResponse(\"/api/developers/search_events?projects=\" + PROJECT_KEY_2 + \",\" + PROJECT_KEY_4 + \"&from=\" +\n      timestamp + \",\" + timestamp, EVENT_PROJECT_P2);\n\n    harness.newBackend()\n      .withSonarQubeConnectionAndNotifications(CONNECTION_ID, mockWebServerExtension.endpointParams().getBaseUrl(),\n        storage -> storage\n          .withProject(PROJECT_KEY, project -> project.withLastSmartNotificationPoll(STORED_DATE))\n          .withProject(PROJECT_KEY_3, project -> project.withLastSmartNotificationPoll(STORED_DATE))\n          .withProject(PROJECT_KEY_4, project -> project.withLastSmartNotificationPoll(STORED_DATE)))\n      .withSonarQubeConnectionAndNotifications(CONNECTION_ID_2, mockWebServerExtension2.endpointParams().getBaseUrl(),\n        storage -> storage\n          .withProject(PROJECT_KEY_2, project -> project.withLastSmartNotificationPoll(STORED_DATE))\n          .withProject(PROJECT_KEY_4, project -> project.withLastSmartNotificationPoll(STORED_DATE)))\n      .withBoundConfigScope(\"scopeId\", CONNECTION_ID, PROJECT_KEY)\n      .withBoundConfigScope(\"scopeId2\", CONNECTION_ID, PROJECT_KEY)\n      .withBoundConfigScope(\"scopeId3\", CONNECTION_ID_2, PROJECT_KEY_2)\n      .withBoundConfigScope(\"scopeId4\", CONNECTION_ID, PROJECT_KEY_3)\n      // We have two bindings with the same project key, but on different connection, so it might be considered as different projects\n      .withBoundConfigScope(\"scopeId5\", CONNECTION_ID, PROJECT_KEY_4)\n      .withBoundConfigScope(\"scopeId6\", CONNECTION_ID_2, PROJECT_KEY_4)\n      .withBackendCapability(SMART_NOTIFICATIONS)\n      .start(fakeClient);\n\n    await().atMost(3, SECONDS).untilAsserted(() -> assertThat(fakeClient.getSmartNotificationsToShow()).hasSize(4));\n\n    var notificationsResult = fakeClient.getSmartNotificationsToShow();\n    assertThat(notificationsResult)\n      .extracting(ShowSmartNotificationParams::getConnectionId, ShowSmartNotificationParams::getScopeIds, ShowSmartNotificationParams::getText)\n      .containsExactlyInAnyOrder(\n        tuple(\"connectionId\", Set.of(\"scopeId\", \"scopeId2\"), \"msg1\"),\n        tuple(\"connectionId\", Set.of(\"scopeId4\"), \"msg3\"),\n        tuple(\"connectionId\", Set.of(\"scopeId5\"), \"msg4\"),\n        tuple(\"connectionId2\", Set.of(\"scopeId3\"), \"msg2\")\n      );\n  }\n\n  @SonarLintTest\n  void it_should_not_send_notification_with_unbound_config_scope(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    mockWebServerExtension.addResponse(\"/api/developers/search_events?projects=&from=\", new MockResponse.Builder().code(200).build());\n    mockWebServerExtension.addStringResponse(\"/api/developers/search_events?projects=\" + PROJECT_KEY + \"&from=\" +\n      UrlUtils.urlEncode(STORED_DATE.format(TIME_FORMATTER)), EVENT_PROJECT_1);\n\n    harness.newBackend()\n      .withSonarQubeConnectionAndNotifications(CONNECTION_ID, mockWebServerExtension.endpointParams().getBaseUrl(),\n        storage -> storage.withProject(PROJECT_KEY, project -> project.withLastSmartNotificationPoll(STORED_DATE)))\n      .withUnboundConfigScope(\"scopeId\")\n      .withBoundConfigScope(\"scopeId2\", CONNECTION_ID, PROJECT_KEY)\n      .withBackendCapability(SMART_NOTIFICATIONS)\n      .start(fakeClient);\n\n    await().atMost(3, SECONDS).until(() -> !fakeClient.getSmartNotificationsToShow().isEmpty());\n\n    var notificationsResult = fakeClient.getSmartNotificationsToShow();\n    assertThat(notificationsResult).hasSize(1);\n    assertThat(notificationsResult.element().getScopeIds()).hasSize(1).contains(\"scopeId2\");\n  }\n\n  @SonarLintTest\n  void it_should_send_notification_after_adding_removing_binding(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    mockWebServerExtension.addResponse(\"/api/developers/search_events?projects=&from=\", new MockResponse.Builder().code(200).build());\n    mockWebServerExtension.addStringResponse(\"/api/developers/search_events?projects=\" + PROJECT_KEY + \"&from=\" +\n      UrlUtils.urlEncode(STORED_DATE.format(TIME_FORMATTER)), EVENT_PROJECT_1);\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnectionAndNotifications(CONNECTION_ID, mockWebServerExtension.endpointParams().getBaseUrl(),\n        storage -> storage.withProject(PROJECT_KEY, project -> project.withLastSmartNotificationPoll(STORED_DATE)))\n      .withBoundConfigScope(\"scopeId\", CONNECTION_ID, PROJECT_KEY)\n      .withBackendCapability(SMART_NOTIFICATIONS)\n      .start(fakeClient);\n\n    backend.getConfigurationService().didRemoveConfigurationScope(new DidRemoveConfigurationScopeParams(\"scopeId\"));\n\n    backend.getConfigurationService()\n      .didAddConfigurationScopes(\n        new DidAddConfigurationScopesParams(List.of(\n          new ConfigurationScopeDto(\"scopeId\", null, true, \"sonarlint-core\",\n            new BindingConfigurationDto(CONNECTION_ID, PROJECT_KEY, false)))));\n\n    await().atMost(3, SECONDS).until(() -> !fakeClient.getSmartNotificationsToShow().isEmpty());\n\n    var notificationsResult = fakeClient.getSmartNotificationsToShow();\n    assertThat(notificationsResult).hasSize(1);\n    assertThat(notificationsResult.element().getScopeIds()).hasSize(1).contains(\"scopeId\");\n  }\n\n  @SonarLintTest\n  void it_should_send_notification_handled_by_sonarcloud_websocket_as_fallback(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    mockWebServerExtension.addResponse(\"/api/developers/search_events?projects=&from=\", new MockResponse.Builder().code(200).build());\n    mockWebServerExtension.addStringResponse(\"/api/developers/search_events?projects=\" + PROJECT_KEY + \"&from=\" +\n      UrlUtils.urlEncode(STORED_DATE.format(TIME_FORMATTER)), EVENT_PROJECT_1);\n\n    harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(mockWebServerExtension.endpointParams().getBaseUrl())\n      .withSonarCloudConnectionAndNotifications(CONNECTION_ID, \"myOrg\", storage -> storage.withProject(PROJECT_KEY, project -> project.withLastSmartNotificationPoll(STORED_DATE)))\n      .withBoundConfigScope(\"scopeId\", CONNECTION_ID, PROJECT_KEY)\n      .withBackendCapability(SMART_NOTIFICATIONS)\n      .start(fakeClient);\n\n    await().atMost(3, SECONDS).until(() -> !fakeClient.getSmartNotificationsToShow().isEmpty());\n\n    var notificationsResult = fakeClient.getSmartNotificationsToShow();\n    assertThat(notificationsResult).hasSize(1);\n    assertThat(notificationsResult.element().getScopeIds()).hasSize(1).contains(\"scopeId\");\n  }\n\n  @SonarLintTest\n  void it_should_skip_polling_notifications_when_sonarcloud_websocket_opened(SonarLintTestHarness harness) {\n    webSocketServer = new WebSocketServer();\n    webSocketServer.start();\n    var fakeClient = harness.newFakeClient().withToken(CONNECTION_ID, \"token\")\n      .build();\n    mockWebServerExtension.addResponse(\"/api/developers/search_events?projects=&from=\", new MockResponse.Builder().code(200).build());\n    mockWebServerExtension.addStringResponse(\"/api/developers/search_events?projects=\" + PROJECT_KEY + \"&from=\" +\n      UrlUtils.urlEncode(STORED_DATE.format(TIME_FORMATTER)), EVENT_PROJECT_1);\n\n    harness.newBackend()\n      .withSonarQubeCloudEuRegionUri(mockWebServerExtension.endpointParams().getBaseUrl())\n      .withSonarQubeCloudEuRegionWebSocketUri(webSocketServer.getUrl())\n      .withSonarCloudConnectionAndNotifications(CONNECTION_ID, \"myOrg\", storage -> storage.withProject(PROJECT_KEY, project -> project.withLastSmartNotificationPoll(STORED_DATE)))\n      .withBoundConfigScope(\"scopeId\", CONNECTION_ID, PROJECT_KEY)\n      .withBackendCapability(SMART_NOTIFICATIONS, SERVER_SENT_EVENTS)\n      .start(fakeClient);\n\n    await().atMost(2, SECONDS).until(() -> webSocketServer.getConnections().size() == 1);\n\n    var notificationsResult = fakeClient.getSmartNotificationsToShow();\n    assertThat(notificationsResult).isEmpty();\n\n    webSocketServer.getConnections().get(0).sendMessage(NEW_ISSUES_EVENT);\n\n    await().atMost(5, SECONDS).untilAsserted(() -> assertThat(fakeClient.getSmartNotificationsToShow()).isNotEmpty());\n\n    notificationsResult = fakeClient.getSmartNotificationsToShow();\n    assertThat(notificationsResult).hasSize(1);\n    assertThat(notificationsResult.element().getScopeIds()).hasSize(1).contains(\"scopeId\");\n  }\n\n  @SonarLintTest\n  void it_should_send_sonarqube_notification(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient().build();\n    mockWebServerExtension.addResponse(\"/api/developers/search_events?projects=&from=\", new MockResponse.Builder().code(200).build());\n    mockWebServerExtension.addStringResponse(\"/api/developers/search_events?projects=\" + PROJECT_KEY + \"&from=\" +\n      UrlUtils.urlEncode(STORED_DATE.format(TIME_FORMATTER)), EVENT_PROJECT_1);\n\n    harness.newBackend()\n      .withSonarQubeConnectionAndNotifications(CONNECTION_ID, mockWebServerExtension.endpointParams().getBaseUrl(),\n        storage -> storage.withProject(PROJECT_KEY, project -> project.withLastSmartNotificationPoll(STORED_DATE)))\n      .withBoundConfigScope(\"scopeId\", CONNECTION_ID, PROJECT_KEY)\n      .withBackendCapability(SMART_NOTIFICATIONS)\n      .start(fakeClient);\n\n    await().atMost(3, SECONDS).until(() -> !fakeClient.getSmartNotificationsToShow().isEmpty());\n\n    var notificationsResult = fakeClient.getSmartNotificationsToShow();\n    assertThat(notificationsResult).hasSize(1);\n    assertThat(notificationsResult.element().getScopeIds()).hasSize(1).contains(\"scopeId\");\n  }\n\n  @SonarLintTest\n  void it_should_not_fail_on_pull_notifications_during_sync(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n    var server = newSonarQubeServer()\n      .withProject(PROJECT_KEY, project -> project.withBranch(\"master\"))\n      .withSmartNotifications(List.of(PROJECT_KEY), EVENT_PROJECT_1)\n      .start();\n    harness.newBackend()\n      .withSonarQubeConnectionAndNotifications(CONNECTION_ID, server.baseUrl(),\n        storage -> storage\n          .withProject(PROJECT_KEY, project -> project\n            .withLastSmartNotificationPoll(STORED_DATE)\n            .shouldThrowOnReadLastEvenPollingTime()))\n      .withBoundConfigScope(\"scopeId\", CONNECTION_ID, PROJECT_KEY)\n      .withBackendCapability(FULL_SYNCHRONIZATION, SMART_NOTIFICATIONS)\n      .start(client);\n    client.waitForSynchronization();\n\n    assertDoesNotThrow(client::getSmartNotificationsToShow);\n\n    await().untilAsserted(() -> assertThat(client.getLogMessages())\n      .anyMatch(log -> log.startsWith(\"Couldn't access storage to read and update last event polling:\")));\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/sonarcodecontext/SonarCodeContextMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.sonarcodecontext;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.attribute.PosixFilePermission;\nimport java.util.List;\nimport java.util.Set;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.condition.DisabledOnOs;\nimport org.junit.jupiter.api.condition.OS;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport uk.org.webcompere.systemstubs.environment.EnvironmentVariables;\nimport uk.org.webcompere.systemstubs.jupiter.SystemStub;\nimport uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension;\nimport utils.TestPlugin;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.sonarsource.sonarlint.core.commons.dogfood.DogfoodEnvironmentDetectionService.SONARSOURCE_DOGFOODING_ENV_VAR_KEY;\n\n@ExtendWith(SystemStubsExtension.class)\nclass SonarCodeContextMediumTests {\n\n  private static final String CONFIG_SCOPE_ID = \"CONFIG_SCOPE_ID\";\n  private static final String CONNECTION_ID = \"connectionId\";\n  private static final String PROJECT_KEY = \"projectKey\";\n\n  @SystemStub\n  private EnvironmentVariables environmentVariables;\n\n  @BeforeEach\n  void clearDogfoodFlag() {\n    environmentVariables.remove(SONARSOURCE_DOGFOODING_ENV_VAR_KEY);\n  }\n\n  @AfterEach\n  void cleanUp() {\n    environmentVariables.remove(SONARSOURCE_DOGFOODING_ENV_VAR_KEY);\n  }\n\n  @SonarLintTest\n  // Relies on bash script\n  @DisabledOnOs(OS.WINDOWS)\n  void should_regenerate_on_binding_change(SonarLintTestHarness harness, @TempDir Path baseDir, @TempDir Path binDir)\n    throws IOException {\n    var cliPath = createFakeCli(binDir);\n    System.setProperty(\"sonar.code.context.executable\", cliPath.toString());\n    environmentVariables.set(SONARSOURCE_DOGFOODING_ENV_VAR_KEY, \"1\");\n\n    var filePath = baseDir.resolve(\"Foo.java\");\n    Files.writeString(filePath, \"public class Foo {}\", UTF_8);\n    var fileDto = new ClientFileDto(filePath.toUri(), baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true);\n\n    var client = harness.newFakeClient()\n      .withToken(CONNECTION_ID, \"token\")\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(fileDto))\n      .build();\n\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY)\n      .start();\n\n    var backend = harness.newBackend()\n      .withBackendCapability(BackendCapability.CONTEXT_GENERATION)\n      .withSonarQubeConnection(CONNECTION_ID, server, storage -> storage\n        .withPlugin(TestPlugin.JAVA)\n        .withProject(PROJECT_KEY, p -> p.withMainBranch(\"main\")))\n      .start(client);\n\n    // Initial add triggers generation\n    backend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(\n      new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, CONFIG_SCOPE_ID,\n        new BindingConfigurationDto(CONNECTION_ID, PROJECT_KEY, true)))));\n\n    var sonarMd = baseDir.resolve(\".sonar-code-context\").resolve(\"SONAR.md\");\n    await().untilAsserted(() -> assertThat(Files.exists(sonarMd)).isTrue());\n\n    // Remove SONAR.md to verify it is re-generated on binding change\n    Files.deleteIfExists(sonarMd);\n    assertThat(Files.exists(sonarMd)).isFalse();\n\n    var newProjectKey = PROJECT_KEY + \"-updated\";\n    backend.getConfigurationService().didUpdateBinding(new DidUpdateBindingParams(CONFIG_SCOPE_ID,\n      new BindingConfigurationDto(CONNECTION_ID, newProjectKey, true), null, null));\n\n    await().untilAsserted(() -> assertThat(Files.exists(sonarMd)).isTrue());\n  }\n\n  @SonarLintTest\n  // Relies on bash script\n  @DisabledOnOs(OS.WINDOWS)\n  void should_generate_sonar_md_and_mdc_on_bound_scope_when_dogfooding(SonarLintTestHarness harness, @TempDir Path baseDir, @TempDir Path binDir)\n    throws IOException {\n    // Arrange PATH with a fake 'sonar-code-context' CLI\n    var cliPath = createFakeCli(binDir);\n    // Force the service to use our fake CLI\n    System.setProperty(\"sonar.code.context.executable\", cliPath.toString());\n    environmentVariables.set(SONARSOURCE_DOGFOODING_ENV_VAR_KEY, \"1\");\n\n    var filePath = baseDir.resolve(\"Foo.java\");\n    Files.writeString(filePath, \"public class Foo {}\", UTF_8);\n    var fileDto = new ClientFileDto(filePath.toUri(), baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true);\n\n    var client = harness.newFakeClient()\n      .withToken(CONNECTION_ID, \"token\")\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(fileDto))\n      .build();\n\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY)\n      .start();\n\n    var backend = harness.newBackend()\n      .withBackendCapability(BackendCapability.CONTEXT_GENERATION)\n      .withSonarQubeConnection(CONNECTION_ID, server, storage -> storage\n        .withPlugin(TestPlugin.JAVA)\n        .withProject(PROJECT_KEY, p -> p.withMainBranch(\"main\")))\n      .start(client);\n\n    // Add a bound configuration scope (triggers the event listener)\n    backend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(\n      new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, CONFIG_SCOPE_ID,\n        new BindingConfigurationDto(CONNECTION_ID, PROJECT_KEY, true)))));\n\n    var sonarMd = baseDir.resolve(\".sonar-code-context\").resolve(\"SONAR.md\");\n    var mdc = baseDir.resolve(\".cursor\").resolve(\"rules\").resolve(\"sonar-code-context.mdc\");\n    await().untilAsserted(() -> assertThat(Files.exists(sonarMd)).isTrue());\n    await().untilAsserted(() -> assertThat(Files.exists(mdc)).isTrue());\n    var mdContent = Files.readString(sonarMd);\n    assertThat(mdContent).contains(\"SONAR.md\");\n    var mdcContent = Files.readString(mdc);\n    assertThat(mdcContent).contains(\"sonar-code-context.mdc\");\n    assertThat(Files.isExecutable(cliPath)).isTrue();\n  }\n\n  @SonarLintTest\n  void should_not_generate_files_when_not_dogfooding(SonarLintTestHarness harness, @TempDir Path baseDir, @TempDir Path binDir)\n    throws IOException {\n    // Arrange PATH with a fake 'sonar-code-context' CLI, but do not set dogfooding flag\n    var cliPath = createFakeCli(binDir);\n    System.setProperty(\"sonar.code.context.executable\", cliPath.toString());\n\n    var filePath = baseDir.resolve(\"Foo.java\");\n    Files.writeString(filePath, \"public class Foo {}\", UTF_8);\n    var fileDto = new ClientFileDto(filePath.toUri(), baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true);\n\n    var client = harness.newFakeClient()\n      .withToken(CONNECTION_ID, \"token\")\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(fileDto))\n      .build();\n\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY)\n      .start();\n\n    var backend = harness.newBackend()\n      .withBackendCapability(BackendCapability.CONTEXT_GENERATION)\n      .withSonarQubeConnection(CONNECTION_ID, server, storage -> storage\n        .withPlugin(TestPlugin.JAVA)\n        .withProject(PROJECT_KEY, p -> p.withMainBranch(\"main\")))\n      .start(client);\n\n    backend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(\n      new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, CONFIG_SCOPE_ID,\n        new BindingConfigurationDto(CONNECTION_ID, PROJECT_KEY, true)))));\n\n    var sonarMd = baseDir.resolve(\".sonar-code-context\").resolve(\"SONAR.md\");\n    var mdc = baseDir.resolve(\".cursor\").resolve(\"rules\").resolve(\"sonar-code-context.mdc\");\n    await().during(java.time.Duration.ofMillis(300)).untilAsserted(() -> {\n      assertThat(Files.exists(sonarMd)).isFalse();\n      assertThat(Files.exists(mdc)).isFalse();\n    });\n  }\n\n  @SonarLintTest\n  void should_not_generate_when_dogfood_enabled_but_capability_missing(SonarLintTestHarness harness, @TempDir Path baseDir, @TempDir Path binDir)\n    throws IOException {\n    var cliPath = createFakeCli(binDir);\n    System.setProperty(\"sonar.code.context.executable\", cliPath.toString());\n    environmentVariables.set(SONARSOURCE_DOGFOODING_ENV_VAR_KEY, \"1\");\n\n    var filePath = baseDir.resolve(\"Foo.java\");\n    Files.writeString(filePath, \"public class Foo {}\", UTF_8);\n    var fileDto = new ClientFileDto(filePath.toUri(), baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true);\n\n    var client = harness.newFakeClient()\n      .withToken(CONNECTION_ID, \"token\")\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(fileDto))\n      .build();\n\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(PROJECT_KEY)\n      .start();\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server, storage -> storage\n        .withPlugin(TestPlugin.JAVA)\n        .withProject(PROJECT_KEY, p -> p.withMainBranch(\"main\")))\n      .start(client);\n\n    backend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(\n      new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, CONFIG_SCOPE_ID,\n        new BindingConfigurationDto(CONNECTION_ID, PROJECT_KEY, true)))));\n\n    var sonarMd = baseDir.resolve(\".sonar-code-context\").resolve(\"SONAR.md\");\n    var mdc = baseDir.resolve(\".cursor\").resolve(\"rules\").resolve(\"sonar-code-context.mdc\");\n    await().during(java.time.Duration.ofMillis(300)).untilAsserted(() -> {\n      assertThat(Files.exists(sonarMd)).isFalse();\n      assertThat(Files.exists(mdc)).isFalse();\n    });\n  }\n\n  private Path createFakeCli(Path binDir) throws IOException {\n    Files.createDirectories(binDir);\n    var cli = binDir.resolve(\"sonar-code-context\");\n    var content = \"\"\"\n      #!/usr/bin/env bash\n      set -e\n      cmd=\"$1\"\n      shift\n      mkdir -p .sonar-code-context\n      if [ \"$cmd\" = \"init\" ]; then\n        echo '{\"version\":1}' > .sonar-code-context/settings.json\n      elif [ \"$cmd\" = \"generate-md-guidelines\" ]; then\n        echo \"SONAR_GUIDELINES generated $*\" > .sonar-code-context/SONAR_GUIDELINES.md\n      elif [ \"$cmd\" = \"merge-md\" ]; then\n        echo \"SONAR.md merged\" > .sonar-code-context/SONAR.md\n      elif [ \"$cmd\" = \"install\" ]; then\n        mkdir -p .cursor/rules\n        echo \"sonar-code-context.mdc generated $*\" > .cursor/rules/sonar-code-context.mdc\n      fi\"\"\";\n    Files.writeString(cli, content, UTF_8);\n    try {\n      Files.setPosixFilePermissions(cli, Set.of(PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE));\n    } catch (UnsupportedOperationException e) {\n      // On non-POSIX FS (e.g., Windows CI), fallback to default and hope exec works via PATHEXT\n    }\n    return cli;\n  }\n\n}\n\n\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/synchronization/BranchSpecificSynchronizationMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.synchronization;\n\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.UUID;\nimport org.assertj.core.api.Condition;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.branch.GetMatchedSonarProjectBranchParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.newcode.GetNewCodeDefinitionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintBackendFixture.FakeSonarLintRpcClient.ProgressReport;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintBackendFixture.FakeSonarLintRpcClient.ProgressStep;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static java.util.concurrent.TimeUnit.MINUTES;\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.waitAtMost;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.after;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.reset;\nimport static org.mockito.Mockito.verify;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\n\nclass BranchSpecificSynchronizationMediumTests {\n\n  @SonarLintTest\n  void it_should_automatically_synchronize_bound_projects_that_have_an_active_branch(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer(\"9.9\")\n      .withProject(\"projectKey\",\n        project -> project.withBranch(\"main\",\n          branch -> branch.withIssue(\"key\", \"ruleKey\", \"msg\", \"author\", \"file/path\", \"REVIEWED\", \"SAFE\", Instant.now(), new TextRange(1, 0, 3, 4))\n            .withSourceFile(\"projectKey:file/path\", sourceFile -> sourceFile.withCode(\"source\\ncode\\nfile\"))))\n      .start();\n\n    var client = harness.newFakeClient().withMatchedBranch(\"configScopeId\", \"branchName\").build();\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n\n    waitAtMost(3, SECONDS)\n      .untilAsserted(() -> assertThat(backend.getNewCodeService().getNewCodeDefinition(new GetNewCodeDefinitionParams(\"configScopeId\"))).succeedsWithin(1, MINUTES));\n  }\n\n  @SonarLintTest\n  void it_should_honor_binding_inheritance(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer(\"9.9\")\n      .withProject(\"projectKey\",\n        project -> project\n          .withBranch(\"branchNameParent\",\n            branch -> branch.withIssue(\"keyParent\", \"ruleKey\", \"msg\", \"author\", \"file/path\", \"REVIEWED\", \"SAFE\", Instant.now(), new TextRange(1, 0, 3, 4))\n              .withSourceFile(\"projectKey:file/path\", sourceFile -> sourceFile.withCode(\"source\\ncode\\nfile\")))\n          .withBranch(\"branchNameChild\",\n            branch -> branch.withIssue(\"keyChild\", \"ruleKey\", \"msg\", \"author\", \"file/path\", \"REVIEWED\", \"SAFE\", Instant.now(), new TextRange(1, 0, 3, 4))\n              .withSourceFile(\"projectKey:file/path\", sourceFile -> sourceFile.withCode(\"source\\ncode\\nfile\"))))\n      .start();\n\n    var client = harness.newFakeClient()\n      .withMatchedBranch(\"parentScope\", \"branchNameParent\")\n      .withMatchedBranch(\"childScope\", \"branchNameChild\")\n      .build();\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n\n    backend.getConfigurationService().didAddConfigurationScopes(\n      new DidAddConfigurationScopesParams(List.of(\n        new ConfigurationScopeDto(\"parentScope\", null, true, \"Parent\", new BindingConfigurationDto(\"connectionId\", \"projectKey\", true)),\n        new ConfigurationScopeDto(\"childScope\", \"parentScope\", true, \"Child\", new BindingConfigurationDto(null, null, true)))));\n\n    waitAtMost(3, SECONDS).untilAsserted(() -> {\n      assertThat(client.getLogs()).extracting(LogParams::getMessage).contains(\n        \"Matching Sonar project branch\",\n        \"Matched Sonar project branch for configuration scope 'parentScope' changed from 'null' to 'branchNameParent'\",\n        \"Matching Sonar project branch\",\n        \"Matched Sonar project branch for configuration scope 'childScope' changed from 'null' to 'branchNameChild'\",\n        \"[SYNC] Synchronizing issues for project 'projectKey' on branch 'branchNameParent'\",\n        \"[SYNC] Synchronizing issues for project 'projectKey' on branch 'branchNameChild'\");\n    });\n\n    assertThat(backend.getSonarProjectBranchService().getMatchedSonarProjectBranch(new GetMatchedSonarProjectBranchParams(\"parentScope\")))\n      .succeedsWithin(1, MINUTES)\n      .matches(response -> \"branchNameParent\".equals(response.getMatchedSonarProjectBranch()));\n    assertThat(backend.getSonarProjectBranchService().getMatchedSonarProjectBranch(new GetMatchedSonarProjectBranchParams(\"childScope\")))\n      .succeedsWithin(1, MINUTES)\n      .matches(response -> \"branchNameChild\".equals(response.getMatchedSonarProjectBranch()));\n  }\n\n  @SonarLintTest\n  void it_should_report_progress_to_the_client_when_synchronizing(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient()\n      .build();\n    var server = harness.newFakeSonarQubeServer(\"9.9\")\n      .withProject(\"projectKey\")\n      .withProject(\"projectKey2\")\n      .start();\n    harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withBoundConfigScope(\"configScopeId2\", \"connectionId\", \"projectKey2\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(fakeClient);\n\n    fakeClient.waitForSynchronization();\n\n    waitAtMost(3, SECONDS).untilAsserted(() -> assertThat(fakeClient.getProgressReportsByTaskId())\n      .hasKeySatisfying(isUUID())\n      .containsValue(new ProgressReport(null, \"Synchronizing projects...\", null, false, false,\n        List.of(\n          new ProgressStep(\"Synchronizing with 'connectionId'...\", 0),\n          new ProgressStep(\"Synchronizing project 'projectKey'...\", 0),\n          new ProgressStep(\"Synchronizing project 'projectKey2'...\", 50)),\n        true)));\n  }\n\n  @SonarLintTest\n  void it_should_not_report_progress_to_the_client_when_synchronizing_if_client_rejects_progress(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient()\n      .build();\n    doThrow(new UnsupportedOperationException(\"Failed to start progress\"))\n      .when(fakeClient)\n      .startProgress(any());\n\n    var server = harness.newFakeSonarQubeServer(\"9.9\")\n      .withProject(\"projectKey\")\n      .withProject(\"projectKey2\")\n      .start();\n    harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withBoundConfigScope(\"configScopeId2\", \"connectionId\", \"projectKey2\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(fakeClient);\n    fakeClient.waitForSynchronization();\n\n    waitAtMost(3, SECONDS).untilAsserted(() -> {\n      assertThat(fakeClient.getSynchronizedConfigScopeIds()).contains(\"configScopeId\");\n      assertThat(fakeClient.getProgressReportsByTaskId()).isEmpty();\n    });\n  }\n\n  @SonarLintTest\n  void it_should_skip_second_consecutive_synchronization_for_the_same_server_project(SonarLintTestHarness harness) {\n    var fakeClient = harness.newFakeClient()\n      .build();\n    var server = harness.newFakeSonarQubeServer(\"9.9\")\n      .withProject(\"projectKey\")\n      .withProject(\"projectKey2\")\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(fakeClient);\n    fakeClient.waitForSynchronization();\n    reset(fakeClient);\n\n    backend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(\n      new ConfigurationScopeDto(\"configScope2\", null, true, \"Child1\", new BindingConfigurationDto(\"connectionId\", \"projectKey\", true)))));\n\n    verify(fakeClient, after(2000).times(0)).didSynchronizeConfigurationScopes(any());\n  }\n\n  private static Condition<String> isUUID() {\n    return new Condition<>() {\n      public boolean matches(String value) {\n        try {\n          UUID.fromString(value);\n          return true;\n        } catch (Exception e) {\n          return false;\n        }\n      }\n    };\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/synchronization/ConnectionSyncMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.synchronization;\n\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.List;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidChangeCredentialsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidUpdateConnectionsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarQubeConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.EffectiveRuleDetailsDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.GetEffectiveRuleDetailsParams;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.get;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.PROJECT_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JAVA;\nimport static org.sonarsource.sonarlint.core.test.utils.SonarLintBackendFixture.newBackend;\nimport static org.sonarsource.sonarlint.core.test.utils.SonarLintBackendFixture.newFakeClient;\nimport static org.sonarsource.sonarlint.core.test.utils.server.ServerFixture.newSonarQubeServer;\n\nclass ConnectionSyncMediumTests {\n  public static final String CONNECTION_ID = \"connectionId\";\n  public static final String SCOPE_ID = \"scopeId\";\n\n  @SonarLintTest\n  void it_should_cache_extracted_rule_metadata_per_connection(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient()\n      .withToken(CONNECTION_ID, \"token\")\n      .build();\n\n    var server = harness.newFakeSonarQubeServer().withPlugin(TestPlugin.JAVA).start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server, storage -> storage.withPlugin(TestPlugin.JAVA))\n      .withBoundConfigScope(SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .withEnabledLanguageInStandaloneMode(JAVA)\n      .start(client);\n    await().untilAsserted(() -> assertThat(client.getLogMessages()).contains(\"Binding suggestion computation queued for config scopes 'scopeId'...\"));\n\n    assertThat(client.getLogMessages()).doesNotContain(\"Extracting rules metadata for connection 'connectionId'\");\n\n    // Trigger lazy initialization of the rules metadata\n    getEffectiveRuleDetails(backend, SCOPE_ID, \"java:S106\");\n    await().untilAsserted(() -> assertThat(client.getLogMessages()).contains(\"Extracting rules metadata for connection 'connectionId'\"));\n\n    // Second call should not trigger init as results are already cached\n    client.clearLogs();\n\n    getEffectiveRuleDetails(backend, SCOPE_ID, \"java:S106\");\n  }\n\n  @SonarLintTest\n  void it_should_evict_cache_when_connection_is_removed(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient()\n      .withToken(CONNECTION_ID, \"token\")\n      .build();\n\n    var server = harness.newFakeSonarQubeServer().withPlugin(TestPlugin.JAVA).start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server, storage -> storage.withPlugin(TestPlugin.JAVA))\n      .withBoundConfigScope(SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .withEnabledLanguageInStandaloneMode(JAVA)\n      .start(client);\n    await().untilAsserted(() -> assertThat(client.getLogMessages()).contains(\"Binding suggestion computation queued for config scopes 'scopeId'...\"));\n    getEffectiveRuleDetails(backend, SCOPE_ID, \"java:S106\");\n\n    backend.getConnectionService().didUpdateConnections(new DidUpdateConnectionsParams(List.of(), List.of()));\n\n    await().untilAsserted(() -> assertThat(client.getLogMessages()).contains(\"Evict cached rules definitions for connection 'connectionId'\"));\n  }\n\n  @SonarLintTest\n  void it_should_sync_when_credentials_are_updated(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient()\n      .withToken(CONNECTION_ID, \"token\")\n      .build();\n\n    var introductionDate = Instant.now().truncatedTo(ChronoUnit.MILLIS);\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(\"projectKey\",\n        project -> project.withBranch(\"main\",\n          branch -> branch.withTaintIssue(\"issueKey\", \"rule:key\", \"message\", \"author\", \"file/path\", \"OPEN\", null, introductionDate, new TextRange(1, 2, 3, 4),\n            RuleType.VULNERABILITY)))\n      .start();\n\n    server.getMockServer().stubFor(get(\"/api/system/status\").willReturn(aResponse().withStatus(404)));\n\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server, storage -> storage.withPlugin(TestPlugin.JAVA))\n      .withBoundConfigScope(SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .withEnabledLanguageInStandaloneMode(JAVA)\n      .withBackendCapability(PROJECT_SYNCHRONIZATION, FULL_SYNCHRONIZATION)\n      .start(client);\n    await().untilAsserted(() -> assertThat(client.getLogMessages()).contains(\"Error while checking if soon unsupported\"));\n\n    server.registerSystemApiResponses();\n\n    backend.getConnectionService().didChangeCredentials(new DidChangeCredentialsParams(CONNECTION_ID));\n\n    await().untilAsserted(() -> assertThat(client.getLogMessages()).contains(\n      \"Synchronizing connection 'connectionId' after credentials changed\",\n      \"Synchronizing project branches for project 'projectKey'\"));\n  }\n\n  @SonarLintTest\n  void it_should_notify_client_on_invalid_token_exactly_once() {\n    var status = 401;\n    var client = newFakeClient()\n      .withToken(CONNECTION_ID, \"token\")\n      .build();\n\n    var server = newSonarQubeServer()\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\"))\n      .withResponseCodes(responseCodes -> responseCodes.withStatusCode(status))\n      .start();\n\n    newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server, storage -> storage.withPlugin(TestPlugin.JAVA).withProject(\"projectKey\"))\n      .withBoundConfigScope(SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .withEnabledLanguageInStandaloneMode(JAVA)\n      .withBackendCapability(PROJECT_SYNCHRONIZATION, FULL_SYNCHRONIZATION)\n      .start(client);\n\n    await().untilAsserted(() -> assertThat(client.getLogMessages()).contains(\"Error during synchronization\"));\n    await().during(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getConnectionIdsWithInvalidToken(CONNECTION_ID)).isEqualTo(1));\n  }\n\n  @SonarLintTest\n  void it_should_renotify_client_on_invalid_token_after_connection_is_recreated() {\n    var status = 401;\n    var client = newFakeClient()\n      .withToken(CONNECTION_ID, \"token\")\n      .build();\n\n    var server = newSonarQubeServer()\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\"))\n      .withResponseCodes(responseCodes -> responseCodes.withStatusCode(status))\n      .start();\n\n    var backend = newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server, storage -> storage.withPlugin(TestPlugin.JAVA).withProject(\"projectKey\"))\n      .withBoundConfigScope(SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .withEnabledLanguageInStandaloneMode(JAVA)\n      .withBackendCapability(PROJECT_SYNCHRONIZATION, FULL_SYNCHRONIZATION)\n      .start(client);\n    await().untilAsserted(() -> assertThat(client.getConnectionIdsWithInvalidToken(CONNECTION_ID)).isEqualTo(1));\n    backend.getConnectionService().didUpdateConnections(new DidUpdateConnectionsParams(List.of(), List.of()));\n    backend.getConnectionService()\n      .didUpdateConnections(new DidUpdateConnectionsParams(List.of(new SonarQubeConnectionConfigurationDto(CONNECTION_ID, server.baseUrl(), true)), List.of()));\n    backend.getConfigurationService().didUpdateBinding(new DidUpdateBindingParams(SCOPE_ID, new BindingConfigurationDto(CONNECTION_ID, \"projectKey\", true)));\n\n    await().untilAsserted(() -> assertThat(client.getConnectionIdsWithInvalidToken(CONNECTION_ID)).isGreaterThan(1));\n  }\n\n  @SonarLintTest\n  void it_should_renotify_client_on_invalid_token_after_connection_credentials_are_changed() {\n    var status = 401;\n    var client = newFakeClient()\n      .withToken(CONNECTION_ID, \"token\")\n      .build();\n\n    var server = newSonarQubeServer()\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\"))\n      .withResponseCodes(responseCodes -> responseCodes.withStatusCode(status))\n      .start();\n\n    var backend = newBackend()\n      .withSonarQubeConnection(CONNECTION_ID, server, storage -> storage.withPlugin(TestPlugin.JAVA).withProject(\"projectKey\"))\n      .withBoundConfigScope(SCOPE_ID, CONNECTION_ID, \"projectKey\")\n      .withEnabledLanguageInStandaloneMode(JAVA)\n      .withBackendCapability(PROJECT_SYNCHRONIZATION, FULL_SYNCHRONIZATION)\n      .start(client);\n    await().untilAsserted(() -> assertThat(client.getConnectionIdsWithInvalidToken(CONNECTION_ID)).isEqualTo(1));\n    backend.getConnectionService().didChangeCredentials(new DidChangeCredentialsParams(CONNECTION_ID));\n\n    await().untilAsserted(() -> assertThat(client.getConnectionIdsWithInvalidToken(CONNECTION_ID)).isEqualTo(2));\n  }\n\n  private EffectiveRuleDetailsDto getEffectiveRuleDetails(SonarLintTestRpcServer backend, String configScopeId, String ruleKey) {\n    try {\n      return backend.getRulesService().getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams(configScopeId, ruleKey, null)).get().details();\n    } catch (InterruptedException | ExecutionException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/synchronization/PluginSynchronizationMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.synchronization;\n\nimport java.io.File;\nimport java.nio.file.Path;\nimport java.util.Map;\nimport org.jetbrains.annotations.NotNull;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.serverconnection.proto.Sonarlint;\nimport org.sonarsource.sonarlint.core.serverconnection.proto.Sonarlint.PluginReferences.PluginReference;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.PluginsStorage;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ProtobufFileUtil;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.PluginLocator;\nimport utils.TestPlugin;\n\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.assertj.core.api.Assertions.as;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.entry;\nimport static org.assertj.core.api.InstanceOfAssertFactories.MAP;\nimport static org.awaitility.Awaitility.waitAtMost;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ProjectStoragePaths.encodeForFs;\nimport static org.sonarsource.sonarlint.core.test.utils.server.ServerFixture.ServerStatus.DOWN;\nimport static org.sonarsource.sonarlint.core.test.utils.server.ServerFixture.ServerStatus.UP;\n\nclass PluginSynchronizationMediumTests {\n\n  @SonarLintTest\n  void it_should_pull_plugins_at_startup_from_the_server(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer(\"10.3\")\n      .withStatus(UP)\n      .withPlugin(TestPlugin.JAVA)\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\"))\n      .start();\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start();\n\n    waitAtMost(20, SECONDS).untilAsserted(() -> {\n      assertThat(getPluginsStorageFolder(backend))\n        .isDirectoryContaining(path -> path.getFileName().toString().equals(\"sonar-java-plugin-\" + TestPlugin.JAVA.getVersion() + \".jar\"));\n      assertThat(getPluginReferencesFilePath(backend))\n        .exists()\n        .extracting(this::readPluginReferences, as(MAP))\n        .containsOnly(\n          entry(\"java\", PluginReference.newBuilder().setFilename(\"sonar-java-plugin-\" + TestPlugin.JAVA.getVersion() + \".jar\").setKey(\"java\")\n            .setHash(PluginLocator.SONAR_JAVA_PLUGIN_JAR_HASH).build()));\n    });\n  }\n\n  @SonarLintTest\n  void it_should_not_pull_plugins_if_server_is_down(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer(\"10.3\")\n      .withStatus(DOWN)\n      .withPlugin(TestPlugin.JAVA)\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\"))\n      .start();\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n\n    waitAtMost(3, SECONDS).untilAsserted(() -> {\n      assertThat(getPluginsStorageFolder(backend)).doesNotExist();\n      assertThat(client.getLogMessages()).contains(\"Error during synchronization\");\n    });\n  }\n\n  @SonarLintTest\n  void it_should_not_pull_already_pulled_plugin(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer(\"10.3\")\n      .withPlugin(TestPlugin.JAVA)\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\"))\n      .start();\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withSonarQubeConnection(\"connectionId\", server, storage -> storage.withPlugin(TestPlugin.JAVA))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n\n    waitAtMost(3, SECONDS).untilAsserted(() -> {\n      assertThat(getPluginsStorageFolder(backend))\n        .isDirectoryContaining(path -> path.getFileName().toString().equals(\"sonar-java-plugin-\" + TestPlugin.JAVA.getVersion() + \".jar\"));\n      assertThat(getPluginReferencesFilePath(backend))\n        .exists()\n        .extracting(this::readPluginReferences, as(MAP))\n        .containsOnly(\n          entry(\"java\", PluginReference.newBuilder().setFilename(\"sonar-java-plugin-\" + TestPlugin.JAVA.getVersion() + \".jar\").setKey(\"java\")\n            .setHash(PluginLocator.SONAR_JAVA_PLUGIN_JAR_HASH).build()));\n      assertThat(client.getLogMessages()).contains(\"[SYNC] Code analyzer 'java' is up-to-date. Skip downloading it.\");\n    });\n  }\n\n  @SonarLintTest\n  void it_should_pull_a_plugin_if_already_pulled_but_hash_is_different(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer(\"10.3\")\n      .withPlugin(TestPlugin.JAVA)\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\"))\n      .start();\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withSonarQubeConnection(\"connectionId\", server, storage -> storage.withPlugin(TestPlugin.JAVA.getPluginKey(), TestPlugin.JAVA.getPath(), \"differentHash\"))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n\n    waitAtMost(3, SECONDS).untilAsserted(() -> assertThat(getPluginReferencesFilePath(backend))\n      .exists()\n      .extracting(this::readPluginReferences, as(MAP))\n      .containsOnly(\n        entry(\"java\", PluginReference.newBuilder().setFilename(\"sonar-java-plugin-\" + TestPlugin.JAVA.getVersion() + \".jar\").setKey(\"java\")\n          .setHash(PluginLocator.SONAR_JAVA_PLUGIN_JAR_HASH).build())));\n  }\n\n  @SonarLintTest\n  void it_should_not_pull_plugins_that_do_not_support_sonarlint(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer(\"10.3\")\n      .withPlugin(\"pluginKey\", plugin -> plugin.withJarPath(TestPlugin.JAVA.getPath()).withHash(TestPlugin.JAVA.getHash()).withSonarLintSupported(false))\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\"))\n      .start();\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n\n    waitAtMost(3, SECONDS).untilAsserted(() -> {\n      File[] files = getPluginsStorageFolder(backend).toFile().listFiles();\n      assertThat(files).hasSize(1);\n      assertThat(files[0]).hasName(PluginsStorage.PLUGIN_REFERENCES_PB);\n    });\n  }\n\n  @SonarLintTest\n  void it_should_not_pull_embedded_plugins(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer(\"10.3\")\n      .withPlugin(TestPlugin.JAVA)\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\"))\n      .start();\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n\n    waitAtMost(3, SECONDS).untilAsserted(() -> {\n      File[] files = getPluginsStorageFolder(backend).toFile().listFiles();\n      assertThat(files).hasSize(1);\n      assertThat(files[0]).hasName(PluginsStorage.PLUGIN_REFERENCES_PB);\n    });\n  }\n\n  @SonarLintTest\n  void it_should_not_pull_plugins_for_not_enabled_languages(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer(\"10.3\")\n      .withPlugin(TestPlugin.JAVA)\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\"))\n      .start();\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n\n    waitAtMost(3, SECONDS).untilAsserted(() -> {\n      File[] files = getPluginsStorageFolder(backend).toFile().listFiles();\n      assertThat(files).hasSize(1);\n      assertThat(files[0]).hasName(PluginsStorage.PLUGIN_REFERENCES_PB);\n    });\n  }\n\n  @SonarLintTest\n  void it_should_pull_third_party_plugins_for_custom_rules(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer(\"10.3\")\n      .withPlugin(\"java-custom\", plugin -> plugin.withJarPath(Path.of(\"java-custom-plugin-4.3.0.1456.jar\")).withHash(\"de5308f43260d357acc97712ce4c5475\"))\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\"))\n      .start();\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n\n    waitAtMost(3, SECONDS).untilAsserted(() -> {\n      assertThat(getPluginsStorageFolder(backend)).isDirectoryContaining(path -> path.getFileName().toString().equals(\"java-custom-plugin-4.3.0.1456.jar\"));\n      assertThat(getPluginReferencesFilePath(backend))\n        .exists()\n        .extracting(this::readPluginReferences, as(MAP))\n        .containsOnly(\n          entry(\"java-custom\",\n            PluginReference.newBuilder().setFilename(\"java-custom-plugin-4.3.0.1456.jar\").setKey(\"java-custom\").setHash(\"de5308f43260d357acc97712ce4c5475\").build()));\n    });\n  }\n\n  @SonarLintTest\n  void it_should_clean_up_plugins_that_are_no_longer_relevant(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer(\"10.3\")\n      .withPlugin(TestPlugin.PHP)\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\"))\n      .start();\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server, storage -> storage.withPlugin(TestPlugin.JAVA))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withEnabledLanguageInStandaloneMode(Language.PHP)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n    client.waitForSynchronization();\n\n    assertThat(getPluginsStorageFolder(backend).toFile().listFiles())\n      .extracting(File::getName)\n      .containsOnly(PluginsStorage.PLUGIN_REFERENCES_PB, TestPlugin.PHP.getPath().getFileName().toString());\n    assertThat(client.getLogMessages()).contains(\"Cleaning up the plugins storage \" + getPluginsStorageFolder(backend) + \", removing 1 unknown files:\");\n    assertThat(getPluginReferencesFilePath(backend))\n      .exists()\n      .extracting(this::readPluginReferences, as(MAP))\n      .containsOnlyKeys(\"php\");\n  }\n\n  @NotNull\n  private Map<String, PluginReference> readPluginReferences(Path filePath) {\n    return ProtobufFileUtil.readFile(filePath, Sonarlint.PluginReferences.parser()).getPluginsByKeyMap();\n  }\n\n  @NotNull\n  private Path getPluginsStorageFolder(SonarLintTestRpcServer backend) {\n    return backend.getStorageRoot().resolve(encodeForFs(\"connectionId\")).resolve(\"plugins\");\n  }\n\n  @NotNull\n  private Path getPluginReferencesFilePath(SonarLintTestRpcServer backend) {\n    return getPluginsStorageFolder(backend).resolve(\"plugin_references.pb\");\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/synchronization/RuleSetSynchronizationMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.synchronization;\n\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport org.assertj.core.api.InstanceOfAssertFactories;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.serverconnection.proto.Sonarlint;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ProtobufFileUtil;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport org.sonarsource.sonarlint.core.test.utils.server.ServerFixture;\n\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.assertj.core.api.Assertions.as;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.InstanceOfAssertFactories.LIST;\nimport static org.awaitility.Awaitility.waitAtMost;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ProjectStoragePaths.encodeForFs;\n\nclass RuleSetSynchronizationMediumTests {\n\n  @SonarLintTest\n  void it_should_pull_active_ruleset_from_server(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer(\"10.3\")\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile.withLanguage(\"java\").withActiveRule(\"ruleKey\", activeRule -> activeRule.withSeverity(IssueSeverity.MAJOR)))\n      .withProject(\"projectKey\", project -> project.withQualityProfile(\"qpKey\").withBranch(\"main\"))\n      .start();\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start();\n\n    addConfigurationScope(backend, \"configScopeId\", \"connectionId\", \"projectKey\");\n\n    waitAtMost(3, SECONDS).untilAsserted(() -> assertThat(getAnalyzerConfigFile(backend, \"connectionId\", \"projectKey\"))\n      .exists()\n      .extracting(this::readRuleSets, as(InstanceOfAssertFactories.map(String.class, Sonarlint.RuleSet.class)))\n      .hasSize(1)\n      .extractingByKey(\"java\")\n      .extracting(Sonarlint.RuleSet::getRuleList, as(LIST))\n      .containsExactly(Sonarlint.RuleSet.ActiveRule.newBuilder().setRuleKey(\"ruleKey\").setSeverity(\"MAJOR\").build()));\n  }\n\n  @SonarLintTest\n  void it_should_not_pull_when_server_is_down(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer(\"10.3\")\n      .withStatus(ServerFixture.ServerStatus.DOWN)\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile.withLanguage(\"java\").withActiveRule(\"ruleKey\", activeRule -> activeRule.withSeverity(IssueSeverity.MAJOR)))\n      .withProject(\"projectKey\", project -> project.withQualityProfile(\"qpKey\").withBranch(\"main\"))\n      .start();\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n\n    addConfigurationScope(backend, \"configScopeId\", \"connectionId\", \"projectKey\");\n\n    waitAtMost(3, SECONDS).untilAsserted(() -> {\n      assertThat(getAnalyzerConfigFile(backend, \"connectionId\", \"projectKey\")).doesNotExist();\n      assertThat(client.getLogMessages()).contains(\"Error during synchronization\");\n    });\n  }\n\n  private void addConfigurationScope(SonarLintTestRpcServer backend, String configScopeId, String connectionId, String projectKey) {\n    backend.getConfigurationService().didAddConfigurationScopes(\n      new DidAddConfigurationScopesParams(List.of(new ConfigurationScopeDto(configScopeId, null, true, \"name\", new BindingConfigurationDto(connectionId, projectKey, true)))));\n  }\n\n  private Path getAnalyzerConfigFile(SonarLintTestRpcServer backend, String connectionId, String projectKey) {\n    return backend.getStorageRoot().resolve(encodeForFs(connectionId)).resolve(\"projects\").resolve(encodeForFs(projectKey)).resolve(\"analyzer_config.pb\");\n  }\n\n  private Map<String, Sonarlint.RuleSet> readRuleSets(Path protoFilePath) {\n    return ProtobufFileUtil.readFile(protoFilePath, Sonarlint.AnalyzerConfiguration.parser()).getRuleSetsByLanguageKeyMap();\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/synchronization/ServerInfoSynchronizationMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.synchronization;\n\nimport java.nio.file.Path;\nimport java.util.List;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.serverconnection.ServerSettings;\nimport org.sonarsource.sonarlint.core.serverconnection.proto.Sonarlint;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ProtobufFileUtil;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.waitAtMost;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ProjectStoragePaths.encodeForFs;\nimport static org.sonarsource.sonarlint.core.test.utils.server.ServerFixture.ServerStatus.DOWN;\n\nclass ServerInfoSynchronizationMediumTests {\n\n  @SonarLintTest\n  void it_should_pull_server_info_when_bound_configuration_scope_is_added(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer(\"10.3\")\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\"))\n      .start();\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start();\n\n    addConfigurationScope(backend, \"configScopeId\", \"connectionId\", \"projectKey\");\n\n    waitAtMost(3, SECONDS).untilAsserted(() -> assertThat(getServerInfoFile(backend))\n      .exists()\n      .extracting(this::readServerVersion, this::readServerMode)\n      .containsExactly(\"10.3\", null));\n  }\n\n  @SonarLintTest\n  void it_should_pull_old_server_info_and_mode_should_be_missing(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer(\"10.1\")\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\"))\n      .start();\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start();\n\n    addConfigurationScope(backend, \"configScopeId\", \"connectionId\", \"projectKey\");\n\n    waitAtMost(3, SECONDS).untilAsserted(() -> assertThat(getServerInfoFile(backend))\n      .exists()\n      .extracting(this::readServerVersion, this::readServerMode)\n      .containsExactly(\"10.1\", null));\n  }\n\n  @SonarLintTest\n  void it_should_synchronize_with_sonarcloud_and_mode_should_be_missing(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(\"test\", organization -> organization\n        .withProject(\"projectKey\", project -> project.withBranch(\"main\")))\n      .start();\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarQubeCloudEuRegionApiUri(server.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"test\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start();\n\n    addConfigurationScope(backend, \"configScopeId\", \"connectionId\", \"projectKey\");\n\n    waitAtMost(3, SECONDS).untilAsserted(() -> assertThat(getServerInfoFile(backend))\n      .exists()\n      .extracting(this::readServerMode)\n      .isNull());\n  }\n\n  @SonarLintTest\n  void it_should_synchronize_with_recent_sonarqube_and_return_mode(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer(\"10.8\")\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\"))\n      .start();\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start();\n\n    addConfigurationScope(backend, \"configScopeId\", \"connectionId\", \"projectKey\");\n\n    waitAtMost(3, SECONDS).untilAsserted(() -> assertThat(getServerInfoFile(backend))\n      .exists()\n      .extracting(this::readServerMode)\n      .isEqualTo(true));\n  }\n\n  @SonarLintTest\n  void it_should_stop_synchronization_if_server_is_down(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer(\"10.3\")\n      .withStatus(DOWN)\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\"))\n      .start();\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n\n    addConfigurationScope(backend, \"configScopeId\", \"connectionId\", \"projectKey\");\n\n    waitAtMost(3, SECONDS).untilAsserted(() -> {\n      assertThat(getServerInfoFile(backend)).doesNotExist();\n      assertThat(client.getLogMessages()).contains(\"Error during synchronization\");\n    });\n  }\n\n  @SonarLintTest\n  void it_should_stop_synchronization_if_server_version_is_unsupported(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer(\"7.8\")\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\"))\n      .start();\n    var client = harness.newFakeClient().build();\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n\n    addConfigurationScope(backend, \"configScopeId\", \"connectionId\", \"projectKey\");\n\n    waitAtMost(3, SECONDS).untilAsserted(() -> {\n      assertThat(getServerInfoFile(backend)).doesNotExist();\n      assertThat(client.getLogMessages()).contains(\"Error during synchronization\");\n    });\n  }\n\n  private void addConfigurationScope(SonarLintTestRpcServer backend, String configScopeId, String connectionId, String projectKey) {\n    backend.getConfigurationService().didAddConfigurationScopes(\n      new DidAddConfigurationScopesParams(List.of(new ConfigurationScopeDto(configScopeId, null, true, \"name\", new BindingConfigurationDto(connectionId, projectKey, true)))));\n  }\n\n  private Path getServerInfoFile(SonarLintTestRpcServer backend) {\n    return backend.getStorageRoot().resolve(encodeForFs(\"connectionId\")).resolve(\"server_info.pb\");\n  }\n\n  private String readServerVersion(Path protoFilePath) {\n    return ProtobufFileUtil.readFile(protoFilePath, Sonarlint.ServerInfo.parser()).getVersion();\n  }\n\n  @Nullable\n  private Boolean readServerMode(Path protoFilePath) {\n    var serverInfo = ProtobufFileUtil.readFile(protoFilePath, Sonarlint.ServerInfo.parser());\n    var mqrModeSetting = serverInfo.getGlobalSettingsMap().get(ServerSettings.MQR_MODE_SETTING);\n    return mqrModeSetting == null ? null : Boolean.valueOf(mqrModeSetting);\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/synchronization/TaintVulnerabilitySynchronizationMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.synchronization;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.UUID;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerTaintIssue;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.waitAtMost;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\n\nclass TaintVulnerabilitySynchronizationMediumTests {\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @SonarLintTest\n  void it_should_incrementally_pull_taint_vulnerabilities_when_connected_to_sonarqube_9_6_plus(SonarLintTestHarness harness) {\n    var introductionDate = Instant.now().truncatedTo(ChronoUnit.MILLIS);\n    var server = harness.newFakeSonarQubeServer(\"9.9\")\n      .withProject(\"projectKey\",\n        project -> project.withBranch(\"main\",\n          branch -> branch.withTaintIssue(\"issueKey\", \"rule:key\", \"message\", \"author\", \"file/path\", \"OPEN\", null, introductionDate, new TextRange(1, 2, 3, 4),\n            RuleType.VULNERABILITY)))\n      .start();\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start();\n\n    addConfigurationScope(backend, \"configScopeId\", \"connectionId\", \"projectKey\");\n\n    waitAtMost(3, SECONDS).untilAsserted(() -> assertThat(readTaintVulnerabilitiesFromStorage(backend, \"connectionId\", \"projectKey\", \"main\"))\n      .usingRecursiveComparison()\n      .ignoringFields(\"id\")\n      .isEqualTo(\n        List.of(new ServerTaintIssue(UUID.randomUUID(), \"issueKey\", false, null, \"rule:key\", \"message\", Path.of(\"file/path\"), introductionDate, IssueSeverity.MAJOR, RuleType.BUG,\n          new TextRangeWithHash(1, 2, 3, 4, \"hash\"), null, null, Collections.emptyMap(), List.of()))));\n  }\n\n  private void addConfigurationScope(SonarLintTestRpcServer backend, String configScopeId, String connectionId, String projectKey) {\n    backend.getConfigurationService().didAddConfigurationScopes(\n      new DidAddConfigurationScopesParams(List.of(new ConfigurationScopeDto(configScopeId, null, true, \"name\", new BindingConfigurationDto(connectionId, projectKey, true)))));\n  }\n\n  private List<ServerTaintIssue> readTaintVulnerabilitiesFromStorage(SonarLintTestRpcServer backend, String connectionId, String projectKey, String branchName) {\n    return backend.getIssueStorageService().connection(connectionId).project(projectKey).findings().loadTaint(branchName);\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/synchronization/UserSynchronizationMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.synchronization;\n\nimport java.nio.file.Path;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static java.util.List.of;\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.waitAtMost;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ProjectStoragePaths.encodeForFs;\n\nclass UserSynchronizationMediumTests {\n\n  @SonarLintTest\n  void it_should_store_user_id_on_sonarcloud(SonarLintTestHarness harness) {\n    var scServer = harness.newFakeSonarCloudServer()\n      .withOrganization(\"orgKey\")\n      .start();\n\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withSonarQubeCloudEuRegionUri(scServer.baseUrl())\n      .withSonarQubeCloudEuRegionApiUri(scServer.baseUrl())\n      .withSonarCloudConnection(\"connectionId\", \"myOrg\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start();\n\n    addConfigurationScope(backend, \"configScopeId\", \"connectionId\", \"projectKey\");\n\n    waitAtMost(3, SECONDS).untilAsserted(() -> assertThat(getUserFile(backend)).exists()\n      .content().asString().contains(\"11111111-1111-1111-1111-111111111111\"));\n  }\n\n  @SonarLintTest\n  void it_should_store_user_id_on_sonarqube_server(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer(\"10.3\")\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\"))\n      .start();\n\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withSonarQubeConnection(\"connectionId\", server)\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start();\n\n    addConfigurationScope(backend, \"configScopeId\", \"connectionId\", \"projectKey\");\n\n    waitAtMost(3, SECONDS).untilAsserted(() -> assertThat(getUserFile(backend)).exists()\n      .content().asString().contains(\"11111111-1111-1111-1111-111111111111\"));\n  }\n\n  private void addConfigurationScope(SonarLintTestRpcServer backend, String configScopeId, String connectionId, String projectKey) {\n    backend.getConfigurationService().didAddConfigurationScopes(\n      new DidAddConfigurationScopesParams(of(new ConfigurationScopeDto(configScopeId, null, true, \"name\", new BindingConfigurationDto(connectionId, projectKey, true)))));\n  }\n\n  private Path getUserFile(SonarLintTestRpcServer backend) {\n    return backend.getStorageRoot().resolve(encodeForFs(\"connectionId\")).resolve(\"user.pb\");\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/taint/vulnerabilities/TaintVulnerabilitiesMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.taint.vulnerabilities;\n\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.List;\nimport java.util.concurrent.ExecutionException;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidUpdateConnectionsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarQubeConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.GetEffectiveIssueDetailsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ResolutionStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.ListAllParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.test.utils.storage.ServerTaintIssueFixtures.aServerTaintIssue;\n\nclass TaintVulnerabilitiesMediumTests {\n\n  @SonarLintTest\n  void it_should_return_no_taint_vulnerabilities_if_the_scope_is_not_bound(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .start();\n\n    var taintVulnerabilities = listAllTaintVulnerabilities(backend, \"configScopeId\");\n\n    assertThat(taintVulnerabilities).isEmpty();\n  }\n\n  @SonarLintTest\n  void it_should_return_no_taint_vulnerabilities_if_the_storage_is_empty(SonarLintTestHarness harness) {\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\")\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .start();\n\n    var taintVulnerabilities = listAllTaintVulnerabilities(backend, \"configScopeId\");\n\n    assertThat(taintVulnerabilities).isEmpty();\n  }\n\n  @SonarLintTest\n  void it_should_return_the_stored_taint_vulnerabilities(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarQubeServer()\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\"))\n      .start();\n    var introductionDate = Instant.now().truncatedTo(ChronoUnit.MILLIS);\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server,\n        storage -> storage.withProject(\"projectKey\",\n          project -> project.withMainBranch(\"main\",\n            branch -> branch.withTaintIssue(aServerTaintIssue(\"key\")\n              .withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\")).withRuleKey(\"ruleKey\")\n              .withType(RuleType.VULNERABILITY)\n              .withIntroductionDate(introductionDate)))))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start();\n\n    await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(listAllTaintVulnerabilities(backend, \"configScopeId\"))\n      .extracting(TaintVulnerabilityDto::getIntroductionDate)\n      .containsOnly(introductionDate));\n  }\n\n  @SonarLintTest\n  void it_should_return_taint_details(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n\n    var server = harness.newFakeSonarQubeServer()\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile.withLanguage(\"java\").withActiveRule(\"javasecurity:S6549\", activeRule -> activeRule\n        .withSeverity(org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity.MAJOR)))\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\")\n        .withQualityProfile(\"qpKey\"))\n      .withPlugin(TestPlugin.JAVA)\n      .start();\n    var introductionDate = Instant.now().truncatedTo(ChronoUnit.MILLIS);\n    var fakeTaintBuilder = aServerTaintIssue(\"key\")\n      .withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\")).withRuleKey(\"javasecurity:S6549\")\n      .withType(RuleType.VULNERABILITY)\n      .withIntroductionDate(introductionDate);\n\n    var backend = harness.newBackend()\n      .withExtraEnabledLanguagesInConnectedMode(Language.JAVA)\n      .withSonarQubeConnection(\"connectionId\", server,\n        storage -> storage.withProject(\"projectKey\",\n          project -> project.withMainBranch(\"main\",\n            branch -> branch.withTaintIssue(fakeTaintBuilder))))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n\n    client.waitForSynchronization();\n\n    await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(listAllTaintVulnerabilities(backend, \"configScopeId\"))\n      .extracting(TaintVulnerabilityDto::getIntroductionDate)\n      .containsOnly(introductionDate));\n    var taintVulnerability = listAllTaintVulnerabilities(backend, \"configScopeId\").get(0);\n    var actualTaintId = taintVulnerability.getId();\n\n    var taintDetails = backend.getIssueService().getEffectiveIssueDetails(new GetEffectiveIssueDetailsParams(\"configScopeId\", actualTaintId)).join();\n\n    assertThat(taintDetails).isNotNull();\n    assertThat(taintVulnerability)\n      .extracting(\"resolved\", \"resolutionStatus\")\n      .containsExactly(false, null);\n  }\n\n  @SonarLintTest\n  void it_should_return_resolved_taint_details(SonarLintTestHarness harness) {\n    var client = harness.newFakeClient().build();\n\n    var server = harness.newFakeSonarQubeServer()\n      .withQualityProfile(\"qpKey\", qualityProfile -> qualityProfile.withLanguage(\"java\").withActiveRule(\"javasecurity:S6549\", activeRule -> activeRule\n        .withSeverity(org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity.MAJOR)))\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\")\n        .withQualityProfile(\"qpKey\"))\n      .withPlugin(TestPlugin.JAVA)\n      .start();\n    var introductionDate = Instant.now().truncatedTo(ChronoUnit.MILLIS);\n    var fakeTaintBuilder = aServerTaintIssue(\"key\")\n      .withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\")).withRuleKey(\"javasecurity:S6549\")\n      .withType(RuleType.VULNERABILITY)\n      .withIntroductionDate(introductionDate)\n      .resolvedWithStatus(IssueStatus.ACCEPT);\n\n    var backend = harness.newBackend()\n      .withExtraEnabledLanguagesInConnectedMode(Language.JAVA)\n      .withSonarQubeConnection(\"connectionId\", server,\n        storage -> storage.withProject(\"projectKey\",\n          project -> project.withMainBranch(\"main\",\n            branch -> branch.withTaintIssue(fakeTaintBuilder))))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start(client);\n\n    client.waitForSynchronization();\n\n    await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(listAllTaintVulnerabilities(backend, \"configScopeId\"))\n      .extracting(TaintVulnerabilityDto::getIntroductionDate)\n      .containsOnly(introductionDate));\n    var taintVulnerability = listAllTaintVulnerabilities(backend, \"configScopeId\").get(0);\n    var actualTaintId = taintVulnerability.getId();\n\n    var taintDetails = backend.getIssueService().getEffectiveIssueDetails(new GetEffectiveIssueDetailsParams(\"configScopeId\", actualTaintId)).join();\n\n    assertThat(taintDetails).isNotNull();\n    assertThat(taintVulnerability)\n      .extracting(\"resolved\", \"resolutionStatus\")\n      .containsExactly(true, ResolutionStatus.ACCEPT);\n  }\n\n  @SonarLintTest\n  void it_should_refresh_taint_vulnerabilities_when_requested(SonarLintTestHarness harness) {\n    var serverWithATaint = harness.newFakeSonarQubeServer()\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\", branch -> branch.withTaintIssue(\"oldIssueKey\", \"rule:key\", \"message\", \"author\", \"file/path\", \"OPEN\", null,\n        Instant.now(), new TextRange(1, 2, 3, 4), RuleType.VULNERABILITY)))\n      .start();\n    var newestIntroductionDate = Instant.now().truncatedTo(ChronoUnit.MILLIS);\n    var serverWithAnotherTaint = harness.newFakeSonarQubeServer()\n      .withProject(\"projectKey\",\n        project -> project.withBranch(\"main\", branch -> branch.withTaintIssue(\"anotherIssueKey\", \"rule:key\", \"message\", \"author\", \"file/path\", \"OPEN\", null,\n          newestIntroductionDate, new TextRange(1, 2, 3, 4), RuleType.VULNERABILITY)))\n      .start();\n    var backend = harness.newBackend()\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withSonarQubeConnection(\"connectionId\", serverWithATaint,\n        storage -> storage.withProject(\"projectKey\",\n          project -> project.withMainBranch(\"main\")))\n      .withBoundConfigScope(\"configScopeId\", \"connectionId\", \"projectKey\")\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .start();\n    await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(listAllTaintVulnerabilities(backend, \"configScopeId\")).isNotEmpty());\n    // switch server to simulate a new dataset. Not ideal, should be handled differently\n    backend.getConnectionService()\n      .didUpdateConnections(new DidUpdateConnectionsParams(List.of(new SonarQubeConnectionConfigurationDto(\"connectionId\", serverWithAnotherTaint.baseUrl(), true)), List.of()));\n\n    var taintVulnerabilities = refreshAndListAllTaintVulnerabilities(backend, \"configScopeId\");\n\n    assertThat(ChronoUnit.MINUTES.between(taintVulnerabilities.get(0).getIntroductionDate(), newestIntroductionDate)).isZero();\n  }\n\n  private List<TaintVulnerabilityDto> listAllTaintVulnerabilities(SonarLintTestRpcServer backend, String configScopeId) {\n    try {\n      return backend.getTaintVulnerabilityTrackingService().listAll(new ListAllParams(configScopeId)).get().getTaintVulnerabilities();\n    } catch (InterruptedException | ExecutionException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  private List<TaintVulnerabilityDto> refreshAndListAllTaintVulnerabilities(SonarLintTestRpcServer backend, String configScopeId) {\n    try {\n      return backend.getTaintVulnerabilityTrackingService().listAll(new ListAllParams(configScopeId, true)).get().getTaintVulnerabilities();\n    } catch (InterruptedException | ExecutionException e) {\n      throw new RuntimeException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/taint/vulnerabilities/TaintVulnerabilityEventsMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.taint.vulnerabilities;\n\nimport java.nio.file.Paths;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.UUID;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ChangeIssueStatusParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ResolutionStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.ImpactDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TextRangeWithHashDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.taint.vulnerability.DidChangeTaintVulnerabilitiesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.MQRModeDetails;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.StandardModeDetails;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerTaintIssue;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.when;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SERVER_SENT_EVENTS;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JAVA;\nimport static org.sonarsource.sonarlint.core.test.utils.storage.ServerTaintIssueFixtures.aServerTaintIssue;\n\nclass TaintVulnerabilityEventsMediumTests {\n\n  @RegisterExtension\n  static SonarLintLogTester logTester = new SonarLintLogTester();\n\n  @Nested\n  class WhenReceivingTaintRaisedEvent {\n    private final String eventPayload = \"\"\"\n      event: TaintVulnerabilityRaised\n      data: {\\\n          \"key\": \"taintKey\",\\\n          \"projectKey\": \"projectKey\",\\\n          \"branch\": \"branchName\",\\\n          \"creationDate\": 123456789,\\\n          \"ruleKey\": \"javasecurity:S123\",\\\n          \"severity\": \"MAJOR\",\\\n          \"type\": \"VULNERABILITY\",\\\n          \"mainLocation\": {\\\n            \"filePath\": \"functions/taint.js\",\\\n            \"message\": \"blah blah\",\\\n            \"textRange\": {\\\n              \"startLine\": 17,\\\n              \"startLineOffset\": 10,\\\n              \"endLine\": 3,\\\n              \"endLineOffset\": 2,\\\n              \"hash\": \"hash\"\\\n            }\\\n          },\\\n          \"flows\": [\\\n            {\\\n              \"locations\": [\\\n                {\\\n                  \"filePath\": \"functions/taint.js\",\\\n                  \"message\": \"sink: tainted value is used to perform a security-sensitive operation\",\\\n                  \"textRange\": {\\\n                    \"startLine\": 17,\\\n                    \"startLineOffset\": 10,\\\n                    \"endLine\": 3,\\\n                    \"endLineOffset\": 2,\\\n                    \"hash\": \"hash1\"\\\n                  }\\\n                },\\\n                {\\\n                  \"filePath\": \"functions/taint2.js\",\\\n                  \"message\": \"sink: tainted value is used to perform a security-sensitive operation\",\\\n                  \"textRange\": {\\\n                    \"startLine\": 18,\\\n                    \"startLineOffset\": 11,\\\n                    \"endLine\": 4,\\\n                    \"endLineOffset\": 3,\\\n                    \"hash\": \"hash2\"\\\n                  }\\\n                }\\\n              ]\\\n            }\\\n          ]\\\n      }\n\n      \"\"\";\n\n    @SonarLintTest\n    void it_should_store_taint_vulnerability_in_storage(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var client = harness.newFakeClient().build();\n      when(client.matchSonarProjectBranch(eq(\"configScope\"), any(), any(), any())).thenReturn(\"branchName\");\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n\n      server.pushEvent(eventPayload);\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(readTaintVulnerabilities(backend, \"connectionId\", \"projectKey\", \"branchName\"))\n        .extracting(ServerTaintIssue::getSonarServerKey)\n        .containsOnly(\"taintKey\"));\n    }\n\n    @SonarLintTest\n    void it_should_notify_client(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var client = harness.newFakeClient().build();\n      when(client.matchSonarProjectBranch(eq(\"configScope\"), any(), any(), any())).thenReturn(\"branchName\");\n      harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS, FULL_SYNCHRONIZATION)\n        .withSonarQubeConnection(\"connectionId\", server)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      // Wait for synchronization so that the local storage has the correct severity mode\n      client.waitForSynchronization();\n\n      server.pushEvent(eventPayload);\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(client.getTaintVulnerabilityChanges())\n        .usingRecursiveComparison()\n        .ignoringFields(\"addedTaintVulnerabilities.id\")\n        .isEqualTo(List.of(new DidChangeTaintVulnerabilitiesParams(\"configScope\", Set.of(),\n          List\n            .of(new TaintVulnerabilityDto(UUID.randomUUID(), \"taintKey\", false, null, \"javasecurity:S123\", \"blah blah\", Paths.get(\"functions/taint.js\"), Instant.ofEpochMilli(123456789),\n              Either.forLeft(new StandardModeDetails(org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity.MAJOR,\n                org.sonarsource.sonarlint.core.rpc.protocol.common.RuleType.VULNERABILITY)),\n              List.of(new TaintVulnerabilityDto.FlowDto(List.of(\n                new TaintVulnerabilityDto.FlowDto.LocationDto(new TextRangeWithHashDto(17, 10, 3, 2, \"hash1\"),\n                  \"sink: tainted value is used to perform a security-sensitive operation\",\n                  Paths.get(\"functions/taint.js\")),\n                new TaintVulnerabilityDto.FlowDto.LocationDto(new TextRangeWithHashDto(18, 11, 4, 3, \"hash2\"),\n                  \"sink: tainted value is used to perform a security-sensitive operation\", Paths.get(\"functions/taint2.js\"))))),\n              new TextRangeWithHashDto(17, 10, 3, 2, \"hash\"), null, true, false)),\n          List.of()))));\n    }\n  }\n\n  @Nested\n  class WhenReceivingIssueChangedEvent {\n    @SonarLintTest\n    void it_should_update_taint_vulnerability_in_storage_with_new_resolution(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var client = harness.newFakeClient().build();\n      when(client.matchSonarProjectBranch(eq(\"configScope\"), any(), any(), any())).thenReturn(\"branchName\");\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server,\n          storage -> storage.withProject(\"projectKey\", project -> project.withMainBranch(\"branchName\", branch -> branch.withTaintIssue(aServerTaintIssue(\"key1\").open()))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n\n      server.pushEvent(\"\"\"\n        event: IssueChanged\n        data: {\\\n          \"projectKey\": \"projectKey\",\\\n          \"issues\": [{\\\n            \"issueKey\": \"key1\",\\\n            \"branchName\": \"branchName\"\\\n          }],\\\n          \"resolved\": true\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(readTaintVulnerabilities(backend, \"connectionId\", \"projectKey\", \"branchName\"))\n        .extracting(ServerTaintIssue::getSonarServerKey, ServerTaintIssue::isResolved)\n        .containsOnly(tuple(\"key1\", true)));\n    }\n\n    @SonarLintTest\n    void it_should_update_taint_vulnerability_in_storage_with_new_severity(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var client = harness.newFakeClient().build();\n      when(client.matchSonarProjectBranch(eq(\"configScope\"), any(), any(), any())).thenReturn(\"branchName\");\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server,\n          storage -> storage.withProject(\"projectKey\",\n            project -> project.withMainBranch(\"branchName\", branch -> branch.withTaintIssue(aServerTaintIssue(\"key1\").withSeverity(IssueSeverity.INFO)))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n\n      server.pushEvent(\"\"\"\n        event: IssueChanged\n        data: {\\\n          \"projectKey\": \"projectKey\",\\\n          \"issues\": [{\\\n            \"issueKey\": \"key1\",\\\n            \"branchName\": \"branchName\"\\\n          }],\\\n          \"userSeverity\": \"CRITICAL\"\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(readTaintVulnerabilities(backend, \"connectionId\", \"projectKey\", \"branchName\"))\n        .extracting(ServerTaintIssue::getSonarServerKey, ServerTaintIssue::getSeverity)\n        .containsOnly(tuple(\"key1\", IssueSeverity.CRITICAL)));\n    }\n\n    @SonarLintTest\n    void it_should_update_taint_vulnerability_in_storage_with_new_type(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var client = harness.newFakeClient().build();\n      when(client.matchSonarProjectBranch(eq(\"configScope\"), any(), any(), any())).thenReturn(\"branchName\");\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server,\n          storage -> storage.withProject(\"projectKey\",\n            project -> project.withMainBranch(\"branchName\", branch -> branch.withTaintIssue(aServerTaintIssue(\"key1\").withType(RuleType.VULNERABILITY)))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n\n      server.pushEvent(\"\"\"\n        event: IssueChanged\n        data: {\\\n          \"projectKey\": \"projectKey\",\\\n          \"issues\": [{\\\n            \"issueKey\": \"key1\",\\\n            \"branchName\": \"branchName\"\\\n          }],\\\n          \"userType\": \"BUG\"\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(readTaintVulnerabilities(backend, \"connectionId\", \"projectKey\", \"branchName\"))\n        .extracting(ServerTaintIssue::getSonarServerKey, ServerTaintIssue::getType)\n        .containsOnly(tuple(\"key1\", RuleType.BUG)));\n    }\n\n    @SonarLintTest\n    void it_should_notify_client(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var client = harness.newFakeClient().build();\n      when(client.matchSonarProjectBranch(eq(\"configScope\"), any(), any(), any())).thenReturn(\"branchName\");\n      var introductionDate = Instant.now().truncatedTo(ChronoUnit.SECONDS);\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS, FULL_SYNCHRONIZATION)\n        .withSonarQubeConnection(\"connectionId\", server,\n          storage -> storage.withProject(\"projectKey\",\n            project -> project.withMainBranch(\"branchName\",\n              branch -> branch.withTaintIssue(aServerTaintIssue(\"key1\").withIntroductionDate(introductionDate).withSeverity(IssueSeverity.MINOR)))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      // Wait for synchronization so that the local storage has the correct severity mode\n      client.waitForSynchronization();\n      var storedTaintIssues = await().atMost(Duration.ofSeconds(2)).until(() -> readTaintVulnerabilities(backend, \"connectionId\", \"projectKey\", \"branchName\"),\n        taints -> taints.size() == 1);\n\n      server.pushEvent(\"\"\"\n        event: IssueChanged\n        data: {\\\n          \"projectKey\": \"projectKey\",\\\n          \"issues\": [{\\\n            \"issueKey\": \"key1\",\\\n            \"branchName\": \"branchName\"\\\n          }],\\\n          \"userSeverity\": \"CRITICAL\"\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(client.getTaintVulnerabilityChanges())\n        .usingRecursiveComparison()\n        .isEqualTo(List.of(new DidChangeTaintVulnerabilitiesParams(\"configScope\", Set.of(), List.of(),\n          List.of(new TaintVulnerabilityDto(storedTaintIssues.get(0).getId(), \"key1\", false, null, \"ruleKey\", \"message\", Paths.get(\"file/path\"), introductionDate,\n            Either.forLeft(\n              new StandardModeDetails(org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity.CRITICAL, org.sonarsource.sonarlint.core.rpc.protocol.common.RuleType.BUG)),\n            List.of(), new TextRangeWithHashDto(1, 2, 3, 4, \"rangeHash\"), \"contextKey\",\n            true, false))))));\n    }\n  }\n\n  @Nested\n  class WhenReceivingTaintClosedEvent {\n    @SonarLintTest\n    void it_should_remove_taint_vulnerability_from_storage(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var client = harness.newFakeClient().build();\n      when(client.matchSonarProjectBranch(eq(\"configScope\"), any(), any(), any())).thenReturn(\"branchName\");\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server,\n          storage -> storage.withProject(\"projectKey\",\n            project -> project.withMainBranch(\"branchName\", branch -> branch.withTaintIssue(aServerTaintIssue(\"key1\").withType(RuleType.VULNERABILITY)))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n\n      server.pushEvent(\"\"\"\n        event: TaintVulnerabilityClosed\n        data: {\\\n          \"projectKey\": \"projectKey\",\\\n          \"key\": \"key1\"\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(readTaintVulnerabilities(backend, \"connectionId\", \"projectKey\", \"branchName\"))\n        .isEmpty());\n    }\n\n    @SonarLintTest\n    void it_should_notify_client(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var client = harness.newFakeClient().build();\n      when(client.matchSonarProjectBranch(eq(\"configScope\"), any(), any(), any())).thenReturn(\"branchName\");\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withBackendCapability(SERVER_SENT_EVENTS)\n        .withSonarQubeConnection(\"connectionId\", server,\n          storage -> storage.withProject(\"projectKey\",\n            project -> project.withMainBranch(\"branchName\", branch -> branch.withTaintIssue(aServerTaintIssue(\"key1\").withType(RuleType.VULNERABILITY)))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      var storedTaintIssues = await().atMost(Duration.ofSeconds(2)).until(() -> readTaintVulnerabilities(backend, \"connectionId\", \"projectKey\", \"branchName\"),\n        taints -> taints.size() == 1);\n\n      server.pushEvent(\"\"\"\n        event: TaintVulnerabilityClosed\n        data: {\\\n          \"projectKey\": \"projectKey\",\\\n          \"key\": \"key1\"\\\n        }\n\n        \"\"\");\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(client.getTaintVulnerabilityChanges())\n        .usingRecursiveComparison()\n        .isEqualTo(List.of(new DidChangeTaintVulnerabilitiesParams(\"configScope\", Set.of(storedTaintIssues.get(0).getId()), List.of(), List.of()))));\n    }\n  }\n\n  @Nested\n  class WhenChangingIssueStatus {\n    @SonarLintTest\n    void it_should_notify_client(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.0\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var client = harness.newFakeClient().build();\n      when(client.matchSonarProjectBranch(eq(\"configScope\"), any(), any(), any())).thenReturn(\"branchName\");\n      var introductionDate = Instant.now().truncatedTo(ChronoUnit.SECONDS);\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withSonarQubeConnection(\"connectionId\", server,\n          storage -> storage.withProject(\"projectKey\",\n            project -> project.withMainBranch(\"branchName\",\n              branch -> branch.withTaintIssue(aServerTaintIssue(\"key1\").withIntroductionDate(introductionDate).withType(RuleType.VULNERABILITY)))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .withBackendCapability(FULL_SYNCHRONIZATION)\n        .start(client);\n      // Wait for synchronization so that the local storage has the correct severity mode\n      client.waitForSynchronization();\n      var storedTaintIssues = await().atMost(Duration.ofSeconds(2)).until(() -> readTaintVulnerabilities(backend, \"connectionId\", \"projectKey\", \"branchName\"),\n        taints -> taints.size() == 1);\n\n      backend.getIssueService().changeStatus(new ChangeIssueStatusParams(\"configScope\", \"key1\", ResolutionStatus.WONT_FIX, true));\n\n      await().atMost(Duration.ofSeconds(5)).untilAsserted(() -> assertThat(client.getTaintVulnerabilityChanges())\n        .usingRecursiveFieldByFieldElementComparatorIgnoringFields(\"addedTaintVulnerabilities.id\")\n        .contains(new DidChangeTaintVulnerabilitiesParams(\"configScope\", Set.of(), List.of(),\n          List.of(new TaintVulnerabilityDto(storedTaintIssues.get(0).getId(), \"key1\", true, null, \"ruleKey\", \"message\", Paths.get(\"file/path\"), introductionDate,\n            Either.forLeft(new StandardModeDetails(org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity.MINOR,\n              org.sonarsource.sonarlint.core.rpc.protocol.common.RuleType.VULNERABILITY)),\n            List.of(),\n            new TextRangeWithHashDto(1, 2, 3, 4, \"rangeHash\"), \"contextKey\", true, false)))));\n    }\n\n    @SonarLintTest\n    void it_should_notify_client_with_correct_mqr_severity_mode(SonarLintTestHarness harness) {\n      var server = harness.newFakeSonarQubeServer(\"10.5\")\n        .withServerSentEventsEnabled()\n        .withProject(\"projectKey\",\n          project -> project.withBranch(\"branchName\"))\n        .start();\n      var client = harness.newFakeClient().build();\n      when(client.matchSonarProjectBranch(eq(\"configScope\"), any(), any(), any())).thenReturn(\"branchName\");\n      var introductionDate = Instant.now().truncatedTo(ChronoUnit.SECONDS);\n      var backend = harness.newBackend()\n        .withExtraEnabledLanguagesInConnectedMode(JAVA)\n        .withSonarQubeConnection(\"connectionId\", server,\n          storage -> storage.withProject(\"projectKey\",\n            project -> project.withMainBranch(\"branchName\",\n              branch -> branch.withTaintIssue(aServerTaintIssue(\"key1\").withIntroductionDate(introductionDate).withType(RuleType.VULNERABILITY)))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .withBackendCapability(FULL_SYNCHRONIZATION)\n        .start(client);\n      // Wait for synchronization so that the local storage has the correct severity mode\n      client.waitForSynchronization();\n      var storedTaintIssues = await().atMost(Duration.ofSeconds(2)).until(() -> readTaintVulnerabilities(backend, \"connectionId\", \"projectKey\", \"branchName\"),\n        taints -> taints.size() == 1);\n\n      backend.getIssueService().changeStatus(new ChangeIssueStatusParams(\"configScope\", \"key1\", ResolutionStatus.WONT_FIX, true));\n\n      await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(client.getTaintVulnerabilityChanges())\n        .usingRecursiveFieldByFieldElementComparatorIgnoringFields(\"addedTaintVulnerabilities.id\")\n        .contains(new DidChangeTaintVulnerabilitiesParams(\"configScope\", Set.of(), List.of(),\n          List.of(new TaintVulnerabilityDto(storedTaintIssues.get(0).getId(), \"key1\", true, null, \"ruleKey\", \"message\", Paths.get(\"file/path\"), introductionDate,\n            Either.forRight(new MQRModeDetails(CleanCodeAttribute.CONVENTIONAL, List.of(new ImpactDto(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.MEDIUM)))), List.of(),\n            new TextRangeWithHashDto(1, 2, 3, 4, \"rangeHash\"), \"contextKey\", true, false)))));\n    }\n  }\n\n  private List<ServerTaintIssue> readTaintVulnerabilities(SonarLintTestRpcServer backend, String connectionId, String projectKey, String branchName) {\n    return backend.getIssueStorageService().connection(connectionId).project(projectKey).findings().loadTaint(branchName);\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/tracking/IssueStreamingRulesDefinition.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.tracking;\n\nimport org.sonar.api.rules.RuleType;\nimport org.sonar.api.server.rule.RulesDefinition;\n\npublic class IssueStreamingRulesDefinition implements RulesDefinition {\n\n  @Override\n  public void define(Context context) {\n    var repository = context.createRepository(\"repo\", \"java\");\n    repository.createRule(\"rule\")\n      .setType(RuleType.BUG)\n      .setName(\"Rule\")\n      .setActivatedByDefault(true)\n      .setHtmlDescription(\"desc\");\n    repository.done();\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/tracking/IssueStreamingSensor.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.tracking;\n\nimport org.sonar.api.batch.sensor.Sensor;\nimport org.sonar.api.batch.sensor.SensorContext;\nimport org.sonar.api.batch.sensor.SensorDescriptor;\nimport org.sonar.api.rule.RuleKey;\n\npublic class IssueStreamingSensor implements Sensor {\n\n  @Override\n  public void describe(SensorDescriptor descriptor) {\n    // no implementation needed for the current tests\n  }\n\n  @Override\n  public void execute(SensorContext context) {\n    raiseIssue(context, 1);\n    pause(500);\n    raiseIssue(context, 2);\n    pause(500);\n  }\n\n  private void raiseIssue(SensorContext context, int issueNumber) {\n    var newIssue = context.newIssue();\n    var newIssueLocation = newIssue.newLocation();\n    var firstFile = context.fileSystem().inputFiles(file -> true).iterator().next();\n    newIssue\n      .at(newIssueLocation\n        .message(\"Issue \" + issueNumber)\n        .at(firstFile.newRange(1, 0, 1, 1))\n        .on(firstFile))\n      .forRule(RuleKey.of(\"repo\", \"rule\")).save();\n  }\n\n  private void pause(long millis) {\n    try {\n      Thread.sleep(millis);\n    } catch (InterruptedException e) {\n      throw new RuntimeException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/tracking/IssueTrackingMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.tracking;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\nimport org.eclipse.jgit.api.errors.GitAPIException;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.mockito.ArgumentCaptor;\nimport org.sonar.scanner.protocol.Constants;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.rpc.client.SonarLintRpcClientDelegate;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingMode;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.DidUpdateFileSystemParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ResolutionStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedFindingDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogLevel;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.RuleType;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Qualityprofiles;\nimport org.sonarsource.sonarlint.core.serverconnection.proto.Sonarlint;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintBackendFixture;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport org.sonarsource.sonarlint.core.test.utils.storage.ServerIssueFixtures;\nimport uk.org.webcompere.systemstubs.environment.EnvironmentVariables;\nimport uk.org.webcompere.systemstubs.jupiter.SystemStub;\nimport uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension;\nimport utils.TestPlugin;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.get;\nimport static org.assertj.core.api.Assertions.as;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.assertj.core.api.InstanceOfAssertFactories.INSTANT;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.atLeast;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.reset;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.sonarsource.sonarlint.core.commons.dogfood.DogfoodEnvironmentDetectionService.SONARSOURCE_DOGFOODING_ENV_VAR_KEY;\nimport static org.sonarsource.sonarlint.core.commons.testutils.GitUtils.commit;\nimport static org.sonarsource.sonarlint.core.commons.testutils.GitUtils.commitAtDate;\nimport static org.sonarsource.sonarlint.core.commons.testutils.GitUtils.createRepository;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.FULL_SYNCHRONIZATION;\nimport static org.sonarsource.sonarlint.core.test.utils.ProtobufUtils.protobufBody;\nimport static org.sonarsource.sonarlint.core.test.utils.plugins.SonarPluginBuilder.newSonarPlugin;\nimport static org.sonarsource.sonarlint.core.test.utils.storage.ServerIssueFixtures.aServerIssue;\n\n@ExtendWith(SystemStubsExtension.class)\nclass IssueTrackingMediumTests {\n\n  @SystemStub\n  EnvironmentVariables environmentVariables;\n\n  @BeforeEach\n  void prepare() {\n    environmentVariables.remove(SONARSOURCE_DOGFOODING_ENV_VAR_KEY);\n  }\n\n  private static final String CONFIG_SCOPE_ID = \"CONFIG_SCOPE_ID\";\n\n  @SonarLintTest\n  void it_should_raise_tracked_and_untracked_issues_in_standalone_mode(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var ideFilePath = \"Foo.java\";\n    var filePath = createFile(baseDir, ideFilePath,\n      \"\"\"\n        package sonar;\n        // FIXME foo bar\n        public interface Foo {\n        }\"\"\");\n    var projectKey = \"projectKey\";\n    var connectionId = \"connectionId\";\n    var branchName = \"main\";\n    var ruleKey = \"java:S1134\";\n\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(connectionId, storage -> storage.withPlugin(TestPlugin.JAVA).withProject(projectKey,\n        project -> project.withRuleSet(\"java\", ruleSet -> ruleSet.withActiveRule(ruleKey, \"MINOR\"))\n          .withMainBranch(branchName)))\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .start(client);\n\n    var firstAnalysisPublishedIssues = analyzeFileAndGetAllIssuesOfRule(backend, client, fileUri, ruleKey);\n\n    assertThat(firstAnalysisPublishedIssues).hasSize(1);\n    changeFileContent(baseDir, ideFilePath,\n      \"\"\"\n        package sonar;\n        // FIXME foo bar\n        public interface Foo {\n        // FIXME bar baz\n        }\"\"\");\n    var secondAnalysisPublishedIssues = analyzeFileAndGetAllIssuesOfRule(backend, client, fileUri, ruleKey);\n    assertThat(secondAnalysisPublishedIssues).hasSize(2);\n\n    verifyClientLog(client, LogLevel.INFO, \"Git Repository not found\");\n  }\n\n  @Disabled(\"https://sonarsource.atlassian.net/browse/SLCORE-873\")\n  @SonarLintTest\n  void it_should_raise_tracked_and_untracked_issues_after_match_with_server_issues(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var ideFilePath = \"Foo.java\";\n    var filePath = createFile(baseDir, ideFilePath,\n      \"\"\"\n        // FIXME foo bar\n        public class Foo {\n        }\"\"\");\n    var projectKey = \"projectKey\";\n    var connectionId = \"connectionId\";\n    var branchName = \"main\";\n    var ruleKey = \"java:S1134\";\n    var message = \"Take the required action to fix the issue indicated by this comment.\";\n\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var server = harness.newFakeSonarQubeServer(\"9.9\")\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\", branch -> branch\n        .withIssue(\"uuid\", \"java:S1134\", message, \"author\", ideFilePath, \"395d7a96efa8afd1b66ab6b680d0e637\", Constants.Severity.BLOCKER,\n          org.sonarsource.sonarlint.core.commons.RuleType.BUG,\n          \"OPEN\", null, Instant.ofEpochMilli(123456789L), new TextRange(2, 0, 2, 16))))\n      .withQualityProfile(\"qp\", qualityProfile -> qualityProfile.withLanguage(\"java\")\n        .withActiveRule(ruleKey, activeRule -> activeRule.withSeverity(IssueSeverity.MAJOR)))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(connectionId, server,\n        storage -> storage.withPlugin(TestPlugin.JAVA).withProject(projectKey,\n          project -> project.withRuleSet(\"java\", ruleSet -> ruleSet.withActiveRule(ruleKey, \"MINOR\"))\n            .withMainBranch(branchName)))\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    backend.getConfigurationService()\n      .didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(\n        new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, CONFIG_SCOPE_ID,\n          new BindingConfigurationDto(connectionId, projectKey, true)))));\n\n    var firstAnalysisPublishedIssues = analyzeFileAndGetAllIssues(backend, client, fileUri);\n\n    assertThat(firstAnalysisPublishedIssues).hasSize(1);\n    changeFileContent(baseDir, ideFilePath,\n      \"\"\"\n        package sonar;\n        // FIXME foo bar\n        public interface Foo {\n        // FIXME bar baz\n        }\"\"\");\n    var secondAnalysisPublishedIssues = analyzeFileAndGetAllIssues(backend, client, fileUri);\n    assertThat(secondAnalysisPublishedIssues).hasSize(2);\n  }\n\n  @Disabled(\"https://sonarsource.atlassian.net/browse/SLCORE-873\")\n  @SonarLintTest\n  void it_should_use_server_new_code_definition_for_server_issues_and_set_true_for_unmatched_issues(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var ideFilePath = \"Foo.java\";\n    var filePath = createFile(baseDir, ideFilePath,\n      \"\"\"\n        // FIXME foo bar\n        // FIXME foo bar2\n        public class Foo {\n        }\"\"\");\n    var projectKey = \"projectKey\";\n    var connectionId = \"connectionId\";\n    var branchName = \"main\";\n    var ruleKey = \"java:S1134\";\n    var message = \"Take the required action to fix the issue indicated by this comment.\";\n\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var server = harness.newFakeSonarQubeServer(\"9.9\")\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\", branch -> branch\n        .withIssue(\"uuid1\", \"java:S1134\", message, \"author\", ideFilePath, \"395d7a96efa8afd1b66ab6b680d0e637\", Constants.Severity.BLOCKER,\n          org.sonarsource.sonarlint.core.commons.RuleType.BUG,\n          \"OPEN\", null, Instant.now().minus(1, ChronoUnit.DAYS), new TextRange(1, 0, 1, 16))\n        .withIssue(\"uuid2\", \"java:S1134\", message, \"author\", ideFilePath, \"395d7a96efa8afd1b66ab6b680d0e637\", Constants.Severity.BLOCKER,\n          org.sonarsource.sonarlint.core.commons.RuleType.BUG,\n          \"OPEN\", null, Instant.now().plus(1, ChronoUnit.DAYS), new TextRange(2, 0, 2, 16))))\n      .withQualityProfile(\"qp\", qualityProfile -> qualityProfile.withLanguage(\"java\")\n        .withActiveRule(ruleKey, activeRule -> activeRule.withSeverity(IssueSeverity.MAJOR)))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(connectionId, server,\n        storage -> storage.withPlugin(TestPlugin.JAVA).withProject(projectKey,\n          project -> project.withRuleSet(\"java\", ruleSet -> ruleSet.withActiveRule(ruleKey, \"MINOR\"))\n            .withNewCodeDefinition(\n              Sonarlint.NewCodeDefinition.newBuilder().setMode(Sonarlint.NewCodeDefinitionMode.PREVIOUS_VERSION).setThresholdDate(Instant.now().toEpochMilli()).build())\n            .withMainBranch(branchName)))\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    backend.getConfigurationService()\n      .didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(\n        new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, CONFIG_SCOPE_ID,\n          new BindingConfigurationDto(connectionId, projectKey, true)))));\n    client.waitForSynchronization();\n\n    var issues = analyzeFileAndGetAllIssues(backend, client, fileUri);\n    assertThat(issues).hasSize(2);\n    assertThat(issues.stream().filter(raisedIssueDto -> raisedIssueDto.getServerKey().equals(\"uuid1\")).findFirst().get().isOnNewCode()).isFalse();\n    assertThat(issues.stream().filter(raisedIssueDto -> raisedIssueDto.getServerKey().equals(\"uuid2\")).findFirst().get().isOnNewCode()).isTrue();\n\n    changeFileContent(baseDir, ideFilePath,\n      \"\"\"\n        package sonar;\n        // FIXME foo bar\n        // FIXME foo bar2\n        public interface Foo {\n        // FIXME bar baz\n        }\"\"\");\n    var secondAnalysisPublishedIssues = analyzeFileAndGetAllIssues(backend, client, fileUri);\n    assertThat(secondAnalysisPublishedIssues).hasSize(3);\n    assertThat(secondAnalysisPublishedIssues.stream().filter(raisedIssueDto -> Objects.isNull(raisedIssueDto.getServerKey())).findFirst().get().isOnNewCode()).isTrue();\n  }\n\n  @SonarLintTest\n  void it_should_use_git_blame_to_set_introduction_date_for_git_repos(SonarLintTestHarness harness, @TempDir Path baseDir) throws IOException, GitAPIException {\n    var repository = createRepository(baseDir);\n    var filePath = createFile(baseDir, \"Foobar.java\",\n      \"\"\"\n        package sonar;\n        public interface Foobar\n        {}\"\"\");\n    var commitInstant = Instant.ofEpochSecond(556437600);\n    commit(repository, commitInstant, filePath.getFileName().toString());\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    var issue = analyzeFileAndGetIssue(backend, client, fileUri);\n\n    assertThat(issue.getIntroductionDate()).isEqualTo(commitInstant);\n  }\n\n  @SonarLintTest\n  void it_should_use_git_blame_to_set_introduction_date_for_git_repos_for_given_content(SonarLintTestHarness harness, @TempDir Path baseDir) throws IOException, GitAPIException {\n    var repository = createRepository(baseDir);\n    var committedFileContent = \"\"\"\n      package sonar;\n      public interface Foobar\n      {}\"\"\";\n    var filePath = createFile(baseDir, \"Foobar.java\", committedFileContent);\n    commit(repository, filePath.getFileName().toString());\n    var fileUri = filePath.toUri();\n    var unsavedFileContent = \"\"\"\n      package sonar;\n      public interface Foobar\n      //TODO introduce new issue\n      {}\"\"\";\n\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir,\n        List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, committedFileContent, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n    changeFileContent(baseDir, filePath.getFileName().toString(), unsavedFileContent);\n\n    var analysisTime = Instant.now();\n    backend.getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(List.of(),\n      List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, unsavedFileContent, null, true)), List.of()));\n    var issues = analyzeFileAndGetAllIssues(backend, client, fileUri);\n\n    assertThat(issues)\n      .extracting(raisedIssueDto -> raisedIssueDto.getIntroductionDate().isAfter(analysisTime), raisedIssueDto -> raisedIssueDto.getTextRange().getStartLine())\n      .containsExactlyInAnyOrder(tuple(false, 1), tuple(true, 3));\n  }\n\n  @Disabled(\"https://sonarsource.atlassian.net/browse/SLCORE-873\")\n  @SonarLintTest\n  void it_should_track_issue_secondary_locations(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var ideFilePath = \"Foo.java\";\n    var filePath = createFile(baseDir, ideFilePath,\n      \"\"\"\n        package devoxx;\n\n        public class Foo {\n          public void run() {\n            prepare(\"action1\");\n            execute(\"action1\");\n            release(\"action1\");\n          }\n        }\n        \"\"\");\n    var projectKey = \"projectKey\";\n    var connectionId = \"connectionId\";\n    var branchName = \"main\";\n    var ruleKey = \"java:S1192\";\n    var message = \"Define a constant instead of duplicating this literal \\\"action1\\\" 3 times.\";\n\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var server = harness.newFakeSonarQubeServer(\"9.9\")\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\", branch -> branch\n        .withIssue(\"uuid\", \"java:S1192\", message, \"author\", ideFilePath, \"395d7a96efa8afd1b66ab6b680d0e637\", Constants.Severity.BLOCKER,\n          org.sonarsource.sonarlint.core.commons.RuleType.BUG,\n          \"OPEN\", null, Instant.ofEpochMilli(123456789L), new TextRange(5, 12, 5, 21))))\n      .withQualityProfile(\"qp\", qualityProfile -> qualityProfile.withLanguage(\"java\")\n        .withActiveRule(ruleKey, activeRule -> activeRule.withSeverity(IssueSeverity.MAJOR)))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(connectionId, server,\n        storage -> storage.withPlugin(TestPlugin.JAVA).withProject(projectKey,\n          project -> project.withRuleSet(\"java\", ruleSet -> ruleSet.withActiveRule(ruleKey, \"MINOR\"))\n            .withMainBranch(branchName)))\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    backend.getConfigurationService()\n      .didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(\n        new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, CONFIG_SCOPE_ID,\n          new BindingConfigurationDto(connectionId, projectKey, true)))));\n\n    var firstPublishedIssue = analyzeFileAndGetIssue(backend, client, fileUri);\n\n    assertThat(firstPublishedIssue)\n      .extracting(\"ruleKey\", \"primaryMessage\", \"severity\", \"type\", \"serverKey\", \"introductionDate\",\n        \"textRange.startLine\", \"textRange.startLineOffset\", \"textRange.endLine\", \"textRange.endLineOffset\")\n      .containsExactly(ruleKey, message, IssueSeverity.BLOCKER, RuleType.BUG, \"uuid\", Instant.ofEpochMilli(123456789L), 5, 12, 5, 21);\n    var flows = firstPublishedIssue.getFlows();\n    assertThat(flows).hasSize(3);\n    assertThat(flows.get(0).getLocations().get(0).getFileUri()).isEqualTo(fileUri);\n    assertThat(flows.get(1).getLocations().get(0).getFileUri()).isEqualTo(fileUri);\n    assertThat(flows.get(2).getLocations().get(0).getFileUri()).isEqualTo(fileUri);\n    assertThat(flows.get(0).getLocations().get(0).getMessage()).isEqualTo(\"Duplication\");\n    assertThat(flows.get(1).getLocations().get(0).getMessage()).isEqualTo(\"Duplication\");\n    assertThat(flows.get(2).getLocations().get(0).getMessage()).isEqualTo(\"Duplication\");\n    var textRange1 = flows.get(0).getLocations().get(0).getTextRange();\n    var textRange2 = flows.get(1).getLocations().get(0).getTextRange();\n    var textRange3 = flows.get(2).getLocations().get(0).getTextRange();\n\n    assertThat(textRange1.getStartLine()).isEqualTo(5);\n    assertThat(textRange1.getStartLineOffset()).isEqualTo(12);\n    assertThat(textRange1.getEndLine()).isEqualTo(5);\n    assertThat(textRange1.getEndLineOffset()).isEqualTo(21);\n\n    assertThat(textRange2.getStartLine()).isEqualTo(6);\n    assertThat(textRange2.getStartLineOffset()).isEqualTo(12);\n    assertThat(textRange2.getEndLine()).isEqualTo(6);\n    assertThat(textRange2.getEndLineOffset()).isEqualTo(21);\n\n    assertThat(textRange3.getStartLine()).isEqualTo(7);\n    assertThat(textRange3.getStartLineOffset()).isEqualTo(12);\n    assertThat(textRange3.getEndLine()).isEqualTo(7);\n    assertThat(textRange3.getEndLineOffset()).isEqualTo(21);\n  }\n\n  @Disabled(\"https://sonarsource.atlassian.net/browse/SLCORE-873\")\n  @SonarLintTest\n  void it_should_track_line_level_server_issue_on_same_line(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var ideFilePath = \"Foo.java\";\n    var filePath = createFile(baseDir, ideFilePath,\n      \"\"\"\n        // FIXME foo bar\n        public class Foo {\n        }\"\"\");\n    var projectKey = \"projectKey\";\n    var connectionId = \"connectionId\";\n    var branchName = \"main\";\n    var ruleKey = \"java:S1134\";\n    var message = \"Take the required action to fix the issue indicated by this comment.\";\n\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var server = harness.newFakeSonarQubeServer(\"9.9\")\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\", branch -> branch\n        .withIssue(\"uuid\", \"java:S1134\", message, \"author\", ideFilePath, \"395d7a96efa8afd1b66ab6b680d0e637\", Constants.Severity.BLOCKER,\n          org.sonarsource.sonarlint.core.commons.RuleType.BUG,\n          \"OPEN\", null, Instant.ofEpochMilli(123456789L), new TextRange(1, 0, 1, 16))))\n      .withQualityProfile(\"qp\", qualityProfile -> qualityProfile.withLanguage(\"java\")\n        .withActiveRule(ruleKey, activeRule -> activeRule.withSeverity(IssueSeverity.MAJOR)))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(connectionId, server,\n        storage -> storage.withPlugin(TestPlugin.JAVA).withProject(projectKey,\n          project -> project.withRuleSet(\"java\", ruleSet -> ruleSet.withActiveRule(ruleKey, \"MINOR\"))\n            .withMainBranch(branchName)))\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    backend.getConfigurationService()\n      .didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(\n        new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, CONFIG_SCOPE_ID,\n          new BindingConfigurationDto(connectionId, projectKey, true)))));\n\n    var firstPublishedIssue = analyzeFileAndGetIssue(backend, client, fileUri);\n\n    assertThat(firstPublishedIssue)\n      .extracting(\"ruleKey\", \"primaryMessage\", \"severity\", \"type\", \"serverKey\", \"introductionDate\",\n        \"textRange.startLine\", \"textRange.startLineOffset\", \"textRange.endLine\", \"textRange.endLineOffset\")\n      .containsExactly(ruleKey, message, IssueSeverity.BLOCKER, RuleType.BUG, \"uuid\", Instant.ofEpochMilli(123456789L), 1, 0, 1, 16);\n  }\n\n  @Disabled(\"https://sonarsource.atlassian.net/browse/SLCORE-873\")\n  @SonarLintTest\n  void it_should_track_line_level_server_issue_on_different_line(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var ideFilePath = \"Foo.java\";\n    var filePath = createFile(baseDir, ideFilePath,\n      \"\"\"\n        // FIXME foo bar\n        public class Foo {\n        }\"\"\");\n    var projectKey = \"projectKey\";\n    var connectionId = \"connectionId\";\n    var branchName = \"main\";\n    var ruleKey = \"java:S1134\";\n    var message = \"Take the required action to fix the issue indicated by this comment.\";\n\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var server = harness.newFakeSonarQubeServer(\"9.9\")\n      .withProject(\"projectKey\", project -> project.withBranch(\"main\", branch -> branch\n        .withIssue(\"uuid\", \"java:S1134\", message, \"author\", ideFilePath, \"395d7a96efa8afd1b66ab6b680d0e637\", Constants.Severity.BLOCKER,\n          org.sonarsource.sonarlint.core.commons.RuleType.BUG,\n          \"OPEN\", null, Instant.ofEpochMilli(123456789L), new TextRange(2, 0, 2, 16))))\n      .withQualityProfile(\"qp\", qualityProfile -> qualityProfile.withLanguage(\"java\")\n        .withActiveRule(ruleKey, activeRule -> activeRule.withSeverity(IssueSeverity.MAJOR)))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(connectionId, server,\n        storage -> storage.withPlugin(TestPlugin.JAVA).withProject(projectKey,\n          project -> project.withRuleSet(\"java\", ruleSet -> ruleSet.withActiveRule(ruleKey, \"MINOR\"))\n            .withMainBranch(branchName)))\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    backend.getConfigurationService()\n      .didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(\n        new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, CONFIG_SCOPE_ID,\n          new BindingConfigurationDto(connectionId, projectKey, true)))));\n    var firstPublishedIssue = analyzeFileAndGetIssue(backend, client, fileUri);\n\n    assertThat(firstPublishedIssue)\n      .extracting(\"ruleKey\", \"primaryMessage\", \"severity\", \"type\", \"serverKey\", \"introductionDate\",\n        \"textRange.startLine\", \"textRange.startLineOffset\", \"textRange.endLine\", \"textRange.endLineOffset\")\n      .containsExactly(ruleKey, message, IssueSeverity.BLOCKER, RuleType.BUG, \"uuid\", Instant.ofEpochMilli(123456789L), 1, 0, 1, 16);\n  }\n\n  @SonarLintTest\n  void it_should_track_file_level_issue(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"Fubar.java\",\n      \"public interface Fubar\\n\" +\n        \"{}\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    var firstPublishedIssue = analyzeFileAndGetIssue(backend, client, fileUri);\n\n    var newPublishedIssue = analyzeFileAndGetIssue(backend, client, fileUri);\n\n    assertThat(newPublishedIssue)\n      .extracting(RaisedIssueDto::getId, RaisedIssueDto::getIntroductionDate)\n      .containsExactly(firstPublishedIssue.getId(), firstPublishedIssue.getIntroductionDate());\n  }\n\n  @SonarLintTest\n  void it_should_test_quick_fixes(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"FileHelper.java\",\n      \"\"\"\n        package myapp.helpers;\n\n        import java.io.IOException;\n        import java.nio.file.*;\n        import java.lang.Runnable;  // Noncompliant - java.lang is imported by default\n\n        public class FileHelper {\n            public static String readFirstLine(String filePath) throws IOException {\n                return Files.readAllLines(Paths.get(filePath)).get(0);\n            }\n        }\"\"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    var issues = analyzeFileAndGetAllIssues(backend, client, fileUri);\n\n    assertThat(issues)\n      .extracting(\"ruleKey\", \"primaryMessage\", \"textRange.startLine\", \"textRange.startLineOffset\", \"textRange.endLine\", \"textRange.endLineOffset\")\n      .contains(tuple(\"java:S1128\", \"Remove this unnecessary import: java.lang classes are always implicitly imported.\", 5, 7, 5, 25));\n    var issue = issues.stream().filter(i -> i.getRuleKey().equals(\"java:S1128\")).findFirst().get();\n    var quickFixes = issue.getQuickFixes();\n    assertThat(quickFixes).isNotEmpty();\n    var quickFix = quickFixes.get(0);\n    assertThat(quickFix.message()).isEqualTo(\"Remove the import\");\n    var fileEdits = quickFix.fileEdits();\n    assertThat(quickFixes).isNotEmpty();\n    var fileEdit = fileEdits.get(0);\n    assertThat(fileEdit.target()).isEqualTo(fileUri);\n    assertThat(fileEdit.textEdits().get(0).newText()).isEmpty();\n    var textRange = fileEdit.textEdits().get(0).range();\n    assertThat(textRange.getStartLine()).isEqualTo(4);\n    assertThat(textRange.getStartLineOffset()).isEqualTo(23);\n    assertThat(textRange.getEndLine()).isEqualTo(5);\n    assertThat(textRange.getEndLineOffset()).isEqualTo(26);\n  }\n\n  @SonarLintTest\n  void it_should_start_tracking_an_issue_in_standalone_mode_when_detected_for_the_first_time(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFileWithAnXmlIssue(baseDir);\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n    var startTime = System.currentTimeMillis();\n\n    var publishedIssue = analyzeFileAndGetIssue(backend, client, fileUri);\n\n    assertThat(publishedIssue)\n      .extracting(RaisedIssueDto::getIntroductionDate, as(INSTANT))\n      .isAfter(Instant.ofEpochMilli(startTime));\n  }\n\n  @SonarLintTest\n  void it_should_match_an_already_tracked_issue_in_standalone_mode_when_detected_for_the_second_time(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFileWithAnXmlIssue(baseDir);\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n\n    var firstPublishedIssue = analyzeFileAndGetIssue(backend, client, fileUri);\n\n    var newPublishedIssue = analyzeFileAndGetIssue(backend, client, fileUri);\n\n    assertThat(newPublishedIssue)\n      .extracting(RaisedIssueDto::getId, RaisedIssueDto::getIntroductionDate)\n      .containsExactly(firstPublishedIssue.getId(), firstPublishedIssue.getIntroductionDate());\n  }\n\n  @SonarLintTest\n  void it_should_start_tracking_an_issue_in_connected_mode_when_detected_for_the_first_time(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFileWithAnXmlIssue(baseDir);\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var server = harness.newFakeSonarQubeServer().withPlugin(TestPlugin.XML).start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server,\n        storage -> storage.withPlugin(TestPlugin.XML).withProject(\"projectKey\", project -> project.withMainBranch(\"main\")\n          .withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MINOR\"))))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, \"connectionId\", \"projectKey\")\n      .withExtraEnabledLanguagesInConnectedMode(Language.XML)\n      .start(client);\n    var startTime = System.currentTimeMillis();\n\n    var publishedIssue = analyzeFileAndGetIssue(backend, client, fileUri);\n\n    assertThat(publishedIssue)\n      .extracting(RaisedIssueDto::getIntroductionDate, as(INSTANT))\n      .isAfter(Instant.ofEpochMilli(startTime));\n  }\n\n  @SonarLintTest\n  void it_should_match_an_already_tracked_issue_in_connected_mode_when_detected_for_the_second_time(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFileWithAnXmlIssue(baseDir);\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var server = harness.newFakeSonarQubeServer().withPlugin(TestPlugin.XML).start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server,\n        storage -> storage.withPlugin(TestPlugin.XML).withProject(\"projectKey\",\n          project -> project.withMainBranch(\"main\").withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MINOR\"))))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, \"connectionId\", \"projectKey\")\n      .withExtraEnabledLanguagesInConnectedMode(Language.XML)\n      .start(client);\n\n    var firstPublishedIssue = analyzeFileAndGetIssue(backend, client, fileUri);\n\n    var newPublishedIssue = analyzeFileAndGetIssue(backend, client, fileUri);\n\n    assertThat(newPublishedIssue)\n      .extracting(RaisedIssueDto::getId, RaisedIssueDto::getIntroductionDate)\n      .containsExactly(firstPublishedIssue.getId(), firstPublishedIssue.getIntroductionDate());\n  }\n\n  @SonarLintTest\n  void it_should_match_a_local_issue_with_a_server_issue_in_connected_mode_when_detected_for_the_first_time(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFileWithAnXmlIssue(baseDir);\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var serverIssueIntroductionDate = Instant.ofEpochMilli(12345678);\n    var server = harness.newFakeSonarQubeServer()\n      .withPlugin(TestPlugin.XML)\n      .withProject(\"projectKey\").start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server,\n        storage -> storage.withPlugin(TestPlugin.XML).withProject(\"projectKey\",\n          project -> project.withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MINOR\"))\n            .withMainBranch(\"main\", branch -> branch.withIssue(\n              pomServerIssue(\"key\", serverIssueIntroductionDate)))))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, \"connectionId\", \"projectKey\")\n      .withExtraEnabledLanguagesInConnectedMode(Language.XML)\n      .start(client);\n\n    var publishedIssue = analyzeFileAndGetIssue(backend, client, fileUri);\n\n    assertThat(publishedIssue)\n      .extracting(RaisedIssueDto::getServerKey, RaisedIssueDto::getIntroductionDate)\n      .containsExactly(\"key\", serverIssueIntroductionDate);\n  }\n\n  @SonarLintTest\n  void it_should_expose_resolution_statuses_of_issues(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var acceptedPath = baseDir.resolve(\"accepted\");\n    var wontFixPath = baseDir.resolve(\"wontFix\");\n    var falsePositivePath = baseDir.resolve(\"falsePositive\");\n    var unresolvedIssueFile = createFileWithAnXmlIssue(baseDir);\n    var acceptedIssueFile = createFileWithAnXmlIssue(acceptedPath);\n    var wontFixIssueFile = createFileWithAnXmlIssue(wontFixPath);\n    var falsePositiveIssueFile = createFileWithAnXmlIssue(falsePositivePath);\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(\n        getClientFileDto(baseDir, unresolvedIssueFile),\n        getClientFileDto(baseDir, acceptedIssueFile),\n        getClientFileDto(baseDir, wontFixIssueFile),\n        getClientFileDto(baseDir, falsePositiveIssueFile)))\n      .build();\n    var serverIssueIntroductionDate = Instant.ofEpochMilli(12345678);\n    var server = harness.newFakeSonarQubeServer()\n      .withPlugin(TestPlugin.XML)\n      .withProject(\"projectKey\").start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server,\n        storage -> storage.withPlugin(TestPlugin.XML).withProject(\"projectKey\",\n          project -> project.withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MINOR\"))\n            .withMainBranch(\"main\", branch -> branch\n              .withIssue(pomServerIssue(\"key\", serverIssueIntroductionDate))\n              .withIssue(pomServerIssue(\"key2\", serverIssueIntroductionDate, \"accepted/\")\n                .resolved(IssueStatus.ACCEPT))\n              .withIssue(pomServerIssue(\"key3\", serverIssueIntroductionDate, \"wontFix/\")\n                .resolved(IssueStatus.WONT_FIX))\n              .withIssue(pomServerIssue(\"key4\", serverIssueIntroductionDate, \"falsePositive/\")\n                .resolved(IssueStatus.FALSE_POSITIVE)))))\n      .withBoundConfigScope(CONFIG_SCOPE_ID, \"connectionId\", \"projectKey\")\n      .withExtraEnabledLanguagesInConnectedMode(Language.XML)\n      .start(client);\n\n    var unresolved = analyzeFileAndGetIssue(backend, client, unresolvedIssueFile.toUri());\n    var accepted = analyzeFileAndGetIssue(backend, client, acceptedIssueFile.toUri());\n    var wontFix = analyzeFileAndGetIssue(backend, client, wontFixIssueFile.toUri());\n    var falsePositive = analyzeFileAndGetIssue(backend, client, falsePositiveIssueFile.toUri());\n\n    assertThat(unresolved).extracting(RaisedIssueDto::isResolved, RaisedIssueDto::getResolutionStatus)\n      .containsExactly(false, null);\n    assertThat(accepted).extracting(RaisedIssueDto::isResolved, RaisedIssueDto::getResolutionStatus)\n      .containsExactly(true, ResolutionStatus.ACCEPT);\n    assertThat(wontFix).extracting(RaisedIssueDto::isResolved, RaisedIssueDto::getResolutionStatus)\n      .containsExactly(true, ResolutionStatus.WONT_FIX);\n    assertThat(falsePositive).extracting(RaisedIssueDto::isResolved, RaisedIssueDto::getResolutionStatus)\n      .containsExactly(true, ResolutionStatus.FALSE_POSITIVE);\n  }\n\n  @SonarLintTest\n  void it_should_match_a_previously_tracked_issue_with_a_server_issue_when_binding(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFileWithAnXmlIssue(baseDir);\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var serverIssueIntroductionDate = Instant.ofEpochMilli(12345678);\n    var server = harness.newFakeSonarQubeServer()\n      .withPlugin(TestPlugin.XML)\n      .withProject(\"projectKey\")\n      .withQualityProfile(\"qp\", qualityProfile -> qualityProfile.withLanguage(\"xml\")\n        .withActiveRule(\"xml:S3421\", activeRule -> activeRule.withSeverity(IssueSeverity.MAJOR)))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(\"connectionId\", server,\n        storage -> storage.withPlugin(TestPlugin.XML).withProject(\"projectKey\",\n          project -> project.withRuleSet(\"xml\", ruleSet -> ruleSet.withActiveRule(\"xml:S3421\", \"MINOR\"))\n            .withMainBranch(\"main\", branch -> branch.withIssue(\n              pomServerIssue(\"key\", serverIssueIntroductionDate)))))\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n\n    var firstPublishedIssue = analyzeFileAndGetIssue(backend, client, fileUri);\n\n    backend.getConfigurationService().didUpdateBinding(new DidUpdateBindingParams(CONFIG_SCOPE_ID,\n      new BindingConfigurationDto(\"connectionId\", \"projectKey\", true),\n      BindingMode.MANUAL,\n      null));\n    server.getMockServer().stubFor(get(\"/api/qualityprofiles/search.protobuf?project=projectKey\")\n      .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(Qualityprofiles.SearchWsResponse.newBuilder().addProfiles(\n        Qualityprofiles.SearchWsResponse.QualityProfile.newBuilder()\n          .setKey(\"qualityProfileKey\")\n          .setLanguage(\"xml\")\n          .setLanguageName(\"xml\")\n          .setName(\"Quality Profile\")\n          .setRulesUpdatedAt(Instant.now().toString())\n          .setUserUpdatedAt(Instant.now().toString())\n          .setIsDefault(true)\n          .setActiveRuleCount(1)\n          .build())\n        .build()))));\n\n    var newPublishedIssue = analyzeFileAndGetIssue(backend, client, fileUri);\n\n    assertThat(newPublishedIssue)\n      .extracting(RaisedIssueDto::getId, RaisedIssueDto::getServerKey, RaisedIssueDto::getIntroductionDate)\n      .containsExactly(firstPublishedIssue.getId(), \"key\", serverIssueIntroductionDate);\n  }\n\n  @SonarLintTest\n  void it_should_submit_server_path_to_sc_web_api(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var folder = createFolder(baseDir, \"local/path/prefix\");\n    var filePath = createFile(baseDir, folder.resolve(\"pom.xml\").toString(),\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n    var fileUri = filePath.toUri();\n    var orgKey = \"myOrganization\";\n    var connectionId = \"connectionId\";\n    var projectKey = \"projectKey\";\n    var server = harness.newFakeSonarCloudServer()\n      .withOrganization(orgKey, organization -> organization\n        .withProject(projectKey, project -> project\n          .withFile(\"server/path/prefix/pom.xml\")\n          .withQualityProfile(\"qp\"))\n        .withQualityProfile(\"qp\", qualityProfile -> qualityProfile.withLanguage(\"xml\")\n          .withActiveRule(\"xml:S3421\", rule -> rule.withSeverity(IssueSeverity.MAJOR))))\n      .start();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withSonarCloudConnection(connectionId, orgKey, true, storage -> {\n        storage.withProject(projectKey, project -> project.withMainBranch(\"main\"));\n      })\n      .withBackendCapability(FULL_SYNCHRONIZATION)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarQubeCloudEuRegionApiUri(server.baseUrl())\n      .withBoundConfigScope(CONFIG_SCOPE_ID, connectionId, projectKey)\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.XML)\n      .start(client);\n    client.waitForSynchronization();\n\n    backend.getAnalysisService().analyzeFilesAndTrack(\n      new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(), List.of(fileUri), Map.of(), true)).join();\n    await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(CONFIG_SCOPE_ID)).isNotEmpty());\n\n    var requests = server.getMockServer().getServeEvents().getRequests();\n    assertThat(requests).extracting(\"request.url\")\n      .contains(\"/batch/issues?key=projectKey%3Aserver%2Fpath%2Fprefix%2Fpom.xml&branch=main\");\n    assertThat(backend.getStorageRoot().resolve(\"cache\")).exists();\n  }\n\n  @SonarLintTest\n  void it_should_stream_issues(SonarLintTestHarness harness, @TempDir Path baseDir) throws IOException, GitAPIException {\n    var repository = createRepository(baseDir);\n    var filePath = createFile(baseDir, \"Foo.java\", \"a\");\n    var introductionDate = Instant.now().minus(5, ChronoUnit.DAYS).truncatedTo(ChronoUnit.SECONDS);\n    var commitDate = commitAtDate(repository, introductionDate, filePath.getFileName().toString());\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var pluginPath = newSonarPlugin(\"java\")\n      .withSensor(IssueStreamingSensor.class)\n      .withRulesDefinition(IssueStreamingRulesDefinition.class)\n      .generate(baseDir);\n    var backend = harness.newBackend()\n      .withBackendCapability(BackendCapability.ISSUE_STREAMING)\n      .withStandaloneEmbeddedPlugin(pluginPath)\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .start(client);\n\n    analyzeFileAndGetAllIssues(backend, client, fileUri);\n\n    ArgumentCaptor<Map<URI, List<RaisedIssueDto>>> intermediateIssuesByFileArgumentCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(client, times(2)).raiseIssues(eq(CONFIG_SCOPE_ID), intermediateIssuesByFileArgumentCaptor.capture(), eq(true), any());\n    var allRaisedIntermediateIssuesByFile = intermediateIssuesByFileArgumentCaptor.getAllValues();\n    var firstRaisedIntermediateIssuesByFile = allRaisedIntermediateIssuesByFile.get(0);\n    assertThat(firstRaisedIntermediateIssuesByFile).containsOnlyKeys(fileUri);\n    assertThat(firstRaisedIntermediateIssuesByFile.get(fileUri))\n      .extracting(RaisedIssueDto::getPrimaryMessage, RaisedFindingDto::getIntroductionDate, RaisedFindingDto::isOnNewCode, f -> f.getSeverityMode().isRight())\n      .containsExactly(tuple(\"Issue 1\", introductionDate, true, true));\n    var secondRaisedIntermediateIssuesByFile = allRaisedIntermediateIssuesByFile.get(1);\n    assertThat(secondRaisedIntermediateIssuesByFile).containsOnlyKeys(fileUri);\n    assertThat(secondRaisedIntermediateIssuesByFile.get(fileUri))\n      .extracting(RaisedIssueDto::getPrimaryMessage, RaisedFindingDto::getIntroductionDate, RaisedFindingDto::isOnNewCode, f -> f.getSeverityMode().isRight())\n      .containsExactly(tuple(\"Issue 1\", introductionDate, true, true), tuple(\"Issue 2\", commitDate, true, true));\n    ArgumentCaptor<Map<URI, List<RaisedIssueDto>>> finalIssuesByFileArgumentCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(client).raiseIssues(eq(CONFIG_SCOPE_ID), finalIssuesByFileArgumentCaptor.capture(), eq(false), any());\n    var finalIssuesByFile = finalIssuesByFileArgumentCaptor.getValue();\n    assertThat(secondRaisedIntermediateIssuesByFile.keySet()).isEqualTo(finalIssuesByFile.keySet());\n    assertThat(secondRaisedIntermediateIssuesByFile.get(fileUri)).usingRecursiveFieldByFieldElementComparatorIgnoringFields().isEqualTo(finalIssuesByFile.get(fileUri));\n  }\n\n  @SonarLintTest\n  void it_should_stream_issues_on_two_analyses_in_a_row(SonarLintTestHarness harness, @TempDir Path baseDir) throws IOException, GitAPIException {\n    var repository = createRepository(baseDir);\n    var filePath = createFile(baseDir, \"Foo.java\", \"a\");\n    var introductionDate = Instant.now().minus(5, ChronoUnit.DAYS).truncatedTo(ChronoUnit.SECONDS);\n    commitAtDate(repository, introductionDate, filePath.getFileName().toString());\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var pluginPath = newSonarPlugin(\"java\")\n      .withSensor(IssueStreamingSensor.class)\n      .withRulesDefinition(IssueStreamingRulesDefinition.class)\n      .generate(baseDir);\n    var backend = harness.newBackend()\n      .withBackendCapability(BackendCapability.ISSUE_STREAMING)\n      .withStandaloneEmbeddedPlugin(pluginPath)\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .start(client);\n    analyzeFileAndGetAllIssues(backend, client, fileUri);\n    reset(client);\n\n    analyzeFileAndGetAllIssues(backend, client, fileUri);\n\n    ArgumentCaptor<Map<URI, List<RaisedIssueDto>>> intermediateIssuesByFileArgumentCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(client, times(2)).raiseIssues(eq(CONFIG_SCOPE_ID), intermediateIssuesByFileArgumentCaptor.capture(), eq(true), any());\n    var allRaisedIntermediateIssuesByFile = intermediateIssuesByFileArgumentCaptor.getAllValues();\n    var firstRaisedIntermediateIssuesByFile = allRaisedIntermediateIssuesByFile.get(0);\n    assertThat(firstRaisedIntermediateIssuesByFile).containsOnlyKeys(fileUri);\n    assertThat(firstRaisedIntermediateIssuesByFile.get(fileUri))\n      .extracting(RaisedIssueDto::getPrimaryMessage, RaisedFindingDto::getIntroductionDate, RaisedFindingDto::isOnNewCode, f -> f.getSeverityMode().isRight())\n      .containsExactly(tuple(\"Issue 1\", introductionDate, true, true));\n    var secondRaisedIntermediateIssuesByFile = allRaisedIntermediateIssuesByFile.get(1);\n    assertThat(secondRaisedIntermediateIssuesByFile).containsOnlyKeys(fileUri);\n    assertThat(secondRaisedIntermediateIssuesByFile.get(fileUri))\n      .extracting(RaisedIssueDto::getPrimaryMessage, RaisedFindingDto::getIntroductionDate, RaisedFindingDto::isOnNewCode, f -> f.getSeverityMode().isRight())\n      .containsExactly(tuple(\"Issue 1\", introductionDate, true, true), tuple(\"Issue 2\", introductionDate, true, true));\n    ArgumentCaptor<Map<URI, List<RaisedIssueDto>>> finalIssuesByFileArgumentCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(client).raiseIssues(eq(CONFIG_SCOPE_ID), finalIssuesByFileArgumentCaptor.capture(), eq(false), any());\n    var finalIssuesByFile = finalIssuesByFileArgumentCaptor.getValue();\n    assertThat(secondRaisedIntermediateIssuesByFile.keySet()).isEqualTo(finalIssuesByFile.keySet());\n    assertThat(secondRaisedIntermediateIssuesByFile.get(fileUri)).usingRecursiveFieldByFieldElementComparatorIgnoringFields().isEqualTo(finalIssuesByFile.get(fileUri));\n  }\n\n  @SonarLintTest\n  void it_should_include_a_file_without_issues_when_raising_issues(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"Foo.java\", \"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var pluginPath = newSonarPlugin(\"java\")\n      .generate(baseDir);\n    var backend = harness.newBackend()\n      .withStandaloneEmbeddedPlugin(pluginPath)\n      .withEnabledLanguageInStandaloneMode(Language.JAVA)\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .start(client);\n    backend.getAnalysisService().analyzeFilesAndTrack(\n      new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, UUID.randomUUID(), List.of(fileUri), Map.of(), true))\n      .join();\n\n    verify(client, timeout(300)).raiseIssues(eq(CONFIG_SCOPE_ID), eq(Map.of(fileUri, List.of())), eq(false), any());\n    verify(client, never()).raiseIssues(eq(CONFIG_SCOPE_ID), any(), eq(true), any());\n  }\n\n  @SonarLintTest\n  void it_should_notify_client_when_analysis_finishes_without_starting(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, \"Ignored.foo\", \"irrelevant\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .withFileExclusions(CONFIG_SCOPE_ID, Set.of(\"**/Ignored.foo\"))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    var analysisId = UUID.randomUUID();\n    backend.getAnalysisService().analyzeFilesAndTrack(\n      new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(fileUri), Map.of(), true))\n      .join();\n\n    verify(client, timeout(300)).raiseIssues(CONFIG_SCOPE_ID, Map.of(), false, analysisId);\n    verify(client, never()).raiseIssues(eq(CONFIG_SCOPE_ID), any(), eq(true), any());\n  }\n\n  @SonarLintTest\n  void it_should_not_match_the_same_issue_twice(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, baseDir.resolve(\"file.py\").toString(),\n      \"\"\"\n        # TODO try this\n        # TODO try that\n        \"\"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(client);\n\n    var raisedIssueDtos = analyzeFileAndGetAllIssues(backend, client, fileUri);\n    assertThat(raisedIssueDtos).hasSize(2);\n\n    changeFileContent(baseDir, filePath.getFileName().toString(), \"\"\"\n        # TODO try that\n      \"\"\");\n    raisedIssueDtos = analyzeFileAndGetAllIssues(backend, client, fileUri);\n    assertThat(raisedIssueDtos).hasSize(1);\n\n    changeFileContent(baseDir, filePath.getFileName().toString(), \"\"\"\n        # TODO try this\n        # TODO try that\n      \"\"\");\n    raisedIssueDtos = analyzeFileAndGetAllIssues(backend, client, fileUri);\n    assertThat(raisedIssueDtos)\n      .hasSize(2);\n    assertThat(raisedIssueDtos.stream().map(RaisedFindingDto::getId).collect(Collectors.toSet()))\n      .hasSize(2);\n  }\n\n  @SonarLintTest\n  void it_should_migrate_the_known_issues_from_xodus_to_the_new_h2_database(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var filePath = createFile(baseDir, baseDir.resolve(\"file.py\").toString(),\n      \"\"\"\n        # TODO try this\n        \"\"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(client);\n\n    var raisedIssueDtos = analyzeFileAndGetAllIssues(backend, client, fileUri);\n    assertThat(raisedIssueDtos).hasSize(1);\n    var raisedIssue = raisedIssueDtos.get(0);\n    harness.shutdown(backend);\n    environmentVariables.set(SONARSOURCE_DOGFOODING_ENV_VAR_KEY, \"1\");\n\n    backend = harness.newBackend()\n      .withStorageRoot(backend.getStorageRoot())\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)\n      .start(client);\n    var newRaisedIssueDtos = analyzeFileAndGetAllIssues(backend, client, fileUri);\n    assertThat(newRaisedIssueDtos)\n      .hasSize(1)\n      .first()\n      .extracting(RaisedFindingDto::getId)\n      .isEqualTo(raisedIssue.getId());\n  }\n\n  private List<RaisedIssueDto> analyzeFileAndGetAllIssuesOfRule(SonarLintTestRpcServer backend, SonarLintRpcClientDelegate client, URI fileUri, String ruleKey) {\n    var raisedIssues = analyzeFileAndGetAllIssues(backend, client, fileUri);\n    return raisedIssues.stream().filter(ri -> ri.getRuleKey().equals(ruleKey)).toList();\n  }\n\n  private List<RaisedIssueDto> analyzeFileAndGetAllIssues(SonarLintTestRpcServer backend, SonarLintRpcClientDelegate client, URI fileUri) {\n    var analysisId = UUID.randomUUID();\n    var analysisResult = backend.getAnalysisService().analyzeFilesAndTrack(\n      new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(fileUri), Map.of(), true)).join();\n    var publishedIssuesByFile = getPublishedIssues(client, analysisId);\n    assertThat(analysisResult.getFailedAnalysisFiles()).isEmpty();\n    assertThat(publishedIssuesByFile).containsOnlyKeys(fileUri);\n    return publishedIssuesByFile.get(fileUri);\n  }\n\n  private RaisedIssueDto analyzeFileAndGetIssue(SonarLintTestRpcServer backend, SonarLintRpcClientDelegate client, URI fileUri) {\n    var publishedIssues = analyzeFileAndGetAllIssues(backend, client, fileUri);\n    assertThat(publishedIssues).hasSize(1);\n    return publishedIssues.get(0);\n  }\n\n  private static Path createFileWithAnXmlIssue(Path folderPath) {\n    return createFile(folderPath, \"pom.xml\",\n      \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <project>\n          <modelVersion>4.0.0</modelVersion>\n          <groupId>com.foo</groupId>\n          <artifactId>bar</artifactId>\n          <version>${pom.version}</version>\n        </project>\"\"\");\n  }\n\n  private Map<URI, List<RaisedIssueDto>> getPublishedIssues(SonarLintRpcClientDelegate client, UUID analysisId) {\n    ArgumentCaptor<Map<URI, List<RaisedIssueDto>>> trackedIssuesCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(client, timeout(300)).raiseIssues(eq(CONFIG_SCOPE_ID), trackedIssuesCaptor.capture(), eq(false), eq(analysisId));\n    return trackedIssuesCaptor.getValue();\n  }\n\n  private static Path createFile(Path folderPath, String fileName, String content) {\n    var filePath = folderPath.resolve(fileName);\n    try {\n      Files.createDirectories(folderPath);\n      Files.writeString(filePath, content);\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n    return filePath;\n  }\n\n  private static Path createFolder(Path baseDir, String folderPath) {\n    var path = baseDir.resolve(folderPath);\n    try {\n      Files.createDirectories(path);\n      return path;\n    } catch (IOException e) {\n      System.out.println(\"Couldn't create folder \" + path);\n    }\n    return null;\n  }\n\n  private static void changeFileContent(Path folderPath, String fileName, String content) {\n    var filePath = folderPath.resolve(fileName);\n    try {\n      Files.writeString(filePath, content);\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  public static void verifyClientLog(SonarLintBackendFixture.FakeSonarLintRpcClient client, LogLevel logLevel, String message) {\n    var argumentCaptor = ArgumentCaptor.forClass(LogParams.class);\n    verify(client, atLeast(1)).log(argumentCaptor.capture());\n    assertThat(argumentCaptor.getAllValues())\n      .anySatisfy(logParam -> {\n        assertThat(logParam.getMessage()).contains(message);\n        assertThat(logParam.getLevel()).isEqualTo(logLevel);\n      });\n\n  }\n\n  private static ServerIssueFixtures.ServerIssueBuilder pomServerIssue(String key, Instant serverIssueIntroductionDate) {\n    return pomServerIssue(key, serverIssueIntroductionDate, \"\");\n  }\n\n  private static ServerIssueFixtures.ServerIssueBuilder pomServerIssue(String key, Instant serverIssueIntroductionDate, String filePath) {\n    return aServerIssue(key)\n      .withMessage(\"Replace \\\"pom.version\\\" with \\\"project.version\\\".\")\n      .withFilePath(filePath + \"pom.xml\")\n      .withRuleKey(\"xml:S3421\")\n      .withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"5507902b11374f7b2a6951d70635435d\"))\n      .withIntroductionDate(serverIssueIntroductionDate);\n  }\n\n  private static ClientFileDto getClientFileDto(Path baseDir, Path filePath) {\n    return new ClientFileDto(filePath.toUri(), baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true);\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/tracking/SecurityHotspotTrackingMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.tracking;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.mockito.ArgumentCaptor;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.rpc.client.SonarLintRpcClientDelegate;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.HotspotStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.RaisedHotspotDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.RuleType;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport utils.TestPlugin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.verify;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SECURITY_HOTSPOTS;\n\nclass SecurityHotspotTrackingMediumTests {\n\n  private static final String CONFIG_SCOPE_ID = \"CONFIG_SCOPE_ID\";\n\n  @SonarLintTest\n  void it_should_track_server_hotspot(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var ideFilePath = \"Foo.java\";\n    var filePath = createFile(baseDir, ideFilePath,\n      \"\"\"\n        package sonar;\n        import java.security.MessageDigest;\n        public class Foo {\n          public void run() throws Exception {\n            MessageDigest md = MessageDigest.getInstance(\"MD5\");\n          }\n        }\"\"\");\n    var projectKey = \"projectKey\";\n    var connectionId = \"connectionId\";\n    var branchName = \"main\";\n    var ruleKey = \"java:S4790\";\n    var message = \"Make sure this weak hash algorithm is not used in a sensitive context here.\";\n\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var server = harness.newFakeSonarQubeServer(\"10.0\")\n      .withProject(projectKey, project -> project.withBranch(branchName, branch -> branch\n        .withHotspot(\"uuid\", hotspot -> hotspot.withAuthor(\"author\")\n          .withCreationDate(Instant.ofEpochSecond(123456789L))\n          .withFilePath(ideFilePath)\n          .withMessage(message)\n          .withRuleKey(ruleKey)\n          .withTextRange(new TextRange(5, 37, 5, 48))\n          .withStatus(HotspotReviewStatus.TO_REVIEW)\n          .withVulnerabilityProbability(VulnerabilityProbability.HIGH))))\n      .withQualityProfile(\"qp\", qualityProfile -> qualityProfile.withLanguage(\"java\")\n        .withActiveRule(ruleKey, activeRule -> activeRule.withSeverity(IssueSeverity.MAJOR)))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(connectionId, server,\n        storage -> storage.withPlugin(TestPlugin.JAVA).withProject(projectKey,\n          project -> project.withRuleSet(\"java\", ruleSet -> ruleSet.withActiveRule(ruleKey, \"MINOR\"))\n            .withMainBranch(branchName)))\n      .withBackendCapability(SECURITY_HOTSPOTS)\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n    backend.getConfigurationService()\n      .didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(\n        new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, CONFIG_SCOPE_ID,\n          new BindingConfigurationDto(connectionId, projectKey, true)))));\n\n    var firstPublishedHotspot = analyzeFileAndGetHotspot(backend, fileUri, client);\n\n    assertThat(firstPublishedHotspot)\n      .extracting(\"ruleKey\", \"primaryMessage\", \"severityMode.left.severity\", \"severityMode.left.type\", \"serverKey\", \"status\", \"introductionDate\",\n        \"textRange.startLine\", \"textRange.startLineOffset\", \"textRange.endLine\", \"textRange.endLineOffset\")\n      .containsExactly(ruleKey, message, IssueSeverity.MINOR, RuleType.SECURITY_HOTSPOT, \"uuid\", HotspotStatus.TO_REVIEW, Instant.ofEpochSecond(123456789L), 5, 37, 5, 48);\n  }\n\n  @SonarLintTest\n  void it_should_track_known_server_hotspots(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var ideFilePath = \"Foo.java\";\n    var filePath = createFile(baseDir, ideFilePath,\n      \"\"\"\n        package sonar;\n        import java.security.MessageDigest;\n        public class Foo {\n          public void run() throws Exception {\n            MessageDigest md = MessageDigest.getInstance(\"MD5\");\n          }\n        }\"\"\");\n    var projectKey = \"projectKey\";\n    var connectionId = \"connectionId\";\n    var branchName = \"main\";\n    var ruleKey = \"java:S4790\";\n    var message = \"Make sure this weak hash algorithm is not used in a sensitive context here.\";\n\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var server = harness.newFakeSonarQubeServer(\"10.0\")\n      .withProject(projectKey, project -> project.withBranch(branchName, branch -> branch\n        .withHotspot(\"uuid\", hotspot -> hotspot.withAuthor(\"author\")\n          .withCreationDate(Instant.ofEpochSecond(123456789L))\n          .withFilePath(ideFilePath)\n          .withMessage(message)\n          .withRuleKey(ruleKey)\n          .withTextRange(new TextRange(5, 37, 5, 48))\n          .withStatus(HotspotReviewStatus.TO_REVIEW)\n          .withVulnerabilityProbability(VulnerabilityProbability.HIGH))))\n      .withQualityProfile(\"qp\", qualityProfile -> qualityProfile.withLanguage(\"java\")\n        .withActiveRule(ruleKey, activeRule -> activeRule.withSeverity(IssueSeverity.MAJOR)))\n      .start();\n    var backend = harness.newBackend()\n      .withSonarQubeConnection(connectionId, server,\n        storage -> storage.withPlugin(TestPlugin.JAVA).withProject(projectKey,\n          project -> project.withRuleSet(\"java\", ruleSet -> ruleSet.withActiveRule(ruleKey, \"MINOR\"))\n            .withMainBranch(branchName)))\n      .withBackendCapability(SECURITY_HOTSPOTS)\n      .withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n    backend.getConfigurationService()\n      .didAddConfigurationScopes(new DidAddConfigurationScopesParams(List.of(\n        new ConfigurationScopeDto(CONFIG_SCOPE_ID, null, true, CONFIG_SCOPE_ID,\n          new BindingConfigurationDto(connectionId, projectKey, true)))));\n\n    var firstPublishedHotspot = analyzeFileAndGetHotspot(backend, fileUri, client);\n    var secondPublishedHotspot = analyzeFileAndGetHotspot(backend, fileUri, client);\n\n    assertThat(secondPublishedHotspot)\n      .extracting(\"id\", \"ruleKey\", \"primaryMessage\", \"severityMode.left.severity\", \"severityMode.left.type\", \"serverKey\", \"introductionDate\",\n        \"textRange.startLine\", \"textRange.startLineOffset\", \"textRange.endLine\", \"textRange.endLineOffset\")\n      .containsExactly(firstPublishedHotspot.getId(), ruleKey, message, IssueSeverity.MINOR, RuleType.SECURITY_HOTSPOT, \"uuid\", Instant.ofEpochSecond(123456789L), 5, 37, 5, 48);\n  }\n\n  @SonarLintTest\n  void it_should_not_track_server_hotspots_in_standalone_mode(SonarLintTestHarness harness, @TempDir Path baseDir) {\n    var ideFilePath = \"Foo.java\";\n    var filePath = createFile(baseDir, ideFilePath,\n      \"\"\"\n        package sonar;\n        import java.security.MessageDigest;\n        public class Foo {\n          public void run() throws Exception {\n            MessageDigest md = MessageDigest.getInstance(\"MD5\");\n          }\n        }\"\"\");\n    var fileUri = filePath.toUri();\n    var client = harness.newFakeClient()\n      .withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))\n      .build();\n    var backend = harness.newBackend()\n      .withBackendCapability(SECURITY_HOTSPOTS)\n      .withUnboundConfigScope(CONFIG_SCOPE_ID)\n      .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVA)\n      .start(client);\n\n    var analysisId = UUID.randomUUID();\n    backend.getAnalysisService().analyzeFilesAndTrack(\n      new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(fileUri), Map.of(), true))\n      .join();\n\n    verify(client, timeout(2000).times(0)).raiseHotspots(any(), any(), anyBoolean(), any());\n  }\n\n  private RaisedHotspotDto analyzeFileAndGetHotspot(SonarLintTestRpcServer backend, URI fileUri, SonarLintRpcClientDelegate client) {\n    var analysisId = UUID.randomUUID();\n    var analysisResult = backend.getAnalysisService().analyzeFilesAndTrack(\n      new AnalyzeFilesAndTrackParams(CONFIG_SCOPE_ID, analysisId, List.of(fileUri), Map.of(), true))\n      .join();\n    var publishedHotspotsByFile = getPublishedHotspots(client, analysisId);\n    assertThat(analysisResult.getFailedAnalysisFiles()).isEmpty();\n    assertThat(publishedHotspotsByFile).containsOnlyKeys(fileUri);\n    var publishedHotspots = publishedHotspotsByFile.get(fileUri);\n    assertThat(publishedHotspots).hasSize(1);\n    return publishedHotspots.get(0);\n  }\n\n  private Map<URI, List<RaisedHotspotDto>> getPublishedHotspots(SonarLintRpcClientDelegate client, UUID analysisId) {\n    ArgumentCaptor<Map<URI, List<RaisedHotspotDto>>> trackedIssuesCaptor = ArgumentCaptor.forClass(Map.class);\n    verify(client, timeout(300)).raiseHotspots(eq(CONFIG_SCOPE_ID), trackedIssuesCaptor.capture(), eq(false), eq(analysisId));\n    return trackedIssuesCaptor.getValue();\n  }\n\n  private static Path createFile(Path folderPath, String fileName, String content) {\n    var filePath = folderPath.resolve(fileName);\n    try {\n      Files.writeString(filePath, content);\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n    return filePath;\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/mediumtest/websockets/WebSocketMediumTests.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage mediumtest.websockets;\n\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Set;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\nimport org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidRemoveConfigurationScopeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidChangeCredentialsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidUpdateConnectionsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarCloudConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarQubeConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.smartnotification.ShowSmartNotificationParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintBackendFixture;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;\nimport org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;\nimport org.sonarsource.sonarlint.core.test.utils.server.websockets.WebSocketConnection;\nimport org.sonarsource.sonarlint.core.test.utils.server.websockets.WebSocketServer;\n\nimport static java.util.Collections.emptyList;\nimport static mediumtest.websockets.WebSocketMediumTests.WebSocketPayloadBuilder.webSocketPayloadBuilder;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SERVER_SENT_EVENTS;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.SMART_NOTIFICATIONS;\nimport static org.sonarsource.sonarlint.core.test.utils.SonarLintBackendFixture.USER_AGENT_FOR_TESTS;\nimport static org.sonarsource.sonarlint.core.test.utils.storage.ServerIssueFixtures.aServerIssue;\nimport static org.sonarsource.sonarlint.core.test.utils.storage.ServerSecurityHotspotFixture.aServerHotspot;\nimport static org.sonarsource.sonarlint.core.test.utils.storage.ServerTaintIssueFixtures.aServerTaintIssue;\n\nclass WebSocketMediumTests {\n\n  // not used but useful to register a log output\n  @RegisterExtension\n  private static final SonarLintLogTester logTester = new SonarLintLogTester();\n\n  private WebSocketServer webSocketServerEU;\n  private WebSocketServer webSocketServerUS;\n\n  @BeforeEach\n  void prepare() {\n    webSocketServerEU = new WebSocketServer();\n    webSocketServerEU.start();\n    webSocketServerUS = new WebSocketServer(WebSocketServer.DEFAULT_PORT + 1);\n    webSocketServerUS.start();\n  }\n\n  @AfterEach\n  void tearDown() {\n    if (webSocketServerEU != null) {\n      webSocketServerEU.stop();\n    }\n    if (webSocketServerUS != null) {\n      webSocketServerUS.stop();\n    }\n  }\n\n  @Nested\n  class WhenScopeBound {\n    @SonarLintTest\n    void should_create_connection_and_subscribe_to_events(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient().withToken(\"connectionId\", \"token\").build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withUnboundConfigScope(\"configScope\")\n        .start(client);\n\n      bind(backend, \"configScope\", \"connectionId\", \"projectKey\");\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections())\n        .extracting(WebSocketConnection::getAuthorizationHeader, WebSocketConnection::isOpened, WebSocketConnection::getReceivedMessages)\n        .containsExactly(tuple(\"Bearer token\", true, webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey\").build())));\n    }\n\n    @SonarLintTest\n    void should_set_user_agent(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient().withToken(\"connectionId\", \"token\").build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withUnboundConfigScope(\"configScope\")\n        .start(client);\n\n      bind(backend, \"configScope\", \"connectionId\", \"projectKey\");\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections())\n        .extracting(WebSocketConnection::getUserAgent)\n        .containsExactly(USER_AGENT_FOR_TESTS));\n    }\n\n    @SonarLintTest\n    void should_not_create_websocket_connection_and_subscribe_when_bound_to_sonarqube(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarQubeConnection(\"connectionId\")\n        .withUnboundConfigScope(\"configScope\")\n        .start(client);\n\n      bind(backend, \"configScope\", \"connectionId\", \"projectKey\");\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections()).isEmpty());\n    }\n\n    @SonarLintTest\n    void should_unsubscribe_from_old_project_and_subscribe_to_new_project_when_key_changed(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      bind(backend, \"configScope\", \"connectionId\", \"newProjectKey\");\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections())\n        .extracting(WebSocketConnection::isOpened, WebSocketConnection::getReceivedMessages)\n        .containsExactly(tuple(true, webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey\").unsubscribeWithProjectKey(\n          \"projectKey\").subscribeWithProjectKey(\"newProjectKey\").build())));\n    }\n\n    @SonarLintTest\n    void should_unsubscribe_from_old_project_and_not_subscribe_to_new_project_if_it_is_already_subscribed(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withBoundConfigScope(\"configScope1\", \"connectionId\", \"projectKey1\")\n        .withBoundConfigScope(\"configScope2\", \"connectionId\", \"projectKey2\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey2\", \"projectKey1\");\n\n      bind(backend, \"configScope1\", \"connectionId\", \"projectKey2\");\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections())\n        .extracting(WebSocketConnection::isOpened, WebSocketConnection::getReceivedMessages)\n        .containsExactly(tuple(true,\n          webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey2\", \"projectKey1\").unsubscribeWithProjectKey(\"projectKey1\").build())));\n    }\n\n    @SonarLintTest\n    void should_unsubscribe_from_old_region_and_subscribe_to_new_when_connection_and_region_changed(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .withToken(\"connectionIdUS\", \"token\")\n        .build();\n\n      // backend with two sonarqube cloud connections - one EU and one US; bound initially to EU\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withSonarCloudConnection(\"connectionIdUS\", \"orgKey\", false, null, SonarCloudRegion.US)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      // Change binding to US connection\n      bind(backend, \"configScope\", \"connectionIdUS\", \"projectKey\");\n\n      // assert unsubscribed and closed connection to EU region; subscribed to US region.\n      await().untilAsserted(() -> {\n        assertThat(webSocketServerEU.getConnections())\n          .hasSize(1)\n          .extracting(WebSocketConnection::isOpened, WebSocketConnection::getReceivedMessages)\n          .containsExactly(tuple(false, webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey\").unsubscribeWithProjectKey(\n              \"projectKey\").build()));\n        assertThat(webSocketServerUS.getConnections())\n          .hasSize(1)\n          .extracting(WebSocketConnection::isOpened, WebSocketConnection::getReceivedMessages)\n          .containsExactly(tuple(true, webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey\").build()));\n      });\n    }\n\n    @SonarLintTest\n    void should_not_open_connection_or_subscribe_if_notifications_disabled_on_connection(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnection(\"connectionId\", \"orgKey\", true, null)\n        .withUnboundConfigScope(\"configScope\")\n        .start(client);\n\n      bind(backend, \"configScope\", \"connectionId\", \"newProjectKey\");\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections()).isEmpty());\n    }\n\n    @SonarLintTest\n    void should_not_resubscribe_if_project_already_bound(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnection(\"connectionId\", \"orgKey\", true, null)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n\n      bind(backend, \"configScope\", \"connectionId\", \"newProjectKey\");\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections()).isEmpty());\n    }\n\n    private void bind(SonarLintTestRpcServer backend, String configScopeId, String connectionId, String newProjectKey) {\n      backend.getConfigurationService().didUpdateBinding(new DidUpdateBindingParams(configScopeId,\n        new BindingConfigurationDto(connectionId, newProjectKey, true)));\n    }\n  }\n\n  @Nested\n  class WhenUnbindingScope {\n    @SonarLintTest\n    void should_unsubscribe_bound_project(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      unbind(backend, \"configScope\");\n\n      await().untilAsserted(() -> {\n        assertThat(webSocketServerEU.getConnections())\n          .extracting(WebSocketConnection::isOpened, WebSocketConnection::getReceivedMessages)\n          .containsExactly(tuple(false, webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey\").unsubscribeWithProjectKey(\n            \"projectKey\").build()));\n      });\n    }\n\n    @SonarLintTest\n    void should_not_unsubscribe_if_the_same_project_key_is_used_in_another_binding(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withBoundConfigScope(\"configScope1\", \"connectionId\", \"projectKey\")\n        .withBoundConfigScope(\"configScope2\", \"connectionId\", \"projectKey\")\n        .start(client);\n\n      unbind(backend, \"configScope1\");\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections())\n        .extracting(WebSocketConnection::isOpened, WebSocketConnection::getReceivedMessages)\n        .containsExactly(tuple(true, webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey\").build())));\n    }\n\n    @SonarLintTest\n    void should_not_unsubscribe_if_notifications_disabled_on_connection(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnection(\"connectionId\", \"orgKey\", true, null)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n\n      unbind(backend, \"configScope\");\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections()).isEmpty());\n    }\n\n    private void unbind(SonarLintTestRpcServer backend, String configScope) {\n      backend.getConfigurationService().didUpdateBinding(new DidUpdateBindingParams(configScope, new BindingConfigurationDto(null, null,\n        true)));\n    }\n  }\n\n  @Nested\n  class WhenScopeAdded {\n    @SonarLintTest\n    void should_subscribe_if_bound_to_sonarcloud(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n\n      await().untilAsserted(() -> {\n        assertThat(webSocketServerEU.getConnections())\n          .extracting(WebSocketConnection::isOpened, WebSocketConnection::getReceivedMessages)\n          .containsExactly(tuple(true, webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey\").build()));\n      });\n    }\n\n    @SonarLintTest\n    void should_not_subscribe_if_not_bound(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withUnboundConfigScope(\"configScope\")\n        .start(client);\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections()).isEmpty());\n    }\n\n    @SonarLintTest\n    void should_not_subscribe_if_bound_to_sonarqube(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      newBackendWithWebSockets(harness)\n        .withSonarQubeConnection(\"connectionId\")\n        .withUnboundConfigScope(\"configScope\")\n        .start(client);\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections()).isEmpty());\n    }\n\n    @SonarLintTest\n    void should_not_subscribe_if_bound_to_sonarcloud_but_notifications_are_disabled(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      newBackendWithWebSockets(harness)\n        .withSonarCloudConnection(\"connectionId\", \"orgKey\", true, null)\n        .withUnboundConfigScope(\"configScope\")\n        .start(client);\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections()).isEmpty());\n    }\n\n    @SonarLintTest\n    void should_not_subscribe_if_connection_is_invalid(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        // return an invalid token\n        .withToken(\"connectionId\", null)\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        // start with disabled notifications\n        .withSonarCloudConnection(\"connectionId\", \"organizationKey\", true, null)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n\n      backend.getConnectionService().didUpdateConnections(new DidUpdateConnectionsParams(List.of(), List.of(new SonarCloudConnectionConfigurationDto(\"connectionId\", \"organizationKey\", SonarCloudRegion.EU, false))));\n\n      await().during(Duration.ofSeconds(2)).untilAsserted(() -> assertThat(webSocketServerEU.getConnections()).isEmpty());\n    }\n  }\n\n  @Nested\n  class WhenScopeRemoved {\n    @SonarLintTest\n    void should_unsubscribe_from_project(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      backend.getConfigurationService().didRemoveConfigurationScope(new DidRemoveConfigurationScopeParams(\"configScope\"));\n\n      await().untilAsserted(() -> {\n        assertThat(webSocketServerEU.getConnections())\n          .extracting(WebSocketConnection::isOpened, WebSocketConnection::getReceivedMessages)\n          .containsExactly(tuple(false, webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey\").unsubscribeWithProjectKey(\n            \"projectKey\").build()));\n      });\n    }\n\n    @SonarLintTest\n    void should_not_unsubscribe_if_another_scope_is_bound_to_same_project(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId1\", \"token1\")\n        .withToken(\"connectionId2\", \"token2\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId1\", \"orgKey1\", null)\n        .withSonarCloudConnectionAndNotifications(\"connectionId2\", \"orgKey2\", null)\n        .withBoundConfigScope(\"configScope1\", \"connectionId1\", \"projectKey\")\n        .withBoundConfigScope(\"configScope2\", \"connectionId2\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      backend.getConfigurationService().didRemoveConfigurationScope(new DidRemoveConfigurationScopeParams(\"configScope1\"));\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections())\n        .extracting(WebSocketConnection::isOpened, WebSocketConnection::getReceivedMessages)\n        .containsExactly(tuple(true, webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey\").build())));\n    }\n\n    @SonarLintTest\n    void should_not_unsubscribe_if_connection_was_already_closed(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n      backend.getConnectionService().didUpdateConnections(new DidUpdateConnectionsParams(emptyList(),\n        List.of(new SonarCloudConnectionConfigurationDto(\"connectionId\", \"orgKey\", SonarCloudRegion.EU, true))));\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections()).extracting(WebSocketConnection::isOpened).containsExactly(false));\n\n      backend.getConfigurationService().didRemoveConfigurationScope(new DidRemoveConfigurationScopeParams(\"configScope\"));\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections())\n        .extracting(WebSocketConnection::isOpened)\n        .containsExactly(false));\n    }\n  }\n\n  @Nested\n  class WhenConnectionCredentialsChanged {\n    @SonarLintTest\n    void should_close_and_reopen_connection_for_sonarcloud_if_already_open(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient().withToken(\"connectionId\", \"firstToken\").build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n      client.setToken(\"connectionId\", \"secondToken\");\n\n      backend.getConnectionService().didChangeCredentials(new DidChangeCredentialsParams(\"connectionId\"));\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections())\n        .extracting(WebSocketConnection::getAuthorizationHeader, WebSocketConnection::isOpened, WebSocketConnection::getReceivedMessages)\n        .containsExactly(tuple(\"Bearer firstToken\", false, webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey\").build()),\n          tuple(\"Bearer secondToken\", true, webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey\").build())));\n    }\n\n    @SonarLintTest\n    void should_do_nothing_for_sonarcloud_if_not_already_open(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .start(client);\n\n      backend.getConnectionService().didChangeCredentials(new DidChangeCredentialsParams(\"connectionId\"));\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections()).isEmpty());\n    }\n\n    @SonarLintTest\n    void should_do_nothing_for_sonarqube(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarQubeConnection(\"connectionId\")\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n\n      backend.getConnectionService().didChangeCredentials(new DidChangeCredentialsParams(\"connectionId\"));\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections()).isEmpty());\n    }\n  }\n\n  @Nested\n  class WhenConnectionAdded {\n\n    @SonarLintTest\n    void should_subscribe_all_projects_bound_to_added_connection(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n\n      backend.getConnectionService()\n        .didUpdateConnections(new DidUpdateConnectionsParams(emptyList(), List.of(new SonarCloudConnectionConfigurationDto(\"connectionId\", \"orgKey\", SonarCloudRegion.EU, false))));\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections())\n        .extracting(WebSocketConnection::isOpened, WebSocketConnection::getReceivedMessages)\n        .containsExactly(tuple(true, webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey\").build())));\n    }\n\n    @SonarLintTest\n    void should_log_failure_and_reconnect_later_if_server_unavailable(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      webSocketServerEU.stop();\n      var backend = newBackendWithWebSockets(harness)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n\n      backend.getConnectionService()\n        .didUpdateConnections(new DidUpdateConnectionsParams(emptyList(), List.of(new SonarCloudConnectionConfigurationDto(\"connectionId\", \"orgKey\", SonarCloudRegion.EU, false))));\n\n      await().untilAsserted(() -> assertThat(client.getLogMessages()).contains(\"Error while trying to create WebSocket connection for ws://localhost:54321/endpoint\"));\n\n      webSocketServerEU.start();\n      // Emulate a change on the connection to force WebSocket service to reconnect\n      backend.getConnectionService().didChangeCredentials(new DidChangeCredentialsParams(\"connectionId\"));\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections())\n        .extracting(WebSocketConnection::isOpened, WebSocketConnection::getReceivedMessages)\n        .containsExactly(tuple(true, webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey\").build())));\n    }\n  }\n\n  @Nested\n  class WhenConnectionRemoved {\n    @SonarLintTest\n    void should_close_connection(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      backend.getConnectionService().didUpdateConnections(new DidUpdateConnectionsParams(emptyList(), emptyList()));\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections())\n        .extracting(WebSocketConnection::isOpened)\n        .containsExactly(false));\n    }\n\n    @SonarLintTest\n    void should_not_close_connection_if_another_sonarcloud_connection_is_active(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId1\", \"token1\")\n        .withToken(\"connectionId2\", \"token2\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId1\", \"orgKey1\", null)\n        .withSonarCloudConnectionAndNotifications(\"connectionId2\", \"orgKey2\", null)\n        .withBoundConfigScope(\"configScope1\", \"connectionId1\", \"projectKey1\")\n        .withBoundConfigScope(\"configScope2\", \"connectionId2\", \"projectKey2\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey2\", \"projectKey1\");\n\n      backend.getConnectionService()\n        .didUpdateConnections(new DidUpdateConnectionsParams(emptyList(), List.of(new SonarCloudConnectionConfigurationDto(\"connectionId2\", \"orgKey2\", SonarCloudRegion.EU, false))));\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections())\n        .extracting(WebSocketConnection::isOpened, WebSocketConnection::getReceivedMessages)\n        .containsExactly(tuple(true,\n          webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey2\", \"projectKey1\").unsubscribeWithProjectKey(\"projectKey1\").build())));\n    }\n  }\n\n  @Nested\n  class WhenConnectionUpdated {\n    @SonarLintTest\n    void should_do_nothing_for_sonarqube(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarQubeConnection(\"connectionId\")\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n\n      backend.getConnectionService()\n        .didUpdateConnections(new DidUpdateConnectionsParams(List.of(new SonarQubeConnectionConfigurationDto(\"connectionid\", \"url\",\n          false)), emptyList()));\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections()).isEmpty());\n    }\n\n    @SonarLintTest\n    void should_do_nothing_when_no_project_bound_to_sonarcloud(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .start(client);\n\n      backend.getConnectionService()\n        .didUpdateConnections(new DidUpdateConnectionsParams(emptyList(), List.of(new SonarCloudConnectionConfigurationDto(\"connectionId\", \"orgKey2\", SonarCloudRegion.EU, false))));\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections()).isEmpty());\n    }\n\n    @SonarLintTest\n    void should_close_websocket_if_notifications_disabled(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      backend.getConnectionService()\n        .didUpdateConnections(new DidUpdateConnectionsParams(emptyList(), List.of(new SonarCloudConnectionConfigurationDto(\"connectionId\", \"orgKey\", SonarCloudRegion.EU, true))));\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections())\n        .extracting(WebSocketConnection::isOpened, WebSocketConnection::getReceivedMessages)\n        .containsExactly(tuple(false, webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey\").build())));\n    }\n\n    @SonarLintTest\n    void should_close_and_reopen_websocket_if_notifications_are_disabled_but_other_connection_is_active(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId1\", \"token1\")\n        .withToken(\"connectionId2\", \"token2\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId1\", \"orgKey1\", null)\n        .withSonarCloudConnectionAndNotifications(\"connectionId2\", \"orgKey2\", null)\n        .withBoundConfigScope(\"configScope1\", \"connectionId1\", \"projectKey1\")\n        .withBoundConfigScope(\"configScope2\", \"connectionId2\", \"projectKey2\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey2\", \"projectKey1\");\n\n      backend.getConnectionService().didUpdateConnections(new DidUpdateConnectionsParams(emptyList(),\n        List.of(new SonarCloudConnectionConfigurationDto(\"connectionId1\", \"orgKey1\", SonarCloudRegion.EU, false), new SonarCloudConnectionConfigurationDto(\n          \"connectionId2\", \"orgKey2\", SonarCloudRegion.EU, true))));\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections())\n        .extracting(WebSocketConnection::isOpened, WebSocketConnection::getReceivedMessages)\n        .containsExactly(tuple(false, webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey2\", \"projectKey1\").build()),\n          tuple(true, webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey1\").build())));\n    }\n\n    @SonarLintTest\n    void should_open_websocket_and_subscribe_to_all_bound_projects_if_enabled_notifications(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnection(\"connectionId\", \"orgKey\", true, null)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n\n      backend.getConnectionService()\n        .didUpdateConnections(new DidUpdateConnectionsParams(emptyList(), List.of(new SonarCloudConnectionConfigurationDto(\"connectionId\", \"orgKey\", SonarCloudRegion.EU, false))));\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections())\n        .extracting(WebSocketConnection::isOpened, WebSocketConnection::getReceivedMessages)\n        .containsExactly(tuple(true, webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey\").build())));\n    }\n  }\n\n  @Nested\n  class WhenReceivingSmartNotificationEvent {\n    @SonarLintTest\n    void should_forward_to_client_as_smart_notifications(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      newBackendWithWebSockets(harness)\n        .withBackendCapability(SMART_NOTIFICATIONS)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      webSocketServerEU.getConnections().get(0).sendMessage(\n        \"{\\\"event\\\": \\\"QualityGateChanged\\\", \\\"data\\\": {\\\"message\\\": \\\"msg\\\", \\\"link\\\": \\\"lnk\\\", \\\"project\\\": \\\"projectKey\\\", \\\"date\\\": \" +\n          \"\\\"2023-07-19T15:08:01+0000\\\"}}\");\n\n      await().untilAsserted(() -> assertThat(client.getSmartNotificationsToShow())\n        .extracting(ShowSmartNotificationParams::getScopeIds, ShowSmartNotificationParams::getCategory,\n          ShowSmartNotificationParams::getLink, ShowSmartNotificationParams::getText,\n          ShowSmartNotificationParams::getConnectionId)\n        .containsExactly(tuple(Set.of(\"configScope\"), \"QUALITY_GATE\", \"lnk\", \"msg\", \"connectionId\")));\n    }\n\n    @SonarLintTest\n    void should_forward_my_new_issues_to_client_as_smart_notifications(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      newBackendWithWebSockets(harness)\n        .withBackendCapability(SMART_NOTIFICATIONS)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      webSocketServerEU.getConnections().get(0).sendMessage(\n        \"{\\\"event\\\": \\\"MyNewIssues\\\", \\\"data\\\": {\\\"message\\\": \\\"msg\\\", \\\"link\\\": \\\"lnk\\\", \\\"project\\\": \\\"projectKey\\\", \\\"date\\\": \" +\n          \"\\\"2023-07-19T15:08:01+0000\\\"}}\");\n\n      await().untilAsserted(() -> assertThat(client.getSmartNotificationsToShow())\n        .extracting(ShowSmartNotificationParams::getScopeIds, ShowSmartNotificationParams::getCategory,\n          ShowSmartNotificationParams::getLink, ShowSmartNotificationParams::getText,\n          ShowSmartNotificationParams::getConnectionId)\n        .containsExactly(tuple(Set.of(\"configScope\"), \"NEW_ISSUES\", \"lnk\", \"msg\", \"connectionId\")));\n    }\n\n    @SonarLintTest\n    void should_not_forward_to_client_if_the_event_data_is_malformed(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      newBackendWithWebSockets(harness)\n        .withBackendCapability(SMART_NOTIFICATIONS)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      webSocketServerEU.getConnections().get(0).sendMessage(\"{\\\"event\\\": [\\\"QualityGateChanged\\\"], \\\"data\\\": {\\\"message\\\": 0}}\");\n\n      await().untilAsserted(() -> assertThat(client.getSmartNotificationsToShow()).isEmpty());\n    }\n\n    @SonarLintTest\n    void should_not_forward_to_client_if_the_message_is_missing(SonarLintTestHarness harness) {\n      should_not_forward_to_client(harness, \"{\\\"event\\\": [\\\"QualityGateChanged\\\"], \\\"data\\\": {\\\"link\\\": \\\"lnk\\\", \\\"project\\\": \\\"projectKey\\\", \\\"date\\\": \" +\n        \"\\\"2023-07-19T15:08:01+0000\\\"}}\");\n    }\n\n    @SonarLintTest\n    void should_not_forward_to_client_if_the_link_is_missing(SonarLintTestHarness harness) {\n      should_not_forward_to_client(harness, \"{\\\"event\\\": [\\\"QualityGateChanged\\\"], \\\"data\\\": {\\\"message\\\": \\\"msg\\\", \\\"project\\\": \\\"projectKey\\\", \\\"date\\\": \" +\n        \"\\\"2023-07-19T15:08:01+0000\\\"}}\");\n    }\n\n    @SonarLintTest\n    void should_not_forward_to_client_if_the_project_is_missing(SonarLintTestHarness harness) {\n      should_not_forward_to_client(harness, \"{\\\"event\\\": [\\\"QualityGateChanged\\\"], \\\"data\\\": {\\\"message\\\": \\\"msg\\\", \\\"link\\\": \\\"lnk\\\", \\\"date\\\": \" +\n        \"\\\"2023-07-19T15:08:01+0000\\\"}}\");\n    }\n\n    @SonarLintTest\n    void should_not_forward_to_client_if_the_date_is_missing(SonarLintTestHarness harness) {\n      should_not_forward_to_client(harness, \"{\\\"event\\\": [\\\"QualityGateChanged\\\"], \\\"data\\\": {\\\"message\\\": \\\"msg\\\", \\\"link\\\": \\\"lnk\\\", \\\"project\\\": \" +\n        \"\\\"projectKey\\\"}}\");\n    }\n\n    void should_not_forward_to_client(SonarLintTestHarness harness, String payload) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      newBackendWithWebSockets(harness)\n        .withBackendCapability(SMART_NOTIFICATIONS)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      webSocketServerEU.getConnections().get(0).sendMessage(payload);\n\n      await().untilAsserted(() -> assertThat(client.getSmartNotificationsToShow()).isEmpty());\n    }\n  }\n\n  @Nested\n  class WhenReceivingIssueChangedEvent {\n    @SonarLintTest\n    void should_change_issue_status(SonarLintTestHarness harness) {\n      var serverIssue = aServerIssue(\"myIssueKey\").withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\"))\n        .withIntroductionDate(Instant.EPOCH.plusSeconds(1)).withType(RuleType.BUG);\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", storage -> storage\n          .withProject(\"projectKey\", project -> project.withMainBranch(\"master\", branch -> branch.withIssue(serverIssue))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      var issueStorage = backend.getIssueStorageService().connection(\"connectionId\").project(\"projectKey\").findings();\n      await().untilAsserted(() -> assertThat(issueStorage.getIssue(\"myIssueKey\").isResolved()).isFalse());\n\n      webSocketServerEU.getConnections().get(0).sendMessage(\n        \"\"\"\n          {\n            \"event\": \"IssueChanged\",\n            \"data\": {\n              \"projectKey\": \"projectKey\",\n              \"issues\": [\n                {\n                  \"issueKey\": \"myIssueKey\",\n                  \"branchName\": \"master\"\n                }\n              ],\n              \"resolved\": true\n            }\n          }\"\"\");\n\n      await().untilAsserted(() -> assertThat(issueStorage.getIssue(\"myIssueKey\").isResolved()).isTrue());\n    }\n\n    @SonarLintTest\n    void should_not_change_issue_if_the_event_data_is_malformed(SonarLintTestHarness harness) {\n      var serverIssue = aServerIssue(\"myIssueKey\").withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\"))\n        .withIntroductionDate(Instant.EPOCH.plusSeconds(1)).withType(RuleType.BUG);\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", storage -> storage\n          .withProject(\"projectKey\", project -> project.withMainBranch(\"master\", branch -> branch.withIssue(serverIssue))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      var issueStorage = backend.getIssueStorageService().connection(\"connectionId\").project(\"projectKey\").findings();\n      await().untilAsserted(() -> assertThat(issueStorage.getIssue(\"myIssueKey\").isResolved()).isFalse());\n\n      webSocketServerEU.getConnections().get(0).sendMessage(\n        \"\"\"\n          {\n            \"event\": \"IssueChanged\",\n            \"data\": {\n              \"projectKey\": \"projectKey\",\n              \"invalid\": [\n                {\n                  \"issueKey\": myIssueKey,\n                  \"branchName\": \"master\"\n                }\n              ],\n              \"resolved\": true\n            }\n          }\"\"\");\n\n      await().untilAsserted(() -> assertThat(issueStorage.getIssue(\"myIssueKey\").isResolved()).isFalse());\n    }\n\n    @SonarLintTest\n    void should_change_issue_if_the_issue_key_is_missing(SonarLintTestHarness harness) {\n      var serverIssue = aServerIssue(\"myIssueKey\").withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\"))\n        .withIntroductionDate(Instant.EPOCH.plusSeconds(1)).withType(RuleType.BUG);\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", storage -> storage\n          .withProject(\"projectKey\", project -> project.withMainBranch(\"master\", branch -> branch.withIssue(serverIssue))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      var issueStorage = backend.getIssueStorageService().connection(\"connectionId\").project(\"projectKey\").findings();\n      await().untilAsserted(() -> assertThat(issueStorage.getIssue(\"myIssueKey\").isResolved()).isFalse());\n\n      webSocketServerEU.getConnections().get(0).sendMessage(\n        \"\"\"\n          {\n            \"event\": \"IssueChanged\",\n            \"data\": {\n              \"projectKey\": \"projectKey\",\n              \"invalid\": [\n                {\n                  \"branchName\": \"master\"\n                }\n              ],\n              \"resolved\": true\n            }\n          }\"\"\");\n\n      await().untilAsserted(() -> assertThat(issueStorage.getIssue(\"myIssueKey\").isResolved()).isFalse());\n    }\n\n    @SonarLintTest\n    void should_not_change_issue_if_the_resolution_is_missing(SonarLintTestHarness harness) {\n      var serverIssue = aServerIssue(\"myIssueKey\").withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\"))\n        .withIntroductionDate(Instant.EPOCH.plusSeconds(1)).withType(RuleType.BUG);\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", storage -> storage\n          .withProject(\"projectKey\", project -> project.withMainBranch(\"master\", branch -> branch.withIssue(serverIssue))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      var issueStorage = backend.getIssueStorageService().connection(\"connectionId\").project(\"projectKey\").findings();\n      await().untilAsserted(() -> assertThat(issueStorage.getIssue(\"myIssueKey\").isResolved()).isFalse());\n\n      webSocketServerEU.getConnections().get(0).sendMessage(\n        \"\"\"\n          {\n            \"event\": \"IssueChanged\",\n            \"data\": {\n              \"projectKey\": \"projectKey\",\n              \"invalid\": [\n                {\n                  \"issueKey\": myIssueKey,\n                  \"branchName\": \"master\"\n                }\n              ]\n            }\n          }\"\"\");\n\n      await().untilAsserted(() -> assertThat(issueStorage.getIssue(\"myIssueKey\").isResolved()).isFalse());\n    }\n\n    @SonarLintTest\n    void should_not_change_issue_if_the_project_is_missing(SonarLintTestHarness harness) {\n      var serverIssue = aServerIssue(\"myIssueKey\").withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\"))\n        .withIntroductionDate(Instant.EPOCH.plusSeconds(1)).withType(RuleType.BUG);\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", storage -> storage\n          .withProject(\"projectKey\", project -> project.withMainBranch(\"master\", branch -> branch.withIssue(serverIssue))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      var issueStorage = backend.getIssueStorageService().connection(\"connectionId\").project(\"projectKey\").findings();\n      await().untilAsserted(() -> assertThat(issueStorage.getIssue(\"myIssueKey\").isResolved()).isFalse());\n\n      webSocketServerEU.getConnections().get(0).sendMessage(\n        \"\"\"\n          {\n            \"event\": \"IssueChanged\",\n            \"data\": {\n              \"invalid\": [\n                {\n                  \"issueKey\": myIssueKey,\n                  \"branchName\": \"master\"\n                }\n              ],\n              \"resolved\": true\n            }\n          }\"\"\");\n\n      await().untilAsserted(() -> assertThat(issueStorage.getIssue(\"myIssueKey\").isResolved()).isFalse());\n    }\n  }\n\n  @Nested\n  class WhenReceivingTaintVulnerabilityRaisedEvent {\n    @SonarLintTest\n    void should_create_taint_vulnerability(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", storage -> storage\n          .withProject(\"projectKey\"))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      var issueStorage = backend.getIssueStorageService().connection(\"connectionId\").project(\"projectKey\").findings();\n\n      webSocketServerEU.getConnections().get(0).sendMessage(\n        \"\"\"\n          {\n            \"event\": \"TaintVulnerabilityRaised\",\n            \"data\": {\n              \"key\": \"taintKey\",\n              \"projectKey\": \"projectKey\",\n              \"branch\": \"branch\",\n              \"creationDate\": 123456789,\n              \"ruleKey\": \"javasecurity:S123\",\n              \"severity\": \"MAJOR\",\n              \"type\": \"VULNERABILITY\",\n              \"mainLocation\": {\n                \"filePath\": \"functions/taint.js\",\n                \"message\": \"blah blah\",\n                \"textRange\": {\n                  \"startLine\": 17,\n                  \"startLineOffset\": 10,\n                  \"endLine\": 3,\n                  \"endLineOffset\": 2,\n                  \"hash\": \"hash\"\n                }\n              },\n              \"flows\": [\n                {\n                  \"locations\": [\n                    {\n                      \"filePath\": \"functions/taint.js\",\n                      \"message\": \"sink: tainted value is used to perform a security-sensitive operation\",\n                      \"textRange\": {\n                        \"startLine\": 17,\n                        \"startLineOffset\": 10,\n                        \"endLine\": 3,\n                        \"endLineOffset\": 2,\n                        \"hash\": \"hash1\"\n                      }\n                    },\n                    {\n                      \"filePath\": \"functions/taint2.js\",\n                      \"message\": \"sink: tainted value is used to perform a security-sensitive operation\",\n                      \"textRange\": {\n                        \"startLine\": 18,\n                        \"startLineOffset\": 11,\n                        \"endLine\": 4,\n                        \"endLineOffset\": 3,\n                        \"hash\": \"hash2\"\n                      }\n                    }\n                  ]\n                }\n              ]\n            }\n          }\"\"\");\n\n      await().untilAsserted(() -> assertThat(issueStorage.containsIssue(\"taintKey\")).isTrue());\n    }\n\n    @SonarLintTest\n    void should_not_create_taint_vulnerability_event_data_is_malformed(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", storage -> storage\n          .withProject(\"projectKey\"))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      var issueStorage = backend.getIssueStorageService().connection(\"connectionId\").project(\"projectKey\").findings();\n\n      webSocketServerEU.getConnections().get(0).sendMessage(\n        \"\"\"\n          {\n            \"event\": \"TaintVulnerabilityRaised\",\n            \"data\": {\n              \"invalidKey\": \"taintKey\",\n              \"projectKey\": \"projectKey\",\n              \"branch\": \"branch\",\n              \"creationDate\": 123456789,\n              \"ruleKey\": \"javasecurity:S123\",\n              \"severity\": \"MAJOR\",\n              \"type\": \"VULNERABILITY\",\n              \"mainLocation\": {\n                \"filePath\": \"functions/taint.js\",\n                \"message\": \"blah blah\",\n                \"textRange\": {\n                  \"startLine\": 17,\n                  \"startLineOffset\": 10,\n                  \"endLine\": 3,\n                  \"endLineOffset\": 2,\n                  \"hash\": \"hash\"\n                }\n              },\n              \"flows\": [\n                {\n                  \"locations\": [\n                    {\n                      \"filePath\": \"functions/taint.js\",\n                      \"message\": \"sink: tainted value is used to perform a security-sensitive operation\",\n                      \"textRange\": {\n                        \"startLine\": 17,\n                        \"startLineOffset\": 10,\n                        \"endLine\": 3,\n                        \"endLineOffset\": 2,\n                        \"hash\": \"hash1\"\n                      }\n                    },\n                    {\n                      \"filePath\": \"functions/taint2.js\",\n                      \"message\": \"sink: tainted value is used to perform a security-sensitive operation\",\n                      \"textRange\": {\n                        \"startLine\": 18,\n                        \"startLineOffset\": 11,\n                        \"endLine\": 4,\n                        \"endLineOffset\": 3,\n                        \"hash\": \"hash2\"\n                      }\n                    }\n                  ]\n                }\n              ]\n            }\n          }\"\"\");\n\n      await().untilAsserted(() -> assertThat(issueStorage.containsIssue(\"taintKey\")).isFalse());\n    }\n  }\n\n  @Nested\n  class WhenReceivingTaintVulnerabilityClosedEvent {\n    @SonarLintTest\n    void should_remove_taint_vulnerability(SonarLintTestHarness harness) {\n      var serverTaintIssue = aServerTaintIssue(\"taintKey\").withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\"))\n        .withIntroductionDate(Instant.EPOCH.plusSeconds(1)).withType(RuleType.BUG);\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", storage -> storage\n          .withProject(\"projectKey\", project -> project.withMainBranch(branch -> branch.withTaintIssue(serverTaintIssue))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      var issueStorage = backend.getIssueStorageService().connection(\"connectionId\").project(\"projectKey\").findings();\n      await().untilAsserted(() -> assertThat(issueStorage.containsIssue(\"taintKey\")).isTrue());\n\n      webSocketServerEU.getConnections().get(0).sendMessage(\n        \"\"\"\n          {\n            \"event\": \"TaintVulnerabilityClosed\",\n            \"data\": {\n              \"projectKey\": \"projectKey\",\n              \"key\": \"taintKey\"\n            }\n          }\"\"\");\n\n      await().untilAsserted(() -> assertThat(issueStorage.containsIssue(\"taintKey\")).isFalse());\n    }\n\n    @SonarLintTest\n    void should_not_remove_taint_vulnerability_event_data_is_malformed(SonarLintTestHarness harness) {\n      var serverTaintIssue = aServerTaintIssue(\"taintKey\").withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\"))\n        .withIntroductionDate(Instant.EPOCH.plusSeconds(1)).withType(RuleType.BUG);\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", storage -> storage\n          .withProject(\"projectKey\", project -> project.withMainBranch(branch -> branch.withTaintIssue(serverTaintIssue))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      var issueStorage = backend.getIssueStorageService().connection(\"connectionId\").project(\"projectKey\").findings();\n      await().untilAsserted(() -> assertThat(issueStorage.containsIssue(\"taintKey\")).isTrue());\n\n      webSocketServerEU.getConnections().get(0).sendMessage(\n        \"\"\"\n          {\n            \"event\": \"TaintVulnerabilityClosed\",\n            \"data\": {\n              \"projectKey\": \"projectKey\",\n              \"taintKey\": \"taintKey\"\n            }\n          }\"\"\");\n\n      await().untilAsserted(() -> assertThat(issueStorage.containsIssue(\"taintKey\")).isTrue());\n    }\n  }\n\n  @Nested\n  class WhenReceivingSecurityHotspotChangedEvent {\n    @SonarLintTest\n    void should_update_security_hotspot(SonarLintTestHarness harness) {\n      var serverHotspot = aServerHotspot(\"hotspotKey\").withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\"))\n        .withIntroductionDate(Instant.EPOCH.plusSeconds(1));\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", storage -> storage\n          .withProject(\"projectKey\", project -> project.withMainBranch(branch -> branch.withHotspot(serverHotspot))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      var issueStorage = backend.getIssueStorageService().connection(\"connectionId\").project(\"projectKey\").findings();\n      await().untilAsserted(() -> assertThat(issueStorage.getHotspot(\"hotspotKey\").getStatus().isResolved()).isFalse());\n\n      webSocketServerEU.getConnections().get(0).sendMessage(\n        \"\"\"\n          {\n            \"event\": \"SecurityHotspotChanged\",\n            \"data\": {\n              \"key\": \"hotspotKey\",\n              \"projectKey\": \"projectKey\",\n              \"updateDate\": 1685007187000,\n              \"status\": \"REVIEWED\",\n              \"assignee\": \"assigneeEmail\",\n              \"resolution\": \"SAFE\",\n              \"filePath\": \"/project/path/to/file\"\n            }\n          }\"\"\");\n\n      await().untilAsserted(() -> assertThat(issueStorage.getHotspot(\"hotspotKey\").getStatus().isResolved()).isTrue());\n    }\n\n    @SonarLintTest\n    void should_not_update_security_hotspot_if_event_data_is_malformed(SonarLintTestHarness harness) {\n      var serverHotspot = aServerHotspot(\"hotspotKey\").withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\"))\n        .withIntroductionDate(Instant.EPOCH.plusSeconds(1));\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", storage -> storage\n          .withProject(\"projectKey\", project -> project.withMainBranch(branch -> branch.withHotspot(serverHotspot))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      var issueStorage = backend.getIssueStorageService().connection(\"connectionId\").project(\"projectKey\").findings();\n      await().untilAsserted(() -> assertThat(issueStorage.getHotspot(\"hotspotKey\").getStatus().isResolved()).isFalse());\n\n      webSocketServerEU.getConnections().get(0).sendMessage(\n        \"\"\"\n          {\n            \"event\": \"SecurityHotspotChanged\",\n            \"data\": {\n              \"key\": \"hotspotKey\",\n              \"projectKey\": \"projectKey\",\n              \"updateDate\": 1685007187000,\n              \"status\": \"REVIEWED\",\n              \"assignee\": \"assigneeEmail\",\n              \"resolution\": \"SAFE\",\n              \"filePath\": \"\"\n            }\n          }\"\"\");\n\n      await().untilAsserted(() -> assertThat(issueStorage.getHotspot(\"hotspotKey\").getStatus().isResolved()).isFalse());\n    }\n  }\n\n  @Nested\n  class WhenReceivingSecurityHotspotRaisedEvent {\n    @SonarLintTest\n    void should_create_new_security_hotspot(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", storage -> storage\n          .withProject(\"projectKey\"))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      var issueStorage = backend.getIssueStorageService().connection(\"connectionId\").project(\"projectKey\").findings();\n      await().untilAsserted(() -> assertThat(issueStorage.getHotspot(\"hotspotKey\")).isNull());\n\n      webSocketServerEU.getConnections().get(0).sendMessage(\n        \"\"\"\n          {\n            \"event\": \"SecurityHotspotRaised\",\n            \"data\": {\n              \"status\": \"REVIEWED\",\n              \"resolution\": \"FIXED\",\n              \"vulnerabilityProbability\": \"MEDIUM\",\n              \"creationDate\": 1685006550000,\n              \"mainLocation\": {\n                \"filePath\": \"src/main/java/org/example/Main.java\",\n                \"message\": \"Make sure that using this pseudorandom number generator is safe here.\",\n                \"textRange\": {\n                  \"startLine\": 12,\n                  \"startLineOffset\": 29,\n                  \"endLine\": 12,\n                  \"endLineOffset\": 36,\n                  \"hash\": \"43b5c9175984c071f30b873fdce0a000\"\n                }\n              },\n              \"ruleKey\": \"java:S2245\",\n              \"key\": \"hotspotKey\",\n              \"projectKey\": \"projectKey\",\n              \"branch\": \"some-branch\"\n          }}\"\"\");\n\n      await().untilAsserted(() -> {\n        assertThat(issueStorage.getHotspot(\"hotspotKey\")).isNotNull();\n        assertThat(issueStorage.getHotspot(\"hotspotKey\").getStatus()).isEqualTo(HotspotReviewStatus.FIXED);\n      });\n    }\n\n    @SonarLintTest\n    void should_not_create_security_hotspot_if_event_data_is_malformed(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", storage -> storage\n          .withProject(\"projectKey\"))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      var issueStorage = backend.getIssueStorageService().connection(\"connectionId\").project(\"projectKey\").findings();\n      await().untilAsserted(() -> assertThat(issueStorage.getHotspot(\"hotspotKey\")).isNull());\n\n      webSocketServerEU.getConnections().get(0).sendMessage(\n        \"\"\"\n          {\n            \"event\": \"SecurityHotspotRaised\",\n            \"data\": {\n              \"status\": \"TO_REVIEW\",\n              \"vulnerabilityProbability\": \"MMMMMMM\",\n              \"creationDate\": 1685006550000,\n              \"mainLocation\": {\n                \"filePath\": \"src/main/java/org/example/Main.java\",\n                \"message\": \"Make sure that using this pseudorandom number generator is safe here.\",\n                \"textRange\": {\n                  \"startLine\": 12,\n                  \"startLineOffset\": 29,\n                  \"endLine\": 12,\n                  \"endLineOffset\": 36,\n                  \"hash\": \"43b5c9175984c071f30b873fdce0a000\"\n                }\n              },\n              \"ruleKey\": \"java:S2245\",\n              \"key\": \"hotspotKey\",\n              \"projectKey\": \"projectKey\",\n              \"branch\": \"some-branch\"\n          }}\"\"\");\n\n      await().untilAsserted(() -> assertThat(issueStorage.getHotspot(\"hotspotKey\")).isNull());\n    }\n  }\n\n  @Nested\n  class WhenReceivingSecurityHotspotClosedEvent {\n    @SonarLintTest\n    void should_remove_security_hotspot(SonarLintTestHarness harness) {\n      var serverHotspot = aServerHotspot(\"hotspotKey\").withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\"))\n        .withIntroductionDate(Instant.EPOCH.plusSeconds(1));\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", storage -> storage\n          .withProject(\"projectKey\", project -> project.withMainBranch(branch -> branch.withHotspot(serverHotspot))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      var issueStorage = backend.getIssueStorageService().connection(\"connectionId\").project(\"projectKey\").findings();\n      await().untilAsserted(() -> assertThat(issueStorage.getHotspot(\"hotspotKey\")).isNotNull());\n\n      webSocketServerEU.getConnections().get(0).sendMessage(\n        \"\"\"\n          {\n            \"event\": \"SecurityHotspotClosed\",\n            \"data\": {\n              \"key\": \"hotspotKey\",\n              \"projectKey\": \"projectKey\",\n              \"filePath\": \"/project/path/to/file\"\n            }\n          }\"\"\");\n\n      await().untilAsserted(() -> assertThat(issueStorage.getHotspot(\"hotspotKey\")).isNull());\n    }\n\n    @SonarLintTest\n    void should_not_remove_security_hotspot(SonarLintTestHarness harness) {\n      var serverHotspot = aServerHotspot(\"hotspotKey\").withTextRange(new TextRangeWithHash(1, 2, 3, 4, \"hash\"))\n        .withIntroductionDate(Instant.EPOCH.plusSeconds(1));\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      var backend = newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", storage -> storage\n          .withProject(\"projectKey\", project -> project.withMainBranch(branch -> branch.withHotspot(serverHotspot))))\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      var issueStorage = backend.getIssueStorageService().connection(\"connectionId\").project(\"projectKey\").findings();\n      await().untilAsserted(() -> assertThat(issueStorage.getHotspot(\"hotspotKey\")).isNotNull());\n\n      webSocketServerEU.getConnections().get(0).sendMessage(\n        \"\"\"\n          {\n            \"event\": \"SecurityHotspotClosed\",\n            \"data\": {\n              \"wrongKey\": \"hotspotKey\",\n              \"projectKey\": \"projectKey\",\n              \"filePath\": \"/project/path/to/file\"\n            }\n          }\"\"\");\n\n      await().untilAsserted(() -> assertThat(issueStorage.getHotspot(\"hotspotKey\")).isNotNull());\n    }\n  }\n\n  @Nested\n  class WhenReceivingUnexpectedEvents {\n    @SonarLintTest\n    void should_ignore_if_the_event_type_is_unknown(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      webSocketServerEU.getConnections().get(0).sendMessage(\"{\\\"event\\\": \\\"UnknownEvent\\\", \\\"data\\\": {\\\"message\\\": \\\"msg\\\"}}\");\n\n      await().untilAsserted(() -> assertThat(client.getSmartNotificationsToShow()).isEmpty());\n    }\n\n    @SonarLintTest\n    void should_ignore_if_the_event_is_malformed(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      await().until(() -> !webSocketServerEU.getConnections().isEmpty());\n\n      webSocketServerEU.getConnections().get(0).sendMessage(\"{\\\"event\\\": \\\"Malformed\");\n\n      await().untilAsserted(() -> assertThat(client.getSmartNotificationsToShow()).isEmpty());\n    }\n\n    @SonarLintTest\n    void should_not_forward_to_client_duplicated_event(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      webSocketServerEU.getConnections().get(0).sendMessage(\n        \"{\\\"event\\\": \\\"QualityGateChanged\\\", \\\"data\\\": {\\\"message\\\": \\\"msg\\\", \\\"link\\\": \\\"lnk\\\", \\\"project\\\": \\\"projectKey\\\", \\\"date\\\": \" +\n          \"\\\"2023-07-19T15:08:01+0000\\\"}}\");\n      webSocketServerEU.getConnections().get(0).sendMessage(\n        \"{\\\"event\\\": \\\"QualityGateChanged\\\", \\\"data\\\": {\\\"message\\\": \\\"msg\\\", \\\"link\\\": \\\"lnk\\\", \\\"project\\\": \\\"projectKey\\\", \\\"date\\\": \" +\n          \"\\\"2023-07-19T15:08:01+0000\\\"}}\");\n\n      await().untilAsserted(() -> assertThat(client.getSmartNotificationsToShow())\n        .extracting(ShowSmartNotificationParams::getScopeIds, ShowSmartNotificationParams::getCategory,\n          ShowSmartNotificationParams::getLink, ShowSmartNotificationParams::getText,\n          ShowSmartNotificationParams::getConnectionId)\n        .containsExactly(tuple(Set.of(\"configScope\"), \"QUALITY_GATE\", \"lnk\", \"msg\", \"connectionId\")));\n    }\n  }\n\n  @Nested\n  class WhenWebSocketClosed {\n    @SonarLintTest\n    void should_refresh_connection_if_closed_by_server(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      webSocketServerEU.getConnections().get(0).close();\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections())\n        .extracting(WebSocketConnection::isOpened, WebSocketConnection::getReceivedMessages)\n        .contains(tuple(false, webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey\").build()),\n          tuple(true, webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey\").build())));\n    }\n    @SonarLintTest\n    void should_send_one_subscribe_message_per_project_key_when_reopening_connection(SonarLintTestHarness harness) {\n      var client = harness.newFakeClient()\n        .withToken(\"connectionId\", \"token\")\n        .build();\n      newBackendWithWebSockets(harness)\n        .withSonarCloudConnectionAndNotifications(\"connectionId\", \"orgKey\", null)\n        .withBoundConfigScope(\"configScope\", \"connectionId\", \"projectKey\")\n        .withBoundConfigScope(\"configScope2\", \"connectionId\", \"projectKey\")\n        .start(client);\n      awaitUntilFirstWebSocketSubscribedTo(\"projectKey\");\n\n      webSocketServerEU.getConnections().get(0).close();\n\n      await().untilAsserted(() -> assertThat(webSocketServerEU.getConnections())\n        .extracting(WebSocketConnection::isOpened, WebSocketConnection::getReceivedMessages)\n        .contains(tuple(false, webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey\").build()),\n          tuple(true, webSocketPayloadBuilder().subscribeWithProjectKey(\"projectKey\").build())));\n    }\n  }\n\n  public SonarLintBackendFixture.SonarLintBackendBuilder newBackendWithWebSockets(SonarLintTestHarness harness) {\n    var server = harness.newFakeSonarCloudServer().start();\n    return harness.newBackend()\n      .withBackendCapability(SERVER_SENT_EVENTS)\n      .withSonarQubeCloudEuRegionUri(server.baseUrl())\n      .withSonarQubeCloudEuRegionApiUri(server.baseUrl())\n      .withSonarQubeCloudUsRegionUri(server.baseUrl())\n      .withSonarQubeCloudUsRegionApiUri(server.baseUrl())\n      .withSonarQubeCloudEuRegionWebSocketUri(webSocketServerEU.getUrl())\n      .withSonarQubeCloudUsRegionWebSocketUri(webSocketServerUS.getUrl());\n  }\n\n  public static class WebSocketPayloadBuilder {\n\n    private final List<String> payload;\n\n    public static WebSocketPayloadBuilder webSocketPayloadBuilder() {\n      return new WebSocketPayloadBuilder();\n    }\n\n    private WebSocketPayloadBuilder() {\n      payload = new ArrayList<>();\n    }\n\n    public WebSocketPayloadBuilder subscribeWithProjectKey(String... projectKey) {\n      Arrays.stream(projectKey).forEach(key -> {\n        subscribeToProjectFilterWithProjectKey(key);\n        subscribeToProjectUserFilterWithProjectKey(key);\n      });\n      return this;\n    }\n\n    public void subscribeToProjectFilterWithProjectKey(String projectKey) {\n      payload.add(\"{\\\"action\\\":\\\"subscribe\\\",\" +\n        \"\\\"events\\\":[\\\"IssueChanged\\\",\\\"QualityGateChanged\\\",\\\"SecurityHotspotChanged\\\",\\\"SecurityHotspotClosed\\\",\" +\n        \"\\\"SecurityHotspotRaised\\\",\\\"TaintVulnerabilityClosed\\\",\\\"TaintVulnerabilityRaised\\\"],\" +\n        \"\\\"filterType\\\":\\\"PROJECT\\\",\" +\n        \"\\\"project\\\":\\\"\" + projectKey + \"\\\"}\");\n    }\n\n    public void subscribeToProjectUserFilterWithProjectKey(String projectKey) {\n      payload.add(\"{\\\"action\\\":\\\"subscribe\\\",\" +\n        \"\\\"events\\\":[\\\"MyNewIssues\\\"],\" +\n        \"\\\"filterType\\\":\\\"PROJECT_USER\\\",\" +\n        \"\\\"project\\\":\\\"\" + projectKey + \"\\\"}\");\n    }\n\n    public WebSocketPayloadBuilder unsubscribeWithProjectKey(String... projectKey) {\n      Arrays.stream(projectKey).forEach(key -> {\n        unsubscribeToProjectFilterWithProjectKey(key);\n        unsubscribeToProjectUserFilterWithProjectKey(key);\n      });\n      return this;\n    }\n\n    public void unsubscribeToProjectFilterWithProjectKey(String projectKey) {\n      payload.add(\"{\\\"action\\\":\\\"unsubscribe\\\",\" +\n        \"\\\"events\\\":[\\\"IssueChanged\\\",\\\"QualityGateChanged\\\",\\\"SecurityHotspotChanged\\\",\\\"SecurityHotspotClosed\\\",\" +\n        \"\\\"SecurityHotspotRaised\\\",\\\"TaintVulnerabilityClosed\\\",\\\"TaintVulnerabilityRaised\\\"],\" +\n        \"\\\"filterType\\\":\\\"PROJECT\\\",\" +\n        \"\\\"project\\\":\\\"\" + projectKey + \"\\\"}\");\n    }\n\n    public void unsubscribeToProjectUserFilterWithProjectKey(String projectKey) {\n      payload.add(\"{\\\"action\\\":\\\"unsubscribe\\\",\" +\n        \"\\\"events\\\":[\\\"MyNewIssues\\\"],\" +\n        \"\\\"filterType\\\":\\\"PROJECT_USER\\\",\" +\n        \"\\\"project\\\":\\\"\" + projectKey + \"\\\"}\");\n    }\n\n    public List<String> build() {\n      return payload;\n    }\n\n  }\n\n  private void awaitUntilFirstWebSocketSubscribedTo(String... projectKey) {\n    await().untilAsserted(() -> {\n      assertThat(webSocketServerEU.getConnections()).hasSize(1)\n        .first()\n        .extracting(WebSocketConnection::getReceivedMessages)\n        .isEqualTo(webSocketPayloadBuilder().subscribeWithProjectKey(projectKey).build());\n    });\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/utils/AnalysisUtils.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage utils;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.TimeUnit;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintBackendFixture;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\n\npublic class AnalysisUtils {\n\n  private AnalysisUtils() {\n    // util\n  }\n\n  public static Path createFile(Path folderPath, String fileName, String content) {\n    var filePath = folderPath.resolve(fileName);\n    try {\n      Files.writeString(filePath, content);\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n    return filePath;\n  }\n\n  public static RaisedIssueDto analyzeFileAndGetIssue(URI fileUri, SonarLintBackendFixture.FakeSonarLintRpcClient client, SonarLintTestRpcServer backend, String scopeId) {\n    var raisedIssues = analyzeFileAndGetIssues(fileUri, client, backend, scopeId);\n    return raisedIssues.get(0);\n  }\n\n  public static List<RaisedIssueDto> analyzeFileAndGetIssues(URI fileUri, SonarLintBackendFixture.FakeSonarLintRpcClient client, SonarLintTestRpcServer backend, String scopeId) {\n    var analysisId = UUID.randomUUID();\n    var analysisResult = backend.getAnalysisService().analyzeFilesAndTrack(\n        new AnalyzeFilesAndTrackParams(scopeId, analysisId, List.of(fileUri), Map.of(), false, System.currentTimeMillis()))\n      .join();\n    assertThat(analysisResult.getFailedAnalysisFiles()).isEmpty();\n    await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(scopeId)).isNotEmpty());\n    return client.getRaisedIssuesForScopeId(scopeId).get(fileUri);\n  }\n\n  public static Map<URI, List<RaisedIssueDto>> analyzeFilesAndGetIssuesAsMap(List<URI> files, SonarLintBackendFixture.FakeSonarLintRpcClient client, SonarLintTestRpcServer backend, String scopeId) {\n    var analysisId = UUID.randomUUID();\n    var analysisResult = backend.getAnalysisService().analyzeFilesAndTrack(\n        new AnalyzeFilesAndTrackParams(scopeId, analysisId, files, Map.of(), true))\n      .join();\n    assertThat(analysisResult.getFailedAnalysisFiles()).isEmpty();\n    await().atMost(20, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(scopeId)).isNotEmpty());\n    return client.getRaisedIssuesForScopeId(scopeId);\n  }\n\n  public static void analyzeFilesAndVerifyNoIssues(List<URI> files, SonarLintBackendFixture.FakeSonarLintRpcClient client, SonarLintTestRpcServer backend, String scopeId) {\n    var analysisId = UUID.randomUUID();\n    var analysisResult = backend.getAnalysisService().analyzeFilesAndTrack(\n        new AnalyzeFilesAndTrackParams(scopeId, analysisId, files, Map.of(), true))\n      .join();\n    assertThat(analysisResult.getFailedAnalysisFiles()).isEmpty();\n    await().during(1, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(scopeId)).isEmpty());\n  }\n\n  public static void analyzeFileAndGetHotspots(URI fileUri, SonarLintBackendFixture.FakeSonarLintRpcClient client, SonarLintTestRpcServer backend, String scopeId) {\n    var analysisId = UUID.randomUUID();\n    var analysisResult = backend.getAnalysisService().analyzeFilesAndTrack(\n        new AnalyzeFilesAndTrackParams(scopeId, analysisId, List.of(fileUri), Map.of(), true))\n      .join();\n    assertThat(analysisResult.getFailedAnalysisFiles()).isEmpty();\n    await().atMost(40, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedHotspotsForScopeIdAsList(scopeId)).isNotEmpty());\n    assertThat(client.getRaisedHotspotsForScopeId(scopeId)).containsOnlyKeys(fileUri);\n  }\n\n  public static Map<URI, List<RaisedIssueDto>> getPublishedIssues(SonarLintBackendFixture.FakeSonarLintRpcClient client, String scopeId) {\n    await().atMost(20, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeId(scopeId)).isNotEmpty());\n    return client.getRaisedIssuesForScopeId(scopeId);\n  }\n\n  public static void waitForRaisedIssues(SonarLintBackendFixture.FakeSonarLintRpcClient client, String scopeId) {\n    await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.getRaisedIssuesForScopeIdAsList(scopeId)).isNotEmpty());\n  }\n\n  public static void waitForAnalysisReady(SonarLintBackendFixture.FakeSonarLintRpcClient client, String scopeId) {\n    await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(client.isAnalysisReadyForScope(scopeId)).isTrue());\n  }\n\n  public static void editFile(Path folderPath, String fileName, String content) {\n    var filePath = folderPath.resolve(fileName);\n    try {\n      Files.writeString(filePath, content);\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  public static void removeFile(Path folderPath, String fileName) {\n    var filePath = folderPath.resolve(fileName);\n    try {\n      Files.deleteIfExists(filePath);\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  public static List<RaisedIssueDto> awaitRaisedIssuesNotification(SonarLintBackendFixture.FakeSonarLintRpcClient client, String configScopeId) {\n    waitForRaisedIssues(client, configScopeId);\n    return client.getRaisedIssuesForScopeIdAsList(configScopeId);\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/utils/JuliSLF4JDelegatingLog.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage utils;\n\nimport java.io.Serializable;\nimport org.apache.juli.logging.Log;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport static java.lang.String.valueOf;\n\n/**\n * Redirect JULI to slf4j\n */\npublic class JuliSLF4JDelegatingLog implements Log, Serializable {\n\n  private transient volatile Logger log;\n\n  /**\n   * The default constructor is mandatory, as per the\n   * {@link java.util.ServiceLoader ServiceLoader} specification.\n   * <p>\n   * This will construct an unusable {@link Log}, though.\n   */\n  public JuliSLF4JDelegatingLog() {\n    super();\n  }\n\n  /**\n   * This {@link String}-based constructor is required by the default \"lean\"\n   * JULI {@link org.apache.juli.logging.LogFactory LogFactory}, in order to\n   * accept this class a {@link Log} implementation\n   * {@linkplain java.util.ServiceLoader provider}.\n   *\n   * @param name\n   *            the name of the logger to create.\n   * @see org.apache.juli.logging.LogFactory\n   */\n  public JuliSLF4JDelegatingLog(final String name) {\n    super();\n    setLogger(LoggerFactory.getLogger(name));\n  }\n\n  private void setLogger(final Logger logger) {\n    log = logger;\n  }\n\n  @Override\n  public boolean isTraceEnabled() {\n    return log.isTraceEnabled();\n  }\n\n  @Override\n  public boolean isDebugEnabled() {\n    return log.isDebugEnabled();\n  }\n\n  @Override\n  public boolean isInfoEnabled() {\n    return log.isInfoEnabled();\n  }\n\n  @Override\n  public boolean isWarnEnabled() {\n    return log.isWarnEnabled();\n  }\n\n  @Override\n  public boolean isErrorEnabled() {\n    return log.isErrorEnabled();\n  }\n\n  /**\n   * <p>\n   * The implementation here delegates to the {@link Logger#isErrorEnabled\n   * isErrorEnabled} method of the wrapped {@link Logger} instance, because\n   * SLF4J has no {@code FATAL} level.\n   *\n   * @return {@code true} if the wrapped logger {@link Logger#isErrorEnabled\n   *         isErrorEnabled}, or {@code false} otherwise.\n   */\n  @Override\n  public boolean isFatalEnabled() {\n    return log.isErrorEnabled();\n  }\n\n  @Override\n  public void trace(final Object msg) {\n    trace(msg, null);\n  }\n\n  @Override\n  public void trace(final Object msg, final Throwable thrown) {\n    doTrace(msg, thrown);\n  }\n\n  private void doTrace(final Object msg, final Throwable thrown) {\n    log.trace(valueOf(msg), thrown);\n  }\n\n  @Override\n  public void debug(final Object msg) {\n    debug(msg, null);\n  }\n\n  @Override\n  public void debug(final Object msg, final Throwable thrown) {\n    doDebug(msg, thrown);\n  }\n\n  private void doDebug(final Object msg, final Throwable thrown) {\n    log.debug(valueOf(msg), thrown);\n  }\n\n  @Override\n  public void info(final Object msg) {\n    info(msg, null);\n  }\n\n  @Override\n  public void info(final Object msg, final Throwable thrown) {\n    doInfo(msg, thrown);\n  }\n\n  private void doInfo(final Object msg, final Throwable thrown) {\n    log.info(valueOf(msg), thrown);\n  }\n\n  @Override\n  public void warn(final Object msg) {\n    warn(msg, null);\n  }\n\n  @Override\n  public void warn(final Object msg, final Throwable thrown) {\n    doWarn(msg, thrown);\n  }\n\n  private void doWarn(final Object msg, final Throwable thrown) {\n    log.warn(String.valueOf(msg), thrown);\n  }\n\n  @Override\n  public void error(final Object msg) {\n    error(msg, null);\n  }\n\n  @Override\n  public void error(final Object msg, final Throwable thrown) {\n    doError(msg, thrown);\n  }\n\n  private void doError(final Object msg, final Throwable thrown) {\n    log.error(valueOf(msg), thrown);\n  }\n\n  /**\n   * <p>\n   * The implementation here converts the message to a {@linkplain String\n   * string}, and logs it as {@code ERROR} level using the wrapped\n   * {@link Logger} instance, SLF4J has no {@code FATAL} level.\n   *\n   * @param msg\n   *            the message to log, to be converted to {@link String}\n   */\n  @Override\n  public void fatal(final Object msg) {\n    error(msg, null);\n  }\n\n  /**\n   * <p>\n   * The implementation here converts the message to a {@linkplain String\n   * string}, and logs it along with the given throwable as {@code ERROR}\n   * level, using the wrapped {@link Logger} instance, because SLF4J has no\n   * {@code FATAL} level.\n   *\n   * @param msg\n   *            the message to log, to be converted to {@link String}\n   * @param thrown\n   *            the throwable to log\n   */\n  @Override\n  public void fatal(final Object msg, final Throwable thrown) {\n    error(msg, thrown);\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/utils/MockWebServerExtensionWithProtobuf.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage utils;\n\nimport com.google.protobuf.Message;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.Arrays;\nimport java.util.Iterator;\nimport javax.annotation.Nullable;\nimport mockwebserver3.MockResponse;\nimport okio.Buffer;\nimport org.sonarsource.sonarlint.core.commons.testutils.MockWebServerExtension;\nimport org.sonarsource.sonarlint.core.http.HttpClientProvider;\nimport org.sonarsource.sonarlint.core.serverapi.EndpointParams;\nimport org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;\n\nimport static org.junit.jupiter.api.Assertions.fail;\n\npublic class MockWebServerExtensionWithProtobuf extends MockWebServerExtension {\n\n  public void addProtobufResponse(String path, Message m) {\n    try (var b = new Buffer()) {\n      m.writeTo(b.outputStream());\n      responsesByPath.put(path, new MockResponse.Builder().body(b).build());\n    } catch (IOException e) {\n      fail(e);\n    }\n  }\n\n  public void addProtobufResponseDelimited(String path, Message... m) {\n    try (var b = new Buffer()) {\n      writeMessages(b.outputStream(), Arrays.asList(m).iterator());\n      responsesByPath.put(path, new MockResponse.Builder().body(b).build());\n    }\n  }\n\n  public static <T extends Message> void writeMessages(OutputStream output, Iterator<T> messages) {\n    while (messages.hasNext()) {\n      writeMessage(output, messages.next());\n    }\n  }\n\n  public static <T extends Message> void writeMessage(OutputStream output, T message) {\n    try {\n      message.writeDelimitedTo(output);\n    } catch (IOException e) {\n      throw new IllegalStateException(\"failed to write message: \" + message, e);\n    }\n  }\n\n  public ServerApiHelper serverApiHelper() {\n    return serverApiHelper(null);\n  }\n\n  public ServerApiHelper serverApiHelper(@Nullable String organizationKey) {\n    return new ServerApiHelper(endpointParams(organizationKey), HttpClientProvider.forTesting().getHttpClientWithoutAuth());\n  }\n\n  public EndpointParams endpointParams() {\n    return endpointParams(null);\n  }\n\n  public EndpointParams endpointParams(@Nullable String organizationKey) {\n    return new EndpointParams(url(\"/\"), url(\"/\"), organizationKey != null, organizationKey);\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/utils/OnDiskTestClientInputFile.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage utils;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.nio.charset.Charset;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\n\npublic class OnDiskTestClientInputFile implements ClientInputFile {\n  private final Path path;\n  private final boolean isTest;\n  private final Charset encoding;\n  private final SonarLanguage language;\n  private final String relativePath;\n\n  public OnDiskTestClientInputFile(final Path path, String relativePath, final boolean isTest, final Charset encoding) {\n    this(path, relativePath, isTest, encoding, null);\n  }\n\n  public OnDiskTestClientInputFile(final Path path, String relativePath, final boolean isTest, final Charset encoding, @Nullable SonarLanguage language) {\n    this.path = path;\n    this.relativePath = relativePath;\n    this.isTest = isTest;\n    this.encoding = encoding;\n    this.language = language;\n  }\n\n  @Override\n  public String getPath() {\n    return path.toString();\n  }\n\n  @Override\n  public String relativePath() {\n    return relativePath;\n  }\n\n  @Override\n  public boolean isTest() {\n    return isTest;\n  }\n\n  @Override\n  public SonarLanguage language() {\n    return language;\n  }\n\n  @Override\n  public Charset getCharset() {\n    return encoding;\n  }\n\n  @Override\n  public <G> G getClientObject() {\n    return null;\n  }\n\n  @Override\n  public InputStream inputStream() throws IOException {\n    return Files.newInputStream(path);\n  }\n\n  @Override\n  public String contents() throws IOException {\n    return new String(Files.readAllBytes(path), encoding);\n  }\n\n  @Override\n  public URI uri() {\n    return path.toUri();\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/utils/PluginLocator.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage utils;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\npublic class PluginLocator {\n  public static final String SONAR_JAVA_PLUGIN_VERSION = \"8.25.0.42802\";\n  public static final String SONAR_JAVA_PLUGIN_JAR = \"sonar-java-plugin-\" + SONAR_JAVA_PLUGIN_VERSION + \".jar\";\n  public static final String SONAR_JAVA_PLUGIN_JAR_HASH = \"fa425ffda3272aef1abc137941a64772\";\n  public static final String SONAR_JAVA_SE_PLUGIN_VERSION = \"8.19.0.1586\";\n  public static final String SONAR_JAVA_SE_PLUGIN_JAR = \"sonar-java-symbolic-execution-plugin-\" + SONAR_JAVA_SE_PLUGIN_VERSION + \".jar\";\n  public static final String SONAR_JAVA_SE_PLUGIN_JAR_HASH = \"unused\";\n\n  public static final String SONAR_DBD_PLUGIN_VERSION = \"2.7.0.20531\";\n  public static final String SONAR_DBD_PLUGIN_JAR = \"sonar-dbd-plugin-\" + SONAR_DBD_PLUGIN_VERSION + \".jar\";\n  public static final String SONAR_DBD_PLUGIN_JAR_HASH = \"unused\";\n  public static final String SONAR_DBD_JAVA_PLUGIN_VERSION = SONAR_DBD_PLUGIN_VERSION;\n  public static final String SONAR_DBD_JAVA_PLUGIN_JAR = \"sonar-dbd-java-frontend-plugin-\" + SONAR_DBD_JAVA_PLUGIN_VERSION + \".jar\";\n  public static final String SONAR_DBD_JAVA_PLUGIN_JAR_HASH = \"unused\";\n\n  public static final String SONAR_JAVASCRIPT_PLUGIN_VERSION = \"11.8.0.37897\";\n  public static final String SONAR_JAVASCRIPT_PLUGIN_JAR = \"sonar-javascript-plugin-\" + SONAR_JAVASCRIPT_PLUGIN_VERSION + \".jar\";\n  public static final String SONAR_JAVASCRIPT_PLUGIN_JAR_HASH = \"2fab92be44e07f1d367f891a55258736\";\n\n  public static final String SONAR_PHP_PLUGIN_VERSION = \"3.55.0.15704\";\n  public static final String SONAR_PHP_PLUGIN_JAR = \"sonar-php-plugin-\" + SONAR_PHP_PLUGIN_VERSION + \".jar\";\n  public static final String SONAR_PHP_PLUGIN_JAR_HASH = \"88ddaa391f3176891a62375e98b76ae9\";\n\n  public static final String SONAR_PYTHON_PLUGIN_VERSION = \"5.18.0.31561\";\n  public static final String SONAR_PYTHON_PLUGIN_JAR = \"sonar-python-plugin-\" + SONAR_PYTHON_PLUGIN_VERSION + \".jar\";\n  public static final String SONAR_PYTHON_PLUGIN_JAR_HASH = \"e1cff9e38811ab71e6efbff087743367\";\n\n  public static final String SONAR_KOTLIN_PLUGIN_VERSION = \"3.4.0.8957\";\n  public static final String SONAR_KOTLIN_PLUGIN_JAR = \"sonar-kotlin-plugin-\" + SONAR_KOTLIN_PLUGIN_VERSION + \".jar\";\n  public static final String SONAR_KOTLIN_PLUGIN_JAR_HASH = \"XXX\";\n\n  public static final String SONAR_XML_PLUGIN_VERSION = \"2.16.0.7616\";\n  public static final String SONAR_XML_PLUGIN_JAR = \"sonar-xml-plugin-\" + SONAR_XML_PLUGIN_VERSION + \".jar\";\n  public static final String SONAR_XML_PLUGIN_JAR_HASH = \"XXX\";\n\n  public static final String SONAR_TEXT_PLUGIN_VERSION = \"2.41.0.10709\";\n  public static final String SONAR_TEXT_PLUGIN_JAR = \"sonar-text-plugin-\" + SONAR_TEXT_PLUGIN_VERSION + \".jar\";\n  public static final String SONAR_TEXT_PLUGIN_JAR_HASH = \"f679af4c0e2992c3cec281d6a9cd5062\";\n\n  public static final String SONAR_CFAMILY_PLUGIN_VERSION = \"6.78.0.96395\";\n  private static final String SONAR_CFAMILY_PLUGIN_JAR = \"sonar-cfamily-plugin-\" + SONAR_CFAMILY_PLUGIN_VERSION + \".jar\";\n  public static final String SONAR_CFAMILY_PLUGIN_JAR_HASH = \"XXX\";\n\n  public static Path getJavaPluginPath() {\n    return getValidPluginPath(SONAR_JAVA_PLUGIN_JAR);\n  }\n\n  public static Path getJavaSePluginPath() {\n    return getPluginPath(SONAR_JAVA_SE_PLUGIN_JAR);\n  }\n\n  public static Path getDbdPluginPath() {\n    return getPluginPath(SONAR_DBD_PLUGIN_JAR);\n  }\n\n  public static Path getDbdJavaPluginPath() {\n    return getPluginPath(SONAR_DBD_JAVA_PLUGIN_JAR);\n  }\n\n  public static Path getJavaScriptPluginPath() {\n    return getValidPluginPath(SONAR_JAVASCRIPT_PLUGIN_JAR);\n  }\n\n  public static Path getPhpPluginPath() {\n    return getValidPluginPath(SONAR_PHP_PLUGIN_JAR);\n  }\n\n  public static Path getPythonPluginPath() {\n    return getValidPluginPath(SONAR_PYTHON_PLUGIN_JAR);\n  }\n\n  public static Path getCppPluginPath() {\n    return getPluginPath(SONAR_CFAMILY_PLUGIN_JAR);\n  }\n\n  public static Path getXmlPluginPath() {\n    return getValidPluginPath(SONAR_XML_PLUGIN_JAR);\n  }\n\n  public static Path getTextPluginPath() {\n    return getValidPluginPath(SONAR_TEXT_PLUGIN_JAR);\n  }\n\n  public static Path getKotlinPluginPath() {\n    return getPluginPath(SONAR_KOTLIN_PLUGIN_JAR);\n  }\n\n  private static Path getPluginPath(String file) {\n    return Paths.get(\"target/plugins/\").resolve(file);\n  }\n\n  private static Path getValidPluginPath(String file) {\n    var path = getPluginPath(file);\n    if (!Files.isRegularFile(path)) {\n      throw new IllegalStateException(\"Unable to find file \" + path);\n    }\n    return path;\n  }\n\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/utils/TestPlugin.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage utils;\n\nimport java.nio.file.Path;\nimport java.util.Set;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.test.utils.plugins.Plugin;\n\npublic class TestPlugin {\n  public static final Plugin JAVA = new Plugin(Language.JAVA, PluginLocator.getJavaPluginPath(), PluginLocator.SONAR_JAVA_PLUGIN_VERSION, PluginLocator.SONAR_JAVA_PLUGIN_JAR_HASH);\n  public static final Plugin JAVA_SE = new Plugin(\"javasymbolicexecution\", Language.JAVA, PluginLocator.getJavaSePluginPath(), PluginLocator.SONAR_JAVA_SE_PLUGIN_VERSION, PluginLocator.SONAR_JAVA_SE_PLUGIN_JAR_HASH);\n\n  public static final Plugin DBD = new Plugin(\"dbd\", Language.JAVA, PluginLocator.getDbdPluginPath(), PluginLocator.SONAR_DBD_PLUGIN_VERSION, PluginLocator.SONAR_DBD_PLUGIN_JAR_HASH);\n  public static final Plugin DBD_JAVA = new Plugin(\"dbdjavafrontend\", Language.JAVA, PluginLocator.getDbdJavaPluginPath(), PluginLocator.SONAR_DBD_JAVA_PLUGIN_VERSION, PluginLocator.SONAR_DBD_JAVA_PLUGIN_JAR_HASH);\n\n  public static final Plugin PHP = new Plugin(Language.PHP, PluginLocator.getPhpPluginPath(), PluginLocator.SONAR_PHP_PLUGIN_VERSION, PluginLocator.SONAR_PHP_PLUGIN_JAR_HASH);\n  public static final Plugin PYTHON = new Plugin(Language.PYTHON, PluginLocator.getPythonPluginPath(), PluginLocator.SONAR_PYTHON_PLUGIN_VERSION,\n    PluginLocator.SONAR_PYTHON_PLUGIN_JAR_HASH);\n  public static final Plugin JAVASCRIPT = new Plugin(Set.of(Language.JS, Language.TS), PluginLocator.getJavaScriptPluginPath(), PluginLocator.SONAR_JAVASCRIPT_PLUGIN_VERSION,\n    PluginLocator.SONAR_JAVASCRIPT_PLUGIN_JAR_HASH);\n  public static final Plugin TEXT = new Plugin(Language.SECRETS, PluginLocator.getTextPluginPath(), PluginLocator.SONAR_TEXT_PLUGIN_VERSION,\n    PluginLocator.SONAR_TEXT_PLUGIN_JAR_HASH);\n  public static final Plugin XML = new Plugin(Language.XML, PluginLocator.getXmlPluginPath(), PluginLocator.SONAR_XML_PLUGIN_VERSION, PluginLocator.SONAR_XML_PLUGIN_JAR_HASH);\n  public static final Plugin CFAMILY = new Plugin(Set.of(Language.C, Language.CPP, Language.OBJC), PluginLocator.getCppPluginPath(), PluginLocator.SONAR_CFAMILY_PLUGIN_VERSION,\n    PluginLocator.SONAR_CFAMILY_PLUGIN_JAR_HASH);\n  public static final Plugin KOTLIN = new Plugin(Set.of(Language.KOTLIN), PluginLocator.getKotlinPluginPath(), PluginLocator.SONAR_KOTLIN_PLUGIN_JAR,\n    PluginLocator.SONAR_KOTLIN_PLUGIN_JAR_HASH);\n\n  private final Set<Language> languages;\n  private final Path path;\n  private final String version;\n  private final String hash;\n\n  TestPlugin(Set<Language> languages, Path path, String version, String hash) {\n    this.languages = languages;\n    this.path = path;\n    this.version = version;\n    this.hash = hash;\n  }\n\n  public Path getPath() {\n    return path;\n  }\n\n  public String getVersion() {\n    return version;\n  }\n\n  public String getHash() {\n    return hash;\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/java/utils/ThreadLeakDetector.java",
    "content": "/*\n * SonarLint Core - Medium Tests\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage utils;\n\nimport java.util.Set;\nimport org.junit.jupiter.api.extension.AfterAllCallback;\nimport org.junit.jupiter.api.extension.BeforeAllCallback;\nimport org.junit.jupiter.api.extension.ExtensionContext;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class ThreadLeakDetector implements BeforeAllCallback, AfterAllCallback {\n  private Set<Thread> beforeThreadSet;\n\n  @Override\n  public void beforeAll(ExtensionContext context) {\n    this.beforeThreadSet = Thread.getAllStackTraces().keySet();\n  }\n\n  @Override\n  public void afterAll(ExtensionContext context) {\n    var afterThreadSet = Thread.getAllStackTraces().keySet();\n    afterThreadSet.removeAll(beforeThreadSet);\n    // There is no way to stop the Xodus threadJobProcessorPoolSpawner, but this is a deamon thread, so we can ignore it\n    removeThread(afterThreadSet, \"threadJobProcessorPoolSpawner\");\n    // This seems to be a JVM thread https://stackoverflow.com/questions/8224844/understanding-jvms-attach-listener-thread\n    removeThread(afterThreadSet, \"Attach Listener\");\n    assertThat(afterThreadSet).isEmpty();\n  }\n\n  private static void removeThread(Set<Thread> afterThreadSet, String name) {\n    var xodusThreadJobProcessorPoolSpawner = afterThreadSet.stream()\n      .filter(thread -> thread.getName().contains(name))\n      .findFirst();\n    xodusThreadJobProcessorPoolSpawner.ifPresent(afterThreadSet::remove);\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/projects/java-with-bytecode/src/Foo.java",
    "content": "public class Foo {\n\n\tpublic static void main(String[] args) {\n\t\tSystem.out.println(\"Foo7\"); //NOSONAR\n\t\tSystem.out.println(\"Foo7\");\n\t}\n\t\n\tprivate void foo() {\n\t\t\n\t}\n}\n"
  },
  {
    "path": "medium-tests/src/test/projects/windows-shortcut/hello.py",
    "content": "# TODO: Hello world\nprint(\"Hello World\")\n"
  },
  {
    "path": "medium-tests/src/test/projects/windows-shortcut/hellp.py.fake.lnk",
    "content": ""
  },
  {
    "path": "medium-tests/src/test/resources/META-INF/services/org.apache.juli.logging.Log",
    "content": "utils.JuliSLF4JDelegatingLog\n"
  },
  {
    "path": "medium-tests/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension",
    "content": "utils.ThreadLeakDetector\n"
  },
  {
    "path": "medium-tests/src/test/resources/file-with-utf8-bom.js",
    "content": "﻿// TODO 1\n"
  },
  {
    "path": "medium-tests/src/test/resources/logback-test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE configuration>\n\n<configuration>\n  <include resource=\"logback-shared.xml\"/>\n\n  <!-- Disable logs from Wiremock -->\n  <logger name=\"org.eclipse.jetty\" level=\"OFF\" />\n  <!-- Disable logs from Tomcat embedded used by the WebSocketServer -->\n  <logger name=\"org.apache.tomcat\" level=\"OFF\" />\n  <logger name=\"org.apache.catalina\" level=\"OFF\" />\n  <logger name=\"org.apache.coyote\" level=\"OFF\" />\n</configuration>"
  },
  {
    "path": "medium-tests/src/test/resources/response/gessie/GessieIntegrationMediumTests/GessieRequest.json",
    "content": "{\n  \"metadata\": {\n    \"event_id\": \"${json-unit.any-string}\",\n    \"source\": {\n      \"domain\": \"SLCore\"\n    },\n    \"event_type\": \"Analytics.Editor.PluginActivated\",\n    \"event_timestamp\": \"${json-unit.any-string}\",\n    \"event_version\": \"0\"\n  },\n  \"event_payload\": {\n    \"message\": \"Gessie integration test event\",\n    \"trigger\": \"slcore_start\"\n  }\n}\n"
  },
  {
    "path": "medium-tests/src/test/resources/ssl/README.md",
    "content": "## Let's create TLS certificates\n\nThe most common format of certificates are PEM, so let's generate them instead of using Java keytool (that can also generate keys in JKS format).\n\nThis README, is a *simplified* version for generating the certificates only for test's purposes. \n\n**DO NOT USE IT FOR PRODUCTION**\n\n### Generation of a TLS server certificate\n\nIn this example the configuration of OpenSSL is entirely in openssl.conf (a stripped version of openssl.cnf that may vary from distribution to distribution)\n\n#### First let's create a Certificate Authority\n\nThe Certificate Authority is a private key that is used to sign other X.509 certificates in order to validate the ownership of a website (trusted tier).\n\n```bash\n$ openssl genrsa -out ca.key 4096\n.....++\n................................................................................................................................................++\ne is 65537 (0x010001)\n```\n\nNow we have our key to sign other certificates : `ca.key` in PEM format.\n\nLet's create our X.509 CA certificate :\n\n```bash\n$ openssl req -key ca.key -new -x509 -days 3650 -sha256 -extensions ca_extensions -out ca.crt -config ./openssl.conf\nYou are about to be asked to enter information that will be incorporated\ninto your certificate request.\nWhat you are about to enter is what is called a Distinguished Name or a DN.\nThere are quite a few fields but you can leave some blank\nFor some fields there will be a default value,\nIf you enter '.', the field will be left blank.\n-----\nCountry Name (2-letter code) [CH]:\nState or Province Name (full name) [Geneva]:\nLocality (e.g. city name) [Geneva]:\nOrganization (e.g. company name) [SonarSource Sàrl]:\nCommon Name (your.domain.com) [localhost]:\n```\n\nThere is no important values here.\n\n#### Let's create a self-signed certificate our TLS server using our CA\n\nWe want to create a X.509 certificate for our https server. This certificate will be a Certificate Signing Request. A certificate that need to be signed by a trusted tier.\nThe default configuration is set in `openssl.conf` and it has been configuration for `localhost`.\nThe most important part is the `Common Name` and `DNS.1` (set in `openssl.conf`).\n\nSo just keep using enter with this command line :\n\n```bash\n$ openssl req -new -keyout server.key -out server.csr -nodes -newkey rsa:4096 -config ./openssl.conf\n  Generating a 4096 bit RSA private key\n  ........................................................................++\n  .........................................................................................++\n  writing new private key to 'server.key'\n  -----\n  You are about to be asked to enter information that will be incorporated\n  into your certificate request.\n  What you are about to enter is what is called a Distinguished Name or a DN.\n  There are quite a few fields but you can leave some blank\n  For some fields there will be a default value,\n  If you enter '.', the field will be left blank.\n  -----\n  Country Name (2-letter code) [CH]:\n  State or Province Name (full name) [Geneva]:\n  Locality (e.g. city name) [Geneva]:\n  Organization (e.g. company name) [SonarSource Sàrl]:\n  Common Name (your.domain.com) [localhost]:\n```\n\nNo we have `server.csr` file valid for 10 years.\nLet's see what's in this certificate :\n```bash\n$ openssl req -verify -in server.csr -text -noout\nverify OK\nCertificate Request:\n    Data:\n        Version: 1 (0x0)\n        Subject: C = CH, ST = Geneva, L = Geneva, O = SonarSource Sàrl, CN = localhost\n        Subject Public Key Info:\n            Public Key Algorithm: rsaEncryption\n                RSA Public-Key: (4096 bit)\n                Modulus:\n                    00:c8:2d:dc:64:1a:b6:d9:a9:3e:bd:3f:d3:ae:27:\n                    ab:00:a8:09:f7:9e:ae:b5:70:c0:11:ab:2d:45:48:\n                    6c:b9:b3:b1:4b:42:b7:4e:48:d3:2e:38:cb:e5:7d:\n                    14:30:d3:b8:1d:2f:e2:09:04:cc:aa:80:09:51:bc:\n                    59:9d:a7:7a:76:34:cc:7a:2b:ae:d3:ef:98:38:ef:\n                    b2:8a:0e:e9:2f:79:4e:d4:a9:10:63:2b:5b:05:05:\n                    ef:6b:98:41:e3:c0:3e:6c:5f:8a:66:10:ca:98:e5:\n                    37:c6:ea:13:48:c9:92:22:53:44:1a:61:27:f4:60:\n                    16:a7:a9:87:a9:d3:cf:88:5e:d4:47:44:24:4f:6d:\n                    5e:c0:4a:ff:ad:e4:82:63:da:82:eb:9e:b3:76:6f:\n                    5d:b4:2d:fc:96:4a:98:e4:f5:20:97:48:38:11:29:\n                    33:7d:5a:96:fa:28:49:9f:cb:24:f8:02:f6:bb:ed:\n                    f3:91:90:51:10:c2:93:28:56:6e:4d:51:51:10:27:\n                    8f:c3:f0:cd:ee:51:2d:dc:e5:a7:21:55:20:44:ac:\n                    8b:66:1d:b7:eb:e0:ed:69:f0:d4:32:82:ee:53:91:\n                    3b:ee:58:83:ba:3b:9d:3f:f7:23:0e:36:46:20:6b:\n                    6a:80:9b:11:46:28:39:60:25:69:9e:e5:d0:34:ba:\n                    2b:c3:33:f2:44:3d:fb:8f:2d:47:a6:ae:64:9a:b3:\n                    5a:f0:ed:cb:3e:86:33:80:23:32:d0:e7:51:91:a8:\n                    c6:97:d1:7c:e4:02:52:5d:7c:a9:97:83:00:c5:10:\n                    fb:13:f9:29:1f:79:c4:a5:8c:7b:64:e0:cd:b6:a1:\n                    34:36:aa:f4:63:63:77:12:d3:fa:fe:1d:54:2e:64:\n                    43:38:a2:71:28:72:7a:bf:33:cb:8c:27:a7:66:51:\n                    8f:6f:e8:d2:90:19:2f:d4:8e:ac:b4:7b:e0:53:a8:\n                    0f:11:d1:7d:08:71:de:0a:a4:63:10:79:c8:e8:bf:\n                    7e:be:8b:06:7d:43:9b:4b:a1:0a:49:a6:c8:c6:43:\n                    c4:24:23:13:2a:b2:f9:f2:b8:e7:8e:ab:3e:2a:b5:\n                    50:26:23:d6:b2:d3:ee:23:ec:d1:36:92:70:2e:df:\n                    82:6a:d2:07:bb:f0:97:51:42:e4:d8:49:69:35:bb:\n                    38:90:1f:8e:aa:1d:27:78:26:26:d4:36:75:ee:83:\n                    17:69:cb:7f:53:45:8f:b4:63:13:d5:fd:42:10:8a:\n                    d3:75:38:4a:bd:13:cf:68:5e:41:6d:f0:57:b5:75:\n                    e3:dc:10:82:ab:29:ed:a1:27:9c:50:74:f2:4c:4a:\n                    a3:78:2a:53:ca:90:a6:89:20:24:85:b5:ec:c9:c7:\n                    be:96:b5\n                Exponent: 65537 (0x10001)\n        Attributes:\n        Requested Extensions:\n            X509v3 Subject Alternative Name: \n                DNS:localhost\n            X509v3 Key Usage: \n                Digital Signature, Key Encipherment, Data Encipherment\n            X509v3 Extended Key Usage: \n                TLS Web Server Authentication\n    Signature Algorithm: sha256WithRSAEncryption\n         bf:9d:6e:2f:cc:40:9b:92:29:c2:f1:0a:85:6c:35:eb:8e:fa:\n         13:0c:53:58:33:5f:7b:09:58:5f:dd:94:7e:2c:65:ed:73:91:\n         2a:6b:cc:2d:ec:26:1c:8e:95:57:d9:35:19:82:4f:42:59:81:\n         d9:b7:bb:08:70:28:70:35:50:f6:6a:46:e0:2a:ab:90:50:5a:\n         dc:b0:c3:b8:52:d7:5c:90:8f:4c:61:09:2c:ba:4a:31:37:6f:\n         e0:b9:6b:98:dd:aa:dd:52:66:7e:06:f1:8a:4b:bc:23:0d:62:\n         d3:b9:86:8f:3e:cc:05:2b:4d:c4:ad:cf:ae:be:33:22:f6:95:\n         00:f0:36:96:26:5e:42:84:d0:2a:79:41:1e:18:10:1c:96:3e:\n         9a:8b:cc:a5:f9:59:5b:78:d0:a1:a5:2e:4d:55:30:10:0b:cd:\n         13:bc:75:9a:49:e0:de:a4:4d:ed:9b:e8:42:2f:74:2b:dc:6f:\n         2d:d3:38:a9:e8:f8:98:2c:56:aa:3e:dd:0d:48:78:16:4c:50:\n         fd:0a:b3:3c:28:ac:64:7e:e9:bb:10:0e:3b:29:68:40:a9:19:\n         5a:2c:5c:d6:7e:32:39:96:49:a7:4c:6a:a6:09:8e:d4:b8:1e:\n         3e:2c:93:c3:2c:da:f2:09:20:ef:f4:a9:d2:ff:de:cd:7b:20:\n         66:46:ff:c2:36:c3:7d:32:d6:55:d1:fe:0f:00:9a:23:56:97:\n         52:a1:0a:52:64:29:50:c7:5d:b4:1e:e4:67:9a:07:3f:fb:85:\n         03:00:22:d8:f5:e6:bc:95:bf:bc:08:ab:4d:32:4c:d6:52:e0:\n         72:3e:8a:a5:85:72:43:d6:d4:51:6e:99:9a:1f:d8:0e:fd:4d:\n         59:81:7e:c1:81:6d:3b:69:76:ce:53:a4:c0:69:46:72:b2:fe:\n         40:b3:a5:5c:b0:ce:d2:61:83:be:0f:c3:85:a0:21:a7:e8:fd:\n         2f:2c:1c:68:24:1d:9b:a3:43:cb:5e:30:21:af:e8:2e:4e:ec:\n         ea:a7:d2:68:f1:bd:3f:3c:41:48:ac:91:f9:9d:e8:f2:3d:cb:\n         d0:82:d2:00:ed:7b:fa:d8:98:e3:a8:74:f2:ce:70:95:0a:9d:\n         c2:b2:cc:08:d1:fd:de:26:d3:3e:c0:62:28:9b:b4:2d:f4:b5:\n         6d:48:c9:d3:05:f5:1e:68:17:6b:fb:02:2e:20:98:1a:de:d4:\n         ae:6b:e0:68:97:98:e0:4f:47:ec:14:fd:dc:57:d2:e2:5c:59:\n         36:a5:0b:94:b7:4e:b8:ae:ee:c9:ac:02:ae:43:bf:9f:07:da:\n         0c:44:b0:47:69:1d:64:ea:bd:68:af:4f:a7:9a:1f:b1:b9:1d:\n         71:0e:86:4e:0c:ff:a3:1d\n```\n\n#### Let's sign this certificate with our own CA\n\nThe CSR will be signed with our previously created ca.key\nWe'll sign it to be valid for 10years (3650)\n\n```bash\n$ openssl x509 -req -days 3650 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.pem -sha256 -extfile v3.ext\nSignature ok\nsubject=C = CH, ST = Geneva, L = Geneva, O = SonarSource Sàrl, CN = localhost\nGetting CA Private Key\n```\n\nLet's verify what are in this certificate :\n\n```bash\n$ openssl x509 -in server.pem -text -noout\nCertificate:\n    Data:\n        Version: 3 (0x2)\n        Serial Number:\n            d5:c5:2a:c2:c8:f6:43:c7\n        Signature Algorithm: sha256WithRSAEncryption\n        Issuer: C = CH, ST = Geneva, L = Geneva, O = SonarSource Sàrl, CN = SonarSource Sàrl\n        Validity\n            Not Before: Mar 17 14:12:29 2020 GMT\n            Not After : Mar 15 14:12:29 2030 GMT\n        Subject: C = CH, ST = Geneva, L = Geneva, O = SonarSource Sàrl, CN = localhost\n        Subject Public Key Info:\n            Public Key Algorithm: rsaEncryption\n                RSA Public-Key: (4096 bit)\n                Modulus:\n                    00:a2:43:1e:8b:60:b5:e0:61:3e:99:a4:54:93:c8:\n                    16:14:c2:fa:fd:e5:7c:05:02:71:09:46:d9:2a:52:\n                    57:12:d7:74:46:6a:bd:d4:de:4a:06:b2:51:83:2c:\n                    98:07:8c:b0:f7:e1:8a:aa:fc:0c:30:c6:d7:ec:57:\n                    0b:a7:12:45:e3:13:1a:26:e8:22:d8:fd:2a:9e:ae:\n                    7b:20:b8:41:99:50:0e:b7:1c:bb:78:18:60:25:67:\n                    78:5b:af:d8:7f:d1:01:12:81:0a:1f:dd:f0:54:bc:\n                    57:16:05:22:7c:65:a2:7e:03:ed:e8:7f:50:b1:cd:\n                    7c:e8:7b:58:cb:df:6d:e3:04:03:78:a4:83:e7:20:\n                    c4:37:bc:00:ba:7c:12:d9:ac:52:88:88:72:df:fc:\n                    35:8f:94:f0:1b:33:f8:94:b8:bc:ab:0e:89:68:5f:\n                    92:1b:af:c9:da:c2:c2:e2:a1:c3:8e:c8:16:1a:9e:\n                    89:7a:b4:24:2c:24:df:c5:26:59:ab:d8:f9:06:39:\n                    02:c0:0d:88:5a:0c:14:e7:bc:c5:b8:4c:e5:e0:85:\n                    b2:0b:88:36:b3:d5:35:10:e9:b8:5a:48:69:1a:b3:\n                    2a:4a:d6:f3:f5:6a:91:41:f8:1e:da:d0:0e:21:c3:\n                    a2:f8:5c:08:42:a2:2b:13:be:63:e5:67:d5:19:2f:\n                    2c:96:6d:17:1c:7f:34:19:68:cf:91:b6:14:d9:9a:\n                    1b:1c:f9:08:d7:f9:2d:c3:48:14:3d:02:d4:90:f7:\n                    f2:74:65:f8:22:2d:46:b2:76:cd:46:c1:8e:ab:a1:\n                    11:d7:12:14:77:e3:1c:c3:1c:fa:32:79:0e:0e:59:\n                    55:e4:9d:60:d7:18:0b:25:82:97:28:30:df:de:89:\n                    5b:56:37:a2:33:86:26:12:83:75:f0:02:ae:88:b5:\n                    d6:5e:a2:b7:e7:57:9d:de:72:ad:d6:55:2a:e1:a8:\n                    4c:15:18:a9:e3:22:52:f1:74:e1:b0:d2:e7:9b:ec:\n                    f9:6d:5f:86:c2:9c:e2:22:f2:f4:11:a2:d1:71:b8:\n                    77:e4:8c:4c:ed:84:e8:f9:82:a2:f1:73:95:19:08:\n                    92:d5:b3:50:be:bc:c2:ec:0e:d7:da:53:d2:22:36:\n                    c8:d8:48:d1:22:0d:42:a7:68:6d:e5:b6:5f:00:7d:\n                    70:e4:5f:fe:df:db:3a:96:30:c8:76:89:e9:d1:98:\n                    1e:63:e2:d0:29:46:b0:3d:f6:38:d7:07:40:47:0e:\n                    a3:a5:70:1c:8b:80:c1:81:d1:35:cd:3d:93:20:c6:\n                    7c:10:a4:09:ed:41:12:2e:c3:66:e5:47:96:58:de:\n                    53:1b:d5:67:2c:1d:55:3b:c1:03:28:cf:5e:aa:33:\n                    2b:8c:e1\n                Exponent: 65537 (0x10001)\n        X509v3 extensions:\n            X509v3 Authority Key Identifier: \n                keyid:26:4F:F6:F9:E6:8B:B6:F7:59:CE:30:23:5C:90:2E:AE:7A:20:C4:DB\n\n            X509v3 Basic Constraints: \n                CA:FALSE\n            X509v3 Key Usage: \n                Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment\n            X509v3 Subject Alternative Name: \n                DNS:localhost\n    Signature Algorithm: sha256WithRSAEncryption\n         b0:df:99:da:44:e1:22:c6:51:da:e1:b5:a9:fd:fe:82:d6:74:\n         07:ad:d4:b4:f8:29:3e:57:7a:1b:98:36:4e:0a:23:68:f5:27:\n         c7:52:59:90:cd:94:23:08:83:6f:a4:af:14:a3:e3:ed:f2:13:\n         e4:17:f7:7c:27:45:bc:8c:9a:1d:f3:90:c6:b4:3e:e8:7a:c1:\n         18:e4:8e:8c:28:ac:02:c7:d1:4c:e3:67:7a:13:69:ff:a4:74:\n         c4:82:d7:54:d3:cb:7b:4e:f9:25:36:90:33:43:f0:b8:a5:e6:\n         7c:ea:3d:41:fe:51:3c:bc:d2:c6:4e:9c:dc:04:69:23:08:70:\n         bf:69:2a:bd:28:8c:3f:a1:f0:b0:88:87:a2:af:63:85:86:e3:\n         07:2a:74:89:d0:69:b3:8c:7d:a5:db:ec:f2:5c:56:33:89:04:\n         c6:75:a9:a2:b8:c0:1b:b5:dd:0f:96:50:71:ad:39:36:39:13:\n         d0:80:f3:c8:50:db:d2:65:4d:56:75:9c:70:c2:d6:0c:6b:4a:\n         6e:f7:f1:76:1b:82:16:13:eb:37:4f:05:fd:8f:06:89:15:d7:\n         6d:a7:4e:43:bb:ee:b1:a8:c0:f4:cd:d7:1f:17:c3:3f:1a:79:\n         8f:6e:46:a4:e5:1f:82:8d:60:6f:6c:a2:f4:9b:6e:59:85:48:\n         73:ae:78:dd:c1:fa:81:1f:38:56:84:fc:31:98:af:a8:e4:bf:\n         62:45:16:38:4a:5d:0e:6a:c4:bf:e1:9b:2b:c4:eb:dc:d4:85:\n         82:0f:6c:31:54:1c:46:62:51:22:c3:0d:e4:ca:2e:c9:5f:f5:\n         8c:7a:8c:c2:1d:f2:a8:f9:65:e6:ca:4e:6d:21:4e:55:07:6c:\n         58:0d:fd:59:76:9c:65:7f:26:8f:8b:7b:01:70:5f:59:25:66:\n         a8:9b:0a:70:a1:d8:fd:61:26:7e:4d:5f:3c:28:74:2b:94:fb:\n         2a:8e:35:51:77:5a:96:a9:9b:4e:18:b6:6d:0b:55:4e:2e:15:\n         ca:e7:cb:15:29:0e:b9:fd:23:56:a7:ad:dc:a1:b9:1b:1b:19:\n         24:10:e3:a5:cb:69:2b:40:74:3c:3e:31:ac:a9:0d:17:6b:51:\n         61:d4:5e:d1:98:b6:81:29:55:92:1f:00:8d:4c:72:d4:3a:0e:\n         fd:1f:30:73:04:b8:99:6f:27:57:9a:6c:2b:e1:fa:c2:d3:bf:\n         d3:d2:24:f3:5c:30:a3:25:d6:f5:18:91:13:d4:55:1e:33:89:\n         b7:99:27:a9:14:e4:d9:32:50:ba:56:2f:53:b7:a1:d7:d3:14:\n         2f:e2:73:5a:d4:b2:94:73:14:ef:ac:6f:a1:c1:84:31:17:fd:\n         fa:f8:62:d3:eb:a5:8a:34\n```\n\n#### Let's create a PKCS12 file to be used for starting a TLS server\n\n```bash\n$ openssl pkcs12 -export -in server.pem -inkey server.key -name localhost -out server.p12\nEnter Export Password: pwdServerP12\nVerifying - Enter Export Password: pwdServerP12\n```\n\nThe password of the PKCS12 file is `pwdServerP12`\n\nThe `server.p12` file can now be used to start a TLS server.\n\n### Create a certificate that will be used to authenticate a user\n\nThe principle is the same we'll have a CA authority signing certificates that will be sent by the user to the server.\nIn this case the server will have to host the CA authority in its TrustedKeyStore while the client will host his certificate in is KeyStore.\nIn this use case, the extensions are not the same, so we'll use openssl-client-auth.conf\n\n#### Generation of CA\n\nOne line to generate both the key `ca-lient-auth.key` and the CA certificate `ca-client-auth.crt`\n\n```bash\nopenssl req -newkey rsa:4096 -nodes -keyout ca-client-auth.key -new -x509 -days 3650 -sha256 -extensions ca_extensions -out ca-client-auth.crt -subj '/C=CH/ST=Geneva/L=Geneva/O=SonarSource Sàrl/CN=SonarSource/' -config ./openssl-client-auth.conf\nGenerating a 4096 bit RSA private key\n...................................++\n............................................................................................................................................................................................................................................................++\nwriting new private key to 'ca-client-auth.key'\n-----\n\n```\n\nFor the certificate, the Common Name is used to identify the user\n```bash\n$ openssl req -new -keyout client.key -out client.csr -nodes -newkey rsa:4096 -subj '/C=CH/ST=Geneva/L=Geneva/O=SonarSource Sàrl/CN=Julien Henry/' -config ./openssl-client-auth.conf\nGenerating a 4096 bit RSA private key\n..............................................++\n................++\nwriting new private key to 'client.key'\n-----\n```\n\nLet's sign this certificate\n```bash\n$ openssl x509 -req -days 3650 -in client.csr -CA ca-client-auth.crt -CAkey ca-client-auth.key -CAcreateserial -out client.pem -sha256\nSignature ok\nsubject=C = CH, ST = Geneva, L = Geneva, O = SonarSource Sàrl, CN = Julien Henry\nGetting CA Private Key\n```\n\nLet's create the pkcs12 store containing the client certificate\n\n```bash\n$ openssl pkcs12 -export -in client.pem -inkey client.key -name julienhenry -out client.p12\nEnter Export Password: pwdClientCertP12\nVerifying - Enter Export Password: pwdClientCertP12\n```\n\nThis will go to client keyStore.\nNow we'll generate the `server-with-client-ca.p12` file that will have the CA certificate. Since we don't need to add the key of the certificate (only required to sign, not to verify), we can import it directly with keytool.\n\n```bash\n$ keytool -import -trustcacerts -alias client-ca -keystore server-with-client-ca.p12 -file ca-client-auth.crt\nEnter keystore password: pwdServerWithClientCA \nRe-enter new password: pwdServerWithClientCA\nOwner: CN=SonarSource, O=SonarSource Sàrl, L=Geneva, ST=Geneva, C=CH\nIssuer: CN=SonarSource, O=SonarSource Sàrl, L=Geneva, ST=Geneva, C=CH\nSerial number: ed8bcadd4888ffac\nValid from: Sat Sep 15 08:10:22 CEST 2018 until: Tue Sep 12 08:10:22 CEST 2028\nCertificate fingerprints:\n\t MD5:  25:38:06:14:D0:B3:36:81:65:FC:44:CA:E3:BA:57:12\n\t SHA1: 77:56:EF:C7:2F:5A:29:D1:A0:54:5F:F8:B4:19:60:91:7B:71:E4:2C\n\t SHA256: 1D:2D:E5:52:21:60:75:08:F3:0A:B3:93:CF:38:F6:30:88:56:28:73:20:BA:76:9A:C0:A1:D7:8C:4D:D3:84:AA\nSignature algorithm name: SHA256withRSA\nSubject Public Key Algorithm: 4096-bit RSA key\nVersion: 3\n\nExtensions: \n\n#1: ObjectId: 2.5.29.35 Criticality=false\nAuthorityKeyIdentifier [\nKeyIdentifier [\n0000: 87 B9 C1 23 E2 F1 A3 68   BD D6 44 99 0E AD FC FC  ...#...h..D.....\n0010: A5 31 90 D4                                        .1..\n]\n]\n\n#2: ObjectId: 2.5.29.19 Criticality=true\nBasicConstraints:[\n  CA:true\n  PathLen:2147483647\n]\n\n#3: ObjectId: 2.5.29.37 Criticality=false\nExtendedKeyUsages [\n  serverAuth\n]\n\n#4: ObjectId: 2.5.29.15 Criticality=false\nKeyUsage [\n  DigitalSignature\n  Key_Encipherment\n  Data_Encipherment\n  Key_CertSign\n  Crl_Sign\n]\n\n#5: ObjectId: 2.5.29.14 Criticality=false\nSubjectKeyIdentifier [\nKeyIdentifier [\n0000: 87 B9 C1 23 E2 F1 A3 68   BD D6 44 99 0E AD FC FC  ...#...h..D.....\n0010: A5 31 90 D4                                        .1..\n]\n]\n\nTrust this certificate? [no]:  yes\nCertificate was added to keystore\n\n```"
  },
  {
    "path": "medium-tests/src/test/resources/ssl/ca-client-auth.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFvzCCA6egAwIBAgIUa0K//0+EYaicnXjGKpltcNKXrbAwDQYJKoZIhvcNAQEL\nBQAwXjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBkdlbmV2YTEPMA0GA1UEBwwGR2Vu\nZXZhMRcwFQYDVQQKDA5Tb25hclNvdXJjZSBTQTEUMBIGA1UEAwwLU29uYXJTb3Vy\nY2UwHhcNMjMwNzExMTU1NjA3WhcNMzMwNzA4MTU1NjA3WjBeMQswCQYDVQQGEwJD\nSDEPMA0GA1UECAwGR2VuZXZhMQ8wDQYDVQQHDAZHZW5ldmExFzAVBgNVBAoMDlNv\nbmFyU291cmNlIFNBMRQwEgYDVQQDDAtTb25hclNvdXJjZTCCAiIwDQYJKoZIhvcN\nAQEBBQADggIPADCCAgoCggIBANMCqzkPIZcGU078AEu5FEkxNNNzlRl7Pfzj+yey\nDqmHLRtkmX4HVnAL1ndq0JVgTxYu6ubA6cZDZM6/8E8QKlPRMJOLhRo5xzbBwzBj\n3ZUqU6Bwz3oQRGjuH7u2KI9XQKTkwga3gq3wpeBXqQh+Tu7Ud3V0MWstknwUiQCS\nVqPnYxLyND0h6hsucsMtUZdshFxOY+pXVHZb6omCZ/85lV6R9Eh8mSlJEqP6TXvG\nmbYFHrsma48GCVghzk+az3nR6S8KKCUdFyexM6WYI0XYvZIRf8s4twQp0KQ/4AKG\nd7dPip7jBnH5qFfGU19+QL9njwwajMlmTtTR+WBtbktSgbG2nZnaVaTlcE0cl0Wj\n9JTTtJd3g277P4oKPCXeD/dCPeBi2WrA2s2xlY5EfbXkix/DIF7xZzDij46+I4gi\nRV6MIMxdPPT45hR3khx7p2q/TsyGyVDzbObpdl5jJL22K6w121mCAqpmZXSrOHO0\nxQcu1jNWsACy58gm0RuPyy3MZPgrkWU4LzYeUkpPNtMWPuAoXsX1j99b0FtUwVxf\nOHj39g/z8nUkpWu64mutkpk2/KyNyVFcVs9S1ggnZADDniDdZ9/ip8U/p6Fk4Pnp\nzmfuiji1isTYSW04uoPB6vrvbQpOOTaSqk/lexbbyZe1bYpUPm5nWZhqhJ0MWQO6\n14f9AgMBAAGjdTBzMAsGA1UdDwQEAwIBtjATBgNVHSUEDDAKBggrBgEFBQcDATAd\nBgNVHQ4EFgQUibp+DX6FhDKLcWIlKcJYbholijkwHwYDVR0jBBgwFoAUibp+DX6F\nhDKLcWIlKcJYbholijkwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC\nAgEAdu5+mRuJwAl3fbBwfIHJtw1cuMccrZ+vEd5oMkXqlXDXNYT1KYEeEBfzuW8y\nW59zK2IKvguasYnKK9ZADK+2utKn2MtZuWcIySBvNKsDMr3ZJZpnPQh+bBJ3TMBE\n9Y4drxXJAShi8n2b5zfyM01EJQZdCgSjT2+0t/ntweZQ0GBbdNg7oqBfP3oJ18Qc\n/M37o9GK3ALwFZX4Sor+ChjmldnYnHPAXP4s87I8SrygaGv/Q7Ok3dRsEbdndTNZ\nB6biGqqqMg5PlRrNnd1ZNQYhJbuwypfQbTrFHHY/9CAfrWvZ9obJcq6xy1Ci8p+u\nwuavftn7NsynL21QbtfZShbWU/P83a629TEjZzpn1a72yYQvtBYFTUv0CH+C8xOo\nJbb5ZOqBKzyJCW5VDqVyvpcP/wfQ3WMBQ8lkm89u0ECPOikAf477R3XP2Ak/1Z21\nRQv5Fs6Cnb3N8xE5Y+zECiJ1DIb0Jsd0Jp8S0sCxQRXG6gE+FaPkcUOoYKfpW0s4\nHiPNAYglWWc9gjlCW6d3hrVh6Xfeltsp0hiVnBB+0EQAk7wxjHxfNio0ZfLCV77I\nvgw1SmgaoX3lCZ+lYyP9UESXXm/zuPG+UX7Zl6YSUIEzIXQeeFIAGbu3SPQxot39\nR4+8EeA3tb/ZdgZ9y7+aORTQvcXn7yKjF6SRPNiv440PKbc=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "medium-tests/src/test/resources/ssl/ca-client-auth.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDTAqs5DyGXBlNO\n/ABLuRRJMTTTc5UZez384/snsg6phy0bZJl+B1ZwC9Z3atCVYE8WLurmwOnGQ2TO\nv/BPECpT0TCTi4UaOcc2wcMwY92VKlOgcM96EERo7h+7tiiPV0Ck5MIGt4Kt8KXg\nV6kIfk7u1Hd1dDFrLZJ8FIkAklaj52MS8jQ9IeobLnLDLVGXbIRcTmPqV1R2W+qJ\ngmf/OZVekfRIfJkpSRKj+k17xpm2BR67JmuPBglYIc5Pms950ekvCiglHRcnsTOl\nmCNF2L2SEX/LOLcEKdCkP+AChne3T4qe4wZx+ahXxlNffkC/Z48MGozJZk7U0flg\nbW5LUoGxtp2Z2lWk5XBNHJdFo/SU07SXd4Nu+z+KCjwl3g/3Qj3gYtlqwNrNsZWO\nRH215IsfwyBe8Wcw4o+OviOIIkVejCDMXTz0+OYUd5Ice6dqv07MhslQ82zm6XZe\nYyS9tiusNdtZggKqZmV0qzhztMUHLtYzVrAAsufIJtEbj8stzGT4K5FlOC82HlJK\nTzbTFj7gKF7F9Y/fW9BbVMFcXzh49/YP8/J1JKVruuJrrZKZNvysjclRXFbPUtYI\nJ2QAw54g3Wff4qfFP6ehZOD56c5n7oo4tYrE2EltOLqDwer6720KTjk2kqpP5XsW\n28mXtW2KVD5uZ1mYaoSdDFkDuteH/QIDAQABAoICAAImmNAwE0uSyO6ZKvean1ZP\nphYxq2djFjKpsDmrWvLnadJJqDbcpEjoFfuJny6mmKfFOqe3d6qJrVFeK3ji1Uud\naj1WwriNtjYcq3ymx+9pSwjrnXHFEK1xH+mt5aPwY7xKiJ/A5xgWx9oNiq+Heg59\nI+qSkJ5jDIcvPmY/F5r8FMpwpY+p9NTqGEldAOPmO0MlmWceFMJQFYfpSF4VnCwm\nmLLUcks1b3c0ae3FDHQKgVGvBpGioh2/2nZ9QXggZ5wPInGeZkoFSF17u4BOwT/n\n5cGQhm0IcBP8grAD5tx7dxI/hiVLQigC4X6rrsVErPSWoLC81jjdGwmck+84nBHK\n6TU524043I6NhEZnqGNZeQ+CiCKxEozhxB+PC0L9lsmwE1Tw7CtSmt0JEUOG89Uz\niwmD4CG/FVC9svF+dW1XjGaPg9t1n4Ki/oQAyQoXSPMKJ1VJtMzxCFJqIDy6QvGp\nM3Vtw4Fv5HdlbPuE1VU15P7/9HCAODPJgcdBDL0U3Gzs2PiIeLrAuCUfHoTAsxkC\nymcryuWxiK5mxp+jO0yIZWwMz1+vu1ljMmiR0WyVyGBrgvicSGy211+3Gg1SG64x\n6kQxJtKRslnYpdZaRr+Em/hhyVaad5DISvcPx6wpYlY44XE+yr1/6RT1rSZc9NDf\nPL4tOCcohDtR//7RkFiBAoIBAQDvryL8OuIcY1lsYyVOVIaHddGLXK4roItY97cw\noHRM9jSEj1nk8r5aNR1GU/MRwBx6KQOWM6ilxgMZiHVsd/ZU1W0E2onDVpoSaYdZ\n62itp/wsY31k+pM2J7lGyiEPgPTeWqwnrcJh9gfmtatY+WjtuuPAasf6mgXao8zp\nZLHHJ1TYoC/Z98bBCguT6heCDdk6YMGfVWPXnfuUbE1CahORxKyEeHKJKz5r5D5T\ns9qor0ig8nT0uu8BBOzIsBgJrD7SBSTt3U4s1qPQWE4bb04mB1mcqCUzWRVg1Q7C\nfrOaO5qEywW7ap9Z2LXI7+yXv3Ffhr9oOfESdfuxrFgF6Hh5AoIBAQDhX9lVYBh4\nqACwpyBcODJgBCUe38TyZw9u90cj7G60NxYTjSAXQ/EnBJpoll8ZjaLzB9BGpFkE\nbaMDxMWGseQCgb/z0S7bgx11lEWv5dP05MX3YXEM4TdntZA+mF5gRxrtTRUfuNpz\nnCcPesGH7I84mKsy2DVmyeyQjx+UAgbniZmjdgttD+A/C8FQIdwR8w3e4zSMnxYt\ncT/EiclrZLbC8+ZpkNBc/x0A5DTtzPYLomP3zZ3XHcsjy4dCbsPf4m/+8gTClOtg\npLXqf9tIlZlDV5HG+wBLMowEcrv+YAmcSJTgjZFTbAhwlchBkBKMXa8pwFbeCgxC\n1HD6XKT6PHKlAoIBAQDRqmGvRQ8giCPkdYJf1/7qpjzMbSGK1mCvTdp49hNmc4UT\nZJbdeR7cqFPYfn1FzMjcb95MWRoxEVsKzdzyKqpYIW7BbS5PqwUSEDAvj4c9SZKF\nfe5mE/AM0oj3jL+I4WTS0XyBJTXCHcdhoQ+nwF1ygCbU3lPbOzrdvKX2kHgXX6eP\n6+iQAnhhyxi/pmo0AUtCDvuxFrBFIFS/zFF1RHej7uh+5SJELYjoNnvzGoSvRM4C\nxLVck92ZBabgE3Kjm4JEk+1vCkVlYclg6q5Av4gcKhfQuyH3nfjegOHtb4pK49LS\nUQrNnIKSxMiYYpCTWguh5aywTuc8H+sxzDIf0PD5AoIBAQDd+EwSdTHE7Bq54fDS\nQxsCTDLPRK8x22Vs8ijj7cJexn58xRsdLvJ9bmAxsCB8B269FC4C1W1DGkRautuc\nvOoHugj3N0iCNJG4M0+rXZ2zbfq4mgUBHXQ3NbCapfEVvBJ+M9QhSSwGf35NFac0\nmz24DU1YJ2/5C9ltyzjbyl9IfdKPFv1L7j37ejVsGd0o5Iz9wCMKeMkb+pVGmjqC\nBlCmlBeUU2eUoELNlPW4XIzcipURBDd4HkTrvVI7kOJkSB3sKCueBZQJvE/bZQ/x\nXj30AHR4WNBCP8d9vg8AC1CL10SiIWxHkHiDglIYmLBcBQ02/tPPnm/TBm87djGa\nsBxtAoIBAD3QzOZNcpnSENu7SGFcb1UvKK4J9IqmbXTMTCmu7gg3+Tvns7Tjkxnf\n71VGLDK6/3DLzMvj47iuUUCjjruWZ+bp8CBbuRfvuBRiONv9+ACDL5Vu1KvxTOyH\nqk8Kp4wCjKE5Ic7e20bpbbtuvrbqJTGmabErQbkLjU2vyN/ckWA+/g1x0Z62uGCN\nGn7rYHczSAclV0nUOkGN7dvEQbxNrfnvjTgS85BMZlT9CrZISCrCZijnkp8Qlhug\nkIOKHHzgUTPb63BaRnBtZFQF2dnvwfO1KHRbNupBUDFsxxuGIbE0YTOsQigZJV9h\nZpW5pkcoQUSaBXuH5Ube9b7hlk0aEB0=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "medium-tests/src/test/resources/ssl/ca.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFvTCCA6WgAwIBAgIUPg9F8DgwkXcdVMK0NIDJzNy4+3AwDQYJKoZIhvcNAQEL\nBQAwXDELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBkdlbmV2YTEPMA0GA1UEBwwGR2Vu\nZXZhMRcwFQYDVQQKDA5Tb25hclNvdXJjZSBTQTESMBAGA1UEAwwJbG9jYWxob3N0\nMCAXDTIzMDYwNTEwMDQxMloYDzIwNjAxMDE4MTAwNDEyWjBcMQswCQYDVQQGEwJD\nSDEPMA0GA1UECAwGR2VuZXZhMQ8wDQYDVQQHDAZHZW5ldmExFzAVBgNVBAoMDlNv\nbmFyU291cmNlIFNBMRIwEAYDVQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEB\nAQUAA4ICDwAwggIKAoICAQDnby1+L2gkG+S+9LW+FT3CNtJIUeKWP24s9LA4Owat\ngxE46lUrUhxUm9tiPEVUl4Dvrge6kBYCT1FnO0A7+EABMX1dw6BAXxdJIzXulEkp\nU23tAZLxCfNKbKzFhVGwxPzu1CcFkF0KZiIOxH97PuFMDHIRmaSJmM0b0sgJ0Fak\nBpeSeyG97hm27/ZYbq3fZPtI47QONRccCoStP1wEbO3mrdhbO3z/SRA0FfxmISIS\nCpLWKyZ9fOGcU7xBo5un0ya4PhJmAfndbCnL6tmKDyIqv3uu4VE91MyKUYeUcZrG\nxyhF64rzf8dM+frFafTiS4WhJ254bOk7xCQP69eMCcFKAdBuJdCKogo4uBKEi26N\nC25h/9w+hW+pHYH9glkE45KE9oD2n+ad89orRzMe3ebtxEJwuh/wGP5pD/EsKFhY\n1Kh2S/5yEqs7pxLvvpyOmBadnccdHKG4A3dsw8QOBOjPiMSO7rW7uGtAbOgCUE78\nj8y/tlihsKx66v0j9RB0iX4pi8nbYnoxghgx+yIy+jPTsfiI8GAOcyjDO9UBmYP9\nuf87ARuOFG40XTIjNh099hkNs3eZjLoD6cGFpdKuOFSDw5EcMqXenUmGX60En19W\nxWHt2uo6Mx5aShVTzyTKE8Ell5+LCIadC9vkahg5XT+YzjMfDkz6pXK3nUHxEr/h\nQwIDAQABo3UwczALBgNVHQ8EBAMCAbYwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHQYD\nVR0OBBYEFPNeg7FQG7+W+2MZVavRYpVSuWW7MB8GA1UdIwQYMBaAFPNeg7FQG7+W\n+2MZVavRYpVSuWW7MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIB\nAIXjV7FmujUw3tLq17Vg+2KXT+DCA/YORE/padw70A4ekDffaTFVQFDBX5OIx3wz\n2EfmYipF1wvR89uFT/PvhlQ/e8Pwk0hZ6nWt+3/edPz7rDkXRVWjnqAqabqwre98\njqG5AVrs9ABX0X5jcniaLcNrAV2d5sKDCuJXKPsMIwmqO9m1Nxhgjomlm4HhLDrK\noDoZIeSBOV1ucaCbzyVCNP/kTFc+TEdBN8X1V8AiieLW9FHrPUtmIldGeDY6M4X1\nRIxmrgDYBTlpXH6RWTArA7cit4sWgjB0lQ8R2S/DJC9PVF0kJanNPqa+544FIuF3\n1AxURTOdDCUjRJXr+6D/IScUd3GGq3y992In0ggmbZO+i53W7oUXQ6T3bS6yjVY+\n4K/NKCYhFJYSdMSHcmt9jOptZ2KJ0pf/ej5nK7BYLs1yzyeNuWI/XQm6iWQEcIZ0\nSz32eHyftsZZTAmn+YkI6HRndgAohkovEcC88tirRFI8fLDPcwhdMDS4MN895Rc3\nukbOPTXDDaDATEiMerPrN5K+7hJRQMr+xaSryUvAR5oMlQdyHLUpVpo4Q3eGOPLa\nHFGCG+PzxbEMcy4swCCvY5mbQzwVn7tb8V41pcupREvzrzPAL9ZDkYQrMoG92hka\nreSX/AJxz2/nmDczpZS89rVyIDK3Fjea5wIYS+o04i0r\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "medium-tests/src/test/resources/ssl/ca.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQDnby1+L2gkG+S+\n9LW+FT3CNtJIUeKWP24s9LA4OwatgxE46lUrUhxUm9tiPEVUl4Dvrge6kBYCT1Fn\nO0A7+EABMX1dw6BAXxdJIzXulEkpU23tAZLxCfNKbKzFhVGwxPzu1CcFkF0KZiIO\nxH97PuFMDHIRmaSJmM0b0sgJ0FakBpeSeyG97hm27/ZYbq3fZPtI47QONRccCoSt\nP1wEbO3mrdhbO3z/SRA0FfxmISISCpLWKyZ9fOGcU7xBo5un0ya4PhJmAfndbCnL\n6tmKDyIqv3uu4VE91MyKUYeUcZrGxyhF64rzf8dM+frFafTiS4WhJ254bOk7xCQP\n69eMCcFKAdBuJdCKogo4uBKEi26NC25h/9w+hW+pHYH9glkE45KE9oD2n+ad89or\nRzMe3ebtxEJwuh/wGP5pD/EsKFhY1Kh2S/5yEqs7pxLvvpyOmBadnccdHKG4A3ds\nw8QOBOjPiMSO7rW7uGtAbOgCUE78j8y/tlihsKx66v0j9RB0iX4pi8nbYnoxghgx\n+yIy+jPTsfiI8GAOcyjDO9UBmYP9uf87ARuOFG40XTIjNh099hkNs3eZjLoD6cGF\npdKuOFSDw5EcMqXenUmGX60En19WxWHt2uo6Mx5aShVTzyTKE8Ell5+LCIadC9vk\nahg5XT+YzjMfDkz6pXK3nUHxEr/hQwIDAQABAoICABaor3MR0Cfs5TX+/oZITR4l\nQOxcvAsK1c4n/NLckJhPY1HHu6P8q5JrGg0hqoYQOUr+JnqRHd6HBojNZ8oXUajs\nQAJ8o0V6Kez6ug6MTSdzKCmSxGC+IShRwI6sMWXVu4/kjJeCXWGlw0Lf4T1fWC6a\nb1aizNW99KL7PaOcVsdDb/ajAJg4ZiMflmZtG6/qEqI9zQSxFPylWP0fXXJ/ecFi\nlvLDvca+0AvRdT9Iki3jBCJc4i1aGzqzhhSFILnLNhjG1wVOvzh7HRJ+gWL/fVBj\nJAhovgwIOCh4uAYEI2u/1wUVfSq/AXPba76FU7kOPNyc4DeBLkqwNvmqFRX8ZISb\nQ9k5INjaLJmC6oAMdeAvQ9csLB3wS+/yPPemuWeoQj+QiwtmaBLrP3R5QqBw4KFC\nnDrV6y0Y3FYsBPvX1yhYVvqP+w4TpDU4uaV4WKInANW5AVRdQpX+hHB9ZLUyekcQ\n0kBJaRzT9CanyXvuMdtug+PpENhdPqfEVt4zpa/OgncoiGQJnXUyeMSuEtVduaov\nvKRiw1V2crDkvi+VA8Tp3s4fXBbEyw726XVkyDpLdJTHOzQFWDpxHb/4XFEA74cp\nW1/5VdV7gbzApMrUejXn2ta/hjDQXz4aqfFg9FevQ3mJkk9/TWdrARIqNzmlkzM8\nJleI0efBCFgcIKg8MmVlAoIBAQD1Br9iIMd4EtyRgknx9gzfGfMQmAddRLt2TUMc\nCqG2AkNyoleVaMh5s3bFKeRXhWBaZj4o+I5bOUQFiBze+W10kc4arN+YQwSVt/V9\nJ8NNDu45YebMQrhyVh7s3UMCeb1hqET2D2aNMEHrFcpGOvvI+Q7JI5thsywzRT9Y\n0TEkTJCBFXZfGkp9OIEFKU+0nrrWdjssQtLAY8bvkH6lpCm99Fep75IVQl60AqSY\nE+lKog0THVbtZmZH2znozFkDGJnfc8nAnsQAFQJwW04tQAV5aVO8syh+6u9cqIRD\nHBxpDv1n9wvx7kVLXwjHY8AysnhvDjHInfWx9mP+nHLvo6WfAoIBAQDxzJh7lvJ2\nDVDhCstZD05bIM8WEyhRkOAzaHBxUMk1WOK/boM29TupLMzvtZOD7LAsnlG4gwt7\ncZlJZtnU2eUckdLI0sba8mJmFQzO4zCG0P9Ar3ItEGkmjCsTZc1L017e+9kn2iEu\nJM6X1r0Ruhb8CW9/PYukIi6APGjjggNVJOx8m+o1pSvdRvqY6V7FZEQ6xS+6MOS7\nM5tPhB1TnQsJeqkBGvFy3CE6LL/cCCF5dW/oMCigoh80X9GwST7qU23a/yBtDFuS\nrvgj14x2ltWIgVhps/QjO3pU20Om2xw8yvse2P0ZT+F7Ad5G/F+ZoYqakWZagytf\n4dvzCceX9rndAoIBAQCHZwsrtuGDwSiSYGVsiIPkZfqkGN8481qErX2AaX+jjOyo\n1H2B2z2twzbHDbdZhiMQ0Z9YFMLWNncYd3c84KlT2Z8DrrSA98f41IecefS7CJ0s\nqKVaCyTSSjUcv8jf/VZiORKixy/spdZHiusUlFO/Y6KAFGUxQEoUqxeuhSua25Sf\nQLUBKQ4w1KC3sXSSY4TsLrnUY7kDCCBPEpJqBqXqirRG/FTF2r9qymsafyZUT+jZ\nv0TiO8wZuVPvCb3GsDmX+exrvVCimvmWDV7OMqrTxRi5FmgyerF7isRMMaShid8P\nLL41G26/j+zSApWbbnSPKlDohCHaDkaD1QDO3KgRAoIBAQCctyotEAxdjwvYj5J0\nSoNSdNVDTW9YTjnBl/Movl9qgA2WXR4WRKNCZpL82gad/o3AnWwB1o1emKXs+Qg9\n+GuSNMEU/+d6iJZV61a90GnnNxX46r1pj7VXEonorJZ+iqR2iWyZc7sFmT19E+S+\n1ImJGOR254DWBIF3A7BHpEBcsu3DZZpK4p6ncBWDfd26ud2u6ZahaocU/cGvq8UG\nKEa7AhD/cmMDOsEeX1qKVYq8mezJ5Eywr+LhoyjYxOBOJ3yP78MoalX/n/+b21xi\nkCgdrReMiuG05BvJW8NhJaoLjNNtjt2bxjYx72gNQg0EGbsnqw1XpKQ8JLrVroT6\nbSLRAoIBAQCSQ3oMIjCRlzkPzgz+Eo2CnIDQ5h3pMXrp2oo5d++XYFSNansVfIv7\n4NHm2YQxYB7oXdOOExPkveP6MS1Q6n7TsgKH69mayGAGTtbJnR3xlgVQzlBb+B9H\nhp/QUKqjfj1sUB2Lna/NrWITQLQ7+6pESxFe2iO2MrcYjUfJH1zaqTBQ+Sgt8a/Y\n1tnY8Df4HKl5czGWTrUi5a3SfByamRzMjL6CQy46Lyz6VAX4y5dMht3Bh5f6XPJe\niyBxZZu57Wedb1URVvlEquChWpgWb54c+7K7GoIEWq2TlFM65KA0kCVk+zRiGXmY\nrKuAr5xgoKOuA+w/CcUoOhtYpGzMSwr7\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "medium-tests/src/test/resources/ssl/client.csr",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIIE9TCCAt0CAQAwXzELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBkdlbmV2YTEPMA0G\nA1UEBwwGR2VuZXZhMRcwFQYDVQQKDA5Tb25hclNvdXJjZSBTQTEVMBMGA1UEAwwM\nSnVsaWVuIEhlbnJ5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7khC\n7A0zY+ljBpEjjenA4njjzsl2AIo7waYrnXd8/V+nJXVCzbAb8iWZUhINI4BOnZVi\nkZr5Tc73xqoX/mEDuTfpayoIWFLDrp22w9VoQdj0013ngGCmkwm1KZEGg5xQlO3N\nh3Bkh4glus+aJ5OZTN0N3vd1m7oib2Tp8K/AAwDU7/N4Q5iguSjtuyipDQ4U6iqh\nqoQaedLLUk+urHEIUgUQx4QpdU8ruyS8JhaRuZlb4sB7JKck1OgKDtABh7pgOZSz\nmLSp456FI6hxRYw5ZjD78ebjtlQ5MlsAXjbUddEqs39JI5233A9TEOlOPP2CyyKL\nSwqr8wDWWICQSESquhgRmCZAA/IjBzH0rP2QibkKF1nzt6z1IleDgml64jxuBRIC\nKRUBLpTbzE4CyIwYXOvbIL/qkAZkmoIhNV2OI+kS5u4cLx/lw1MQ9puC01SEIr0t\ntWmbuCr6YbekMiTR1vJdGcx18DMPRqSQjvJbvFOdcTy28nbYJM0X+UQZvMoRH1lJ\n9UmUvaBRr+AE38t12rADuf9pM/I8JJQiR/NtYjBPYM2FAODVf2nhUMwDV/obnBdq\nOdCgrAscFgIfY0gGUfII6O+1JC22YcaJ3+pGmwrygAUUu0cxH3QpLR5vXoJWbHRG\ngVhZ1isw0d22VidVZ9Y7Qc4N090+q8zerLxQ5ZMCAwEAAaBRME8GCSqGSIb3DQEJ\nDjFCMEAwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwEwYDVR0lBAwwCgYIKwYBBQUH\nAwIwEQYJYIZIAYb4QgEBBAQDAgeAMA0GCSqGSIb3DQEBCwUAA4ICAQC3Ni6bqztW\nrGb0oBeeq3ezYPgzcyPLW+uaYgTqYyETRJ1ewjRoj5B5Ioq9oVKMxsyQCrFlbU+t\nbyqGC5Wqeqp/BePXtSRCsbzysQCj6sMSqNuMeyWKePxHmcCt84NvmYAQA7ywTw4B\nHji5uq0qZYF2DJMYhX17CahneNECNH2VlYSryexCAPoCGbwgm5vD/HOIaI1UKjZW\n4wLaziANaTXpbTEyt1ewpbj0jqASgobMpyHW5UGy2sL8HEtNhGMd7hT12vSZidBY\nXJgbyAXY/I486ios7xb/U6epnRKxFDOAInRS0VDsk0RU2HpODxHhpnNXbiku6FDL\n/h17gFq3qsU+6eBuwKtkcG7XOgWh2dgcLEHo0yByQCtHxPMrSaI6j3g5P5uv64TV\n8Jy40UgWpbyIdM8A/hRokVTMMeh9ce7+WWl8MYRf/fc8zNixBG9e64XEKIY72KaT\nRi26ptqv4YCOBvTSDg6nmUKOsb58cx0zvRSYrs55xh0GJkMvV2desYVlk8xMyW0k\nT+TUvkpsF1Wh8A5aXbErPycQB2Te0bQ8cojg9mB3Kju6T6uZqa9Uktktd3e2T4DS\nNtogF7wFhC7nFvIAJeOTkQakY/5XrqXfXmQmG+Jp8b4zaTlk0mV8085DBI2+Zm9L\ny0+dpwk7ZT1nCTE/FZyZgIgY8iHCywT2Ow==\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "medium-tests/src/test/resources/ssl/client.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDuSELsDTNj6WMG\nkSON6cDieOPOyXYAijvBpiudd3z9X6cldULNsBvyJZlSEg0jgE6dlWKRmvlNzvfG\nqhf+YQO5N+lrKghYUsOunbbD1WhB2PTTXeeAYKaTCbUpkQaDnFCU7c2HcGSHiCW6\nz5onk5lM3Q3e93WbuiJvZOnwr8ADANTv83hDmKC5KO27KKkNDhTqKqGqhBp50stS\nT66scQhSBRDHhCl1Tyu7JLwmFpG5mVviwHskpyTU6AoO0AGHumA5lLOYtKnjnoUj\nqHFFjDlmMPvx5uO2VDkyWwBeNtR10Sqzf0kjnbfcD1MQ6U48/YLLIotLCqvzANZY\ngJBIRKq6GBGYJkAD8iMHMfSs/ZCJuQoXWfO3rPUiV4OCaXriPG4FEgIpFQEulNvM\nTgLIjBhc69sgv+qQBmSagiE1XY4j6RLm7hwvH+XDUxD2m4LTVIQivS21aZu4Kvph\nt6QyJNHW8l0ZzHXwMw9GpJCO8lu8U51xPLbydtgkzRf5RBm8yhEfWUn1SZS9oFGv\n4ATfy3XasAO5/2kz8jwklCJH821iME9gzYUA4NV/aeFQzANX+hucF2o50KCsCxwW\nAh9jSAZR8gjo77UkLbZhxonf6kabCvKABRS7RzEfdCktHm9eglZsdEaBWFnWKzDR\n3bZWJ1Vn1jtBzg3T3T6rzN6svFDlkwIDAQABAoICAFHgm7pP+Ooqfp/5jG2Ntn07\njA/40ubxmQH8Lv1/WwAXd7Bb4DYcfyXQPKRiVoht5NWqSMH+rg7KAQMPXIpI3MYu\ny7LKsS5kViAwymX1dUs3GT1uBYfr7iEVU15ymnnr4xahth+Fg828fzZ3cqGJysIK\nLWOBxbRam82KMAbH5WNCzGidUSRtFEE7qqDGonw9a/tg5cdZeF2HDN1+SuevOWwx\nmGfuZm6MY1r9jvdJhw5u/49lk4rvMi1EhHw9hmEUrk7q5WGiJsRzqo0rqqx6uBiA\n2XnwXCvBOhwu/+vfzFalppYiNUgbQFvKGkFz4bH8bI6PkqyZAKfSEFU97BYhHkaF\nEw3zQB1sN3c+R7ifNFVotamlb30brYY0o/XJ2NdReQSEYGof3MR2Nf0uiuz4Hok2\nFq1hhPIUdhIUkgwOZjqD3I7SIzrxj9/nN5bRzlfnJMvBf3I6RuTWXwJTP/SBnq8m\nidLytRf/LGODpgPIv9XfhWdyg8afcW5nupS5+jVUUKjGspyNN6BfAnYryMPJ/g+S\nEiKBtlJ5RAupwz6Ytx9O47kDyws86gSQqX2odFdJCKD8j/Y9CBWANi9snvdc3wE0\nLhTK1/to4kcMkpzkMusvN2H1FDNWvG3PNbJke9M6+Vr4Ha1VjoCTeMSELhw9kwmF\nRfTmUcsbk/GoNgGz0J2dAoIBAQD5eVjqR97Jv0+078uilTjNaZ3wx9dkUKdGZ91x\n0WJQfmteoXERZhZQxUsvppgir6TeUfqopqSSCWxdna8JlI7ciooAysI3/zj57HLV\nSOyGgjW7S4pLPuSH3o7UIJ8dX7cAcBoiHSNiIp4LSAA6bTntMQsly2ulE6Z4VL8Z\nCJiqiF3/RJ9iMsq7NroWCr+D4MHM2EHwQqTfR/Cn/qQCCCYBp89C0Kp2L7H0GZo+\nC7jj4a3pOnrgmvaBxNTzVRY1z1QOMfZQ9+Y9oHo0GXsxIXA18A8QsLSJJryOhRPt\nsYn7Qq6ZuuMsY1tux77nnB3Wzi9S0Yf7DkNvEZYOzXdmyBCPAoIBAQD0g/disA8l\nTHhEvhfk1GWO8vNASnGnp3Rrn4qkOcp4CrOe6pPfTy0UtPrrfMfji8YdE7OP/qGQ\nZi2hJQKl9Qk48kJC5qJawinYSBcalHwJjOq5iSNRUtmdGMfXgw3eurqI8AXQquzh\nykHSKCX3Ohfj01dlvKwTRkzD33RMJfsP3RfHJgVtMrhB9GpEgz1PdNcK+OXbp+ap\ntCmUbOQntu+kUWOtgEFLo4SSlIOWS1Z5wlbvBW5WzT6In17mmaWCgMTMVU1zXhKN\nvVCqHdh9miZ4jyJnGNQU99JtGeRFSV0E2F4aG4N9EbLMFTlxPnkjV93YTCxI4x3k\n8SItXO/PVpS9AoIBAAapXNx0qthMSX6VYKSow2mIxnTlgTTminDw2RLu+mcX2Rof\nyXsjdLkXa5iMJEfY1Ngy7DSQrphPtBsT2NayptXwxRqcT29bW1QhIjfbyKUW2QR1\nUe4MceHG6Z10eYs5QRO76pa6nHVEY1/9vjPRMtjIlHmf4vH3glx4NtoeRqVXGtt0\nI6VlodA59+Iay15kJAiqRah6vxhPPXfe7tY3DXzB1GD+XW7meyP/gwThH0DGh/RQ\nYXOZ77crYFxXC07IiIAQiXlEBVvj6x91BrsbH3nxPRW/AjMfW62alBumv/57m1nz\niZln1csxfkGFA672toDJo43qpZZTq+QdTHDsKrUCggEAZQqrjgKHH7ir8mxDX3JN\nzXakAqtAsDFIsLirCBZBjr2NYFw7YwpK7MWk8u8LgwJdieJ8T0K/7PXsYSz0HTRW\n7UtfClpYOP0HYbLYqUk2wJOU47rB93cvKfX1jx8++g1by/6zyHj5joyQCpJArhs5\niBHricUfro9cZYzPeDAxwWpxyv/xOiC9d7PSviElktILkyMriUumy/YBQK6G0cqM\n2PAxKp9XXDRkMlZg0hir0YD3f6pU4FXMQ5ToGI7hkD46FN4jNUtoi13yGXpf3fd3\nclZjtwrRiSCnuiH67I7We4wVxuAbw/EuW5ths2RHls1WhW1k13HTBqqGnwxKpPl4\nWQKCAQEAlh+Y+LrQHRza9ejPilz15LSgUnEP8DMUSfQ4LYlWPbSg9x+i1Cl/KuRK\nRmM0ztVlGYeRPz1yJ8jAUDxn+hjrqd0/PvBTl8eH1VKlzkH+dS4uVx/OrFTlDq69\nnKa+ecDniaM9hlPldm0+ZYQft/1kuAs9o4X1NFbLILYLIt+4NWljMERNFKGkzpmW\nxeiZcJrmFxQnaM+Bx+r7tGj5UCEhmSp2oKxkAIH3/Cb5smg1Qsbs+JHDSdxqlmJ2\nhNWfPYs4XnRV0Evwjpev+/3MphzCSwJpTf/gztkkacVroCNFqLQsXArYUn8JTiYB\nIu6lGGJni0JKcT1Y//ApwsRD1BCksg==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "medium-tests/src/test/resources/ssl/client.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFRDCCAywCFAW9QyColr/tASsYJbmtlHfOHwq0MA0GCSqGSIb3DQEBCwUAMF4x\nCzAJBgNVBAYTAkNIMQ8wDQYDVQQIDAZHZW5ldmExDzANBgNVBAcMBkdlbmV2YTEX\nMBUGA1UECgwOU29uYXJTb3VyY2UgU0ExFDASBgNVBAMMC1NvbmFyU291cmNlMB4X\nDTIzMDcxMTE1NTcwOFoXDTMzMDcwODE1NTcwOFowXzELMAkGA1UEBhMCQ0gxDzAN\nBgNVBAgMBkdlbmV2YTEPMA0GA1UEBwwGR2VuZXZhMRcwFQYDVQQKDA5Tb25hclNv\ndXJjZSBTQTEVMBMGA1UEAwwMSnVsaWVuIEhlbnJ5MIICIjANBgkqhkiG9w0BAQEF\nAAOCAg8AMIICCgKCAgEA7khC7A0zY+ljBpEjjenA4njjzsl2AIo7waYrnXd8/V+n\nJXVCzbAb8iWZUhINI4BOnZVikZr5Tc73xqoX/mEDuTfpayoIWFLDrp22w9VoQdj0\n013ngGCmkwm1KZEGg5xQlO3Nh3Bkh4glus+aJ5OZTN0N3vd1m7oib2Tp8K/AAwDU\n7/N4Q5iguSjtuyipDQ4U6iqhqoQaedLLUk+urHEIUgUQx4QpdU8ruyS8JhaRuZlb\n4sB7JKck1OgKDtABh7pgOZSzmLSp456FI6hxRYw5ZjD78ebjtlQ5MlsAXjbUddEq\ns39JI5233A9TEOlOPP2CyyKLSwqr8wDWWICQSESquhgRmCZAA/IjBzH0rP2QibkK\nF1nzt6z1IleDgml64jxuBRICKRUBLpTbzE4CyIwYXOvbIL/qkAZkmoIhNV2OI+kS\n5u4cLx/lw1MQ9puC01SEIr0ttWmbuCr6YbekMiTR1vJdGcx18DMPRqSQjvJbvFOd\ncTy28nbYJM0X+UQZvMoRH1lJ9UmUvaBRr+AE38t12rADuf9pM/I8JJQiR/NtYjBP\nYM2FAODVf2nhUMwDV/obnBdqOdCgrAscFgIfY0gGUfII6O+1JC22YcaJ3+pGmwry\ngAUUu0cxH3QpLR5vXoJWbHRGgVhZ1isw0d22VidVZ9Y7Qc4N090+q8zerLxQ5ZMC\nAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAPS70Hw0f6WZjjsUz8Bhel2FzFrxpofkx\nDtsofZMP97LMoeoE08ph09hZI9YWR/4kN65FG3QOvZAdfJvUai3dG24niurZigbX\nLDUYvuA8kLBbu7GxJq1F2eDoG1RAOzO3AHcQiuT7VoMMAMTsfEi7iHSLcq2xXYP+\n+ckw2qGBsY4rbJJ67cEn5wFmvtUH6yJCR82IVDvB0KWXK8Uxv3uF4gw2zYoT32GE\n/Ol0Y7SFrUjmBfDEL2MJH2v2JUXRtZQCO16VuApafvUSAr3ynBrkIte1R31tX/mH\n70P0AXr/fqrDbtD/8AC/QMtaX/vZYoo7gvTQ2ODeZydpMSdhjgUy47ia092wvX0G\n2r59z/0sfynGrz/+vluixOE2Pdb4fFKwdLspaGMnVDRwMqy8T6fr84ow25ouGmfN\nyUQJ+n85kjdmW2/EYWd4yDx2rzD/pdjSdlBTAU1Ef6DrLiwH3gya0b8KaNpCJnrK\nBVPf0aqQ8sjTe0ChvhogFRcJVkl4prbWECdiXwrPvpx5w7QpiBXPqVDHfp0hNfND\ng2+RUGvvSZOhmEKl8/c/Dht3Pe99mkbzRxGIq6Nb7U7Cq2z6yhVtpL0cIK73/vF7\no0ezIkZbACu78BVZDohmGChtpO5O7M4GBUvv78cPxqDqct3z6OaQh1Uk7cWlMcre\nTx9thKl7kCI=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "medium-tests/src/test/resources/ssl/openssl-client-auth.conf",
    "content": "HOME            = .\nRANDFILE        = $ENV::HOME/.rnd\n\n[ req ]\ndefault_bits       = 4096\ndistinguished_name = req_distinguished_name\nreq_extensions     = client_extensions\n\n[ req_distinguished_name ]\ncountryName                   = Country Name (2-letter code)\ncountryName_default           = CH\nstateOrProvinceName           = State or Province Name (full name)\nstateOrProvinceName_default   = Geneva\nlocalityName                  = Locality (e.g. city name)\nlocalityName_default          = Geneva\norganizationName              = Organization (e.g. company name)\norganizationName_default      = SonarSource Sàrl\ncommonName                    = Common Name (your.domain.com)\ncommonName_default            = Julien Henry\n\n[ client_extensions ]\nbasicConstraints = CA:FALSE\nkeyUsage = digitalSignature, keyEncipherment, dataEncipherment\nextendedKeyUsage = clientAuth\nnsCertType = client\n\n[ ca_extensions ]\nbasicConstraints = CA:FALSE\nkeyUsage = keyEncipherment, dataEncipherment, keyCertSign, cRLSign, digitalSignature\nextendedKeyUsage = serverAuth\nsubjectKeyIdentifier   = hash\nauthorityKeyIdentifier = keyid:always, issuer\nbasicConstraints       = critical, CA:true"
  },
  {
    "path": "medium-tests/src/test/resources/ssl/openssl.conf",
    "content": "HOME            = .\n\n[ req ]\ndefault_bits       = 4096\ndistinguished_name = req_distinguished_name\nreq_extensions     = req_extensions\n\n[ req_distinguished_name ]\ncountryName                   = Country Name (2-letter code)\ncountryName_default           = CH\nstateOrProvinceName           = State or Province Name (full name)\nstateOrProvinceName_default   = Geneva\nlocalityName                  = Locality (e.g. city name)\nlocalityName_default          = Geneva\norganizationName              = Organization (e.g. company name)\norganizationName_default      = SonarSource Sàrl\ncommonName                    = Common Name (your.domain.com)\ncommonName_default            = localhost\n\n[ req_extensions ]\nsubjectAltName    = @alt_names\nkeyUsage          = keyEncipherment, dataEncipherment, digitalSignature\nextendedKeyUsage  = serverAuth\n\n[ ca_extensions ]\nbasicConstraints        = CA:FALSE\nkeyUsage                = keyEncipherment, dataEncipherment, keyCertSign, cRLSign, digitalSignature\nextendedKeyUsage        = serverAuth\nsubjectKeyIdentifier    = hash\nauthorityKeyIdentifier  = keyid:always, issuer\nbasicConstraints        = critical, CA:true\n\n[ alt_names ]\nDNS.1 = localhost\n\n"
  },
  {
    "path": "medium-tests/src/test/resources/ssl/server.csr",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIIE6jCCAtICAQAwXDELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBkdlbmV2YTEPMA0G\nA1UEBwwGR2VuZXZhMRcwFQYDVQQKDA5Tb25hclNvdXJjZSBTQTESMBAGA1UEAwwJ\nbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvY4BE6CX\nE2fypP8UlYpvVlNG8no4zaNcfCMnHCctHD7ADr9ODpS5HRyuGaqkVvlsUeVIV4F+\nLa9YjafpBz4cclNYIssdeGFmsSQyVCET2NvKH9JMPB19ZD6dFju5av08/WOgkenR\nC2L1CqLMzfLCmEOJ6jTw9uc+KM/gebLRHbNQqpb+OUuuKgPBcEepZst3Bwoebg3I\nAoK/MgfkwaPw7WWXKmTIQxxbT/ZyDwDU//iKBOVI7kbhoZh+M4X3e+jBVpXbjZA0\nuTNpPrj8djr3KtKmKVZ736cBuHHMpk7xmCJgBjj+uO6AXNhpD+t2femiHwYZq31C\nNwOh5x2Wo2OOmxefm+67TZQ1f9apKN6D6AJGZSbyMZPW0Ox5O1RgF4ReUINXSvqy\nJjyAmMmkCQsPKwqPGi4n6Tpj9UQWGeuS00NZFghm+JAbfvD5f0W73HHtlxddigb8\nmYxVYBP81HGSroqPSXrueRfFUr1za+E8wR8pzlItVYYXKwomprXtkb2jR7+RiDsX\ntMPVqzg36Dad7MBGDrYeUXoy61fuGfVcowb/Rdxt5PsqpFzx9dgxf9KCx2UaWucK\nIcd6Gbxg5jchr4k/okdm4ongq34dC2LX2RmOrehRWJ6CMgJEtQLKYbmecg32L+q6\ngBYGFIlQHpkXQ+ffs303yvvJai2wWF2CkR0CAwEAAaBJMEcGCSqGSIb3DQEJDjE6\nMDgwFAYDVR0RBA0wC4IJbG9jYWxob3N0MAsGA1UdDwQEAwIEsDATBgNVHSUEDDAK\nBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAgEAkSYUK1Pk0RqX2ur+zHehoVko\nGWSLRiLbIE9RCI9AhYWdwdlE2h4qgsNKv/bhkpH/nCBjq2bF8R6wVZv314V3gVaE\n7gVQ06Q1N76squ/sHencjlZj8UdxFgm2+IIIukWeGi0z+iB4rFBxFyK2F98g2GWR\nhT3JTJrL//P6ndBzuUbGbJurjlNuPlsGUUFmj0VvJB8kU8wLX34e1iodMIgW1PHb\nXbENFOb2xDgWXzVWqb++tZ4uMdY8u6aWiiXyh8HaL3O/D8/fQYfIvqQkhzA5tqVE\nDa882goFaCnp0GYNjDu8XrjhWsXAGcIXhAMbJ88Y3pevXW1xX2w+UMiuFpuBkifk\nbdIv2GNt/NIu9k5XNuqM2BzGxk3Vu4/AlYkxbGsRbh4bLa3XWAVwnH+xEfytTszA\ngz0eMlL+zRswT34orv3Tvh3txFxvVJ+SP6PeRycLQP6ptdOFsoVPvvcpCG/q+lFh\n7DcKKSMaSWETEeev0CuWG1/9Ip6hN+6DmxyB0zv2bnBN80Jm4Sesumhq/S/1jVWH\naInY3IcQkGllv1EQX+Ov60AtlvURjQ6VJuQsmihpYJP2Occc8hGcnrqGCUmm2NWr\npou4p6Qoqu0FZigOTxpwg7yxgKUtPc8EGpJJC6XlU3MH3uQuDrK9vzHYX5BMpzjW\nKKI+RJ1hh10PVNE6Q+Y=\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "medium-tests/src/test/resources/ssl/server.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQC9jgEToJcTZ/Kk\n/xSVim9WU0byejjNo1x8IyccJy0cPsAOv04OlLkdHK4ZqqRW+WxR5UhXgX4tr1iN\np+kHPhxyU1giyx14YWaxJDJUIRPY28of0kw8HX1kPp0WO7lq/Tz9Y6CR6dELYvUK\noszN8sKYQ4nqNPD25z4oz+B5stEds1Cqlv45S64qA8FwR6lmy3cHCh5uDcgCgr8y\nB+TBo/DtZZcqZMhDHFtP9nIPANT/+IoE5UjuRuGhmH4zhfd76MFWlduNkDS5M2k+\nuPx2Ovcq0qYpVnvfpwG4ccymTvGYImAGOP647oBc2GkP63Z96aIfBhmrfUI3A6Hn\nHZajY46bF5+b7rtNlDV/1qko3oPoAkZlJvIxk9bQ7Hk7VGAXhF5Qg1dK+rImPICY\nyaQJCw8rCo8aLifpOmP1RBYZ65LTQ1kWCGb4kBt+8Pl/Rbvcce2XF12KBvyZjFVg\nE/zUcZKuio9Jeu55F8VSvXNr4TzBHynOUi1VhhcrCiamte2RvaNHv5GIOxe0w9Wr\nODfoNp3swEYOth5RejLrV+4Z9VyjBv9F3G3k+yqkXPH12DF/0oLHZRpa5wohx3oZ\nvGDmNyGviT+iR2biieCrfh0LYtfZGY6t6FFYnoIyAkS1AsphuZ5yDfYv6rqAFgYU\niVAemRdD59+zfTfK+8lqLbBYXYKRHQIDAQABAoICABad2GQKn6v5lZp5MvAKxfED\nW643xgpHKRRIr3Rs+jVqCUlZKwuTVCj0kJNQaG5WMUXuOPzz3uW9i6/PLn8mOoer\niRMRsBD6ciHFo1mlXrRbuRil42Gy1RC+ZcjO1Hc/AuVq2zA9uSsGIh2kQhJ6mhxH\nxq9SpMOY4LJG504t90R3bjMEdA3LCVtK+mwWqNAyi5+j5fCUpV4jUoO6LGGsWWVB\n31D4GGrlecGnMoG2xDNMaM9fuh1wCKDazIgCPn6izVqy47DW2WuVhkOtF7ZVn8MU\nwi2he4M6bnqe3V+kgK/5vgJQD5hqIJezXInBOvCYDTdtZ2euONXOkaU9Qwl4qf2D\nrYng2K45ezs93nc7UgYymn5GEGRy4zNzFsVcRg5cQiV80Od14x+VcLbwAz+DPqxK\n6oOp9ZUlDgto6yI2RM2mcSLwY+wBwOAj1qeMBoKHCbITh9XO/67Gx2JZuHWjIzTx\n/AfSOmMxs/nPVMghlccSVNTbCBQb0nKhKgDICw+JEdvPBrmhUqNkwzZxTGicuj3n\n7CCF9g8fXEtTDC9HkbzF/xfmwEHBx+xrK24i4X71UDdKSK/8i1PLQziEThdVaXUY\nQjK6uq2GwNYaDgXTs1HkUsM9j0mLueVGGxQUPzzM5CuuU5bGB07rMjtNmh13sR2n\nlBFlhhI15TM5+HaAQk3BAoIBAQDlskJ2zJbC3RQLBqRJb1q5d557AwNDIcJ8trxG\n8WcoNAKK4JUqOKg6Tx46oznrxjKKnOhcBeE9buf5JMHEnrsdbSNdhsd1Vn24HDOU\nH75lEST0Or4XjJY/8rsEDIDNfUhavewAn+8Ctv93fOgwhntB6gYxzcBvhq7CAp6t\nf0vFJkLiDhrdBhHU+knOa4zv6tewrwRoefBe43wgPChmckwftUwzPKoEiUsI8GQe\ntJxVD1GwBsoT4JzH1G6tZ9cgWvY0R1QhLOvhIGdbPS8oI+ACkTdyWmPuflppcfZW\nPjh3+hKmGE+peJwnaSXrshy3EXa5kG5a6chFC1kbvwE1TW3hAoIBAQDTQvWR+WJ2\nFnpkw14rp0UGpyqSYFd5l8sZmcmUoSKCa4+hEkZ6G3OL5hG40htFx5YVngEn2d8s\nxA9GTw30AvoJci4rTDdmqWB34HE9ONXs5p+zuqkCMnSGvJs8u0MSbzLYpuShgSfp\nks1HvoQi+RIuxYeJnSzzH3kini5eTzfW83iyzgAy5OiJ519d4uI4clZR7w9Xl4sD\nylAe+iNUP0TKlN5HtS7qEa9lIiaLWtAdcG2WkJHIdnXvlp2aIANvFExJ6TrBCMPq\noC/f4xbCPH1/JKHpK88WNe6tfHS9TKFWbp0JTwBNXjYGLhAH7B6Z3SlVeWOgh5tL\nIiURXRWXMrK9AoIBAQCwuIN2T1W2us0O5p9DeI7+ns+pqNm7rp+pwBX/Jv/M6KcE\nHmHlSA32PqTtTGPlyGleR/ZlxohS8Z88ClGiatVfgHZzIJ0Y4+B55Aiy7/FWZOhf\nl0plZ965eJD2PoBimP8wAWsv19zV4+GNFua1XuI6BkmZy8lAhQizb7yRk5zCI/vV\nXDDC2Eaj66UZUZnvhGlKIH1tvFN5TeBUFLSojUuQIbZsvUG5l7/JMtp03VRiBRU+\nLxsOTAOToxc8BuZ2mEVQV4ictakNXhbnFZ3QbjmNABj+vBiih0gcVHfIRrq/ZAEx\nFQdFRewuTjHEsxZwqtkDT+H3xGI27NI0DLJrRhVBAoIBAFAKaHv73mAbOBgBJSao\n4zAYGbsHoVzCNALpD0SuoCKFmg9Om0EcMXTpoAFEKW0Qpz5ddNaiZznQtnDO9txi\nXFVD21YwNDVDKveAI6csZ3CwGAXCTNI2R4vc3Xvu/wR2+O64nJrMElEee4QZHHsb\nntX1pNpwoF0kqYKCJ9M3dFKOQEaOYejmbWwCK1Go6ki0mRvF1Sw8kEDJX/28i7IZ\nnniaWnUUokewl6FbhPr7VyowNHT9JR6sMxapG+EPC/4D1LuUD9ye84trUXgj07WX\nsXyAZo+vN3CU0m8MXjZyWBYlJn4mXljxvsosnYpC3X0yG7z0lth1SFL5Bs5DVDDK\n1okCggEAWG45DwBB23e32BBsBvdTQMxSIeQhZJ77ptyKklotsRf2P+FtXflFVZVm\n7ugLnoYBgBJzMAfFINo0rngTDXKn80r2DY6tQlkyj+tHE+78NzqRdsMHR7FGL/Il\njX932W4KAsSnhtrrZucdVwZBWLHCSB28mQv8hxu9QEkd4Vhu4LXtNOvnRCUrIX9J\nFspLrnEFGxHE9WVB4nYswfhSB2S5atLT79fHVk1V3afdBHvHKFXsHGvu7f+9qMhA\nFLHFYzQRp/4LVMASMCNx2kYDwh394pRf5wBRACF/FImdcEuqyC/5n8pFXtVjt/3+\n+TgZdkTCDRYQRTnGbimFE5LrBM2Xvg==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "medium-tests/src/test/resources/ssl/server.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFtjCCA56gAwIBAgIUEUxP1JdCWeCCHRj8ydnOfyshHu8wDQYJKoZIhvcNAQEL\nBQAwXDELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBkdlbmV2YTEPMA0GA1UEBwwGR2Vu\nZXZhMRcwFQYDVQQKDA5Tb25hclNvdXJjZSBTQTESMBAGA1UEAwwJbG9jYWxob3N0\nMB4XDTIzMDYwNTEwMDgwOVoXDTMzMDYwMjEwMDgwOVowXDELMAkGA1UEBhMCQ0gx\nDzANBgNVBAgMBkdlbmV2YTEPMA0GA1UEBwwGR2VuZXZhMRcwFQYDVQQKDA5Tb25h\nclNvdXJjZSBTQTESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF\nAAOCAg8AMIICCgKCAgEAvY4BE6CXE2fypP8UlYpvVlNG8no4zaNcfCMnHCctHD7A\nDr9ODpS5HRyuGaqkVvlsUeVIV4F+La9YjafpBz4cclNYIssdeGFmsSQyVCET2NvK\nH9JMPB19ZD6dFju5av08/WOgkenRC2L1CqLMzfLCmEOJ6jTw9uc+KM/gebLRHbNQ\nqpb+OUuuKgPBcEepZst3Bwoebg3IAoK/MgfkwaPw7WWXKmTIQxxbT/ZyDwDU//iK\nBOVI7kbhoZh+M4X3e+jBVpXbjZA0uTNpPrj8djr3KtKmKVZ736cBuHHMpk7xmCJg\nBjj+uO6AXNhpD+t2femiHwYZq31CNwOh5x2Wo2OOmxefm+67TZQ1f9apKN6D6AJG\nZSbyMZPW0Ox5O1RgF4ReUINXSvqyJjyAmMmkCQsPKwqPGi4n6Tpj9UQWGeuS00NZ\nFghm+JAbfvD5f0W73HHtlxddigb8mYxVYBP81HGSroqPSXrueRfFUr1za+E8wR8p\nzlItVYYXKwomprXtkb2jR7+RiDsXtMPVqzg36Dad7MBGDrYeUXoy61fuGfVcowb/\nRdxt5PsqpFzx9dgxf9KCx2UaWucKIcd6Gbxg5jchr4k/okdm4ongq34dC2LX2RmO\nrehRWJ6CMgJEtQLKYbmecg32L+q6gBYGFIlQHpkXQ+ffs303yvvJai2wWF2CkR0C\nAwEAAaNwMG4wHwYDVR0jBBgwFoAU816DsVAbv5b7YxlVq9FilVK5ZbswCQYDVR0T\nBAIwADALBgNVHQ8EBAMCBPAwFAYDVR0RBA0wC4IJbG9jYWxob3N0MB0GA1UdDgQW\nBBRpdEFeZzUMXO+ifWNgbK2j8LMKZDANBgkqhkiG9w0BAQsFAAOCAgEA15vv+Vok\npHpV5J6Yj9iWEqsgj3vTB6o9YiJtBUJw5to6GCJilgwKN835NZ5Ru3tbNoT9jO9f\nlHlgBu0W04srMBXB6RrKdiYnkvMDi3u4TIgqyjg08w0tRLn/87kgume/VkufZowO\nj57xebeMCjTDrxSFnn3AmtglV9aP13lKxC9oMg8e/UCOylHtGQfw7p/82LmrC8y4\nk3/zjY7AbcK4/ZiYuu4y16XjsJE9+1p/F6Y2v/axiyeZma/gkrF0tgfV65yYzYHy\nNHvjS83M+sD82NESs3Nw2YchugzzBQV+IlGE33WWt8vvOgblqwzz8p9CeAV3BT6o\nMT0o3E7QsxEM6niH6335suuazH3PCXBgmzU5efmDxYKXo/z13ms3bgnlkgaLhtav\nsV04sKDcTh8vg/NUvnNvcKIvQuuHgNLopFK3MzWS92wHSf6CJ1ztRAeA5aOGVUps\n1HFS1g9NV9xD4oIGhittuZkXUK0D/2LwRoUEQlpHIoSWTv3gOR4jDueHxeYIqYyV\n4VdzASOmYwJSrt9C+WEQhTgQRIPaLABKFlwly09AEMsnrTTSLV+6d8Tn6K73hz5Z\n1uk9lxwDpX4aZY6ZKNZ+mF5jmySmpmENOxIWvNrdlQRUkujcDAjRbrQIQpr+FVVs\nfrkrM9dFBxEb6tlVWRU3F4ApcFHxYI+4cSU=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "medium-tests/src/test/resources/ssl/v3.ext",
    "content": "authorityKeyIdentifier=keyid,issuer\nbasicConstraints=CA:FALSE\nkeyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment\nsubjectAltName = @alt_names\n\n[alt_names]\nDNS.1 = localhost \n"
  },
  {
    "path": "mise.toml",
    "content": "[tools]\njava = [\"21\", \"temurin-17.0.18+8\"]\nmaven = \"3.9\"\nnode = \"20.20.0\"\n"
  },
  {
    "path": "mvnw",
    "content": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\n# ----------------------------------------------------------------------------\n\n# ----------------------------------------------------------------------------\n# Apache Maven Wrapper startup batch script, version 3.3.4\n#\n# Optional ENV vars\n# -----------------\n#   JAVA_HOME - location of a JDK home dir, required when download maven via java source\n#   MVNW_REPOURL - repo url base for downloading maven distribution\n#   MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven\n#   MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output\n# ----------------------------------------------------------------------------\n\nset -euf\n[ \"${MVNW_VERBOSE-}\" != debug ] || set -x\n\n# OS specific support.\nnative_path() { printf %s\\\\n \"$1\"; }\ncase \"$(uname)\" in\nCYGWIN* | MINGW*)\n  [ -z \"${JAVA_HOME-}\" ] || JAVA_HOME=\"$(cygpath --unix \"$JAVA_HOME\")\"\n  native_path() { cygpath --path --windows \"$1\"; }\n  ;;\nesac\n\n# set JAVACMD and JAVACCMD\nset_java_home() {\n  # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched\n  if [ -n \"${JAVA_HOME-}\" ]; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ]; then\n      # IBM's JDK on AIX uses strange locations for the executables\n      JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n      JAVACCMD=\"$JAVA_HOME/jre/sh/javac\"\n    else\n      JAVACMD=\"$JAVA_HOME/bin/java\"\n      JAVACCMD=\"$JAVA_HOME/bin/javac\"\n\n      if [ ! -x \"$JAVACMD\" ] || [ ! -x \"$JAVACCMD\" ]; then\n        echo \"The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run.\" >&2\n        echo \"JAVA_HOME is set to \\\"$JAVA_HOME\\\", but \\\"\\$JAVA_HOME/bin/java\\\" or \\\"\\$JAVA_HOME/bin/javac\\\" does not exist.\" >&2\n        return 1\n      fi\n    fi\n  else\n    JAVACMD=\"$(\n      'set' +e\n      'unset' -f command 2>/dev/null\n      'command' -v java\n    )\" || :\n    JAVACCMD=\"$(\n      'set' +e\n      'unset' -f command 2>/dev/null\n      'command' -v javac\n    )\" || :\n\n    if [ ! -x \"${JAVACMD-}\" ] || [ ! -x \"${JAVACCMD-}\" ]; then\n      echo \"The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run.\" >&2\n      return 1\n    fi\n  fi\n}\n\n# hash string like Java String::hashCode\nhash_string() {\n  str=\"${1:-}\" h=0\n  while [ -n \"$str\" ]; do\n    char=\"${str%\"${str#?}\"}\"\n    h=$(((h * 31 + $(LC_CTYPE=C printf %d \"'$char\")) % 4294967296))\n    str=\"${str#?}\"\n  done\n  printf %x\\\\n $h\n}\n\nverbose() { :; }\n[ \"${MVNW_VERBOSE-}\" != true ] || verbose() { printf %s\\\\n \"${1-}\"; }\n\ndie() {\n  printf %s\\\\n \"$1\" >&2\n  exit 1\n}\n\ntrim() {\n  # MWRAPPER-139:\n  #   Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.\n  #   Needed for removing poorly interpreted newline sequences when running in more\n  #   exotic environments such as mingw bash on Windows.\n  printf \"%s\" \"${1}\" | tr -d '[:space:]'\n}\n\nscriptDir=\"$(dirname \"$0\")\"\nscriptName=\"$(basename \"$0\")\"\n\n# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties\nwhile IFS=\"=\" read -r key value; do\n  case \"${key-}\" in\n  distributionUrl) distributionUrl=$(trim \"${value-}\") ;;\n  distributionSha256Sum) distributionSha256Sum=$(trim \"${value-}\") ;;\n  esac\ndone <\"$scriptDir/.mvn/wrapper/maven-wrapper.properties\"\n[ -n \"${distributionUrl-}\" ] || die \"cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties\"\n\ncase \"${distributionUrl##*/}\" in\nmaven-mvnd-*bin.*)\n  MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/\n  case \"${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)\" in\n  *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;\n  :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;\n  :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;\n  :Linux*x86_64*) distributionPlatform=linux-amd64 ;;\n  *)\n    echo \"Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version\" >&2\n    distributionPlatform=linux-amd64\n    ;;\n  esac\n  distributionUrl=\"${distributionUrl%-bin.*}-$distributionPlatform.zip\"\n  ;;\nmaven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;\n*) MVN_CMD=\"mvn${scriptName#mvnw}\" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;\nesac\n\n# apply MVNW_REPOURL and calculate MAVEN_HOME\n# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>\n[ -z \"${MVNW_REPOURL-}\" ] || distributionUrl=\"$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*\"$_MVNW_REPO_PATTERN\"}\"\ndistributionUrlName=\"${distributionUrl##*/}\"\ndistributionUrlNameMain=\"${distributionUrlName%.*}\"\ndistributionUrlNameMain=\"${distributionUrlNameMain%-bin}\"\nMAVEN_USER_HOME=\"${MAVEN_USER_HOME:-${HOME}/.m2}\"\nMAVEN_HOME=\"${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string \"$distributionUrl\")\"\n\nexec_maven() {\n  unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :\n  exec \"$MAVEN_HOME/bin/$MVN_CMD\" \"$@\" || die \"cannot exec $MAVEN_HOME/bin/$MVN_CMD\"\n}\n\nif [ -d \"$MAVEN_HOME\" ]; then\n  verbose \"found existing MAVEN_HOME at $MAVEN_HOME\"\n  exec_maven \"$@\"\nfi\n\ncase \"${distributionUrl-}\" in\n*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;\n*) die \"distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'\" ;;\nesac\n\n# prepare tmp dir\nif TMP_DOWNLOAD_DIR=\"$(mktemp -d)\" && [ -d \"$TMP_DOWNLOAD_DIR\" ]; then\n  clean() { rm -rf -- \"$TMP_DOWNLOAD_DIR\"; }\n  trap clean HUP INT TERM EXIT\nelse\n  die \"cannot create temp dir\"\nfi\n\nmkdir -p -- \"${MAVEN_HOME%/*}\"\n\n# Download and Install Apache Maven\nverbose \"Couldn't find MAVEN_HOME, downloading and installing it ...\"\nverbose \"Downloading from: $distributionUrl\"\nverbose \"Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName\"\n\n# select .zip or .tar.gz\nif ! command -v unzip >/dev/null; then\n  distributionUrl=\"${distributionUrl%.zip}.tar.gz\"\n  distributionUrlName=\"${distributionUrl##*/}\"\nfi\n\n# verbose opt\n__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''\n[ \"${MVNW_VERBOSE-}\" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v\n\n# normalize http auth\ncase \"${MVNW_PASSWORD:+has-password}\" in\n'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;\nhas-password) [ -n \"${MVNW_USERNAME-}\" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;\nesac\n\nif [ -z \"${MVNW_USERNAME-}\" ] && command -v wget >/dev/null; then\n  verbose \"Found wget ... using wget\"\n  wget ${__MVNW_QUIET_WGET:+\"$__MVNW_QUIET_WGET\"} \"$distributionUrl\" -O \"$TMP_DOWNLOAD_DIR/$distributionUrlName\" || die \"wget: Failed to fetch $distributionUrl\"\nelif [ -z \"${MVNW_USERNAME-}\" ] && command -v curl >/dev/null; then\n  verbose \"Found curl ... using curl\"\n  curl ${__MVNW_QUIET_CURL:+\"$__MVNW_QUIET_CURL\"} -f -L -o \"$TMP_DOWNLOAD_DIR/$distributionUrlName\" \"$distributionUrl\" || die \"curl: Failed to fetch $distributionUrl\"\nelif set_java_home; then\n  verbose \"Falling back to use Java to download\"\n  javaSource=\"$TMP_DOWNLOAD_DIR/Downloader.java\"\n  targetZip=\"$TMP_DOWNLOAD_DIR/$distributionUrlName\"\n  cat >\"$javaSource\" <<-END\n\tpublic class Downloader extends java.net.Authenticator\n\t{\n\t  protected java.net.PasswordAuthentication getPasswordAuthentication()\n\t  {\n\t    return new java.net.PasswordAuthentication( System.getenv( \"MVNW_USERNAME\" ), System.getenv( \"MVNW_PASSWORD\" ).toCharArray() );\n\t  }\n\t  public static void main( String[] args ) throws Exception\n\t  {\n\t    setDefault( new Downloader() );\n\t    java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );\n\t  }\n\t}\n\tEND\n  # For Cygwin/MinGW, switch paths to Windows format before running javac and java\n  verbose \" - Compiling Downloader.java ...\"\n  \"$(native_path \"$JAVACCMD\")\" \"$(native_path \"$javaSource\")\" || die \"Failed to compile Downloader.java\"\n  verbose \" - Running Downloader.java ...\"\n  \"$(native_path \"$JAVACMD\")\" -cp \"$(native_path \"$TMP_DOWNLOAD_DIR\")\" Downloader \"$distributionUrl\" \"$(native_path \"$targetZip\")\"\nfi\n\n# If specified, validate the SHA-256 sum of the Maven distribution zip file\nif [ -n \"${distributionSha256Sum-}\" ]; then\n  distributionSha256Result=false\n  if [ \"$MVN_CMD\" = mvnd.sh ]; then\n    echo \"Checksum validation is not supported for maven-mvnd.\" >&2\n    echo \"Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties.\" >&2\n    exit 1\n  elif command -v sha256sum >/dev/null; then\n    if echo \"$distributionSha256Sum  $TMP_DOWNLOAD_DIR/$distributionUrlName\" | sha256sum -c - >/dev/null 2>&1; then\n      distributionSha256Result=true\n    fi\n  elif command -v shasum >/dev/null; then\n    if echo \"$distributionSha256Sum  $TMP_DOWNLOAD_DIR/$distributionUrlName\" | shasum -a 256 -c >/dev/null 2>&1; then\n      distributionSha256Result=true\n    fi\n  else\n    echo \"Checksum validation was requested but neither 'sha256sum' or 'shasum' are available.\" >&2\n    echo \"Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties.\" >&2\n    exit 1\n  fi\n  if [ $distributionSha256Result = false ]; then\n    echo \"Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised.\" >&2\n    echo \"If you updated your Maven version, you need to update the specified distributionSha256Sum property.\" >&2\n    exit 1\n  fi\nfi\n\n# unzip and move\nif command -v unzip >/dev/null; then\n  unzip ${__MVNW_QUIET_UNZIP:+\"$__MVNW_QUIET_UNZIP\"} \"$TMP_DOWNLOAD_DIR/$distributionUrlName\" -d \"$TMP_DOWNLOAD_DIR\" || die \"failed to unzip\"\nelse\n  tar xzf${__MVNW_QUIET_TAR:+\"$__MVNW_QUIET_TAR\"} \"$TMP_DOWNLOAD_DIR/$distributionUrlName\" -C \"$TMP_DOWNLOAD_DIR\" || die \"failed to untar\"\nfi\n\n# Find the actual extracted directory name (handles snapshots where filename != directory name)\nactualDistributionDir=\"\"\n\n# First try the expected directory name (for regular distributions)\nif [ -d \"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain\" ]; then\n  if [ -f \"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD\" ]; then\n    actualDistributionDir=\"$distributionUrlNameMain\"\n  fi\nfi\n\n# If not found, search for any directory with the Maven executable (for snapshots)\nif [ -z \"$actualDistributionDir\" ]; then\n  # enable globbing to iterate over items\n  set +f\n  for dir in \"$TMP_DOWNLOAD_DIR\"/*; do\n    if [ -d \"$dir\" ]; then\n      if [ -f \"$dir/bin/$MVN_CMD\" ]; then\n        actualDistributionDir=\"$(basename \"$dir\")\"\n        break\n      fi\n    fi\n  done\n  set -f\nfi\n\nif [ -z \"$actualDistributionDir\" ]; then\n  verbose \"Contents of $TMP_DOWNLOAD_DIR:\"\n  verbose \"$(ls -la \"$TMP_DOWNLOAD_DIR\")\"\n  die \"Could not find Maven distribution directory in extracted archive\"\nfi\n\nverbose \"Found extracted Maven distribution directory: $actualDistributionDir\"\nprintf %s\\\\n \"$distributionUrl\" >\"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url\"\nmv -- \"$TMP_DOWNLOAD_DIR/$actualDistributionDir\" \"$MAVEN_HOME\" || [ -d \"$MAVEN_HOME\" ] || die \"fail to move MAVEN_HOME\"\n\nclean || :\nexec_maven \"$@\"\n"
  },
  {
    "path": "mvnw.cmd",
    "content": "<# : batch portion\r\n@REM ----------------------------------------------------------------------------\r\n@REM Licensed to the Apache Software Foundation (ASF) under one\r\n@REM or more contributor license agreements.  See the NOTICE file\r\n@REM distributed with this work for additional information\r\n@REM regarding copyright ownership.  The ASF licenses this file\r\n@REM to you under the Apache License, Version 2.0 (the\r\n@REM \"License\"); you may not use this file except in compliance\r\n@REM with the License.  You may obtain a copy of the License at\r\n@REM\r\n@REM    http://www.apache.org/licenses/LICENSE-2.0\r\n@REM\r\n@REM Unless required by applicable law or agreed to in writing,\r\n@REM software distributed under the License is distributed on an\r\n@REM \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\r\n@REM KIND, either express or implied.  See the License for the\r\n@REM specific language governing permissions and limitations\r\n@REM under the License.\r\n@REM ----------------------------------------------------------------------------\r\n\r\n@REM ----------------------------------------------------------------------------\r\n@REM Apache Maven Wrapper startup batch script, version 3.3.4\r\n@REM\r\n@REM Optional ENV vars\r\n@REM   MVNW_REPOURL - repo url base for downloading maven distribution\r\n@REM   MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven\r\n@REM   MVNW_VERBOSE - true: enable verbose log; others: silence the output\r\n@REM ----------------------------------------------------------------------------\r\n\r\n@IF \"%__MVNW_ARG0_NAME__%\"==\"\" (SET __MVNW_ARG0_NAME__=%~nx0)\r\n@SET __MVNW_CMD__=\r\n@SET __MVNW_ERROR__=\r\n@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%\r\n@SET PSModulePath=\r\n@FOR /F \"usebackq tokens=1* delims==\" %%A IN (`powershell -noprofile \"& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}\"`) DO @(\r\n  IF \"%%A\"==\"MVN_CMD\" (set __MVNW_CMD__=%%B) ELSE IF \"%%B\"==\"\" (echo %%A) ELSE (echo %%A=%%B)\r\n)\r\n@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%\r\n@SET __MVNW_PSMODULEP_SAVE=\r\n@SET __MVNW_ARG0_NAME__=\r\n@SET MVNW_USERNAME=\r\n@SET MVNW_PASSWORD=\r\n@IF NOT \"%__MVNW_CMD__%\"==\"\" (\"%__MVNW_CMD__%\" %*)\r\n@echo Cannot start maven from wrapper >&2 && exit /b 1\r\n@GOTO :EOF\r\n: end batch / begin powershell #>\r\n\r\n$ErrorActionPreference = \"Stop\"\r\nif ($env:MVNW_VERBOSE -eq \"true\") {\r\n  $VerbosePreference = \"Continue\"\r\n}\r\n\r\n# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties\r\n$distributionUrl = (Get-Content -Raw \"$scriptDir/.mvn/wrapper/maven-wrapper.properties\" | ConvertFrom-StringData).distributionUrl\r\nif (!$distributionUrl) {\r\n  Write-Error \"cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties\"\r\n}\r\n\r\nswitch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {\r\n  \"maven-mvnd-*\" {\r\n    $USE_MVND = $true\r\n    $distributionUrl = $distributionUrl -replace '-bin\\.[^.]*$',\"-windows-amd64.zip\"\r\n    $MVN_CMD = \"mvnd.cmd\"\r\n    break\r\n  }\r\n  default {\r\n    $USE_MVND = $false\r\n    $MVN_CMD = $script -replace '^mvnw','mvn'\r\n    break\r\n  }\r\n}\r\n\r\n# apply MVNW_REPOURL and calculate MAVEN_HOME\r\n# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>\r\nif ($env:MVNW_REPOURL) {\r\n  $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { \"/org/apache/maven/\" } else { \"/maven/mvnd/\" }\r\n  $distributionUrl = \"$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace \"^.*$MVNW_REPO_PATTERN\",'')\"\r\n}\r\n$distributionUrlName = $distributionUrl -replace '^.*/',''\r\n$distributionUrlNameMain = $distributionUrlName -replace '\\.[^.]*$','' -replace '-bin$',''\r\n\r\n$MAVEN_M2_PATH = \"$HOME/.m2\"\r\nif ($env:MAVEN_USER_HOME) {\r\n  $MAVEN_M2_PATH = \"$env:MAVEN_USER_HOME\"\r\n}\r\n\r\nif (-not (Test-Path -Path $MAVEN_M2_PATH)) {\r\n    New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null\r\n}\r\n\r\n$MAVEN_WRAPPER_DISTS = $null\r\nif ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {\r\n  $MAVEN_WRAPPER_DISTS = \"$MAVEN_M2_PATH/wrapper/dists\"\r\n} else {\r\n  $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + \"/wrapper/dists\"\r\n}\r\n\r\n$MAVEN_HOME_PARENT = \"$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain\"\r\n$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString(\"x2\")}) -join ''\r\n$MAVEN_HOME = \"$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME\"\r\n\r\nif (Test-Path -Path \"$MAVEN_HOME\" -PathType Container) {\r\n  Write-Verbose \"found existing MAVEN_HOME at $MAVEN_HOME\"\r\n  Write-Output \"MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD\"\r\n  exit $?\r\n}\r\n\r\nif (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {\r\n  Write-Error \"distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl\"\r\n}\r\n\r\n# prepare tmp dir\r\n$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile\r\n$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path \"$TMP_DOWNLOAD_DIR_HOLDER.dir\"\r\n$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null\r\ntrap {\r\n  if ($TMP_DOWNLOAD_DIR.Exists) {\r\n    try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }\r\n    catch { Write-Warning \"Cannot remove $TMP_DOWNLOAD_DIR\" }\r\n  }\r\n}\r\n\r\nNew-Item -Itemtype Directory -Path \"$MAVEN_HOME_PARENT\" -Force | Out-Null\r\n\r\n# Download and Install Apache Maven\r\nWrite-Verbose \"Couldn't find MAVEN_HOME, downloading and installing it ...\"\r\nWrite-Verbose \"Downloading from: $distributionUrl\"\r\nWrite-Verbose \"Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName\"\r\n\r\n$webclient = New-Object System.Net.WebClient\r\nif ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {\r\n  $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)\r\n}\r\n[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12\r\n$webclient.DownloadFile($distributionUrl, \"$TMP_DOWNLOAD_DIR/$distributionUrlName\") | Out-Null\r\n\r\n# If specified, validate the SHA-256 sum of the Maven distribution zip file\r\n$distributionSha256Sum = (Get-Content -Raw \"$scriptDir/.mvn/wrapper/maven-wrapper.properties\" | ConvertFrom-StringData).distributionSha256Sum\r\nif ($distributionSha256Sum) {\r\n  if ($USE_MVND) {\r\n    Write-Error \"Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties.\"\r\n  }\r\n  Import-Module $PSHOME\\Modules\\Microsoft.PowerShell.Utility -Function Get-FileHash\r\n  if ((Get-FileHash \"$TMP_DOWNLOAD_DIR/$distributionUrlName\" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {\r\n    Write-Error \"Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property.\"\r\n  }\r\n}\r\n\r\n# unzip and move\r\nExpand-Archive \"$TMP_DOWNLOAD_DIR/$distributionUrlName\" -DestinationPath \"$TMP_DOWNLOAD_DIR\" | Out-Null\r\n\r\n# Find the actual extracted directory name (handles snapshots where filename != directory name)\r\n$actualDistributionDir = \"\"\r\n\r\n# First try the expected directory name (for regular distributions)\r\n$expectedPath = Join-Path \"$TMP_DOWNLOAD_DIR\" \"$distributionUrlNameMain\"\r\n$expectedMvnPath = Join-Path \"$expectedPath\" \"bin/$MVN_CMD\"\r\nif ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {\r\n  $actualDistributionDir = $distributionUrlNameMain\r\n}\r\n\r\n# If not found, search for any directory with the Maven executable (for snapshots)\r\nif (!$actualDistributionDir) {\r\n  Get-ChildItem -Path \"$TMP_DOWNLOAD_DIR\" -Directory | ForEach-Object {\r\n    $testPath = Join-Path $_.FullName \"bin/$MVN_CMD\"\r\n    if (Test-Path -Path $testPath -PathType Leaf) {\r\n      $actualDistributionDir = $_.Name\r\n    }\r\n  }\r\n}\r\n\r\nif (!$actualDistributionDir) {\r\n  Write-Error \"Could not find Maven distribution directory in extracted archive\"\r\n}\r\n\r\nWrite-Verbose \"Found extracted Maven distribution directory: $actualDistributionDir\"\r\nRename-Item -Path \"$TMP_DOWNLOAD_DIR/$actualDistributionDir\" -NewName $MAVEN_HOME_NAME | Out-Null\r\ntry {\r\n  Move-Item -Path \"$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME\" -Destination $MAVEN_HOME_PARENT | Out-Null\r\n} catch {\r\n  if (! (Test-Path -Path \"$MAVEN_HOME\" -PathType Container)) {\r\n    Write-Error \"fail to move MAVEN_HOME\"\r\n  }\r\n} finally {\r\n  try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }\r\n  catch { Write-Warning \"Cannot remove $TMP_DOWNLOAD_DIR\" }\r\n}\r\n\r\nWrite-Output \"MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD\"\r\n"
  },
  {
    "path": "pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.parent</groupId>\n    <artifactId>parent</artifactId>\n    <version>87.0.0.3057</version>\n    <relativePath/>\n  </parent>\n  <groupId>org.sonarsource.sonarlint.core</groupId>\n  <artifactId>sonarlint-core-parent</artifactId>\n  <version>11.2-SNAPSHOT</version>\n  <packaging>pom</packaging>\n  <name>SonarLint Core</name>\n  <description>Library used by SonarLint flavors (Eclipse, IntelliJ, VSCode...)</description>\n  <inceptionYear>2016</inceptionYear>\n  <organization>\n    <name>SonarSource</name>\n    <url>http://www.sonarsource.com/</url>\n  </organization>\n  <issueManagement>\n    <system>JIRA</system>\n    <url>https://jira.sonarsource.com/browse/SLCORE</url>\n  </issueManagement>\n\n  <modules>\n    <module>buildSrc/maven-shade-ext-bnd-transformer</module>\n    <module>backend</module>\n    <module>client</module>\n    <module>medium-tests</module>\n    <module>report-aggregate</module>\n    <module>rpc-protocol</module>\n    <module>test-utils</module>\n  </modules>\n\n  <properties>\n    <license.name>GNU LGPL v3</license.name>\n\n    <!--\n      Most modules compile with Java 21.\n      client/ and rpc-protocol/ modules override this to Java 11 because they are\n      dependencies for the SonarLint Core OSGi bundle used on SonarQube for\n      Eclipse, which itself compiles with Java 11.\n    -->\n    <maven.compiler.release>21</maven.compiler.release>\n\n    <sonar-plugin-api.version>13.4.2.4284</sonar-plugin-api.version>\n    <sonar-markdown.version>25.3.0.104237</sonar-markdown.version>\n    <sonar-scanner-protocol.version>9.9.0.65466</sonar-scanner-protocol.version>\n    <protobuf.version>4.28.2</protobuf.version>\n    <gitRepositoryName>sonarlint-core</gitRepositoryName>\n    <okhttp.version>5.0.0-alpha.16</okhttp.version>\n    <junit.jupiter.version>5.14.3</junit.jupiter.version>\n    <artifactsToPublish>${project.groupId}:sonarlint-core:jar</artifactsToPublish>\n    <jdk.min.version>11</jdk.min.version>\n    <gson.version>2.13.2</gson.version>\n    <mockito.version>5.22.0</mockito.version>\n    <kotlin.version>1.9.25</kotlin.version>\n    <lsp4j.version>0.24.0</lsp4j.version>\n    <slf4j.version>2.0.17</slf4j.version>\n    <logback.version>1.5.32</logback.version>\n    <version.surefire.plugin>3.1.2</version.surefire.plugin>\n    <system.stubs.version>2.1.8</system.stubs.version>\n\n    <!-- JGit 7 rely on Java 17 and only used by the backend, JGit 6 relying on Java 11 for the Java clients -->\n    <jgit6.version>6.10.1.202505221210-r</jgit6.version>\n    <jgit7.version>7.5.0.202512021534-r</jgit7.version>\n    <sentry.version>8.34.0</sentry.version>\n\n    <!-- Not only used as a plug-in but also as a dependency for 'maven-shade-ext-bnd-transformer' -->\n    <version.shade.plugin>3.6.1</version.shade.plugin>\n\n    <!-- Exclude Maven Shade plug-in dependency as the test is done via the Maven build itself ... -->\n    <sonar.coverage.exclusions>buildSrc/maven-shade-ext-bnd-transformer/**</sonar.coverage.exclusions>\n    <!-- We don't compute coverage on windows UTs   -->\n    <sonar.coverage.exclusions>backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/util/git/WinGitUtils.java</sonar.coverage.exclusions>\n    <!-- Exclude from the duplication detection deprecated DTOs that were replaced by new types. They should be removed in 10.3 -->\n    <sonar.cpd.exclusions>rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/analysis/*Dto.java</sonar.cpd.exclusions>\n    <sonar.organization>sonarsource</sonar.organization>\n    <h2.version>2.4.240</h2.version>\n    <!-- More recent versions require Java 21 -->\n    <jooq.version>3.19.15</jooq.version>\n    <flyway.version>11.20.3</flyway.version>\n    <bouncycastle.version>1.84</bouncycastle.version>\n    <!-- On-demand plugin versions -->\n    <cfamily.version>6.80.0.98490</cfamily.version>\n    <csharp.version>10.24.0.138807</csharp.version>\n    <omnisharp.version>1.39.15</omnisharp.version>\n  </properties>\n\n  <dependencyManagement>\n    <dependencies>\n      <dependency>\n        <groupId>org.jetbrains.kotlin</groupId>\n        <artifactId>kotlin-bom</artifactId>\n        <version>${kotlin.version}</version>\n        <type>pom</type>\n        <scope>import</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.springframework</groupId>\n         <artifactId>spring-framework-bom</artifactId>\n         <version>6.2.16</version>\n         <type>pom</type>\n         <scope>import</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.junit.jupiter</groupId>\n        <artifactId>junit-jupiter-api</artifactId>\n        <version>${junit.jupiter.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>org.junit.jupiter</groupId>\n        <artifactId>junit-jupiter-params</artifactId>\n        <version>${junit.jupiter.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>org.junit.jupiter</groupId>\n        <artifactId>junit-jupiter-engine</artifactId>\n        <version>${junit.jupiter.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>org.awaitility</groupId>\n        <artifactId>awaitility</artifactId>\n        <version>4.3.0</version>\n      </dependency>\n      <dependency>\n        <groupId>org.apache.commons</groupId>\n        <artifactId>commons-lang3</artifactId>\n        <version>3.20.0</version>\n      </dependency>\n      <dependency>\n          <groupId>org.apache.commons</groupId>\n          <artifactId>commons-text</artifactId>\n          <version>1.15.0</version>\n      </dependency>\n      <!-- This dependency is imported by the SLLS, ping them when it is modified -->\n      <dependency>\n        <groupId>commons-io</groupId>\n        <artifactId>commons-io</artifactId>\n        <version>2.21.0</version>\n      </dependency>\n      <dependency>\n        <groupId>commons-codec</groupId>\n        <artifactId>commons-codec</artifactId>\n        <version>1.21.0</version>\n      </dependency>\n      <dependency>\n        <groupId>org.apache.commons</groupId>\n        <artifactId>commons-compress</artifactId>\n        <version>1.28.0</version>\n      </dependency>\n      <dependency>\n        <groupId>com.google.code.gson</groupId>\n        <artifactId>gson</artifactId>\n        <version>${gson.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>com.google.guava</groupId>\n        <artifactId>guava</artifactId>\n        <version>33.5.0-jre</version>\n      </dependency>\n      <dependency>\n        <groupId>org.assertj</groupId>\n        <artifactId>assertj-core</artifactId>\n        <version>3.27.7</version>\n      </dependency>\n      <!-- Resolve dependency conflict between assertj and mockito -->\n      <dependency>\n        <groupId>net.bytebuddy</groupId>\n        <artifactId>byte-buddy</artifactId>\n        <version>1.18.7</version>\n      </dependency>\n      <dependency>\n        <groupId>org.mockito</groupId>\n        <artifactId>mockito-core</artifactId>\n        <version>${mockito.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>org.mockito</groupId>\n        <artifactId>mockito-junit-jupiter</artifactId>\n        <version>${mockito.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>com.google.code.findbugs</groupId>\n        <artifactId>jsr305</artifactId>\n        <version>3.0.2</version>\n      </dependency>\n      <dependency>\n        <groupId>javax.annotation</groupId>\n        <artifactId>javax.annotation-api</artifactId>\n        <version>1.3.2</version>\n      </dependency>\n      <dependency>\n        <groupId>jakarta.inject</groupId>\n        <artifactId>jakarta.inject-api</artifactId>\n        <version>2.0.1</version>\n      </dependency>\n      <dependency>\n        <groupId>jakarta.annotation</groupId>\n        <artifactId>jakarta.annotation-api</artifactId>\n        <version>3.0.0</version>\n      </dependency>\n      <dependency>\n        <groupId>org.bouncycastle</groupId>\n        <artifactId>bcpg-jdk18on</artifactId>\n        <version>${bouncycastle.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>org.bouncycastle</groupId>\n        <artifactId>bcprov-jdk18on</artifactId>\n        <version>${bouncycastle.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>uk.org.webcompere</groupId>\n        <artifactId>system-stubs-jupiter</artifactId>\n        <version>${system.stubs.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>com.squareup.okhttp3</groupId>\n        <artifactId>okhttp</artifactId>\n        <version>${okhttp.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>com.squareup.okhttp3</groupId>\n        <artifactId>mockwebserver3</artifactId>\n        <version>${okhttp.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>org.apache.httpcomponents.client5</groupId>\n        <artifactId>httpclient5</artifactId>\n        <version>5.4.4</version>\n      </dependency>\n      <dependency>\n        <groupId>io.github.hakky54</groupId>\n        <artifactId>ayza</artifactId>\n        <version>10.0.3</version>\n      </dependency>\n      <!-- Use the Jetty 12 variant that supports Servlet 6, to not conflict with Tomcat -->\n      <dependency>\n        <groupId>org.wiremock</groupId>\n        <artifactId>wiremock-jetty12</artifactId>\n        <version>3.13.2</version>\n      </dependency>\n      <dependency>\n        <groupId>org.slf4j</groupId>\n        <artifactId>slf4j-api</artifactId>\n        <version>${slf4j.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>org.slf4j</groupId>\n        <artifactId>jul-to-slf4j</artifactId>\n        <version>${slf4j.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>ch.qos.logback</groupId>\n        <artifactId>logback-classic</artifactId>\n        <version>${logback.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>com.h2database</groupId>\n        <artifactId>h2</artifactId>\n        <version>${h2.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>org.jooq</groupId>\n        <artifactId>jooq</artifactId>\n        <version>${jooq.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>org.flywaydb</groupId>\n        <artifactId>flyway-core</artifactId>\n        <version>${flyway.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>org.sonarsource.classloader</groupId>\n        <artifactId>sonar-classloader</artifactId>\n        <version>1.2.1.2095</version>\n      </dependency>\n    </dependencies>\n  </dependencyManagement>\n\n  <build>\n    <pluginManagement>\n      <plugins>\n        <plugin>\n          <groupId>biz.aQute.bnd</groupId>\n          <artifactId>bnd-maven-plugin</artifactId>\n          <version>6.4.0</version>\n        </plugin>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-antrun-plugin</artifactId>\n          <version>3.2.0</version>\n        </plugin>\n        <plugin>\n          <groupId>org.codehaus.mojo</groupId>\n          <artifactId>build-helper-maven-plugin</artifactId>\n          <version>3.6.1</version>\n        </plugin>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-javadoc-plugin</artifactId>\n          <version>3.12.0</version>\n          <configuration>\n            <source>11</source>\n          </configuration>\n        </plugin>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-surefire-plugin</artifactId>\n          <configuration>\n            <trimStackTrace>false</trimStackTrace>\n            <!-- Some tests are asserting on localized messages or dates -->\n            <systemPropertyVariables>\n              <user.language>en</user.language>\n              <user.country>US</user.country>\n            </systemPropertyVariables>\n            <environmentVariables>\n              <LANGUAGE>en_US</LANGUAGE>\n            </environmentVariables>\n            <redirectTestOutputToFile>true</redirectTestOutputToFile>\n          </configuration>\n        </plugin>\n        <plugin>\n          <groupId>kr.motd.maven</groupId>\n          <artifactId>os-maven-plugin</artifactId>\n          <version>1.7.1</version>\n        </plugin>\n        <plugin>\n          <groupId>org.xolstice.maven.plugins</groupId>\n          <artifactId>protobuf-maven-plugin</artifactId>\n          <version>0.6.1</version>\n        </plugin>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-shade-plugin</artifactId>\n          <version>${version.shade.plugin}</version>\n        </plugin>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-dependency-plugin</artifactId>\n          <version>3.10.0</version>\n        </plugin>\n        <plugin>\n          <groupId>org.jacoco</groupId>\n          <artifactId>jacoco-maven-plugin</artifactId>\n          <version>0.8.14</version>\n        </plugin>\n        <plugin>\n          <groupId>com.googlecode.maven-download-plugin</groupId>\n          <artifactId>download-maven-plugin</artifactId>\n          <version>1.13.0</version>\n        </plugin>\n        <plugin>\n          <groupId>org.jooq</groupId>\n          <artifactId>jooq-codegen-maven</artifactId>\n          <version>${jooq.version}</version>\n        </plugin>\n        <plugin>\n          <groupId>org.flywaydb</groupId>\n          <artifactId>flyway-maven-plugin</artifactId>\n          <version>${flyway.version}</version>\n      </plugin>\n      </plugins>\n    </pluginManagement>\n    <plugins>\n      <plugin>\n        <groupId>com.mycila</groupId>\n        <artifactId>license-maven-plugin</artifactId>\n        <configuration>\n          <licenseSets>\n            <licenseSet>\n              <excludes combine.children=\"append\">\n                <exclude>**/resources/ai/hooks/**</exclude>\n              </excludes>\n            </licenseSet>\n          </licenseSets>\n          <includes combine.children=\"append\">\n            <include>src/*/proto/**/*.proto</include>\n          </includes>\n          <mapping combine.children=\"append\">\n            <proto>SLASHSTAR_STYLE</proto>\n          </mapping>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n\n  <profiles>\n    <profile>\n      <id>its</id>\n      <modules>\n        <module>its</module>\n      </modules>\n    </profile>\n    <profile>\n      <id>coverage</id>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>org.jacoco</groupId>\n            <artifactId>jacoco-maven-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>prepare-agent</id>\n                <goals>\n                  <goal>prepare-agent</goal>\n                </goals>\n              </execution>\n              <execution>\n                <id>report</id>\n                <goals>\n                  <goal>report</goal>\n                </goals>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n  </profiles>\n</project>\n"
  },
  {
    "path": "report-aggregate/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-core-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n  </parent>\n  <artifactId>sonarlint-report-aggregate</artifactId>\n  <name>SonarLint Coverage Reports Aggregator</name>\n  <description>Aggregate Coverage Reports</description>\n\n  <properties>\n    <!-- This is a technical module, no need to deploy an empty JAR -->\n    <maven.deploy.skip>true</maven.deploy.skip>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-commons</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-analysis-engine</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-rule-extractor</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-server-api</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-plugin-commons</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-core</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-plugin-api</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-telemetry</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-server-connection</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-rpc-protocol</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-rpc-impl</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-backend-cli</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-http</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-medium-tests</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-rpc-java-client</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-java-client-utils</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-core-test-utils</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.jacoco</groupId>\n        <artifactId>jacoco-maven-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>report-aggregate</id>\n            <phase>verify</phase>\n            <goals>\n              <goal>report-aggregate</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n    </plugins>\n  </build>\n\n</project>\n"
  },
  {
    "path": "rpc-protocol/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-core-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n  </parent>\n  <artifactId>sonarlint-rpc-protocol</artifactId>\n  <name>SonarLint Core - RPC Protocol</name>\n  <description>Protocol used to communicate with clients (IDEs) through RPC</description>\n  \n  <properties>\n    <!--\n      As this is a dependency of the SonarLint Core OSGi bundle that is used on\n      SonarQube for Eclipse, we have to stick to Java 11 for the compiler!\n    -->\n    <maven.compiler.release>11</maven.compiler.release>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.code.findbugs</groupId>\n      <artifactId>jsr305</artifactId>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.eclipse.lsp4j</groupId>\n      <artifactId>org.eclipse.lsp4j.jsonrpc</artifactId>\n      <version>${lsp4j.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>io.sentry</groupId>\n      <artifactId>sentry</artifactId>\n      <version>${sentry.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-engine</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.assertj</groupId>\n      <artifactId>assertj-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <profiles>\n    <!-- Workaround for https://issues.apache.org/jira/projects/MJAR/issues/MJAR-138 -->\n    <profile>\n      <id>conditionally-add-commons-tests-if-tests-not-skipped</id>\n      <activation>\n        <property>\n          <name>maven.test.skip</name>\n          <value>!true</value>\n        </property>\n      </activation>\n      <dependencies>\n        <dependency>\n          <groupId>${project.groupId}</groupId>\n          <artifactId>sonarlint-commons</artifactId>\n          <version>${project.version}</version>\n          <classifier>tests</classifier>\n          <type>test-jar</type>\n          <scope>test</scope>\n        </dependency>\n      </dependencies>\n    </profile>\n  </profiles>\n</project>\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/Lsp4jUtils.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol;\n\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\n\npublic class Lsp4jUtils {\n\n  private Lsp4jUtils() {\n    // Utils class\n  }\n\n  /**\n   * Test whether the given type is Either.\n   */\n  public static boolean isEither(Type type) {\n    if (type instanceof ParameterizedType) {\n      return isEither(((ParameterizedType) type).getRawType());\n    }\n    if (type instanceof Class) {\n      return Either.class.isAssignableFrom((Class<?>) type);\n    }\n    return false;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/RpcErrorHandler.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol;\n\nimport io.sentry.Attachment;\nimport io.sentry.Hint;\nimport io.sentry.Sentry;\nimport java.io.ByteArrayOutputStream;\nimport java.io.PrintWriter;\nimport java.lang.reflect.InvocationTargetException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.concurrent.CompletionException;\nimport org.eclipse.lsp4j.jsonrpc.ResponseErrorException;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseError;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;\n\npublic class RpcErrorHandler {\n\n  private RpcErrorHandler() {\n  }\n\n  public static ResponseError handleError(Throwable throwable) {\n    if (throwable instanceof ResponseErrorException) {\n      return ((ResponseErrorException) throwable).getResponseError();\n    } else if (isWrappedResponseErrorException(throwable)) {\n      return ((ResponseErrorException) throwable.getCause()).getResponseError();\n    } else {\n      return createInternalErrorResponse(\"Internal error\", throwable);\n    }\n  }\n\n  private static boolean isWrappedResponseErrorException(Throwable throwable) {\n    return (throwable instanceof CompletionException || throwable instanceof InvocationTargetException)\n      && throwable.getCause() instanceof ResponseErrorException;\n  }\n\n  public static ResponseError createInternalErrorResponse(String header, Throwable throwable) {\n    var error = new ResponseError();\n    error.setMessage(header + \".\");\n    error.setCode(ResponseErrorCode.InternalError);\n    var stackTraceString = toStringStacktrace(throwable);\n\n    // Send to Sentry with hint being the full stacktrace\n    var stackTraceAttachment = new Attachment(stackTraceString.getBytes(StandardCharsets.UTF_8), \"stacktrace.txt\");\n    Sentry.captureException(throwable, Hint.withAttachment(stackTraceAttachment));\n\n    error.setData(stackTraceString);\n    return error;\n  }\n\n  private static String toStringStacktrace(Throwable throwable) {\n    var stackTrace = new ByteArrayOutputStream();\n    var stackTraceWriter = new PrintWriter(stackTrace);\n    throwable.printStackTrace(stackTraceWriter);\n    stackTraceWriter.flush();\n    return stackTrace.toString(StandardCharsets.UTF_8);\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/SingleThreadedMessageConsumer.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol;\n\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.function.Consumer;\nimport org.eclipse.lsp4j.jsonrpc.JsonRpcException;\nimport org.eclipse.lsp4j.jsonrpc.MessageConsumer;\nimport org.eclipse.lsp4j.jsonrpc.MessageIssueException;\nimport org.eclipse.lsp4j.jsonrpc.messages.Message;\n\n/**\n * Workaround for PipedInputStream not liking to be fed from multiple threads. We are wrapping the lsp4j {@link org.eclipse.lsp4j.jsonrpc.json.StreamMessageConsumer}\n * with our own implementation that uses a queue and a single thread to consume messages.\n */\npublic class SingleThreadedMessageConsumer implements MessageConsumer {\n\n  private final LinkedBlockingQueue<Message> queue = new LinkedBlockingQueue<>();\n\n  public SingleThreadedMessageConsumer(MessageConsumer syncMessageConsumer, ExecutorService threadPool, Consumer<Throwable> errorLogger) {\n    threadPool.execute(() -> {\n      while (true) {\n        Message message;\n        try {\n          message = queue.take();\n        } catch (InterruptedException e) {\n          Thread.currentThread().interrupt();\n          return;\n        }\n        try {\n          syncMessageConsumer.consume(message);\n        } catch (Exception e) {\n          errorLogger.accept(e);\n        }\n      }\n    });\n  }\n\n  @Override\n  public void consume(Message message) throws MessageIssueException, JsonRpcException {\n    queue.add(message);\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/SonarLintLauncherBuilder.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol;\n\nimport com.google.gson.GsonBuilder;\nimport java.net.URI;\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.time.OffsetDateTime;\nimport java.util.Map;\nimport java.util.UUID;\nimport org.eclipse.lsp4j.jsonrpc.Launcher;\nimport org.eclipse.lsp4j.jsonrpc.json.JsonRpcMethod;\nimport org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler;\nimport org.eclipse.lsp4j.jsonrpc.json.adapters.MessageTypeAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.DurationTypeAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.EitherStandardOrMQRModeAdapterFactory;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.EitherTypeAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.InstantTypeAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.OffsetDateTimeAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.PathTypeAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.UriTypeAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.UuidTypeAdapter;\n\n/**\n * A modified version of the LSP4J LauncherBuilder with some customization/workarounds.\n */\npublic class SonarLintLauncherBuilder<T> extends Launcher.Builder<T> {\n\n  @Override\n  protected MessageJsonHandler createJsonHandler() {\n    Map<String, JsonRpcMethod> supportedMethods = getSupportedMethods();\n    return new MessageJsonHandler(supportedMethods) {\n      @Override\n      public GsonBuilder getDefaultGsonBuilder() {\n        // We don't want the EnumTypeAdapter from lsp4j, as we want to serialize enums as string (this is the default in Gson)\n        return new GsonBuilder()\n          .registerTypeAdapterFactory(new EitherTypeAdapter.Factory())\n          // We need to register those adapters globally, because we can't use the @JsonAdapter annotation on generic types\n          .registerTypeAdapterFactory(new EitherStandardOrMQRModeAdapterFactory())\n\n          .registerTypeAdapterFactory(new MessageTypeAdapter.Factory(this))\n          .registerTypeHierarchyAdapter(Path.class, new PathTypeAdapter())\n          .registerTypeHierarchyAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter())\n          .registerTypeHierarchyAdapter(Instant.class, new InstantTypeAdapter())\n          .registerTypeHierarchyAdapter(UUID.class, new UuidTypeAdapter())\n          .registerTypeHierarchyAdapter(URI.class, new UriTypeAdapter())\n          .registerTypeHierarchyAdapter(Duration.class, new DurationTypeAdapter())\n          .serializeNulls();\n      }\n    };\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/SonarLintRpcClient.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonNotification;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonRequest;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.ConnectionRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidChangeCredentialsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.ClientConstantInfoDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.OpenUrlInBrowserParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.DidChangeAnalysisReadinessParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.DidDetectSecretParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.GetFileExclusionsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.GetFileExclusionsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.GetInferredAnalysisPropertiesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.GetInferredAnalysisPropertiesResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.AssistBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.AssistBindingResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.NoBindingSuggestionFoundParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.SuggestBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.branch.DidChangeMatchedSonarProjectBranchParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.branch.MatchProjectBranchParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.branch.MatchProjectBranchResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.branch.MatchSonarProjectBranchParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.branch.MatchSonarProjectBranchResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.GetCredentialsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.GetCredentialsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.SuggestConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.embeddedserver.EmbeddedServerStartedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.event.DidReceiveServerHotspotEvent;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fix.ShowFixSuggestionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fs.GetBaseDirParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fs.GetBaseDirResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fs.ListFilesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fs.ListFilesResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.RaiseHotspotsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.ShowHotspotParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.CheckServerTrustedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.CheckServerTrustedResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.GetProxyPasswordAuthenticationParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.GetProxyPasswordAuthenticationResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.SelectProxiesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.SelectProxiesResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.info.GetClientLiveInfoResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaiseIssuesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.ShowIssueParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowMessageParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowMessageRequestParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowMessageRequestResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowSoonUnsupportedMessageParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.plugin.DidChangePluginStatusesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.plugin.DidSkipLoadingPluginParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.progress.ReportProgressParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.progress.StartProgressParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.promotion.PromoteExtraEnabledLanguagesInConnectedModeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.sca.DidChangeDependencyRisksParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.smartnotification.ShowSmartNotificationParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.sync.DidSynchronizeConfigurationScopeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.sync.InvalidTokenParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.taint.vulnerability.DidChangeTaintVulnerabilitiesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.TelemetryClientLiveAttributesResponse;\n\n/**\n * This interface defines the RPC requests or notifications the backend can call on the client.\n */\npublic interface SonarLintRpcClient {\n\n  /**\n   * Suggest a list of binding suggestions for each eligible configuration scope,\n   * based on registered connections, config scope, binding clues, and git remote URL.\n   * Scopes without any available suggestions are automatically excluded from the results.\n   */\n  @JsonNotification\n  void suggestBinding(SuggestBindingParams params);\n\n  /**\n   * Suggest to create a connection and a binding to the client\n   */\n  @JsonNotification\n  void suggestConnection(SuggestConnectionParams params);\n\n  @JsonNotification\n  void openUrlInBrowser(OpenUrlInBrowserParams params);\n\n  /**\n   * Display a message to the user, usually in a small notification.\n   * The message is informative and does not imply applying an action.\n   */\n  @JsonNotification\n  void showMessage(ShowMessageParams params);\n\n  /**\n   * Display a message to the user, usually in a small notification.\n   * This message has options that the user can pick from. Once user clicked on option, its String key is returned.\n   * If the user explicitly dismisses/closes the notification without clicking option, then the selectedKey in the ShowMessageRequestResponse\n   * will be null AND closedByUser will be true.\n   * IMPORTANT: As users might not react to the notification at all, the returned future might block for an indefinite amount of time.\n   * So the caller should not block waiting for the result, but provide a callback instead.\n   */\n  @JsonRequest\n  default CompletableFuture<ShowMessageRequestResponse> showMessageRequest(ShowMessageRequestParams params) {\n    return CompletableFuture.completedFuture(new ShowMessageRequestResponse(null, false));\n  }\n\n  @JsonNotification\n  void log(LogParams params);\n\n  /**\n   * Display a one-time message to the user as a small notification.\n   * The message is informative and a link to the documentation should be available.\n   * The one-time mechanism should be handled on the client side (via a \"Don't show again\" button for example).\n   * There is an in-memory cache for the pair of connection ID + version that were already seen on the core side, but it is cleared after each restart.\n   */\n  @JsonNotification\n  void showSoonUnsupportedMessage(ShowSoonUnsupportedMessageParams params);\n\n  @JsonNotification\n  void showSmartNotification(ShowSmartNotificationParams params);\n\n  /**\n   * Ask the client to provide its dynamic info that can change during the runtime. This is used as a complement to\n   * static information provided during {@link SonarLintRpcServer#initialize(InitializeParams)}\n   * in {@link ClientConstantInfoDto}.\n   */\n  @JsonRequest\n  CompletableFuture<GetClientLiveInfoResponse> getClientLiveInfo();\n\n  @JsonNotification\n  void showHotspot(ShowHotspotParams params);\n\n  /**\n   * Sends a notification to the client to show a specific issue (specified by {@link ShowIssueParams}) in the IDE\n   */\n  @JsonNotification\n  void showIssue(ShowIssueParams params);\n\n  /**\n   * Sends a notification to the client to show a fix suggestion (specific by {@link ShowFixSuggestionParams}) for a specific issue in the IDE\n   */\n  @JsonNotification\n  default void showFixSuggestion(ShowFixSuggestionParams params) {\n\n  }\n\n  /**\n   * Can be triggered by the backend when trying to handle a feature that needs a connection, e.g. open hotspot.\n   *\n   * @return the response to this connection creation assist request, that contains the new connection. The client can cancel the request if the user stops the creation process.\n   * When cancelling the request from the client side, the error code should be {@link org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode#ServerCancelled}\n   */\n  @JsonRequest\n  CompletableFuture<AssistCreatingConnectionResponse> assistCreatingConnection(AssistCreatingConnectionParams params);\n\n  /**\n   * Can be triggered by the backend when trying to handle a feature that needs a bound project, e.g. open hotspot.\n   *\n   * @return the response to this binding assist request, that contains the bound project. The client can cancel the request if the user stops the binding process.\n   * When cancelling the request from the client side, the error code should be {@link org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode#ServerCancelled}\n   */\n  @JsonRequest\n  CompletableFuture<AssistBindingResponse> assistBinding(AssistBindingParams params);\n\n  /**\n   * Requests the client to start showing progress to users.\n   * If there is an error while creating the corresponding UI, clients can fail the returned future.\n   * Tasks requesting the start of the progress should wait for the client to answer before continuing.\n   */\n  @JsonRequest\n  CompletableFuture<Void> startProgress(StartProgressParams params);\n\n  /**\n   * Reports progress to the client.\n   */\n  @JsonNotification\n  void reportProgress(ReportProgressParams params);\n\n  @JsonNotification\n  void didSynchronizeConfigurationScopes(DidSynchronizeConfigurationScopeParams params);\n\n  /**\n   * @throws org.eclipse.lsp4j.jsonrpc.ResponseErrorException with {@link org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode#CONNECTION_NOT_FOUND} if the connection doesn't exist on the client side\n   */\n  @JsonRequest\n  CompletableFuture<GetCredentialsResponse> getCredentials(GetCredentialsParams params);\n\n  @JsonRequest\n  CompletableFuture<TelemetryClientLiveAttributesResponse> getTelemetryLiveAttributes();\n\n  @JsonRequest\n  CompletableFuture<SelectProxiesResponse> selectProxies(SelectProxiesParams params);\n\n  /**\n   * @throws org.eclipse.lsp4j.jsonrpc.ResponseErrorException with {@link ResponseErrorCode#InvalidParams} if the targetHostUrl is not a valid URL\n   */\n  @JsonRequest\n  CompletableFuture<GetProxyPasswordAuthenticationResponse> getProxyPasswordAuthentication(GetProxyPasswordAuthenticationParams params);\n\n  @JsonRequest\n  CompletableFuture<CheckServerTrustedResponse> checkServerTrusted(CheckServerTrustedParams params);\n\n  /**\n   * @deprecated Should have no-op implementation until method is removed, since event is fully handled by backend now\n   */\n  @Deprecated(since = \"10.3\")\n  @JsonNotification\n  void didReceiveServerHotspotEvent(DidReceiveServerHotspotEvent params);\n\n  @JsonRequest\n  CompletableFuture<MatchSonarProjectBranchResponse> matchSonarProjectBranch(MatchSonarProjectBranchParams params);\n\n  /**\n   * @deprecated Should not be implemented as it is not used anymore\n   */\n  @Deprecated(since = \"10.23\", forRemoval = true)\n  @JsonRequest\n  default CompletableFuture<MatchProjectBranchResponse> matchProjectBranch(MatchProjectBranchParams params) {\n    return CompletableFuture.completedFuture(new MatchProjectBranchResponse(true));\n  }\n\n  @JsonNotification\n  void didChangeMatchedSonarProjectBranch(DidChangeMatchedSonarProjectBranchParams params);\n\n  /**\n   * Returns the base dir for the given configuration scope.\n   * Can be null if the configuration scope does not correspond to a file system\n   */\n  @JsonRequest\n  CompletableFuture<GetBaseDirResponse> getBaseDir(GetBaseDirParams params);\n\n  /**\n   * Must return all file paths for the given configuration scope.\n   */\n  @JsonRequest\n  CompletableFuture<ListFilesResponse> listFiles(ListFilesParams params);\n\n  /**\n   * Called whenever there is a change in the list of taint vulnerabilities of a configuration scope. The change can be caused by:\n   * <ul>\n   *   <li>a synchronization</li>\n   *   <li>a server event</li>\n   *   <li>a taint vulnerability has been resolved by the client</li>\n   *   <li>a vulnerability was on new code and is not anymore</li>\n   * </ul>\n   */\n  @JsonNotification\n  void didChangeTaintVulnerabilities(DidChangeTaintVulnerabilitiesParams params);\n\n  /**\n   * Called whenever there is a change in the list of dependency risks of a configuration scope. The change can be caused by:\n   * <ul>\n   *   <li>a synchronization</li>\n   * </ul>\n   */\n  @JsonNotification\n  void didChangeDependencyRisks(DidChangeDependencyRisksParams params);\n\n  @JsonNotification\n  void noBindingSuggestionFound(NoBindingSuggestionFoundParams params);\n\n  /**\n   * Called when the backend is ready for analyzes to be triggered. The client is supposed to start analyzes only after receiving this notification.\n   * The backend can also notify clients if analyzes become un-ready to be triggered. It can be the case when changing the binding and conditions are not met yet (e.g. no storage)\n   */\n  @JsonNotification\n  void didChangeAnalysisReadiness(DidChangeAnalysisReadinessParams params);\n\n  /**\n   * Called when clients should update the issues list in the UI. This can happen in several situations:\n   * <ul>\n   *   <li>when an analysis ends</li>\n   *   <li>in the middle of an analysis, when issues are raised (issue streaming)</li>\n   *   <li>when an issue changes on the server, e.g. when it is resolved</li>\n   * </ul>\n   * The parameters contain a Map of issues by file URI. This list doesn't include Security Hotspots.\n   * If a file does not have any issues, its URI is mapped to an empty list.\n   * Files that were excluded prior to an analysis will not appear in the map.\n   * The Map contains all known issues so far. It is up to the client to apply some filtering in the UI if needed, e.g. to display issues on open files only.\n   * This method might be called in the context of issue streaming, so it might be called frequently.\n   */\n  @JsonNotification\n  default void raiseIssues(RaiseIssuesParams params) {\n  }\n\n  /**\n   * Called when clients should update the hotspots list in the UI. This can happen in several situations:\n   * <ul>\n   *   <li>when an analysis ends</li>\n   *   <li>in the middle of an analysis, when hotspots are raised (issue streaming)</li>\n   *   <li>when a hotspot changes on the server, e.g. when it is resolved</li>\n   * </ul>\n   * The parameters contain a Map of Security Hotspots by file URI. This list doesn't include Security Hotspots.\n   * If a file does not have any hotspots, its URI is mapped to an empty list.\n   * Files that were excluded prior to an analysis will not appear in the map.\n   * The Map contains all known Security Hotspots so far. It is up to the client to apply some filtering in the UI if needed, e.g. to display Security Hotspots on open files only.\n   * This method might be called in the context of issue streaming, so it might be called frequently.\n   */\n  @JsonNotification\n  default void raiseHotspots(RaiseHotspotsParams params) {\n  }\n\n  /**\n   * Called at the end of an analysis when a language was supposed to be analyzed but the corresponding plugin could not be loaded.\n   * The skip reason is provided in the parameters.\n   * Clients are expected to display a visual notification to users, ideally suggesting them fixes.\n   * This method is called only once per session per skipped plugin.\n   * Clients can decide to persist that they already notified the user, and can skip showing the notification for next sessions.\n   */\n  @JsonNotification\n  default void didSkipLoadingPlugin(DidSkipLoadingPluginParams params) {\n  }\n\n  /**\n   * Called during an analysis when a secret is detected in user code.\n   * Clients are expected to display a visual notification to users.\n   * This method might be called after each analysis.\n   * Clients can decide to persist that they already notified the user, and can skip showing the notification.\n   */\n  @JsonNotification\n  default void didDetectSecret(DidDetectSecretParams params) {\n  }\n\n  /**\n   * Called after an analysis in standalone mode when languages supported only in connected mode were detected.\n   * Clients are expected to display a visual notification to users.\n   * A promotion might be sent after each analysis, even for languages that were already promoted.\n   * Clients can decide to persist that they already notified the user, and can skip showing the notification.\n   */\n  @JsonNotification\n  default void promoteExtraEnabledLanguagesInConnectedMode(PromoteExtraEnabledLanguagesInConnectedModeParams params) {\n  }\n\n  /**\n   *  Called before every analysis to update the backend with analysis properties inferred by the client.\n   *  Example of such properties: sonar.java.* configurations\n   *  @param params configuration scope ID\n   *  @return inferred analysis properties\n   */\n  @JsonRequest\n  CompletableFuture<GetInferredAnalysisPropertiesResponse> getInferredAnalysisProperties(GetInferredAnalysisPropertiesParams params);\n\n  /**\n   * Returns the file exclusion patterns in glob format.\n   * Can be empty set if there is no exclusion\n   */\n  @JsonRequest\n  CompletableFuture<GetFileExclusionsResponse> getFileExclusions(GetFileExclusionsParams params);\n\n  /**\n   * This notification is called when an invalid token is used for a given connection. It can happen in two cases:\n   * <ul>\n   *   <li>An HTTP request is made and the response status code is 401</li>\n   *   <li>The client does not return valid credentials from getCredentials (e.g. unexpected null fields)</li>\n   * </ul>\n   * In both cases, the notification will be called once. No further HTTP requests attempts will be made for this connection,\n   * until credentials are changed via {@link ConnectionRpcService#didChangeCredentials(DidChangeCredentialsParams)}\n   */\n  @JsonNotification\n  default void invalidToken(InvalidTokenParams params) {\n  }\n\n  @JsonNotification\n  default void embeddedServerStarted(EmbeddedServerStartedParams params) {\n  }\n\n  /**\n   * Called whenever the status of one or more analyzer plugins changes.\n   * This can happen when plugins are loaded, unloaded, synced from a connection, or when a connection is removed.\n   * The parameters contain the full updated list of plugin statuses (one entry per known language).\n   * Clients should use this to refresh the \"Supported Languages\" panel.\n   */\n  @JsonNotification\n  default void didChangePluginStatuses(DidChangePluginStatusesParams params) {\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/SonarLintRpcErrorCode.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol;\n\npublic class SonarLintRpcErrorCode {\n\n  public static final int CONNECTION_NOT_FOUND = -1;\n  public static final int CONFIG_SCOPE_NOT_FOUND = -2;\n  public static final int RULE_NOT_FOUND = -3;\n  public static final int BACKEND_ALREADY_INITIALIZED = -4;\n  public static final int ISSUE_NOT_FOUND = -5;\n  public static final int CONFIG_SCOPE_NOT_BOUND = -6;\n  public static final int HTTP_REQUEST_TIMEOUT = -7;\n  public static final int HTTP_REQUEST_FAILED = -8;\n  public static final int TASK_EXECUTION_TIMEOUT = -9;\n  public static final int PROGRESS_CREATION_FAILED = -10;\n  public static final int CONNECTION_KIND_NOT_SUPPORTED = -11;\n  public static final int FILE_NOT_FOUND = -12;\n  public static final int TOO_MANY_REQUESTS = -13;\n  public static final int UNAUTHORIZED = -14;\n  public static final int INVALID_ARGUMENT = -15;\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/SonarLintRpcServer.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonDelegate;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonRequest;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.AiAgentRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.binding.BindingRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.branch.SonarProjectBranchRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.ConfigurationRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.ConnectionRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.dogfooding.DogfoodingRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.FileRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.HotspotRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.IssueRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.labs.IdeLabsRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.log.LogRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.newcode.NewCodeRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.PluginRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.progress.TaskProgressRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.remediation.aicodefix.AiCodeFixRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RulesRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.DependencyRiskRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.TelemetryRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityTrackingRpcService;\n\npublic interface SonarLintRpcServer {\n\n  /**\n   * Called by client once at startup, in order to initialize the backend\n   */\n  @JsonRequest\n  CompletableFuture<Void> initialize(InitializeParams params);\n\n  @JsonDelegate\n  ConnectionRpcService getConnectionService();\n\n  @JsonDelegate\n  ConfigurationRpcService getConfigurationService();\n\n  @JsonDelegate\n  FileRpcService getFileService();\n\n  @JsonDelegate\n  RulesRpcService getRulesService();\n\n  @JsonDelegate\n  BindingRpcService getBindingService();\n\n  @JsonDelegate\n  HotspotRpcService getHotspotService();\n\n  @JsonDelegate\n  TelemetryRpcService getTelemetryService();\n\n  @JsonDelegate\n  AnalysisRpcService getAnalysisService();\n\n  @JsonDelegate\n  SonarProjectBranchRpcService getSonarProjectBranchService();\n\n  @JsonDelegate\n  IssueRpcService getIssueService();\n\n  @JsonDelegate\n  NewCodeRpcService getNewCodeService();\n\n  @JsonDelegate\n  TaintVulnerabilityTrackingRpcService getTaintVulnerabilityTrackingService();\n\n  @JsonDelegate\n  DogfoodingRpcService getDogfoodingService();\n\n  @JsonDelegate\n  AiCodeFixRpcService getAiCodeFixRpcService();\n\n  @JsonDelegate\n  TaskProgressRpcService getTaskProgressRpcService();\n\n  @JsonDelegate\n  DependencyRiskRpcService getDependencyRiskService();\n\n  @JsonDelegate\n  AiAgentRpcService getAiAgentService();\n\n  @JsonDelegate\n  LogRpcService getLogService();\n\n  @JsonDelegate\n  IdeLabsRpcService getIdeLabsService();\n\n  @JsonDelegate\n  PluginRpcService getPluginService();\n\n  @JsonRequest\n  CompletableFuture<Void> shutdown();\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/adapter/CustomEitherAdapterFactory.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.adapter;\n\nimport com.google.gson.Gson;\nimport com.google.gson.JsonElement;\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.TypeAdapterFactory;\nimport com.google.gson.reflect.TypeToken;\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\nimport java.util.function.Predicate;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\n\nimport static java.util.function.Predicate.not;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.Lsp4jUtils.isEither;\n\npublic abstract class CustomEitherAdapterFactory<L, R> implements TypeAdapterFactory {\n\n  private final TypeToken<Either<L, R>> elementType;\n  private final Class<L> leftClass;\n  private final Class<R> rightClass;\n  private final Predicate<JsonElement> leftChecker;\n\n  protected CustomEitherAdapterFactory(TypeToken<Either<L, R>> elementType, Class<L> leftClass,\n    Class<R> rightClass, Predicate<JsonElement> leftChecker) {\n    this.elementType = elementType;\n    this.leftClass = leftClass;\n    this.rightClass = rightClass;\n    this.leftChecker = leftChecker;\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  public final <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {\n    if (!isEither(type.getType())) {\n      return null;\n    }\n    Type[] typeParameters = ((ParameterizedType) type.getType()).getActualTypeArguments();\n    var leftType = typeParameters[0];\n    var rightType = typeParameters[1];\n    if (!leftClass.isAssignableFrom((Class<?>) leftType) && !rightClass.isAssignableFrom((Class<?>) rightType)) {\n      return null;\n    }\n    return (TypeAdapter<T>) new EitherTypeAdapter<>(gson, elementType, leftChecker, not(leftChecker));\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/adapter/DurationTypeAdapter.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.adapter;\n\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonToken;\nimport com.google.gson.stream.JsonWriter;\nimport java.io.IOException;\nimport java.time.Duration;\nimport javax.annotation.Nullable;\n\npublic class DurationTypeAdapter extends TypeAdapter<Duration> {\n  @Override\n  public void write(JsonWriter out, @Nullable Duration value) throws IOException {\n    if (value == null) {\n      out.nullValue();\n    } else {\n      out.value(value.toString());\n    }\n  }\n\n  @Override\n  public Duration read(JsonReader in) throws IOException {\n    var peek = in.peek();\n    if (peek == JsonToken.NULL) {\n      in.nextNull();\n      return null;\n    }\n    return Duration.parse(in.nextString());\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/adapter/EitherCredentialsAdapterFactory.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.adapter;\n\nimport com.google.gson.reflect.TypeToken;\nimport org.eclipse.lsp4j.jsonrpc.json.adapters.EitherTypeAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\n\npublic class EitherCredentialsAdapterFactory extends CustomEitherAdapterFactory<TokenDto, UsernamePasswordDto> {\n\n  private static final TypeToken<Either<TokenDto, UsernamePasswordDto>> ELEMENT_TYPE = new TypeToken<>() {\n  };\n\n  public EitherCredentialsAdapterFactory() {\n    super(ELEMENT_TYPE, TokenDto.class, UsernamePasswordDto.class, new EitherTypeAdapter.PropertyChecker(\"token\"));\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/adapter/EitherProgressNotificationAdapterFactory.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.adapter;\n\nimport com.google.gson.reflect.TypeToken;\nimport org.eclipse.lsp4j.jsonrpc.json.adapters.EitherTypeAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.progress.ProgressEndNotification;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.progress.ProgressUpdateNotification;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\n\npublic class EitherProgressNotificationAdapterFactory extends CustomEitherAdapterFactory<ProgressUpdateNotification, ProgressEndNotification> {\n\n  private static final TypeToken<Either<ProgressUpdateNotification, ProgressEndNotification>> ELEMENT_TYPE = new TypeToken<>() {\n  };\n\n  public EitherProgressNotificationAdapterFactory() {\n    super(ELEMENT_TYPE, ProgressUpdateNotification.class, ProgressEndNotification.class, new EitherTypeAdapter.PropertyChecker(\"percentage\"));\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/adapter/EitherRuleDescriptionAdapterFactory.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.adapter;\n\nimport com.google.gson.reflect.TypeToken;\nimport org.eclipse.lsp4j.jsonrpc.json.adapters.EitherTypeAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleMonolithicDescriptionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleSplitDescriptionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\n\npublic class EitherRuleDescriptionAdapterFactory extends CustomEitherAdapterFactory<RuleMonolithicDescriptionDto, RuleSplitDescriptionDto> {\n\n  private static final TypeToken<Either<RuleMonolithicDescriptionDto, RuleSplitDescriptionDto>> ELEMENT_TYPE = new TypeToken<>() {\n  };\n\n  public EitherRuleDescriptionAdapterFactory() {\n    super(ELEMENT_TYPE, RuleMonolithicDescriptionDto.class, RuleSplitDescriptionDto.class, new EitherTypeAdapter.PropertyChecker(\"htmlContent\"));\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/adapter/EitherRuleDescriptionTabContentAdapterFactory.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.adapter;\n\nimport com.google.gson.reflect.TypeToken;\nimport org.eclipse.lsp4j.jsonrpc.json.adapters.EitherTypeAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleContextualSectionWithDefaultContextKeyDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleNonContextualSectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\n\npublic class EitherRuleDescriptionTabContentAdapterFactory extends CustomEitherAdapterFactory<RuleNonContextualSectionDto, RuleContextualSectionWithDefaultContextKeyDto> {\n\n  private static final TypeToken<Either<RuleNonContextualSectionDto, RuleContextualSectionWithDefaultContextKeyDto>> ELEMENT_TYPE = new TypeToken<>() {\n  };\n\n  public EitherRuleDescriptionTabContentAdapterFactory() {\n    super(ELEMENT_TYPE, RuleNonContextualSectionDto.class, RuleContextualSectionWithDefaultContextKeyDto.class, new EitherTypeAdapter.PropertyChecker(\"htmlContent\"));\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/adapter/EitherSonarQubeSonarCloudConnectionAdapterFactory.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.adapter;\n\nimport com.google.gson.reflect.TypeToken;\nimport org.eclipse.lsp4j.jsonrpc.json.adapters.EitherTypeAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.SonarCloudConnectionSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.SonarQubeConnectionSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\n\npublic class EitherSonarQubeSonarCloudConnectionAdapterFactory extends CustomEitherAdapterFactory<SonarQubeConnectionSuggestionDto, SonarCloudConnectionSuggestionDto> {\n\n  private static final TypeToken<Either<SonarQubeConnectionSuggestionDto, SonarCloudConnectionSuggestionDto>> ELEMENT_TYPE = new TypeToken<>() {\n  };\n\n  public EitherSonarQubeSonarCloudConnectionAdapterFactory() {\n    super(ELEMENT_TYPE, SonarQubeConnectionSuggestionDto.class, SonarCloudConnectionSuggestionDto.class, new EitherTypeAdapter.PropertyChecker(\"serverUrl\"));\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/adapter/EitherSonarQubeSonarCloudConnectionParamsAdapterFactory.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.adapter;\n\nimport com.google.gson.reflect.TypeToken;\nimport org.eclipse.lsp4j.jsonrpc.json.adapters.EitherTypeAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.SonarCloudConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.SonarQubeConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\n\npublic class EitherSonarQubeSonarCloudConnectionParamsAdapterFactory extends CustomEitherAdapterFactory<SonarQubeConnectionParams, SonarCloudConnectionParams> {\n\n  private static final TypeToken<Either<SonarQubeConnectionParams, SonarCloudConnectionParams>> ELEMENT_TYPE = new TypeToken<>() {\n  };\n\n  public EitherSonarQubeSonarCloudConnectionParamsAdapterFactory() {\n    super(ELEMENT_TYPE, SonarQubeConnectionParams.class, SonarCloudConnectionParams.class, new EitherTypeAdapter.PropertyChecker(\"serverUrl\"));\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/adapter/EitherStandardOrMQRModeAdapterFactory.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.adapter;\n\nimport com.google.gson.reflect.TypeToken;\nimport org.eclipse.lsp4j.jsonrpc.json.adapters.EitherTypeAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.MQRModeDetails;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.StandardModeDetails;\n\npublic class EitherStandardOrMQRModeAdapterFactory extends CustomEitherAdapterFactory<StandardModeDetails, MQRModeDetails> {\n\n  private static final TypeToken<Either<StandardModeDetails, MQRModeDetails>> ELEMENT_TYPE = new TypeToken<>() {\n  };\n\n  public EitherStandardOrMQRModeAdapterFactory() {\n    super(ELEMENT_TYPE, StandardModeDetails.class, MQRModeDetails.class, new EitherTypeAdapter.PropertyChecker(\"severity\"));\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/adapter/EitherTransientConnectionAdapterFactory.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.adapter;\n\nimport com.google.gson.reflect.TypeToken;\nimport org.eclipse.lsp4j.jsonrpc.json.adapters.EitherTypeAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarCloudConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarQubeConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\n\npublic class EitherTransientConnectionAdapterFactory extends CustomEitherAdapterFactory<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> {\n\n  private static final TypeToken<Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto>> ELEMENT_TYPE = new TypeToken<>() {\n  };\n\n  public EitherTransientConnectionAdapterFactory() {\n    super(ELEMENT_TYPE, TransientSonarQubeConnectionDto.class, TransientSonarCloudConnectionDto.class, new EitherTypeAdapter.PropertyChecker(\"serverUrl\"));\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/adapter/EitherTypeAdapter.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.adapter;\n\nimport com.google.gson.Gson;\nimport com.google.gson.JsonElement;\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.TypeAdapterFactory;\nimport com.google.gson.reflect.TypeToken;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonWriter;\nimport java.io.IOException;\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\nimport java.util.function.Predicate;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\n\nimport static org.sonarsource.sonarlint.core.rpc.protocol.Lsp4jUtils.isEither;\n\npublic class EitherTypeAdapter<L, R> extends TypeAdapter<Either<L, R>> {\n\n  private final org.eclipse.lsp4j.jsonrpc.json.adapters.EitherTypeAdapter<L, R> lsp4jEitherAdapter;\n\n  public EitherTypeAdapter(Gson gson, TypeToken<? extends Either<L, R>> typeToken, @Nullable Predicate<JsonElement> leftChecker,\n    @Nullable Predicate<JsonElement> rightChecker) {\n    var eitherType = (ParameterizedType) typeToken.getType();\n    var lsp4jEitherType = new ParameterizedTypeImpl(org.eclipse.lsp4j.jsonrpc.messages.Either.class, eitherType.getActualTypeArguments());\n    var lsp4jTypeToken = (TypeToken<? extends org.eclipse.lsp4j.jsonrpc.messages.Either<L, R>>) TypeToken.get(lsp4jEitherType);\n    this.lsp4jEitherAdapter = new org.eclipse.lsp4j.jsonrpc.json.adapters.EitherTypeAdapter<>(gson, lsp4jTypeToken, leftChecker,\n      rightChecker, null, null);\n  }\n\n  public EitherTypeAdapter(Gson gson, TypeToken<? extends Either<L, R>> typeToken) {\n    this(gson, typeToken, null, null);\n  }\n\n  public static class Factory implements TypeAdapterFactory {\n\n    @SuppressWarnings({\"rawtypes\", \"unchecked\"})\n    @Override\n    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {\n      if (!isEither(typeToken.getType())) {\n        return null;\n      }\n      return new EitherTypeAdapter(gson, typeToken);\n    }\n  }\n\n  @Override\n  public void write(JsonWriter out, Either<L, R> value) throws IOException {\n    this.lsp4jEitherAdapter.write(out, value.getLsp4jEither());\n  }\n\n  @Override\n  public Either<L, R> read(JsonReader in) throws IOException {\n    return new Either<>(this.lsp4jEitherAdapter.read(in));\n  }\n\n  private static class ParameterizedTypeImpl implements ParameterizedType {\n\n    private final Type rawType;\n    private final Type[] actualTypeArguments;\n\n    ParameterizedTypeImpl(Type rawType, Type[] typeArguments) {\n      this.rawType = rawType;\n      this.actualTypeArguments = typeArguments;\n    }\n\n    @Override\n    public Type getOwnerType() {\n      return null;\n    }\n\n    @Override\n    public Type getRawType() {\n      return rawType;\n    }\n\n    @Override\n    public Type[] getActualTypeArguments() {\n      return actualTypeArguments;\n    }\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/adapter/InstantTypeAdapter.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.adapter;\n\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonToken;\nimport com.google.gson.stream.JsonWriter;\nimport java.io.IOException;\nimport java.time.Instant;\nimport javax.annotation.Nonnull;\nimport javax.annotation.Nullable;\n\npublic class InstantTypeAdapter extends TypeAdapter<Instant> {\n\n  @Override\n  public void write(@Nonnull JsonWriter out, @Nullable Instant value) throws IOException {\n    if (value == null) {\n      out.nullValue();\n    } else {\n      out.value(value.toEpochMilli());\n    }\n  }\n\n  @Override\n  public Instant read(@Nonnull JsonReader in) throws IOException {\n    var peek = in.peek();\n    if (peek == JsonToken.NULL) {\n      in.nextNull();\n      return null;\n    }\n    return Instant.ofEpochMilli(in.nextLong());\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/adapter/OffsetDateTimeAdapter.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.adapter;\n\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonToken;\nimport com.google.gson.stream.JsonWriter;\nimport java.io.IOException;\nimport java.time.OffsetDateTime;\nimport javax.annotation.Nullable;\n\npublic class OffsetDateTimeAdapter extends TypeAdapter<OffsetDateTime> {\n\n  @Override\n  public void write(JsonWriter out, @Nullable OffsetDateTime value) throws IOException {\n    if (value == null) {\n      out.nullValue();\n    } else {\n      out.value(value.toString());\n    }\n  }\n\n  @Override\n  public OffsetDateTime read(JsonReader in) throws IOException {\n    var peek = in.peek();\n    if (peek == JsonToken.NULL) {\n      in.nextNull();\n      return null;\n    }\n    return OffsetDateTime.parse(in.nextString());\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/adapter/PathTypeAdapter.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.adapter;\n\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonToken;\nimport com.google.gson.stream.JsonWriter;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport javax.annotation.Nullable;\n\npublic class PathTypeAdapter extends TypeAdapter<Path> {\n\n  @Override\n  public void write(JsonWriter out, @Nullable Path value) throws IOException {\n    if (value == null) {\n      out.nullValue();\n    } else {\n      out.value(value.toString());\n    }\n  }\n\n  @Override\n  public Path read(JsonReader in) throws IOException {\n    var peek = in.peek();\n    if (peek == JsonToken.NULL) {\n      in.nextNull();\n      return null;\n    }\n    return Path.of(in.nextString());\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/adapter/UriTypeAdapter.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.adapter;\n\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonToken;\nimport com.google.gson.stream.JsonWriter;\nimport java.io.IOException;\nimport java.net.URI;\nimport javax.annotation.Nullable;\n\npublic class UriTypeAdapter extends TypeAdapter<URI> {\n  @Override\n  public void write(JsonWriter out, @Nullable URI value) throws IOException {\n    if (value == null) {\n      out.nullValue();\n    } else {\n      out.value(value.toString());\n    }\n  }\n\n  @Override\n  public URI read(JsonReader in) throws IOException {\n    var peek = in.peek();\n    if (peek == JsonToken.NULL) {\n      in.nextNull();\n      return null;\n    }\n    return URI.create(in.nextString());\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/adapter/UuidTypeAdapter.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.adapter;\n\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonToken;\nimport com.google.gson.stream.JsonWriter;\nimport java.io.IOException;\nimport java.util.UUID;\nimport javax.annotation.Nonnull;\nimport javax.annotation.Nullable;\n\npublic class UuidTypeAdapter extends TypeAdapter<UUID> {\n\n  @Override\n  public void write(@Nonnull JsonWriter out, @Nullable UUID value) throws IOException {\n    if (value == null) {\n      out.nullValue();\n    } else {\n      out.value(value.toString());\n    }\n  }\n\n  @Override\n  public UUID read(@Nonnull JsonReader in) throws IOException {\n    var peek = in.peek();\n    if (peek == JsonToken.NULL) {\n      in.nextNull();\n      return null;\n    }\n    return UUID.fromString(in.nextString());\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/adapter/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.adapter;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/ai/AiAgent.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.ai;\n\npublic enum AiAgent {\n  CURSOR, GITHUB_COPILOT, KIRO, WINDSURF\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/ai/AiAgentRpcService.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.ai;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonRequest;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonSegment;\n\n@JsonSegment(\"ai\")\npublic interface AiAgentRpcService {\n\n  /**\n   * Returns the content of rule file to be written to each IDE's rule folder, based on the agent.\n   * The rule file provides good practices to the agent.\n   */\n  @JsonRequest\n  CompletableFuture<GetRuleFileContentResponse> getRuleFileContent(GetRuleFileContentParams params);\n\n  /**\n   * Returns hook script content with auto-detected executable type.\n   * The hook script will analyze code after write events using the sonarqube_analysis_hook hook.\n   */\n  @JsonRequest\n  CompletableFuture<GetHookScriptContentResponse> getHookScriptContent(GetHookScriptContentParams params);\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/ai/GetHookScriptContentParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.ai;\n\npublic class GetHookScriptContentParams {\n  private final AiAgent agent;\n\n  public GetHookScriptContentParams(AiAgent agent) {\n    this.agent = agent;\n  }\n\n  public AiAgent getAiAgent() {\n    return agent;\n  }\n}\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/ai/GetHookScriptContentResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.ai;\n\npublic class GetHookScriptContentResponse {\n  private final String scriptContent;\n  private final String scriptFileName;\n  private final String configContent;\n  private final String configFileName;\n\n  public GetHookScriptContentResponse(String scriptContent, String scriptFileName, String configContent, String configFileName) {\n    this.scriptContent = scriptContent;\n    this.scriptFileName = scriptFileName;\n    this.configContent = configContent;\n    this.configFileName = configFileName;\n  }\n\n  public String getScriptContent() {\n    return scriptContent;\n  }\n\n  public String getScriptFileName() {\n    return scriptFileName;\n  }\n\n  public String getConfigContent() {\n    return configContent;\n  }\n\n  public String getConfigFileName() {\n    return configFileName;\n  }\n}\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/ai/GetRuleFileContentParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.ai;\n\npublic class GetRuleFileContentParams {\n  private final AiAgent agent;\n\n  public GetRuleFileContentParams(AiAgent agent) {\n    this.agent = agent;\n  }\n\n  public AiAgent getAiAgent() {\n    return agent;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/ai/GetRuleFileContentResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.ai;\n\npublic class GetRuleFileContentResponse {\n  private final String content;\n\n  public GetRuleFileContentResponse(String content) {\n    this.content = content;\n  }\n\n  public String getContent() {\n    return content;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/ai/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.ai;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/analysis/AnalysisRpcService.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonNotification;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonRequest;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonSegment;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.RaiseHotspotsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaiseIssuesParams;\n\n@JsonSegment(\"analysis\")\npublic interface AnalysisRpcService {\n  /**\n   * This is the list of file patterns declared as part of a language by one of the enabled analyzer.\n   * Beware that some analyzers may analyze more files that the one matching one of those patterns.\n   * @param params contains configuration scope id as string\n   * @return list of the glob patterns\n   */\n  @JsonRequest\n  CompletableFuture<GetSupportedFilePatternsResponse> getSupportedFilePatterns(GetSupportedFilePatternsParams params);\n\n  /**\n   * Inform the backend that the client has changed the location of the nodejs executable to be used by analyzer\n   * @return The Node.js path and version that were forced on the user's machine.\n   */\n  @JsonRequest\n  CompletableFuture<GetForcedNodeJsResponse> didChangeClientNodeJsPath(DidChangeClientNodeJsPathParams params);\n\n  /**\n   * @return The Node.js path and version that were automatically detected on the user's machine.\n   */\n  @JsonRequest\n  CompletableFuture<GetAutoDetectedNodeJsResponse> getAutoDetectedNodeJs();\n\n  /**\n   * Analyze and track issues in the provided files.\n   * Issues will be reported to the client via\n   * {@link SonarLintRpcClient#raiseIssues(RaiseIssuesParams)} and {@link SonarLintRpcClient#raiseHotspots(RaiseHotspotsParams)}\n   */\n  @JsonRequest\n  CompletableFuture<AnalyzeFilesResponse> analyzeFilesAndTrack(AnalyzeFilesAndTrackParams params);\n\n  /**\n   *  Inform the backend that user settings analysis properties has changed.\n   *  The backend will take the provided set of properties as new user configuration, and previous user values will be cleared.\n   * @param params configuration scope ID, new properties for this scope\n   */\n  @JsonNotification\n  void didSetUserAnalysisProperties(DidChangeAnalysisPropertiesParams params);\n\n  /**\n   * Inform the backend that path to compile commands has changed.\n   * The backend will trigger the analysis for all open files after updating the setting value.\n   * @param params configuration scope ID, path to compile commands\n   */\n  @JsonNotification\n  void didChangePathToCompileCommands(DidChangePathToCompileCommandsParams params);\n\n  /**\n   * Allows to enable or disable automatic analysis.\n   * Automatic analysis happens on the following triggers:\n   * <ul>\n   *   <li>on file open</li>\n   *   <li>on open file content change</li>\n   *   <li>on some server events, e.g. when some rules were enabled</li>\n   * </ul>\n   * When this setting becomes enabled, an automatic analysis of open files will be triggered.\n   */\n  @JsonNotification\n  void didChangeAutomaticAnalysisSetting(DidChangeAutomaticAnalysisSettingParams params);\n\n  /**\n   * Analyze all files in the project. User file exclusions and .gitignore will be respected.\n   * @param params configuration scope ID, flag to report only hotspots\n   * @return analysis ID or null if not ready for analysis\n   * Issues will be reported to the client via\n   * {@link SonarLintRpcClient#raiseIssues(RaiseIssuesParams)} and {@link SonarLintRpcClient#raiseHotspots(RaiseHotspotsParams)}\n   */\n  @JsonRequest\n  CompletableFuture<ForceAnalyzeResponse> analyzeFullProject(AnalyzeFullProjectParams params);\n\n  /**\n   * Analyze all files in the provided list. User file exclusions and .gitignore will be respected.\n   * @param params configuration scope ID, list of files to analyse\n   * @return analysis ID or null if not ready for analysis\n   * Issues will be reported to the client via\n   * {@link SonarLintRpcClient#raiseIssues(RaiseIssuesParams)} and {@link SonarLintRpcClient#raiseHotspots(RaiseHotspotsParams)}\n   */\n  @JsonRequest\n  CompletableFuture<ForceAnalyzeResponse> analyzeFileList(AnalyzeFileListParams params);\n\n  /**\n   * Analyze all files that were reported by the client as opened. User file exclusions and .gitignore will be respected.\n   * @param params configuration scope ID\n   * @return analysis ID or null if not ready for analysis\n   * Issues will be reported to the client via\n   * {@link SonarLintRpcClient#raiseIssues(RaiseIssuesParams)} and {@link SonarLintRpcClient#raiseHotspots(RaiseHotspotsParams)}\n   */\n  @JsonRequest\n  CompletableFuture<ForceAnalyzeResponse> analyzeOpenFiles(AnalyzeOpenFilesParams params);\n\n  /**\n   * Analyze all files that were created/modified and tracked by git since the last commit. User file exclusions and .gitignore will be respected.\n   * @param params configuration scope ID, list of files to analyse\n   * @return analysis ID or null if not ready for analysis\n   * Issues will be reported to the client via\n   * {@link SonarLintRpcClient#raiseIssues(RaiseIssuesParams)} and {@link SonarLintRpcClient#raiseHotspots(RaiseHotspotsParams)}\n   */\n  @JsonRequest\n  CompletableFuture<ForceAnalyzeResponse> analyzeVCSChangedFiles(AnalyzeVCSChangedFilesParams params);\n\n\n  /**\n   * For a given configuration scope, returns whether an enterprise csharp analyzer should be used.\n   * 1. configScope is not bound to anything -> false\n   * 2. configScope is bound to a SonarQube server that does not have enterprise CSharp analyzer -> false\n   * 3. configScope is bound to a SonarQube server that has enterprise CSharp analyzer -> true\n   * 4. configScope is bound to a SonarQube server that does not have repackaged analyzer (<10.8) -> true\n   * 5. configScope is bound to SonarCloud -> true\n   */\n  @JsonRequest\n  CompletableFuture<ShouldUseEnterpriseCSharpAnalyzerResponse> shouldUseEnterpriseCSharpAnalyzer(ShouldUseEnterpriseCSharpAnalyzerParams params);\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/analysis/AnalyzeFileListParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis;\n\nimport java.net.URI;\nimport java.util.List;\n\npublic class AnalyzeFileListParams {\n  private final String configScopeId;\n  private final List<URI> filesToAnalyze;\n\n  public AnalyzeFileListParams(String configScopeId, List<URI> filesToAnalyze) {\n    this.configScopeId = configScopeId;\n    this.filesToAnalyze = filesToAnalyze;\n  }\n\n  public String getConfigScopeId() {\n    return configScopeId;\n  }\n\n  public List<URI> getFilesToAnalyze() {\n    return filesToAnalyze;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/analysis/AnalyzeFilesAndTrackParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis;\n\nimport java.net.URI;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\n\npublic class AnalyzeFilesAndTrackParams {\n  private final String configurationScopeId;\n  // this is a random id provided by the client that will be used to correlate the raw issues notified back to the client\n  private final UUID analysisId;\n  private final List<URI> filesToAnalyze;\n  private final Map<String, String> extraProperties;\n  private final boolean shouldFetchServerIssues;\n\n  /**\n   * @deprecated As analysis might not execute straight away, the startTime parameter is not relevant, it doesn't help measure the duration of the actual analysis.\n   * Use the other constructor instead.\n   */\n  @Deprecated(since = \"10.18\")\n  public AnalyzeFilesAndTrackParams(String configurationScopeId, UUID analysisId, List<URI> filesToAnalyze, Map<String, String> extraProperties, boolean shouldFetchServerIssues,\n    long startTime) {\n    this(configurationScopeId, analysisId, filesToAnalyze, extraProperties, shouldFetchServerIssues);\n  }\n\n  public AnalyzeFilesAndTrackParams(String configurationScopeId, UUID analysisId, List<URI> filesToAnalyze, Map<String, String> extraProperties,\n    boolean shouldFetchServerIssues) {\n    this.configurationScopeId = configurationScopeId;\n    this.analysisId = analysisId;\n    this.filesToAnalyze = filesToAnalyze;\n    this.extraProperties = extraProperties;\n    this.shouldFetchServerIssues = shouldFetchServerIssues;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public UUID getAnalysisId() {\n    return analysisId;\n  }\n\n  public List<URI> getFilesToAnalyze() {\n    return filesToAnalyze;\n  }\n\n  public Map<String, String> getExtraProperties() {\n    return extraProperties;\n  }\n\n  public boolean isShouldFetchServerIssues() {\n    return shouldFetchServerIssues;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/analysis/AnalyzeFilesResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis;\n\nimport java.net.URI;\nimport java.util.List;\nimport java.util.Set;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.analysis.RawIssueDto;\n\npublic class AnalyzeFilesResponse {\n  private final Set<URI> failedAnalysisFiles;\n  private final List<RawIssueDto> rawIssues;\n\n  public AnalyzeFilesResponse(Set<URI> failedAnalysisFiles, List<RawIssueDto> rawIssues) {\n    this.failedAnalysisFiles = failedAnalysisFiles;\n    this.rawIssues = rawIssues;\n  }\n\n  public Set<URI> getFailedAnalysisFiles() {\n    return failedAnalysisFiles;\n  }\n\n  public List<RawIssueDto> getRawIssues() {\n    return rawIssues;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/analysis/AnalyzeFullProjectParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis;\n\npublic class AnalyzeFullProjectParams {\n\n  private final String configScopeId;\n  private final boolean hotspotsOnly;\n\n  public AnalyzeFullProjectParams(String configScopeId, boolean hotspotsOnly) {\n    this.configScopeId = configScopeId;\n    this.hotspotsOnly = hotspotsOnly;\n  }\n\n  public String getConfigScopeId() {\n    return configScopeId;\n  }\n\n  public boolean isHotspotsOnly() {\n    return hotspotsOnly;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/analysis/AnalyzeOpenFilesParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis;\n\npublic class AnalyzeOpenFilesParams {\n\n  private final String configScopeId;\n\n  public AnalyzeOpenFilesParams(String configScopeId) {\n    this.configScopeId = configScopeId;\n  }\n\n  public String getConfigScopeId() {\n    return configScopeId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/analysis/AnalyzeVCSChangedFilesParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis;\n\npublic class AnalyzeVCSChangedFilesParams {\n  private final String configScopeId;\n\n  public AnalyzeVCSChangedFilesParams(String configScopeId) {\n    this.configScopeId = configScopeId;\n  }\n\n  public String getConfigScopeId() {\n    return configScopeId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/analysis/DidChangeAnalysisPropertiesParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis;\n\nimport java.util.Map;\n\npublic class DidChangeAnalysisPropertiesParams {\n  private final String configurationScopeId;\n  private final Map<String, String> properties;\n\n  public DidChangeAnalysisPropertiesParams(String configurationScopeId, Map<String, String> extraProperties) {\n    this.configurationScopeId = configurationScopeId;\n    this.properties = extraProperties;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public Map<String, String> getProperties() {\n    return properties;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/analysis/DidChangeAutomaticAnalysisSettingParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis;\n\npublic class DidChangeAutomaticAnalysisSettingParams {\n  private final boolean enabled;\n\n  public DidChangeAutomaticAnalysisSettingParams(boolean enabled) {\n    this.enabled = enabled;\n  }\n\n  public boolean isEnabled() {\n    return enabled;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/analysis/DidChangeClientNodeJsPathParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis;\n\nimport java.nio.file.Path;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class DidChangeClientNodeJsPathParams {\n\n  private final Path clientNodeJsPath;\n\n  public DidChangeClientNodeJsPathParams(@Nullable Path clientNodeJsPath) {\n    this.clientNodeJsPath = clientNodeJsPath;\n  }\n\n  @CheckForNull\n  public Path getClientNodeJsPath() {\n    return clientNodeJsPath;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/analysis/DidChangePathToCompileCommandsParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class DidChangePathToCompileCommandsParams {\n\n  private final String configurationScopeId;\n  private final String pathToCompileCommands;\n\n  public DidChangePathToCompileCommandsParams(String configScopeId, @Nullable String pathToCompileCommands) {\n    this.configurationScopeId = configScopeId;\n    this.pathToCompileCommands = pathToCompileCommands;\n  }\n\n  @CheckForNull\n  public String getPathToCompileCommands() {\n    return pathToCompileCommands;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/analysis/ForceAnalyzeResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis;\n\nimport java.util.UUID;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class ForceAnalyzeResponse {\n  UUID analysisId;\n\n  public ForceAnalyzeResponse(@Nullable UUID analysisId) {\n    this.analysisId = analysisId;\n  }\n\n  @CheckForNull\n  public UUID getAnalysisId() {\n    return analysisId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/analysis/GetAutoDetectedNodeJsResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class GetAutoDetectedNodeJsResponse {\n  private final NodeJsDetailsDto details;\n\n  public GetAutoDetectedNodeJsResponse(@Nullable NodeJsDetailsDto details) {\n    this.details = details;\n  }\n\n  @CheckForNull\n  public NodeJsDetailsDto getDetails() {\n    return details;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/analysis/GetForcedNodeJsResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class GetForcedNodeJsResponse {\n  private final NodeJsDetailsDto details;\n\n  public GetForcedNodeJsResponse(@Nullable NodeJsDetailsDto details) {\n    this.details = details;\n  }\n\n  @CheckForNull\n  public NodeJsDetailsDto getDetails() {\n    return details;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/analysis/GetSupportedFilePatternsParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis;\n\npublic class GetSupportedFilePatternsParams {\n\n  private final String configScopeId;\n\n  public GetSupportedFilePatternsParams(String configScopeId) {\n    this.configScopeId = configScopeId;\n  }\n\n  public String getConfigScopeId() {\n    return configScopeId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/analysis/GetSupportedFilePatternsResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis;\n\nimport java.util.List;\n\npublic class GetSupportedFilePatternsResponse {\n  private final List<String> patterns;\n\n  public GetSupportedFilePatternsResponse(List<String> patterns) {\n    this.patterns = patterns;\n  }\n\n  public List<String> getPatterns() {\n    return patterns;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/analysis/NodeJsDetailsDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis;\n\nimport java.nio.file.Path;\n\npublic class NodeJsDetailsDto {\n  private final Path path;\n  private final String version;\n\n  public NodeJsDetailsDto(Path path, String version) {\n    this.path = path;\n    this.version = version;\n  }\n\n  public Path getPath() {\n    return path;\n  }\n\n  public String getVersion() {\n    return version;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/analysis/ShouldUseEnterpriseCSharpAnalyzerParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis;\n\npublic class ShouldUseEnterpriseCSharpAnalyzerParams {\n  private final String configurationScopeId;\n\n  public ShouldUseEnterpriseCSharpAnalyzerParams(String configurationScopeId) {\n    this.configurationScopeId = configurationScopeId;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/analysis/ShouldUseEnterpriseCSharpAnalyzerResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis;\n\npublic class ShouldUseEnterpriseCSharpAnalyzerResponse {\n  private final boolean shouldUseEnterpriseAnalyzer;\n\n  public ShouldUseEnterpriseCSharpAnalyzerResponse(boolean shouldUseEnterpriseAnalyzer) {\n    this.shouldUseEnterpriseAnalyzer = shouldUseEnterpriseAnalyzer;\n  }\n\n  public boolean shouldUseEnterpriseAnalyzer() {\n    return shouldUseEnterpriseAnalyzer;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/analysis/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/binding/BindingRpcService.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.binding;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonRequest;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonSegment;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.GetBindingSuggestionsResponse;\n\n@JsonSegment(\"binding\")\npublic interface BindingRpcService {\n\n  /**\n   * Calculates a suggested binding for a 'configScopeId' and 'connectionId' specified in the {@link GetBindingSuggestionParams}\n   * @return {@link GetBindingSuggestionsResponse} containing binding suggestions\n   */\n  @JsonRequest\n  CompletableFuture<GetBindingSuggestionsResponse> getBindingSuggestions(GetBindingSuggestionParams params);\n\n  /**\n   * <p> It gets file contents for a shared Connected Mode configuration file.\n   * It returns file contents that look like either\n   <li>{\n   \"sonarCloudOrganization\": \"$organization\",\n   \"projectKey\": \"$projectKey\"\n   }</li>\n   OR\n   <li>   {\n   \"sonarQubeUri\": \"$serverUrl\",\n   \"projectKey\": \"$projectKey\"\n   }</li>\n\n   <p>Fails if there is no binding found for the configScopeId</p>\n   */\n  @JsonRequest\n  CompletableFuture<GetSharedConnectedModeConfigFileResponse> getSharedConnectedModeConfigFileContents(GetSharedConnectedModeConfigFileParams params);\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/binding/GetBindingSuggestionParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.binding;\n\npublic class GetBindingSuggestionParams {\n  private final String configScopeId;\n  private final String connectionId;\n\n  public GetBindingSuggestionParams(String configScopeId, String connectionId) {\n    this.configScopeId = configScopeId;\n    this.connectionId = connectionId;\n  }\n\n  public String getConfigScopeId() {\n    return configScopeId;\n  }\n\n  public String getConnectionId() {\n    return connectionId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/binding/GetSharedConnectedModeConfigFileParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.binding;\n\npublic class GetSharedConnectedModeConfigFileParams {\n  private String configScopeId;\n\n  public GetSharedConnectedModeConfigFileParams(String configScopeId) {\n    this.configScopeId = configScopeId;\n  }\n\n  public String getConfigScopeId() {\n    return configScopeId;\n  }\n\n  public void setConfigScopeId(String configScopeId) {\n    this.configScopeId = configScopeId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/binding/GetSharedConnectedModeConfigFileResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.binding;\n\npublic class GetSharedConnectedModeConfigFileResponse {\n  private final String jsonFileContent;\n\n  public GetSharedConnectedModeConfigFileResponse(String jsonFileContent) {\n    this.jsonFileContent = jsonFileContent;\n  }\n\n  public String getJsonFileContent() {\n    return jsonFileContent;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/binding/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.binding;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/branch/DidVcsRepositoryChangeParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.branch;\n\npublic class DidVcsRepositoryChangeParams {\n  private final String configurationScopeId;\n\n  public DidVcsRepositoryChangeParams(String configurationScopeId) {\n    this.configurationScopeId = configurationScopeId;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/branch/GetMatchedSonarProjectBranchParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.branch;\n\npublic class GetMatchedSonarProjectBranchParams {\n  private final String configurationScopeId;\n\n  public GetMatchedSonarProjectBranchParams(String configurationScopeId) {\n    this.configurationScopeId = configurationScopeId;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/branch/GetMatchedSonarProjectBranchResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.branch;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class GetMatchedSonarProjectBranchResponse {\n  private final String matchedSonarProjectBranch;\n\n  public GetMatchedSonarProjectBranchResponse(@Nullable String matchedSonarProjectBranch) {\n    this.matchedSonarProjectBranch = matchedSonarProjectBranch;\n  }\n\n  @CheckForNull\n  public String getMatchedSonarProjectBranch() {\n    return matchedSonarProjectBranch;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/branch/SonarProjectBranchRpcService.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.branch;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonNotification;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonRequest;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonSegment;\n\n@JsonSegment(\"branch\")\npublic interface SonarProjectBranchRpcService {\n\n  /**\n   * Must be called when any change on the VCS might lead to a different sonar project branch being resolved (could be a different HEAD, a branch checkout).\n   */\n  @JsonNotification\n  void didVcsRepositoryChange(DidVcsRepositoryChangeParams params);\n\n  /**\n   * Returns the currently matched Sonar Project branch. Might return a null value in {@link GetMatchedSonarProjectBranchResponse} if no matching happened.\n   */\n  @JsonRequest\n  CompletableFuture<GetMatchedSonarProjectBranchResponse> getMatchedSonarProjectBranch(GetMatchedSonarProjectBranchParams params);\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/branch/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.branch;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/config/ConfigurationRpcService.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.config;\n\nimport org.eclipse.lsp4j.jsonrpc.services.JsonNotification;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonSegment;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidRemoveConfigurationScopeParams;\n\n/**\n * The client is the source of truth for the configuration, but the backend needs to be kept in sync.\n */\n@JsonSegment(\"configuration\")\npublic interface ConfigurationRpcService {\n\n  /**\n   * Called by the client when configuration scopes have been added.\n   */\n  @JsonNotification\n  void didAddConfigurationScopes(DidAddConfigurationScopesParams params);\n\n  /**\n   * Called by the client when a configuration scope has been removed.\n   */\n  @JsonNotification\n  void didRemoveConfigurationScope(DidRemoveConfigurationScopeParams params);\n\n\n  /**\n   * Called by the client when the binding configuration has been updated on an existing configuration scope.\n   */\n  @JsonNotification\n  void didUpdateBinding(DidUpdateBindingParams params);\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/config/binding/BindingConfigurationDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class BindingConfigurationDto {\n\n  private final String connectionId;\n  private final String sonarProjectKey;\n  private final boolean bindingSuggestionDisabled;\n\n  public BindingConfigurationDto(@Nullable String connectionId, @Nullable String sonarProjectKey, boolean bindingSuggestionDisabled) {\n    this.connectionId = connectionId;\n    this.sonarProjectKey = sonarProjectKey;\n    this.bindingSuggestionDisabled = bindingSuggestionDisabled;\n  }\n\n  @CheckForNull\n  public String getConnectionId() {\n    return connectionId;\n  }\n\n  @CheckForNull\n  public String getSonarProjectKey() {\n    return sonarProjectKey;\n  }\n\n  public boolean isBindingSuggestionDisabled() {\n    return bindingSuggestionDisabled;\n  }\n\n  @Override\n  public String toString() {\n    return \"BindingConfigurationDto{\" +\n      \"connectionId='\" + connectionId + '\\'' +\n      \", sonarProjectKey='\" + sonarProjectKey + '\\'' +\n      \", bindingSuggestionDisabled=\" + bindingSuggestionDisabled +\n      '}';\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/config/binding/BindingMode.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding;\n\npublic enum BindingMode {\n  MANUAL,\n  FROM_SUGGESTION\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/config/binding/BindingSuggestionDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding;\n\n\npublic class BindingSuggestionDto {\n  private final String connectionId;\n  private final String sonarProjectKey;\n  private final String sonarProjectName;\n  @Deprecated(forRemoval = true)\n  private final boolean isFromSharedConfiguration;\n  private final BindingSuggestionOrigin origin;\n\n  public BindingSuggestionDto(String connectionId, String sonarProjectKey, String sonarProjectName, BindingSuggestionOrigin origin) {\n    this.connectionId = connectionId;\n    this.sonarProjectKey = sonarProjectKey;\n    this.sonarProjectName = sonarProjectName;\n    this.isFromSharedConfiguration = origin == BindingSuggestionOrigin.SHARED_CONFIGURATION;\n    this.origin = origin;\n  }\n\n\n  public String getConnectionId() {\n    return connectionId;\n  }\n\n  public String getSonarProjectKey() {\n    return sonarProjectKey;\n  }\n\n  public String getSonarProjectName() {\n    return sonarProjectName;\n  }\n\n  /**\n   * @deprecated avoid calling this method if possible, since it will be removed once all the clients are migrated.\n   * Rely on {@link #getOrigin()}  instead.\n   */\n  @Deprecated(forRemoval = true)\n  public boolean isFromSharedConfiguration() {\n    return isFromSharedConfiguration;\n  }\n\n  public BindingSuggestionOrigin getOrigin() {\n    return origin;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/config/binding/BindingSuggestionOrigin.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding;\n\npublic enum BindingSuggestionOrigin {\n  REMOTE_URL,\n  SHARED_CONFIGURATION,\n  PROPERTIES_FILE,\n  PROJECT_NAME\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/config/binding/DidUpdateBindingParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding;\n\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AcceptedBindingSuggestionParams;\n\npublic class DidUpdateBindingParams {\n\n  private final String configScopeId;\n  private final BindingConfigurationDto updatedBinding;\n  /**\n   * @deprecated As it's hard to obtain for some IDEs on this event.\n   */\n  @Deprecated(since = \"10.37\", forRemoval = true)\n  @Nullable\n  private final BindingMode bindingMode;\n  /**\n   * @deprecated As it's hard to obtain for some IDEs on this event.\n   */\n  @Deprecated(since = \"10.37\", forRemoval = true)\n  @Nullable\n  private final BindingSuggestionOrigin origin;\n\n\n  /**\n   * @deprecated avoid calling this constructor if possible, since it will be removed once all the clients are migrated.\n   * Rely on the constructor without origin and bindingMode params instead as not IDEs can get this info at this point.\n   * For manual addition use {@link org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.TelemetryRpcService#addedManualBindings()}\n   * For accepted suggestion use {@link org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.TelemetryRpcService#acceptedBindingSuggestion(AcceptedBindingSuggestionParams)}\n   */\n  @Deprecated(since = \"10.37\", forRemoval = true)\n  public DidUpdateBindingParams(String configScopeId, BindingConfigurationDto updatedBinding, BindingMode bindingMode, @Nullable BindingSuggestionOrigin origin) {\n    this.configScopeId = configScopeId;\n    this.updatedBinding = updatedBinding;\n    this.bindingMode = bindingMode;\n    this.origin = origin;\n  }\n\n  public DidUpdateBindingParams(String configScopeId, BindingConfigurationDto updatedBinding) {\n    this.configScopeId = configScopeId;\n    this.updatedBinding = updatedBinding;\n    this.bindingMode = null;\n    this.origin = null;\n  }\n\n\n  public BindingMode getBindingMode() {\n    return bindingMode;\n  }\n\n  public BindingSuggestionOrigin getOrigin() {\n    return origin;\n  }\n\n  public String getConfigScopeId() {\n    return configScopeId;\n  }\n\n  public BindingConfigurationDto getUpdatedBinding() {\n    return updatedBinding;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/config/binding/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/config/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.config;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/config/scope/ConfigurationScopeDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\n\npublic class ConfigurationScopeDto {\n\n  private final String id;\n  private final String parentId;\n  private final boolean bindable;\n  /**\n   * The name of this configuration scope. Used for auto-binding.\n   */\n  private final String name;\n  private final BindingConfigurationDto binding;\n\n  public ConfigurationScopeDto(String id, @Nullable String parentId, boolean bindable, String name, @Nullable BindingConfigurationDto binding) {\n    this.id = id;\n    this.parentId = parentId;\n    this.bindable = bindable;\n    this.name = name;\n    this.binding = binding;\n  }\n\n  public String getId() {\n    return id;\n  }\n\n  @CheckForNull\n  public String getParentId() {\n    return parentId;\n  }\n\n  public boolean isBindable() {\n    return bindable;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  @CheckForNull\n  public BindingConfigurationDto getBinding() {\n    return binding;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/config/scope/DidAddConfigurationScopesParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope;\n\nimport java.util.List;\n\npublic class DidAddConfigurationScopesParams {\n\n  private final List<ConfigurationScopeDto> addedScopes;\n\n  public DidAddConfigurationScopesParams(List<ConfigurationScopeDto> addedScopes) {\n    this.addedScopes = addedScopes;\n  }\n\n  public List<ConfigurationScopeDto> getAddedScopes() {\n    return addedScopes;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/config/scope/DidRemoveConfigurationScopeParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope;\n\n\npublic class DidRemoveConfigurationScopeParams {\n  private final String removedId;\n\n  public DidRemoveConfigurationScopeParams(String removedId) {\n    this.removedId = removedId;\n  }\n\n  public String getRemovedId() {\n    return removedId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/config/scope/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/ConnectionRpcService.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonNotification;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonRequest;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonSegment;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth.HelpGenerateUserTokenParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth.HelpGenerateUserTokenResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidChangeCredentialsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidUpdateConnectionsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.FuzzySearchUserOrganizationsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.FuzzySearchUserOrganizationsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.GetOrganizationParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.GetOrganizationResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.ListUserOrganizationsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.ListUserOrganizationsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.FuzzySearchProjectsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.FuzzySearchProjectsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.GetAllProjectsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.GetAllProjectsResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.GetProjectNamesByKeyParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects.GetProjectNamesByKeyResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.validate.ValidateConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.validate.ValidateConnectionResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.GetConnectionSuggestionsParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.GetCredentialsParams;\n\n/**\n * The client is the source of truth for connection configuration, but the backend also need to be kept in sync.\n * The client will use {@link SonarLintRpcServer#initialize(InitializeParams)} to register existing connection configurations at startup, and then\n * update the service as needed using {@link #didUpdateConnections(DidUpdateConnectionsParams)}, when a connection configuration is added/removed/updated.\n * <p>\n * One source of complexity for connection configuration is that some attributes (like credentials) should be stored in\n * the IDE secure storage. Accessing secure storage may be delayed after IDE startup, request manual user\n * actions, or even be prevented. So the backend should be able to handle \"partial\" connection configuration, where\n * credentials are missing.\n */\n@JsonSegment(\"connection\")\npublic interface ConnectionRpcService {\n\n  /**\n   * Called by the client when connection configurations have been changed.\n   */\n  @JsonNotification\n  void didUpdateConnections(DidUpdateConnectionsParams params);\n\n  /**\n   * Called by the client when connection credentials have been changed. The backend might later retrieve them with\n   * {@link SonarLintRpcClient#getCredentials(GetCredentialsParams)}.\n   */\n  @JsonNotification\n  void didChangeCredentials(DidChangeCredentialsParams params);\n\n  /**\n   * @param params url of the server on which to create the token\n   * @return For servers that support automatic token generation, will return the token in the response. Else no token will be returned.\n   * If the local server is not started or the server URL can not be reached, the future will fail\n   */\n  @JsonRequest\n  CompletableFuture<HelpGenerateUserTokenResponse> helpGenerateUserToken(HelpGenerateUserTokenParams params);\n\n  /**\n   * Validate that connection is valid:\n   * <ul>\n   * <li>check that the server is reachable</li>\n   * <li>check that the server minimal version is satisfied</li>\n   * <li>check that the credentials are valid</li>\n   * <li>check that the organization exists (for SonarCloud)</li>\n   * </ul>\n   */\n  @JsonRequest\n  CompletableFuture<ValidateConnectionResponse> validateConnection(ValidateConnectionParams params);\n\n  @JsonRequest\n  CompletableFuture<ListUserOrganizationsResponse> listUserOrganizations(ListUserOrganizationsParams params);\n\n  /**\n   * Find an organization by key. If not found the response will contain null.\n   */\n  @JsonRequest\n  CompletableFuture<GetOrganizationResponse> getOrganization(GetOrganizationParams params);\n\n  /**\n   * Fuzzy search among SonarCloud user organizations.\n   */\n  @JsonRequest\n  CompletableFuture<FuzzySearchUserOrganizationsResponse> fuzzySearchUserOrganizations(FuzzySearchUserOrganizationsParams params);\n\n  /**\n   * Get all Sonar projects existing on SonarQube or in a SonarCloud organization.\n   * As this data might be needed during connection creation, it accepts a transient connection.\n   * The number of returned projects is limited to 10000.\n   */\n  @JsonRequest\n  CompletableFuture<GetAllProjectsResponse> getAllProjects(GetAllProjectsParams params);\n\n  /**\n   * Fuzzy search among Sonar projects existing on SonarQube or in a SonarCloud organization.\n   */\n  @JsonRequest\n  CompletableFuture<FuzzySearchProjectsResponse> fuzzySearchProjects(FuzzySearchProjectsParams params);\n\n  /**\n   * Returns a map of project names by project key; project name is null if it wasn't found\n   */\n  @JsonRequest\n  CompletableFuture<GetProjectNamesByKeyResponse> getProjectNamesByKey(GetProjectNamesByKeyParams params);\n\n  /*\n   * Returns a list of connection suggestions for the given configuration scope. To be used when neither connection nor binding exists.\n   */\n  @JsonRequest\n  CompletableFuture<GetConnectionSuggestionsResponse> getConnectionSuggestions(GetConnectionSuggestionsParams params);\n\n  /**\n   * Generate MCP server settings JSON based on the requested Connection ID.\n   */\n  @JsonRequest\n  CompletableFuture<GetMCPServerConfigurationResponse> getMCPServerConfiguration(GetMCPServerConfigurationParams params);\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/GetConnectionSuggestionsResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection;\n\nimport java.util.List;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.ConnectionSuggestionDto;\n\npublic class GetConnectionSuggestionsResponse {\n  private final List<ConnectionSuggestionDto> connectionSuggestions;\n\n  public GetConnectionSuggestionsResponse(List<ConnectionSuggestionDto> connectionSuggestions) {\n    this.connectionSuggestions = connectionSuggestions;\n  }\n\n  public List<ConnectionSuggestionDto> getConnectionSuggestions() {\n    return connectionSuggestions;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/GetMCPServerConfigurationParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection;\n\npublic class GetMCPServerConfigurationParams {\n  private String connectionId;\n  private String token;\n\n  public GetMCPServerConfigurationParams(String connectionId, String token) {\n    this.connectionId = connectionId;\n    this.token = token;\n  }\n\n  public String getConnectionId() {\n    return connectionId;\n  }\n\n  public void setConnectionId(String connectionId) {\n    this.connectionId = connectionId;\n  }\n\n  public String getToken() {\n    return token;\n  }\n\n  public void setToken(String token) {\n    this.token = token;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/GetMCPServerConfigurationResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection;\n\npublic class GetMCPServerConfigurationResponse {\n  private final String jsonConfiguration;\n\n  public GetMCPServerConfigurationResponse(String jsonConfiguration) {\n    this.jsonConfiguration = jsonConfiguration;\n  }\n\n  public String getJsonConfiguration() {\n    return jsonConfiguration;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/auth/HelpGenerateUserTokenParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth;\n\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class HelpGenerateUserTokenParams {\n  private final String serverUrl;\n\n  private final Utm utm;\n\n  @Deprecated\n  public HelpGenerateUserTokenParams(String serverUrl) {\n    this(serverUrl, null);\n  }\n\n  public HelpGenerateUserTokenParams(String serverUrl, @Nullable Utm utm) {\n    this.serverUrl = serverUrl;\n    this.utm = utm;\n  }\n\n  public String getServerUrl() {\n    return serverUrl;\n  }\n\n  @CheckForNull\n  public Utm getUtm() {\n    return utm;\n  }\n\n  public static class Utm {\n    private final String medium;\n    private final String source;\n    private final String content;\n    private final String term;\n\n    public Utm(String medium, String source, String content, String term) {\n      this.medium = medium;\n      this.source = source;\n      this.content = content;\n      this.term = term;\n    }\n\n    public String getMedium() {\n      return medium;\n    }\n\n    public String getSource() {\n      return source;\n    }\n\n    public String getContent() {\n      return content;\n    }\n\n    public String getTerm() {\n      return term;\n    }\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/auth/HelpGenerateUserTokenResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\n/**\n * For older SQ servers or SC, automatic token generation is not supported. In this case a null token will be returned.\n */\npublic class HelpGenerateUserTokenResponse {\n  private final String token;\n\n  public HelpGenerateUserTokenResponse(@Nullable String token) {\n    this.token = token;\n  }\n\n  @CheckForNull\n  public String getToken() {\n    return token;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/auth/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/common/TransientSonarCloudConnectionDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common;\n\nimport com.google.gson.annotations.JsonAdapter;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.EitherCredentialsAdapterFactory;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\n\npublic class TransientSonarCloudConnectionDto {\n\n  private final String organization;\n\n  @JsonAdapter(EitherCredentialsAdapterFactory.class)\n  private final Either<TokenDto, UsernamePasswordDto> credentials;\n  private final SonarCloudRegion region;\n\n  public TransientSonarCloudConnectionDto(@Nullable String organization, Either<TokenDto, UsernamePasswordDto> credentials, SonarCloudRegion region) {\n    this.organization = organization;\n    this.credentials = credentials;\n    this.region = region;\n  }\n\n  @CheckForNull\n  public String getOrganization() {\n    return organization;\n  }\n\n  public Either<TokenDto, UsernamePasswordDto> getCredentials() {\n    return credentials;\n  }\n\n  public SonarCloudRegion getRegion() {\n    return region;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/common/TransientSonarQubeConnectionDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common;\n\nimport com.google.gson.annotations.JsonAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.EitherCredentialsAdapterFactory;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\n\npublic class TransientSonarQubeConnectionDto {\n\n  private final String serverUrl;\n\n  @JsonAdapter(EitherCredentialsAdapterFactory.class)\n  private final Either<TokenDto, UsernamePasswordDto> credentials;\n\n  public TransientSonarQubeConnectionDto(String serverUrl, Either<TokenDto, UsernamePasswordDto> credentials) {\n    this.serverUrl = serverUrl;\n    this.credentials = credentials;\n  }\n\n  public String getServerUrl() {\n    return serverUrl;\n  }\n\n  public Either<TokenDto, UsernamePasswordDto> getCredentials() {\n    return credentials;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/common/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/config/DidChangeCredentialsParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config;\n\n\npublic class DidChangeCredentialsParams {\n  private final String connectionId;\n\n  public DidChangeCredentialsParams(String connectionId) {\n    this.connectionId = connectionId;\n  }\n\n  public String getConnectionId() {\n    return connectionId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/config/DidUpdateConnectionsParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config;\n\nimport java.util.List;\n\npublic class DidUpdateConnectionsParams {\n\n  private final List<SonarQubeConnectionConfigurationDto> sonarQubeConnections;\n  private final List<SonarCloudConnectionConfigurationDto> sonarCloudConnections;\n\n  public DidUpdateConnectionsParams(List<SonarQubeConnectionConfigurationDto> sonarQubeConnections, List<SonarCloudConnectionConfigurationDto> sonarCloudConnections) {\n    this.sonarQubeConnections = sonarQubeConnections;\n    this.sonarCloudConnections = sonarCloudConnections;\n  }\n\n  public List<SonarQubeConnectionConfigurationDto> getSonarQubeConnections() {\n    return sonarQubeConnections != null ? sonarQubeConnections : List.of();\n  }\n\n  public List<SonarCloudConnectionConfigurationDto> getSonarCloudConnections() {\n    return sonarCloudConnections != null ? sonarCloudConnections : List.of();\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/config/SonarCloudConnectionConfigurationDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config;\n\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion;\n\npublic class SonarCloudConnectionConfigurationDto {\n\n  /**\n   * The id of the connection on the client side\n   */\n  private final String connectionId;\n  private final String organization;\n  private final SonarCloudRegion region;\n  private final boolean disableNotifications;\n\n  public SonarCloudConnectionConfigurationDto(String connectionId, String organization, SonarCloudRegion region, boolean disableNotifications) {\n    this.connectionId = connectionId;\n    this.organization = organization;\n    this.region = region;\n    this.disableNotifications = disableNotifications;\n  }\n\n  public String getConnectionId() {\n    return connectionId;\n  }\n\n  public String getOrganization() {\n    return organization;\n  }\n\n  public SonarCloudRegion getRegion() {\n    return region;\n  }\n\n  public boolean isDisableNotifications() {\n    return disableNotifications;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/config/SonarQubeConnectionConfigurationDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config;\n\npublic class SonarQubeConnectionConfigurationDto {\n\n  /**\n   * The id of the connection on the client side\n   */\n  private final String connectionId;\n  private final String serverUrl;\n  private final boolean disableNotifications;\n\n  public SonarQubeConnectionConfigurationDto(String connectionId, String serverUrl, boolean disableNotifications) {\n    this.connectionId = connectionId;\n    this.serverUrl = serverUrl;\n    this.disableNotifications = disableNotifications;\n  }\n\n  public String getConnectionId() {\n    return connectionId;\n  }\n\n  public String getServerUrl() {\n    return serverUrl;\n  }\n\n  public boolean getDisableNotifications() {\n    return disableNotifications;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/config/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/org/FuzzySearchUserOrganizationsParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org;\n\nimport com.google.gson.annotations.JsonAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.EitherCredentialsAdapterFactory;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\n\npublic class FuzzySearchUserOrganizationsParams {\n\n  @JsonAdapter(EitherCredentialsAdapterFactory.class)\n  private final Either<TokenDto, UsernamePasswordDto> credentials;\n  private final String searchText;\n  private final SonarCloudRegion region;\n\n  @Deprecated(since = \"10.14\")\n  public FuzzySearchUserOrganizationsParams(Either<TokenDto, UsernamePasswordDto> credentials, String searchText) {\n    this(credentials, searchText, SonarCloudRegion.EU);\n  }\n\n  public FuzzySearchUserOrganizationsParams(Either<TokenDto, UsernamePasswordDto> credentials, String searchText, SonarCloudRegion region) {\n    this.credentials = credentials;\n    this.searchText = searchText;\n    this.region = region;\n  }\n\n  public Either<TokenDto, UsernamePasswordDto> getCredentials() {\n    return credentials;\n  }\n\n  public String getSearchText() {\n    return searchText;\n  }\n\n  public SonarCloudRegion getRegion() {\n    return region;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/org/FuzzySearchUserOrganizationsResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org;\n\nimport java.util.List;\n\npublic class FuzzySearchUserOrganizationsResponse {\n\n  private final List<OrganizationDto> topResults;\n\n  public FuzzySearchUserOrganizationsResponse(List<OrganizationDto> topResults) {\n    this.topResults = topResults;\n  }\n\n  public List<OrganizationDto> getTopResults() {\n    return topResults;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/org/GetOrganizationParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org;\n\nimport com.google.gson.annotations.JsonAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.EitherCredentialsAdapterFactory;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\n\npublic class GetOrganizationParams {\n\n  @JsonAdapter(EitherCredentialsAdapterFactory.class)\n  private final Either<TokenDto, UsernamePasswordDto> credentials;\n  private final String organizationKey;\n  private final SonarCloudRegion region;\n\n  public GetOrganizationParams(Either<TokenDto, UsernamePasswordDto> credentials, String organizationKey, SonarCloudRegion region) {\n    this.credentials = credentials;\n    this.organizationKey = organizationKey;\n    this.region = region;\n  }\n\n  public String getOrganizationKey() {\n    return organizationKey;\n  }\n\n  public Either<TokenDto, UsernamePasswordDto> getCredentials() {\n    return credentials;\n  }\n\n  public SonarCloudRegion getRegion() {\n    return region;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/org/GetOrganizationResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class GetOrganizationResponse {\n\n  @Nullable\n  private final OrganizationDto organization;\n\n  public GetOrganizationResponse(@Nullable OrganizationDto organization) {\n    this.organization = organization;\n  }\n\n  @CheckForNull\n  public OrganizationDto getOrganization() {\n    return organization;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/org/ListUserOrganizationsParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org;\n\nimport com.google.gson.annotations.JsonAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.EitherCredentialsAdapterFactory;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\n\npublic class ListUserOrganizationsParams {\n\n  @JsonAdapter(EitherCredentialsAdapterFactory.class)\n  private final Either<TokenDto, UsernamePasswordDto> credentials;\n  private final SonarCloudRegion region;\n\n  @Deprecated(since = \"10.14\")\n  public ListUserOrganizationsParams(Either<TokenDto, UsernamePasswordDto> credentials) {\n    this(credentials, SonarCloudRegion.EU);\n  }\n\n  public ListUserOrganizationsParams(Either<TokenDto, UsernamePasswordDto> credentials, SonarCloudRegion region) {\n    this.credentials = credentials;\n    this.region = region;\n  }\n\n  public Either<TokenDto, UsernamePasswordDto> getCredentials() {\n    return credentials;\n  }\n\n  public SonarCloudRegion getRegion() {\n    return region;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/org/ListUserOrganizationsResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org;\n\nimport java.util.List;\n\npublic class ListUserOrganizationsResponse {\n\n  private final List<OrganizationDto> userOrganizations;\n\n  public ListUserOrganizationsResponse(List<OrganizationDto> userOrganizations) {\n    this.userOrganizations = userOrganizations;\n  }\n\n  public List<OrganizationDto> getUserOrganizations() {\n    return userOrganizations;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/org/OrganizationDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org;\n\npublic class OrganizationDto {\n\n  private final String key;\n  private final String name;\n  private final String description;\n\n\n  public OrganizationDto(String key, String name, String description) {\n    this.key = key;\n    this.name = name;\n    this.description = description;\n  }\n\n  public String getKey() {\n    return key;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public String getDescription() {\n    return description;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/org/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/projects/FuzzySearchProjectsParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects;\n\npublic class FuzzySearchProjectsParams {\n\n  private final String connectionId;\n  private final String searchText;\n\n  public FuzzySearchProjectsParams(String connectionId, String searchText) {\n    this.connectionId = connectionId;\n    this.searchText = searchText;\n  }\n\n  public String getConnectionId() {\n    return connectionId;\n  }\n\n  public String getSearchText() {\n    return searchText;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/projects/FuzzySearchProjectsResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects;\n\nimport java.util.List;\n\npublic class FuzzySearchProjectsResponse {\n\n  private final List<SonarProjectDto> topResults;\n\n  public FuzzySearchProjectsResponse(List<SonarProjectDto> topResults) {\n    this.topResults = topResults;\n  }\n\n  public List<SonarProjectDto> getTopResults() {\n    return topResults;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/projects/GetAllProjectsParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects;\n\nimport com.google.gson.annotations.JsonAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.EitherTransientConnectionAdapterFactory;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarCloudConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarQubeConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\n\npublic class GetAllProjectsParams {\n\n  @JsonAdapter(EitherTransientConnectionAdapterFactory.class)\n  private final Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> transientConnection;\n\n  public GetAllProjectsParams(Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> transientConnection) {\n    this.transientConnection = transientConnection;\n  }\n\n  public GetAllProjectsParams(TransientSonarQubeConnectionDto transientConnection) {\n    this(Either.forLeft(transientConnection));\n  }\n\n  public GetAllProjectsParams(TransientSonarCloudConnectionDto transientConnection) {\n    this(Either.forRight(transientConnection));\n  }\n\n  public Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> getTransientConnection() {\n    return transientConnection;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/projects/GetAllProjectsResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects;\n\nimport java.util.List;\n\npublic class GetAllProjectsResponse {\n\n  private final List<SonarProjectDto> sonarProjects;\n\n  public GetAllProjectsResponse(List<SonarProjectDto> sonarProjects) {\n    this.sonarProjects = sonarProjects;\n  }\n\n  public List<SonarProjectDto> getSonarProjects() {\n    return sonarProjects;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/projects/GetProjectNamesByKeyParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects;\n\nimport com.google.gson.annotations.JsonAdapter;\nimport java.util.List;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.EitherTransientConnectionAdapterFactory;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarCloudConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarQubeConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\n\npublic class GetProjectNamesByKeyParams {\n  @JsonAdapter(EitherTransientConnectionAdapterFactory.class)\n  private final Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> transientConnection;\n\n  private final List<String> projectKeys;\n\n  public GetProjectNamesByKeyParams(Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> transientConnection, List<String> projectKeys) {\n    this.transientConnection = transientConnection;\n    this.projectKeys = projectKeys;\n  }\n\n  public GetProjectNamesByKeyParams(TransientSonarQubeConnectionDto transientConnection, List<String> projectKeys) {\n    this(Either.forLeft(transientConnection), projectKeys);\n  }\n\n  public GetProjectNamesByKeyParams(TransientSonarCloudConnectionDto transientConnection, List<String> projectKeys) {\n    this(Either.forRight(transientConnection), projectKeys);\n  }\n\n  public Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> getTransientConnection() {\n    return transientConnection;\n  }\n\n  public List<String> getProjectKeys() {\n    return projectKeys;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/projects/GetProjectNamesByKeyResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects;\n\nimport java.util.Map;\n\npublic class GetProjectNamesByKeyResponse {\n\n  private final Map<String, String> projectNamesByKey;\n\n  public GetProjectNamesByKeyResponse(Map<String, String> projectNamesByKey) {\n    this.projectNamesByKey = projectNamesByKey;\n  }\n\n  public Map<String, String> getProjectNamesByKey() {\n    return projectNamesByKey;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/projects/SonarProjectDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects;\n\nimport java.util.Objects;\n\npublic class SonarProjectDto {\n  private final String key;\n  private final String name;\n\n  public SonarProjectDto(String key, String name) {\n    this.key = key;\n    this.name = name;\n  }\n\n  public String getKey() {\n    return key;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o == null || getClass() != o.getClass()) return false;\n    SonarProjectDto that = (SonarProjectDto) o;\n    return Objects.equals(key, that.key) && Objects.equals(name, that.name);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(key, name);\n  }\n\n  @Override\n  public String toString() {\n    return \"SonarProjectDto{\" +\n      \"key='\" + key + '\\'' +\n      \", name='\" + name + '\\'' +\n      '}';\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/projects/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/validate/ValidateConnectionParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.validate;\n\nimport com.google.gson.annotations.JsonAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.EitherTransientConnectionAdapterFactory;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarCloudConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarQubeConnectionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\n\npublic class ValidateConnectionParams {\n\n  @JsonAdapter(EitherTransientConnectionAdapterFactory.class)\n  private final Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> transientConnection;\n\n  public ValidateConnectionParams(Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> transientConnection) {\n    this.transientConnection = transientConnection;\n  }\n\n  public ValidateConnectionParams(TransientSonarQubeConnectionDto transientConnection) {\n    this(Either.forLeft(transientConnection));\n  }\n\n  public ValidateConnectionParams(TransientSonarCloudConnectionDto transientConnection) {\n    this(Either.forRight(transientConnection));\n  }\n\n  public Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> getTransientConnection() {\n    return transientConnection;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/validate/ValidateConnectionResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.validate;\n\npublic class ValidateConnectionResponse {\n\n  private final boolean success;\n\n  private final String message;\n\n  public ValidateConnectionResponse(boolean success, String message) {\n    this.success = success;\n    this.message = message;\n  }\n\n  public boolean isSuccess() {\n    return success;\n  }\n\n  public String getMessage() {\n    return message;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/validate/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.validate;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/dogfooding/DogfoodingRpcService.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.dogfooding;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonRequest;\n\npublic interface DogfoodingRpcService {\n  /**\n   * Returns true if there is a dogfooding environment variable set on user's machine\n   */\n  @JsonRequest\n  CompletableFuture<IsDogfoodingEnvironmentResponse> isDogfoodingEnvironment();\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/dogfooding/IsDogfoodingEnvironmentResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.dogfooding;\n\npublic class IsDogfoodingEnvironmentResponse {\n  private final boolean isDogfoodingEnvironment;\n\n  public IsDogfoodingEnvironmentResponse(boolean isDogfoodingEnvironment) {\n    this.isDogfoodingEnvironment = isDogfoodingEnvironment;\n  }\n\n  public boolean isDogfoodingEnvironment() {\n    return isDogfoodingEnvironment;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/dogfooding/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.dogfooding;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/file/DidCloseFileParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.file;\n\nimport java.net.URI;\n\npublic class DidCloseFileParams {\n  private final String configurationScopeId;\n  private final URI fileUri;\n\n  public DidCloseFileParams(String configurationScopeId, URI fileUri) {\n    this.configurationScopeId = configurationScopeId;\n    this.fileUri = fileUri;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public URI getFileUri() {\n    return fileUri;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/file/DidOpenFileParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.file;\n\nimport java.net.URI;\n\npublic class DidOpenFileParams {\n  private final String configurationScopeId;\n  private final URI fileUri;\n\n  public DidOpenFileParams(String configurationScopeId, URI fileUri) {\n    this.configurationScopeId = configurationScopeId;\n    this.fileUri = fileUri;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public URI getFileUri() {\n    return fileUri;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/file/DidUpdateFileSystemParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.file;\n\nimport java.net.URI;\nimport java.util.List;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\n\npublic class DidUpdateFileSystemParams {\n\n  private final List<ClientFileDto> addedFiles;\n  private final List<ClientFileDto> changedFiles;\n  private final List<URI> removedFiles;\n\n  public DidUpdateFileSystemParams(List<ClientFileDto> addedFiles, List<ClientFileDto> changedFiles, List<URI> removedFiles) {\n    this.addedFiles = addedFiles;\n    this.changedFiles = changedFiles;\n    this.removedFiles = removedFiles;\n  }\n\n  public List<ClientFileDto> getAddedFiles() {\n    return addedFiles;\n  }\n\n  public List<ClientFileDto> getChangedFiles() {\n    return changedFiles;\n  }\n  public List<URI> getRemovedFiles() {\n    return removedFiles;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/file/FileRpcService.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.file;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonNotification;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonRequest;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonSegment;\n\n@JsonSegment(\"file\")\npublic interface FileRpcService {\n\n  /**\n   * Returns whether a file is currently excluded or not\n   * It is based on the same criteria as for the exclusions during an analysis\n   */\n  @JsonRequest\n  CompletableFuture<GetFilesStatusResponse> getFilesStatus(GetFilesStatusParams params);\n\n  @JsonNotification\n  void didUpdateFileSystem(DidUpdateFileSystemParams params);\n\n  /**\n   * Should be called by clients when a file has been opened in the editor.\n   */\n  @JsonNotification\n  void didOpenFile(DidOpenFileParams params);\n\n  /**\n   * Should be called by clients when a file has been closed in the editor.\n   */\n  @JsonNotification\n  void didCloseFile(DidCloseFileParams params);\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/file/FileStatusDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.file;\n\npublic class FileStatusDto {\n  \n  private final boolean excluded;\n\n  public FileStatusDto(boolean excluded) {\n    this.excluded = excluded;\n  }\n\n  public boolean isExcluded() {\n    return excluded;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/file/GetFilesStatusParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.file;\n\nimport java.net.URI;\nimport java.util.List;\nimport java.util.Map;\n\npublic class GetFilesStatusParams {\n  \n  private final Map<String, List<URI>> fileUrisByConfigScopeId;\n\n  public GetFilesStatusParams(Map<String, List<URI>> fileUrisByConfigScopeId) {\n    this.fileUrisByConfigScopeId = fileUrisByConfigScopeId;\n  }\n\n  public Map<String, List<URI>> getFileUrisByConfigScopeId() {\n    return fileUrisByConfigScopeId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/file/GetFilesStatusResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.file;\n\nimport java.net.URI;\nimport java.util.Map;\n\npublic class GetFilesStatusResponse {\n\n  private final Map<URI, FileStatusDto> fileStatuses;\n\n  public GetFilesStatusResponse(Map<URI, FileStatusDto> fileStatuses) {\n    this.fileStatuses = fileStatuses;\n  }\n\n  public Map<URI, FileStatusDto> getFileStatuses() {\n    return fileStatuses;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/file/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.file;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/hotspot/ChangeHotspotStatusParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot;\n\npublic class ChangeHotspotStatusParams {\n  private final String configurationScopeId;\n  private final String hotspotKey;\n  private final HotspotStatus newStatus;\n\n  public ChangeHotspotStatusParams(String configurationScopeId, String hotspotKey, HotspotStatus newStatus) {\n    this.configurationScopeId = configurationScopeId;\n    this.hotspotKey = hotspotKey;\n    this.newStatus = newStatus;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public String getHotspotKey() {\n    return hotspotKey;\n  }\n\n  public HotspotStatus getNewStatus() {\n    return newStatus;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/hotspot/CheckLocalDetectionSupportedParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot;\n\npublic class CheckLocalDetectionSupportedParams {\n  private final String configScopeId;\n\n  public CheckLocalDetectionSupportedParams(String configScopeId) {\n    this.configScopeId = configScopeId;\n  }\n\n  public String getConfigScopeId() {\n    return configScopeId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/hotspot/CheckLocalDetectionSupportedResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class CheckLocalDetectionSupportedResponse {\n  private final boolean supported;\n  private final String reason;\n\n  public CheckLocalDetectionSupportedResponse(boolean supported, @Nullable String reason) {\n    this.supported = supported;\n    this.reason = reason;\n  }\n\n  public boolean isSupported() {\n    return supported;\n  }\n\n  @CheckForNull\n  public String getReason() {\n    return reason;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/hotspot/CheckStatusChangePermittedParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot;\n\npublic class CheckStatusChangePermittedParams {\n  private final String connectionId;\n  private final String hotspotKey;\n\n  public CheckStatusChangePermittedParams(String connectionId, String hotspotKey) {\n    this.connectionId = connectionId;\n    this.hotspotKey = hotspotKey;\n  }\n\n  public String getConnectionId() {\n    return connectionId;\n  }\n\n  public String getHotspotKey() {\n    return hotspotKey;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/hotspot/CheckStatusChangePermittedResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot;\n\nimport java.util.List;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class CheckStatusChangePermittedResponse {\n  private final boolean permitted;\n  private final String notPermittedReason;\n  private final List<HotspotStatus> allowedStatuses;\n\n  public CheckStatusChangePermittedResponse(boolean permitted, @Nullable String notPermittedReason, List<HotspotStatus> allowedStatuses) {\n    this.permitted = permitted;\n    this.notPermittedReason = notPermittedReason;\n    this.allowedStatuses = allowedStatuses;\n  }\n\n  public boolean isPermitted() {\n    return permitted;\n  }\n\n  @CheckForNull\n  public String getNotPermittedReason() {\n    return notPermittedReason;\n  }\n\n  public List<HotspotStatus> getAllowedStatuses() {\n    return allowedStatuses;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/hotspot/HotspotRpcService.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonNotification;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonRequest;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonSegment;\n\n@JsonSegment(\"hotspot\")\npublic interface HotspotRpcService {\n\n  @JsonNotification\n  void openHotspotInBrowser(OpenHotspotInBrowserParams params);\n\n  /**\n   * <p>Request if the local detection of hotspots is supported. It is the case when the configuration scope provided as a parameter is bound.</p>\n   * <p>\n   * If the configuration scope has no effective binding, local detection is not supported. The corresponding reason will be returned in the response.\n   * </p>\n   * <p>\n   * This method will fail if:\n   * <ul>\n   *   <li>the provided configuration scope is unknown</li>\n   *   <li>the configuration scope is bound to an unknown connection</li>\n   * </ul>\n   * In those cases, a failed future will be returned.\n   * </p>\n   */\n  @JsonRequest\n  CompletableFuture<CheckLocalDetectionSupportedResponse> checkLocalDetectionSupported(CheckLocalDetectionSupportedParams params);\n\n  /**\n   * Checks if the user has permission to change the hotspot review status.\n   * Also returns the list of allowed statuses.The list differs between SonarQube and SonarCloud, so different values will be returned based on the connectionId:\n   * <ul>\n   *   <li>For SonarCloud, the allowed statuses are {@link HotspotStatus#TO_REVIEW}, {@link HotspotStatus#SAFE} and {@link HotspotStatus#FIXED}</li>\n   *   <li>For SonarQube, on top of the previous ones, the {@link HotspotStatus#ACKNOWLEDGED} status is also allowed</li>\n   * </ul>\n   * <p>\n   * This method will fail if:\n   * <ul>\n   *   <li>there is a communication problem with the server: network outage, server is down, unauthorized</li>\n   *   <li>the connectionId provided as a parameter is unknown</li>\n   * </ul>\n   * In those cases, a failed future will be returned.\n   * </p>\n   */\n  @JsonRequest\n  CompletableFuture<CheckStatusChangePermittedResponse> checkStatusChangePermitted(CheckStatusChangePermittedParams params);\n\n  /**\n   * <p>This method achieves several things:\n   * <ul>\n   *   <li>Changes the hotspot status on the SonarQube/SonarCloud bound to the provided configuration scope</li>\n   *   <li>Updates the hotspot status in the local storage</li>\n   *   <li>Increments the 'hotspot.status_changed_count' counter for telemetry</li>\n   * </ul>\n   * </p>\n   * <p>\n   * This method will fail if:\n   * <ul>\n   *   <li>there is a communication problem with the server: network outage, server is down, unauthorized</li>\n   * </ul>\n   * In those cases, a failed future will be returned.\n   * </p>\n   * <p>\n   * This method will silently deal with the following conditions:\n   * <ul>\n   *   <li>the provided configuration scope ID is unknown</li>\n   *   <li>the connection bound to the configuration scope is unknown</li>\n   *   <li>the hotspotKey is not found in the local storage</li>\n   * </ul>\n   * In those cases, a completed future will be returned.\n   * </p>\n   */\n  @JsonRequest\n  CompletableFuture<Void> changeStatus(ChangeHotspotStatusParams params);\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/hotspot/HotspotStatus.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot;\n\npublic enum HotspotStatus {\n  TO_REVIEW,\n  ACKNOWLEDGED,\n  FIXED,\n  SAFE\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/hotspot/OpenHotspotInBrowserParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot;\n\npublic class OpenHotspotInBrowserParams {\n  private final String configScopeId;\n  private final String hotspotKey;\n\n  public OpenHotspotInBrowserParams(String configScopeId, String hotspotKey) {\n    this.configScopeId = configScopeId;\n    this.hotspotKey = hotspotKey;\n  }\n\n  public String getConfigScopeId() {\n    return configScopeId;\n  }\n\n  public String getHotspotKey() {\n    return hotspotKey;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/hotspot/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/initialize/BackendCapability.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize;\n\npublic enum BackendCapability {\n  SMART_NOTIFICATIONS,\n  PROJECT_SYNCHRONIZATION,\n  EMBEDDED_SERVER,\n  SECURITY_HOTSPOTS,\n  SERVER_SENT_EVENTS,\n  DATAFLOW_BUG_DETECTION,\n  FULL_SYNCHRONIZATION,\n  TELEMETRY,\n  GESSIE_TELEMETRY,\n  MONITORING,\n  ISSUE_STREAMING,\n  SCA_SYNCHRONIZATION,\n  CONTEXT_GENERATION,\n  PROMOTIONAL_CAMPAIGNS\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/initialize/ClientConstantInfoDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize;\n\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;\n\n/**\n * Static information to describe the client. Dynamic information will be provided when needed by calling {@link SonarLintRpcClient#getClientLiveInfo()}\n */\npublic class ClientConstantInfoDto {\n  /**\n   * Name of the client, that could be used outside the IDE, e.g. for the sonarlint/api/status endpoint or when opening the page to generate the user token\n   */\n  private final String name;\n\n  /**\n   * User agent used for all HTTP requests made by the backend\n   */\n  private final String userAgent;\n\n  public ClientConstantInfoDto(String name, String userAgent) {\n    this.name = name;\n    this.userAgent = userAgent;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public String getUserAgent() {\n    return userAgent;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/initialize/HttpConfigurationDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize;\n\nimport java.time.Duration;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class HttpConfigurationDto {\n\n  public static HttpConfigurationDto defaultConfig() {\n    return new HttpConfigurationDto(SslConfigurationDto.defaultConfig(), null, null, null, null);\n  }\n\n  private final SslConfigurationDto sslConfiguration;\n  private final Duration connectTimeout;\n  private final Duration socketTimeout;\n  private final Duration connectionRequestTimeout;\n  private final Duration responseTimeout;\n\n  public HttpConfigurationDto(SslConfigurationDto sslConfiguration, @Nullable Duration connectTimeout, @Nullable Duration socketTimeout,\n    @Nullable Duration connectionRequestTimeout, @Nullable Duration responseTimeout) {\n    this.sslConfiguration = sslConfiguration;\n    this.connectTimeout = connectTimeout;\n    this.socketTimeout = socketTimeout;\n    this.connectionRequestTimeout = connectionRequestTimeout;\n    this.responseTimeout = responseTimeout;\n  }\n\n  public SslConfigurationDto getSslConfiguration() {\n    return sslConfiguration;\n  }\n\n  @CheckForNull\n  public Duration getConnectTimeout() {\n    return connectTimeout;\n  }\n\n  @CheckForNull\n  public Duration getSocketTimeout() {\n    return socketTimeout;\n  }\n\n  @CheckForNull\n  public Duration getConnectionRequestTimeout() {\n    return connectionRequestTimeout;\n  }\n\n  @CheckForNull\n  public Duration getResponseTimeout() {\n    return responseTimeout;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/initialize/InitializeParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize;\n\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarCloudConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarQubeConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.log.LogLevel;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.StandaloneRuleConfigDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\n\npublic class InitializeParams {\n  private final ClientConstantInfoDto clientConstantInfo;\n  private final TelemetryClientConstantAttributesDto telemetryConstantAttributes;\n  private final HttpConfigurationDto httpConfiguration;\n  private final SonarCloudAlternativeEnvironmentDto alternativeSonarCloudEnvironment;\n  private final Set<BackendCapability> backendCapabilities;\n  private final Path storageRoot;\n  private final Path workDir;\n  private final Set<Path> embeddedPluginPaths;\n  private final Map<String, Path> connectedModeEmbeddedPluginPathsByKey;\n  private final Set<Language> enabledLanguagesInStandaloneMode;\n  private final Set<Language> extraEnabledLanguagesInConnectedMode;\n  private final Set<String> disabledPluginKeysForAnalysis;\n  private final List<SonarQubeConnectionConfigurationDto> sonarQubeConnections;\n  private final List<SonarCloudConnectionConfigurationDto> sonarCloudConnections;\n  private final String sonarlintUserHome;\n  private final Map<String, StandaloneRuleConfigDto> standaloneRuleConfigByKey;\n  private final boolean isFocusOnNewCode;\n  private final LanguageSpecificRequirements languageSpecificRequirements;\n  private final boolean automaticAnalysisEnabled;\n  private final TelemetryMigrationDto telemetryMigration;\n  private final LogLevel logLevel;\n\n  /**\n   * @deprecated use newer constructor with log level\n   * @param enabledLanguagesInStandaloneMode if IPYTHON is part of the list and a configuration scope is bound, standalone active rules will be used\n   * @param telemetryConstantAttributes Static information about the client, that will be sent with the telemetry payload\n   * @param workDir                     Path to work directory. If null, will default to [sonarlintUserHome]/work\n   * @param sonarlintUserHome           Path to SonarLint user home directory. If null, will default to the SONARLINT_USER_HOME env variable if set, else ~/.sonarlint\n   * @param standaloneRuleConfigByKey   Local rule configuration for standalone analysis. This configuration will override defaults rule activation and parameters.\n   */\n  @Deprecated(since = \"10.35\", forRemoval = true)\n  public InitializeParams(\n    ClientConstantInfoDto clientConstantInfo,\n    TelemetryClientConstantAttributesDto telemetryConstantAttributes,\n    HttpConfigurationDto httpConfiguration,\n    @Nullable SonarCloudAlternativeEnvironmentDto alternativeSonarCloudEnvironment,\n    Set<BackendCapability> backendCapabilities,\n    Path storageRoot,\n    @Nullable Path workDir,\n    @Nullable Set<Path> embeddedPluginPaths,\n    @Nullable Map<String, Path> connectedModeEmbeddedPluginPathsByKey,\n    @Nullable Set<Language> enabledLanguagesInStandaloneMode,\n    @Nullable Set<Language> extraEnabledLanguagesInConnectedMode,\n    @Nullable Set<String> disabledPluginKeysForAnalysis,\n    @Nullable List<SonarQubeConnectionConfigurationDto> sonarQubeConnections,\n    @Nullable List<SonarCloudConnectionConfigurationDto> sonarCloudConnections,\n    @Nullable String sonarlintUserHome,\n    @Nullable Map<String, StandaloneRuleConfigDto> standaloneRuleConfigByKey,\n    boolean isFocusOnNewCode,\n    @Nullable LanguageSpecificRequirements languageSpecificRequirements,\n    boolean automaticAnalysisEnabled,\n    @Nullable TelemetryMigrationDto telemetryMigration) {\n    this(clientConstantInfo, telemetryConstantAttributes, httpConfiguration, alternativeSonarCloudEnvironment, backendCapabilities, storageRoot, workDir, embeddedPluginPaths,\n      connectedModeEmbeddedPluginPathsByKey, enabledLanguagesInStandaloneMode, extraEnabledLanguagesInConnectedMode, disabledPluginKeysForAnalysis, sonarQubeConnections,\n      sonarCloudConnections, sonarlintUserHome, standaloneRuleConfigByKey, isFocusOnNewCode, languageSpecificRequirements, automaticAnalysisEnabled, telemetryMigration,\n      LogLevel.TRACE);\n  }\n\n  /**\n   * @param telemetryConstantAttributes      Static information about the client, that will be sent with the telemetry payload\n   * @param workDir                          Path to work directory. If null, will default to [sonarlintUserHome]/work\n   * @param enabledLanguagesInStandaloneMode if IPYTHON is part of the list and a configuration scope is bound, standalone active rules will be used\n   * @param sonarlintUserHome                Path to SonarLint user home directory. If null, will default to the SONARLINT_USER_HOME env variable if set, else ~/.sonarlint\n   * @param standaloneRuleConfigByKey        Local rule configuration for standalone analysis. This configuration will override defaults rule activation and parameters.\n   */\n  public InitializeParams(\n    ClientConstantInfoDto clientConstantInfo,\n    TelemetryClientConstantAttributesDto telemetryConstantAttributes,\n    HttpConfigurationDto httpConfiguration,\n    @Nullable SonarCloudAlternativeEnvironmentDto alternativeSonarCloudEnvironment,\n    Set<BackendCapability> backendCapabilities,\n    Path storageRoot,\n    @Nullable Path workDir,\n    @Nullable Set<Path> embeddedPluginPaths,\n    @Nullable Map<String, Path> connectedModeEmbeddedPluginPathsByKey,\n    @Nullable Set<Language> enabledLanguagesInStandaloneMode,\n    @Nullable Set<Language> extraEnabledLanguagesInConnectedMode,\n    @Nullable Set<String> disabledPluginKeysForAnalysis,\n    @Nullable List<SonarQubeConnectionConfigurationDto> sonarQubeConnections,\n    @Nullable List<SonarCloudConnectionConfigurationDto> sonarCloudConnections,\n    @Nullable String sonarlintUserHome,\n    @Nullable Map<String, StandaloneRuleConfigDto> standaloneRuleConfigByKey,\n    boolean isFocusOnNewCode,\n    @Nullable LanguageSpecificRequirements languageSpecificRequirements,\n    boolean automaticAnalysisEnabled,\n    @Nullable TelemetryMigrationDto telemetryMigration,\n    LogLevel logLevel) {\n    this.clientConstantInfo = clientConstantInfo;\n    this.telemetryConstantAttributes = telemetryConstantAttributes;\n    this.httpConfiguration = httpConfiguration;\n    this.alternativeSonarCloudEnvironment = alternativeSonarCloudEnvironment;\n    this.backendCapabilities = backendCapabilities;\n    this.storageRoot = storageRoot;\n    this.workDir = workDir;\n    this.embeddedPluginPaths = embeddedPluginPaths;\n    this.connectedModeEmbeddedPluginPathsByKey = connectedModeEmbeddedPluginPathsByKey;\n    this.enabledLanguagesInStandaloneMode = enabledLanguagesInStandaloneMode;\n    this.extraEnabledLanguagesInConnectedMode = extraEnabledLanguagesInConnectedMode;\n    this.disabledPluginKeysForAnalysis = disabledPluginKeysForAnalysis;\n    this.sonarQubeConnections = sonarQubeConnections;\n    this.sonarCloudConnections = sonarCloudConnections;\n    this.sonarlintUserHome = sonarlintUserHome;\n    this.standaloneRuleConfigByKey = standaloneRuleConfigByKey;\n    this.isFocusOnNewCode = isFocusOnNewCode;\n    this.languageSpecificRequirements = languageSpecificRequirements;\n    this.automaticAnalysisEnabled = automaticAnalysisEnabled;\n    this.telemetryMigration = telemetryMigration;\n    this.logLevel = logLevel;\n  }\n\n  public ClientConstantInfoDto getClientConstantInfo() {\n    return clientConstantInfo;\n  }\n\n  public TelemetryClientConstantAttributesDto getTelemetryConstantAttributes() {\n    return telemetryConstantAttributes;\n  }\n\n  public HttpConfigurationDto getHttpConfiguration() {\n    return httpConfiguration;\n  }\n\n  @CheckForNull\n  public SonarCloudAlternativeEnvironmentDto getAlternativeSonarCloudEnvironment() {\n    return alternativeSonarCloudEnvironment;\n  }\n\n  public Set<BackendCapability> getBackendCapabilities() {\n    return backendCapabilities;\n  }\n\n  public Path getStorageRoot() {\n    return storageRoot;\n  }\n\n  @CheckForNull\n  public Path getWorkDir() {\n    return workDir;\n  }\n\n  public Set<Path> getEmbeddedPluginPaths() {\n    return embeddedPluginPaths != null ? embeddedPluginPaths : Set.of();\n  }\n\n  public Map<String, Path> getConnectedModeEmbeddedPluginPathsByKey() {\n    return connectedModeEmbeddedPluginPathsByKey != null ? connectedModeEmbeddedPluginPathsByKey : Map.of();\n  }\n\n  public Set<Language> getEnabledLanguagesInStandaloneMode() {\n    return enabledLanguagesInStandaloneMode != null ? enabledLanguagesInStandaloneMode : Set.of();\n  }\n\n  public Set<Language> getExtraEnabledLanguagesInConnectedMode() {\n    return extraEnabledLanguagesInConnectedMode != null ? extraEnabledLanguagesInConnectedMode : Set.of();\n  }\n\n  public List<SonarQubeConnectionConfigurationDto> getSonarQubeConnections() {\n    return sonarQubeConnections != null ? sonarQubeConnections : List.of();\n  }\n\n  public List<SonarCloudConnectionConfigurationDto> getSonarCloudConnections() {\n    return sonarCloudConnections != null ? sonarCloudConnections : List.of();\n  }\n\n  @CheckForNull\n  public String getSonarlintUserHome() {\n    return sonarlintUserHome;\n  }\n\n  public Map<String, StandaloneRuleConfigDto> getStandaloneRuleConfigByKey() {\n    return standaloneRuleConfigByKey != null ? standaloneRuleConfigByKey : Map.of();\n  }\n\n  public boolean isFocusOnNewCode() {\n    return isFocusOnNewCode;\n  }\n\n  @Nullable\n  public LanguageSpecificRequirements getLanguageSpecificRequirements() {\n    return languageSpecificRequirements;\n  }\n\n  public boolean isAutomaticAnalysisEnabled() {\n    return automaticAnalysisEnabled;\n  }\n\n  public Set<String> getDisabledPluginKeysForAnalysis() {\n    return disabledPluginKeysForAnalysis != null ? disabledPluginKeysForAnalysis : Set.of();\n  }\n\n  @CheckForNull\n  public TelemetryMigrationDto getTelemetryMigration() {\n    return telemetryMigration;\n  }\n\n  public LogLevel getLogLevel() {\n    return logLevel;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/initialize/JsTsRequirementsDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize;\n\nimport java.nio.file.Path;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class JsTsRequirementsDto {\n  @Nullable\n  private final Path clientNodeJsPath;\n  @Nullable\n  private final Path bundlePath;\n\n  public JsTsRequirementsDto(@Nullable Path clientNodeJsPath, @Nullable Path bundlePath) {\n    this.clientNodeJsPath = clientNodeJsPath;\n    this.bundlePath = bundlePath;\n  }\n\n  @CheckForNull\n  public Path getClientNodeJsPath() {\n    return clientNodeJsPath;\n  }\n\n  @CheckForNull\n  public Path getBundlePath() {\n    return bundlePath;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/initialize/LanguageSpecificRequirements.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class LanguageSpecificRequirements {\n  private final JsTsRequirementsDto jsTsRequirements;\n  private final OmnisharpRequirementsDto omnisharpRequirements;\n  private final boolean omnisharpDownloadEnabled;\n\n  @Deprecated(since = \"11.2\", forRemoval = true)\n  public LanguageSpecificRequirements(@Nullable JsTsRequirementsDto jsTsRequirements, @Nullable OmnisharpRequirementsDto omnisharpRequirements) {\n    this.jsTsRequirements = jsTsRequirements;\n    this.omnisharpRequirements = omnisharpRequirements;\n    this.omnisharpDownloadEnabled = omnisharpRequirements != null;\n  }\n\n  public LanguageSpecificRequirements(@Nullable JsTsRequirementsDto jsTsRequirements, boolean omnisharpDownloadEnabled) {\n    this.jsTsRequirements = jsTsRequirements;\n    this.omnisharpRequirements = null;\n    this.omnisharpDownloadEnabled = omnisharpDownloadEnabled;\n  }\n\n  @CheckForNull\n  public JsTsRequirementsDto getJsTsRequirements() {\n    return jsTsRequirements;\n  }\n\n  @CheckForNull\n  @Deprecated(since = \"11.2\")\n  public OmnisharpRequirementsDto getOmnisharpRequirements() {\n    return omnisharpRequirements;\n  }\n\n  public boolean isOmnisharpDownloadEnabled() {\n    return omnisharpDownloadEnabled;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/initialize/OmnisharpRequirementsDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize;\n\nimport java.nio.file.Path;\nimport javax.annotation.Nullable;\n\n/**\n * @deprecated this is now entirely managed by SLCORE, ignoring values provided by clients.\n */\n@Deprecated(since = \"11.2\", forRemoval = true)\npublic class OmnisharpRequirementsDto {\n  /**\n   * Path to the Mono OmniSharp runtime distribution. May be null when SLCORE downloads OmniSharp automatically.\n   */\n  @Nullable\n  private final Path monoDistributionPath;\n  /**\n   * Path to the .NET 6 OmniSharp runtime distribution. May be null when SLCORE downloads OmniSharp automatically.\n   */\n  @Nullable\n  private final Path dotNet6DistributionPath;\n  /**\n   * Path to the .NET Framework 4.7.2 OmniSharp runtime distribution. May be null when SLCORE downloads OmniSharp automatically.\n   */\n  @Nullable\n  private final Path dotNet472DistributionPath;\n  private final Path ossAnalyzerPath;\n  private final Path enterpriseAnalyzerPath;\n\n  public OmnisharpRequirementsDto(@Nullable Path monoDistributionPath, @Nullable Path dotNet6DistributionPath, @Nullable Path dotNet472DistributionPath,\n    Path ossAnalyzerPath, Path enterpriseAnalyzerPath) {\n    this.monoDistributionPath = monoDistributionPath;\n    this.dotNet6DistributionPath = dotNet6DistributionPath;\n    this.dotNet472DistributionPath = dotNet472DistributionPath;\n    this.ossAnalyzerPath = ossAnalyzerPath;\n    this.enterpriseAnalyzerPath = enterpriseAnalyzerPath;\n  }\n\n  @Nullable\n  public Path getMonoDistributionPath() {\n    return monoDistributionPath;\n  }\n\n  @Nullable\n  public Path getDotNet6DistributionPath() {\n    return dotNet6DistributionPath;\n  }\n\n  @Nullable\n  public Path getDotNet472DistributionPath() {\n    return dotNet472DistributionPath;\n  }\n\n  public Path getOssAnalyzerPath() {\n    return ossAnalyzerPath;\n  }\n\n  public Path getEnterpriseAnalyzerPath() {\n    return enterpriseAnalyzerPath;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/initialize/SonarCloudAlternativeEnvironmentDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize;\n\nimport java.util.Map;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion;\n\n/** This contains the alternative environment information for SonarQube Cloud with both the EU and US region */\npublic class SonarCloudAlternativeEnvironmentDto {\n  private final Map<SonarCloudRegion, SonarQubeCloudRegionDto> alternativeRegionUris;\n\n  public SonarCloudAlternativeEnvironmentDto(Map<SonarCloudRegion, SonarQubeCloudRegionDto> alternateRegionUris) {\n    this.alternativeRegionUris = alternateRegionUris;\n  }\n\n  public Map<SonarCloudRegion, SonarQubeCloudRegionDto> getAlternateRegionUris() {\n    return alternativeRegionUris;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/initialize/SonarQubeCloudRegionDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize;\n\nimport java.net.URI;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\n/** This is used to configure the required URIs for a SonarQube Cloud Region */\npublic class SonarQubeCloudRegionDto {\n  @Nullable\n  private final URI uri;\n  @Nullable\n  private final URI apiUri;\n  @Nullable\n  private final URI webSocketsEndpointUri;\n\n  /**\n   *  All of the URIs can be null!\n   *  \n   *  @param uri the base URI, e.g. https://sonarcloud.io\n   *  @param apiUri the base URI of new endpoints, e.g. https://api.sonarcloud.io. Must be specified because for some env it cannot be deduced from the base URI (e.g. Dev)\n   */\n  public SonarQubeCloudRegionDto(@Nullable URI uri, @Nullable URI apiUri, @Nullable URI webSocketsEndpointUri) {\n    this.uri = uri;\n    this.apiUri = apiUri;\n    this.webSocketsEndpointUri = webSocketsEndpointUri;\n  }\n\n  @CheckForNull\n  public URI getUri() {\n    return uri;\n  }\n\n  @CheckForNull\n  public URI getApiUri() {\n    return apiUri;\n  }\n\n  @CheckForNull\n  public URI getWebSocketsEndpointUri() {\n    return webSocketsEndpointUri;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/initialize/SslConfigurationDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize;\n\nimport java.nio.file.Path;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class SslConfigurationDto {\n\n  public static SslConfigurationDto defaultConfig() {\n    return new SslConfigurationDto(null, null, null, null, null, null);\n  }\n\n  private final Path trustStorePath;\n  private final String trustStorePassword;\n  private final String trustStoreType;\n  private final Path keyStorePath;\n  private final String keyStorePassword;\n  private final String keyStoreType;\n\n  public SslConfigurationDto(@Nullable Path trustStorePath, @Nullable String trustStorePassword, @Nullable String trustStoreType, @Nullable Path keyStorePath,\n    @Nullable String keyStorePassword, @Nullable String keyStoreType) {\n    this.trustStorePath = trustStorePath;\n    this.trustStorePassword = trustStorePassword;\n    this.trustStoreType = trustStoreType;\n    this.keyStorePath = keyStorePath;\n    this.keyStorePassword = keyStorePassword;\n    this.keyStoreType = keyStoreType;\n  }\n\n  @CheckForNull\n  public Path getTrustStorePath() {\n    return trustStorePath;\n  }\n\n  @CheckForNull\n  public String getTrustStorePassword() {\n    return trustStorePassword;\n  }\n\n  @CheckForNull\n  public String getTrustStoreType() {\n    return trustStoreType;\n  }\n\n  @CheckForNull\n  public Path getKeyStorePath() {\n    return keyStorePath;\n  }\n\n  @CheckForNull\n  public String getKeyStorePassword() {\n    return keyStorePassword;\n  }\n\n  @CheckForNull\n  public String getKeyStoreType() {\n    return keyStoreType;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/initialize/TelemetryClientConstantAttributesDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize;\n\nimport java.util.Map;\nimport javax.annotation.Nullable;\n\npublic class TelemetryClientConstantAttributesDto {\n\n  private final String productKey;\n  private final String productName;\n  private final String productVersion;\n  private final String ideVersion;\n  private final Map<String, Object> additionalAttributes;\n\n  public TelemetryClientConstantAttributesDto(String productKey, String productName, String productVersion, String ideVersion,\n    @Nullable Map<String, Object> additionalAttributes) {\n    this.productKey = productKey;\n    this.productName = productName;\n    this.productVersion = productVersion;\n    this.ideVersion = ideVersion;\n    this.additionalAttributes = additionalAttributes;\n  }\n\n  public String getProductKey() {\n    return productKey;\n  }\n\n  public String getProductName() {\n    return productName;\n  }\n\n  public String getProductVersion() {\n    return productVersion;\n  }\n\n  public String getIdeVersion() {\n    return ideVersion;\n  }\n\n  public Map<String, Object> getAdditionalAttributes() {\n    return additionalAttributes != null ? additionalAttributes : Map.of();\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/initialize/TelemetryMigrationDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize;\n\nimport java.time.OffsetDateTime;\n\npublic class TelemetryMigrationDto {\n  private final OffsetDateTime installTime;\n  private final long numUseDays;\n  private final boolean isEnabled;\n\n  public TelemetryMigrationDto(OffsetDateTime installTime, long numUseDays, boolean isEnabled) {\n    this.installTime = installTime;\n    this.numUseDays = numUseDays;\n    this.isEnabled = isEnabled;\n  }\n\n  public OffsetDateTime getInstallTime() {\n    return installTime;\n  }\n\n  public long getNumUseDays() {\n    return numUseDays;\n  }\n\n  public boolean isEnabled() {\n    return isEnabled;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/initialize/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/issue/AddIssueCommentParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.issue;\n\npublic class AddIssueCommentParams {\n\n  private final String configurationScopeId;\n  private final String issueKey;\n  private final String text;\n\n  public AddIssueCommentParams(String configurationScopeId, String issueKey, String text) {\n    this.configurationScopeId = configurationScopeId;\n    this.issueKey = issueKey;\n    this.text = text;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public String getIssueKey() {\n    return issueKey;\n  }\n\n  public String getText() {\n    return text;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/issue/ChangeIssueStatusParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.issue;\n\npublic class ChangeIssueStatusParams {\n\n  private final String configurationScopeId;\n  private final String issueKey;\n  private final ResolutionStatus newStatus;\n  private final boolean isTaintIssue;\n\n  public ChangeIssueStatusParams(String configurationScopeId, String issueKey, ResolutionStatus newStatus,\n    boolean isTaintIssue) {\n    this.configurationScopeId = configurationScopeId;\n    this.issueKey = issueKey;\n    this.newStatus = newStatus;\n    this.isTaintIssue = isTaintIssue;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public String getIssueKey() {\n    return issueKey;\n  }\n\n  public ResolutionStatus getNewStatus() {\n    return newStatus;\n  }\n\n  public boolean isTaintIssue() {\n    return isTaintIssue;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/issue/CheckAnticipatedStatusChangeSupportedParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.issue;\n\npublic class CheckAnticipatedStatusChangeSupportedParams {\n  private final String configScopeId;\n\n  public CheckAnticipatedStatusChangeSupportedParams(String configScopeId) {\n    this.configScopeId = configScopeId;\n  }\n\n  public String getConfigScopeId() {\n    return configScopeId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/issue/CheckAnticipatedStatusChangeSupportedResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.issue;\n\npublic class CheckAnticipatedStatusChangeSupportedResponse {\n  private final boolean supported;\n\n  public CheckAnticipatedStatusChangeSupportedResponse(boolean supported) {\n    this.supported = supported;\n  }\n\n  public boolean isSupported() {\n    return supported;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/issue/CheckStatusChangePermittedParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.issue;\n\npublic class CheckStatusChangePermittedParams {\n  private final String connectionId;\n  private final String issueKey;\n\n  public CheckStatusChangePermittedParams(String connectionId, String issueKey) {\n    this.connectionId = connectionId;\n    this.issueKey = issueKey;\n  }\n\n  public String getConnectionId() {\n    return connectionId;\n  }\n\n  public String getIssueKey() {\n    return issueKey;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/issue/CheckStatusChangePermittedResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.issue;\n\nimport java.util.List;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class CheckStatusChangePermittedResponse {\n  private final boolean permitted;\n  private final String notPermittedReason;\n  private final List<ResolutionStatus> allowedStatuses;\n\n  public CheckStatusChangePermittedResponse(boolean permitted, @Nullable String notPermittedReason, List<ResolutionStatus> allowedStatuses) {\n    this.permitted = permitted;\n    this.notPermittedReason = notPermittedReason;\n    this.allowedStatuses = allowedStatuses;\n  }\n\n  public boolean isPermitted() {\n    return permitted;\n  }\n\n  @CheckForNull\n  public String getNotPermittedReason() {\n    return notPermittedReason;\n  }\n\n  public List<ResolutionStatus> getAllowedStatuses() {\n    return allowedStatuses;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/issue/EffectiveIssueDetailsDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.issue;\n\nimport com.google.gson.annotations.JsonAdapter;\nimport java.util.Collection;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.EitherRuleDescriptionAdapterFactory;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.EitherStandardOrMQRModeAdapterFactory;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.EffectiveRuleParamDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleMonolithicDescriptionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleSplitDescriptionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.MQRModeDetails;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.StandardModeDetails;\n\npublic class EffectiveIssueDetailsDto {\n  private final String ruleKey;\n  private final String name;\n  private final Language language;\n  private final VulnerabilityProbability vulnerabilityProbability;\n  @JsonAdapter(EitherRuleDescriptionAdapterFactory.class)\n  private final Either<RuleMonolithicDescriptionDto, RuleSplitDescriptionDto> description;\n  private final Collection<EffectiveRuleParamDto> params;\n  @JsonAdapter(EitherStandardOrMQRModeAdapterFactory.class)\n  private final Either<StandardModeDetails, MQRModeDetails> severityDetails;\n  private final String ruleDescriptionContextKey;\n\n  public EffectiveIssueDetailsDto(String ruleKey, String name, Language language, @Nullable VulnerabilityProbability vulnerabilityProbability,\n    Either<RuleMonolithicDescriptionDto, RuleSplitDescriptionDto> description,\n    Collection<EffectiveRuleParamDto> params, Either<StandardModeDetails, MQRModeDetails> severityDetails,\n    @Nullable String ruleDescriptionContextKey) {\n    this.ruleKey = ruleKey;\n    this.name = name;\n    this.language = language;\n    this.vulnerabilityProbability = vulnerabilityProbability;\n    this.description = description;\n    this.params = params;\n    this.severityDetails = severityDetails;\n    this.ruleDescriptionContextKey = ruleDescriptionContextKey;\n  }\n\n  public Either<StandardModeDetails, MQRModeDetails> getSeverityDetails() {\n    return severityDetails;\n  }\n\n  @CheckForNull\n  public String getRuleDescriptionContextKey() {\n    return ruleDescriptionContextKey;\n  }\n\n  public String getRuleKey() {\n    return ruleKey;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public Language getLanguage() {\n    return language;\n  }\n\n  public VulnerabilityProbability getVulnerabilityProbability() {\n    return vulnerabilityProbability;\n  }\n\n  public Either<RuleMonolithicDescriptionDto, RuleSplitDescriptionDto> getDescription() {\n    return description;\n  }\n\n  public Collection<EffectiveRuleParamDto> getParams() {\n    return params;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/issue/GetEffectiveIssueDetailsParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.issue;\n\nimport java.util.UUID;\n\npublic class GetEffectiveIssueDetailsParams {\n  private final String configurationScopeId;\n  private final UUID issueId;\n\n  public GetEffectiveIssueDetailsParams(String configurationScopeId, UUID issueId) {\n    this.configurationScopeId = configurationScopeId;\n    this.issueId = issueId;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public UUID getIssueId() {\n    return issueId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/issue/GetEffectiveIssueDetailsResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.issue;\n\npublic class GetEffectiveIssueDetailsResponse {\n  private final EffectiveIssueDetailsDto details;\n\n  public GetEffectiveIssueDetailsResponse(EffectiveIssueDetailsDto details) {\n    this.details = details;\n  }\n\n  public EffectiveIssueDetailsDto getDetails() {\n    return details;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/issue/IssueRpcService.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.issue;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonRequest;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonSegment;\n\n@JsonSegment(\"issue\")\npublic interface IssueRpcService {\n\n  /**\n   * <p> It changes a status of an issue that is existing on the server or local-only. In detail, it is responsible for:\n   * <ul>\n   *   <li>Changes the status of an issue (identified by {@link ChangeIssueStatusParams#getIssueKey()} )}</li>\n   *   <li>Updates the issue status in the local storage</li>\n   *   <li>In case of a local-only issue, it stores the issue in the xodus database for local-only issues</li>\n   *   <li>Increments the 'issue.status_changed_count' counter for telemetry when issue exists in the server</li>\n   * </ul>\n   *</p>\n   * It silently deals with the following conditions:\n   * <ul>\n   *   <li>the provided configuration scope (identified by {@link ChangeIssueStatusParams#getConfigurationScopeId()} is unknown</li>\n   *   <li>the connection bound to the configuration scope is unknown</li>\n   *   <li>the issueKey is not found in the local storage</li>\n   * </ul>\n   * In those cases a completed future will be returned.\n   * </p>\n   * <p>\n   * It returns a failed future if:\n   * <ul>\n   *   <li>the issue is not found either on the server or in the local-only storage for issues</li>\n   *   <li>there is a communication problem with the server: network outage, server is down, unauthorized</li>\n   * </ul>\n   * </p>\n   */\n  @JsonRequest\n  CompletableFuture<Void> changeStatus(ChangeIssueStatusParams params);\n\n  /**\n   * <p>\n   * Adds a new comment to an existing issue (identified by {@link AddIssueCommentParams#getIssueKey()})\n   * </p>\n   * <p>\n   * If no binding is found for the provided configuration scope (identified by {@link AddIssueCommentParams#getConfigurationScopeId()})\n   * then returns a future completed with <code>null</code>\n   * </p>\n   * <p>\n   * It returns a failed future if:\n   * <ul>\n   *   <li>there is a communication problem with the server: network outage, server is down, unauthorized</li>\n   * </ul>\n   * </p>\n   */\n  @JsonRequest\n  CompletableFuture<Void> addComment(AddIssueCommentParams params);\n\n  /**\n   * Checks if the anticipated transitions are supported. They are allowed in one case:\n   * <ul>\n   *   <li>If the configScopeId is bound, its connection should link to a SonarQube 10.2+ instance</li>\n   * <p>\n   * This method will fail if:\n   * <ul>\n   *   <li>the configScopeId provided as a parameter has no binding</li>\n   *   <li>the configScopeId provided loses its binding in the middle of the function call</li>\n   *   <li>there is a communication problem with the server: network outage, server is down, unauthorized</li>\n   * </ul>\n   * In those cases, a failed future will be returned.\n   * </p>\n   */\n  @JsonRequest\n  CompletableFuture<CheckAnticipatedStatusChangeSupportedResponse> checkAnticipatedStatusChangeSupported(CheckAnticipatedStatusChangeSupportedParams params);\n\n  /**\n   * Checks if the user can change the issue status. They are allowed in two cases:\n   * <ul>\n   *   <li>If it is a server-matched issue, users need the 'Administer Issues' permission</li>\n   *   <li>If it is a local-only issue, the provided connection should link to a SonarQube 10.2+ instance</li>\n   * </ul>Also returns the list of allowed statuses.\n   * <p>\n   * This method will fail if:\n   * <ul>\n   *   <li>the connectionId provided as a parameter is unknown</li>\n   *   <li>there is a communication problem with the server: network outage, server is down, unauthorized</li>\n   * </ul>\n   * In those cases, a failed future will be returned.\n   * </p>\n   */\n  @JsonRequest\n  CompletableFuture<CheckStatusChangePermittedResponse> checkStatusChangePermitted(CheckStatusChangePermittedParams params);\n\n  /**\n   * Reopens the issue, two cases are possible:\n   * <ul>\n   *   <li>If it is a server-matched issue, it is reopened on the server</li>\n   *   <li>If it is a local-only issue, it is deleted from the local storage</li>\n   * </ul>\n   * @return true if issue was found and actually reopened on the server or deleted locally, false otherwise\n   */\n  @JsonRequest\n  CompletableFuture<ReopenIssueResponse> reopenIssue(ReopenIssueParams params);\n\n  /**\n   * Notifying server that anticipated issues for given file should be removed and removes them from local storage\n   * @return true if entity for file was found and actually deleted and false otherwise\n   */\n  @JsonRequest\n  CompletableFuture<ReopenAllIssuesForFileResponse> reopenAllIssuesForFile(ReopenAllIssuesForFileParams params);\n\n  @JsonRequest\n  CompletableFuture<GetEffectiveIssueDetailsResponse> getEffectiveIssueDetails(GetEffectiveIssueDetailsParams params);\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/issue/ReopenAllIssuesForFileParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.issue;\n\nimport java.nio.file.Path;\n\npublic class ReopenAllIssuesForFileParams {\n\n  private final String configurationScopeId;\n  private final Path ideRelativePath;\n\n  public ReopenAllIssuesForFileParams(String configurationScopeId, Path ideRelativePath) {\n    this.configurationScopeId = configurationScopeId;\n    this.ideRelativePath = ideRelativePath;\n  }\n\n  public Path getIdeRelativePath() {\n    return ideRelativePath;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/issue/ReopenAllIssuesForFileResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.issue;\n\npublic class ReopenAllIssuesForFileResponse {\n\n  private final boolean success;\n\n  public ReopenAllIssuesForFileResponse(boolean success) {\n    this.success = success;\n  }\n\n  public boolean isSuccess() {\n    return success;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/issue/ReopenIssueParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.issue;\n\npublic class ReopenIssueParams {\n\n  private final String configurationScopeId;\n  private final String issueId;\n  private final boolean isTaintIssue;\n\n  public ReopenIssueParams(String configurationScopeId, String issueId, boolean isTaintIssue) {\n    this.configurationScopeId = configurationScopeId;\n    this.issueId = issueId;\n    this.isTaintIssue = isTaintIssue;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public String getIssueId() {\n    return issueId;\n  }\n\n  public boolean isTaintIssue() {\n    return isTaintIssue;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/issue/ReopenIssueResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.issue;\n\npublic class ReopenIssueResponse {\n\n  private final boolean success;\n\n  public ReopenIssueResponse(boolean success) {\n    this.success = success;\n  }\n\n  public boolean isSuccess() {\n    return success;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/issue/ResolutionStatus.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.issue;\n\npublic enum ResolutionStatus {\n  ACCEPT,\n  WONT_FIX,\n  FALSE_POSITIVE\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/issue/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.issue;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/labs/IdeLabsRpcService.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.labs;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonRequest;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonSegment;\n\n@JsonSegment(\"labs\")\npublic interface IdeLabsRpcService {\n  /**\n   * Allows a user to join the IDE Labs program.\n   *\n   * @param params the parameters containing the user's email address and requesting IDE name\n   * @return a CompletableFuture that resolves to a JoinIdeLabsProgramResponse\n   * <p>\n   * <p>JoinIdeLabsProgramResponse.isSuccess() will be false if email validation failed on the server side, or if an unexpected error occurred.\n   * If isSuccess() is false, JoinIdeLabsProgramResponse.getMessage() contains a human-readable explanation.</p>\n   */\n  @JsonRequest\n  CompletableFuture<JoinIdeLabsProgramResponse> joinIdeLabsProgram(JoinIdeLabsProgramParams params);\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/labs/JoinIdeLabsProgramParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.labs;\n\npublic class JoinIdeLabsProgramParams {\n  private final String email;\n  private final String ide;\n\n  public JoinIdeLabsProgramParams(String email, String ide) {\n    this.email = email;\n    this.ide = ide;\n  }\n\n  public String getEmail() {\n    return email;\n  }\n\n  public String getIde() {\n    return ide;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/labs/JoinIdeLabsProgramResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.labs;\n\nimport javax.annotation.Nullable;\n\npublic class JoinIdeLabsProgramResponse {\n  private final boolean success;\n  @Nullable\n  private final String message;\n\n  public JoinIdeLabsProgramResponse(boolean success, @Nullable String message) {\n    this.success = success;\n    this.message = message;\n  }\n\n  public boolean isSuccess() {\n    return success;\n  }\n\n  @Nullable\n  public String getMessage() {\n    return message;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/labs/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.labs;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/log/LogLevel.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.log;\n\npublic enum LogLevel {\n  OFF, ERROR, WARN, INFO, DEBUG, TRACE\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/log/LogRpcService.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.log;\n\nimport org.eclipse.lsp4j.jsonrpc.services.JsonNotification;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonSegment;\n\n@JsonSegment(\"log\")\npublic interface LogRpcService {\n  @JsonNotification\n  void setLogLevel(SetLogLevelParams params);\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/log/SetLogLevelParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.log;\n\npublic class SetLogLevelParams {\n  private final LogLevel newLevel;\n\n  public SetLogLevelParams(LogLevel newLevel) {\n    this.newLevel = newLevel;\n  }\n\n  public LogLevel getNewLevel() {\n    return newLevel;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/log/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.log;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/newcode/GetNewCodeDefinitionParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.newcode;\n\npublic class GetNewCodeDefinitionParams {\n\n  String configScopeId;\n\n  public GetNewCodeDefinitionParams(String configurationScopeId) {\n    this.configScopeId = configurationScopeId;\n  }\n\n  public String getConfigScopeId() {\n    return configScopeId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/newcode/GetNewCodeDefinitionResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.newcode;\n\npublic class GetNewCodeDefinitionResponse {\n\n  private final String description;\n\n  private final boolean isSupported;\n\n  public GetNewCodeDefinitionResponse(String description, boolean isSupported) {\n    this.description = description;\n    this.isSupported = isSupported;\n  }\n\n  public String getDescription() {\n    return description;\n  }\n\n  public boolean isSupported() {\n    return isSupported;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/newcode/NewCodeRpcService.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.newcode;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonNotification;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonRequest;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonSegment;\n\n@JsonSegment(\"newCode\")\npublic interface NewCodeRpcService {\n\n  @JsonRequest\n  CompletableFuture<GetNewCodeDefinitionResponse> getNewCodeDefinition(GetNewCodeDefinitionParams params);\n\n  /**\n   * Clients are expected to call this method when the \"focus on new code\" setting is modified by the user. The implementation will update the telemetry accordingly.\n   */\n  @JsonNotification\n  void didToggleFocus();\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/newcode/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.newcode;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/plugin/ArtifactSourceDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin;\n\n/**\n * Describes where an analyzer artifact was obtained from.\n */\npublic enum ArtifactSourceDto {\n\n  /** The artifact is bundled with the IDE extension. */\n  EMBEDDED(\"SonarQube for IDE\"),\n\n  /** The artifact was downloaded on demand from an external source (e.g. binaries.sonarsource.com). */\n  ON_DEMAND(\"SonarQube for IDE\"),\n\n  /** The artifact was synchronized from a SonarQube Server connection. */\n  SONARQUBE_SERVER(\"SonarQube Server\"),\n\n  /** The artifact was synchronized from a SonarQube Cloud connection. */\n  SONARQUBE_CLOUD(\"SonarQube Cloud\");\n\n  private final String label;\n\n  ArtifactSourceDto(String label) {\n    this.label = label;\n  }\n\n  public String getLabel() {\n    return label;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/plugin/GetPluginStatusesParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin;\n\nimport javax.annotation.Nullable;\n\npublic class GetPluginStatusesParams {\n\n  @Nullable\n  private final String configurationScopeId;\n\n  /**\n   * @param configurationScopeId the ID of the configuration scope used to resolve a bound connection, or\n   *                             {@code null} to return the standalone (embedded) statuses\n   */\n  public GetPluginStatusesParams(@Nullable String configurationScopeId) {\n    this.configurationScopeId = configurationScopeId;\n  }\n\n  @Nullable\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/plugin/GetPluginStatusesResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin;\n\nimport java.util.List;\n\n/**\n * Response to {@link PluginRpcService#getPluginStatuses(GetPluginStatusesParams)}.\n */\npublic class GetPluginStatusesResponse {\n\n  private final List<PluginStatusDto> pluginStatuses;\n\n  /**\n   * @param pluginStatuses the full list of analyzer plugin statuses, one entry per language\n   *                       known to the backend (including unsupported ones)\n   */\n  public GetPluginStatusesResponse(List<PluginStatusDto> pluginStatuses) {\n    this.pluginStatuses = pluginStatuses;\n  }\n\n  public List<PluginStatusDto> getPluginStatuses() {\n    return pluginStatuses;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/plugin/PluginRpcService.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonRequest;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonSegment;\n\n@JsonSegment(\"plugin\")\npublic interface PluginRpcService {\n\n  /**\n   * Returns the status of all known analyzer plugins, including unsupported ones.\n   *\n   * <p>This method is intended for initial population of the \"Supported Languages\" panel.\n   * The returned list contains one entry per language known to the backend, covering:\n   * <ul>\n   *   <li>the human-readable plugin/language name</li>\n   *   <li>the current lifecycle state (active, synced, downloading, failed, premium, unsupported)</li>\n   *   <li>the source of the plugin artifact (embedded, on-demand, SQS, SQC), if available</li>\n   *   <li>the version currently in use, if the plugin is loaded</li>\n   *   <li>the overridden version (a locally present version superseded by a sync), if applicable</li>\n   * </ul>\n   *\n   * <p>When {@link GetPluginStatusesParams#getConfigurationScopeId()} is provided, the response reflects\n   * the plugin landscape for the connection bound to that scope (connected mode).\n   * When it is {@code null}, only embedded/standalone plugins are considered.\n   */\n  @JsonRequest\n  CompletableFuture<GetPluginStatusesResponse> getPluginStatuses(GetPluginStatusesParams params);\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/plugin/PluginStateDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin;\n\n/**\n * Represents the current state of an analyzer plugin as observed by the backend.\n */\npublic enum PluginStateDto {\n\n  /** The plugin is loaded and ready for analysis. */\n  ACTIVE(\"Active\"),\n\n  /** The plugin was downloaded from a SonarQube Server or SonarQube Cloud connection. */\n  SYNCED(\"Synced\"),\n\n  /** The plugin is currently being downloaded. */\n  DOWNLOADING(\"Downloading…\"),\n\n  /** The plugin failed to load or is otherwise unavailable. */\n  FAILED(\"Failed\"),\n\n  /** The plugin is available only in connected mode (premium feature). */\n  PREMIUM(\"Premium\"),\n\n  /** The plugin is not supported in the current IDE or platform. */\n  UNSUPPORTED(\"Unsupported\");\n\n  private final String label;\n\n  PluginStateDto(String label) {\n    this.label = label;\n  }\n\n  public String getLabel() {\n    return label;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/plugin/PluginStatusDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin;\n\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\n\npublic class PluginStatusDto {\n\n  @Nullable\n  private final Language language;\n  @Nullable\n  private final String pluginName;\n  private final PluginStateDto state;\n  @Nullable\n  private final ArtifactSourceDto source;\n  @Nullable\n  private final String actualVersion;\n  @Nullable\n  private final String overriddenVersion;\n  @Nullable\n  private final String serverVersion;\n\n  /**\n   * @param language          language that this plugin provides analysis for\n   * @param pluginName        human-readable name of the language/analyzer (e.g. \"Java\", \"C/C++/Objective-C\")\n   * @param state             current lifecycle state of the plugin in the backend\n   * @param source            where the plugin artifact came from; {@code null} when the plugin is not available\n   * @param actualVersion     version of the plugin that is currently in use; {@code null} when the plugin is not loaded\n   * @param overriddenVersion a local plugin version that was superseded by the one obtained via SQS/SQC sync, if any;\n   *                          {@code null} when no override is in effect\n   * @param serverVersion     version of the SonarQube Server that provided this plugin (e.g. \"10.8.1\");\n   *                          {@code null} for non-server sources (embedded, cloud, unavailable)\n   */\n  public PluginStatusDto(@Nullable Language language, @Nullable String pluginName, PluginStateDto state, @Nullable ArtifactSourceDto source,\n    @Nullable String actualVersion, @Nullable String overriddenVersion, @Nullable String serverVersion) {\n    this.language = language;\n    this.pluginName = pluginName;\n    this.state = state;\n    this.source = source;\n    this.actualVersion = actualVersion;\n    this.overriddenVersion = overriddenVersion;\n    this.serverVersion = serverVersion;\n  }\n\n  @Nullable\n  public Language getLanguage() {\n    return language;\n  }\n\n  @Nullable\n  public String getPluginName() {\n    return pluginName;\n  }\n\n  public PluginStateDto getState() {\n    return state;\n  }\n\n  @Nullable\n  public ArtifactSourceDto getSource() {\n    return source;\n  }\n\n  @Nullable\n  public String getActualVersion() {\n    return actualVersion;\n  }\n\n  @Nullable\n  public String getOverriddenVersion() {\n    return overriddenVersion;\n  }\n\n  @Nullable\n  public String getServerVersion() {\n    return serverVersion;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/plugin/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/progress/CancelTaskParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.progress;\n\npublic class CancelTaskParams {\n  private final String taskId;\n\n  public CancelTaskParams(String taskId) {\n    this.taskId = taskId;\n  }\n\n  public String getTaskId() {\n    return taskId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/progress/TaskProgressRpcService.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.progress;\n\nimport org.eclipse.lsp4j.jsonrpc.services.JsonNotification;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonSegment;\n\n@JsonSegment(\"taskProgress\")\npublic interface TaskProgressRpcService {\n\n  @JsonNotification\n  void cancelTask(CancelTaskParams params);\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/progress/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.progress;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/remediation/aicodefix/AiCodeFixRpcService.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.remediation.aicodefix;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonRequest;\n\npublic interface AiCodeFixRpcService {\n  /**\n   * Throws an exception if the issue is not fixable:\n   * <ul>\n   *   <li>the configuration scope is not bound</li>\n   *   <li>the configuration scope is bound to SonarQube Server</li>\n   *   <li>the issue is a file-level issue</li>\n   *   <li>the issue rule is not supported</li>\n   * </ul>\n   */\n  @JsonRequest\n  CompletableFuture<SuggestFixResponse> suggestFix(SuggestFixParams params);\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/remediation/aicodefix/SuggestFixChangeDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.remediation.aicodefix;\n\npublic class SuggestFixChangeDto {\n  private final int startLine;\n  private final int endLine;\n  private final String newCode;\n\n  public SuggestFixChangeDto(int startLine, int endLine, String newCode) {\n    this.startLine = startLine;\n    this.endLine = endLine;\n    this.newCode = newCode;\n  }\n\n  public int getStartLine() {\n    return startLine;\n  }\n\n  public int getEndLine() {\n    return endLine;\n  }\n\n  public String getNewCode() {\n    return newCode;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/remediation/aicodefix/SuggestFixParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.remediation.aicodefix;\n\nimport java.util.UUID;\n\npublic class SuggestFixParams {\n  private final String configurationScopeId;\n  private final UUID issueId;\n\n  public SuggestFixParams(String configurationScopeId, UUID issueId) {\n    this.configurationScopeId = configurationScopeId;\n    this.issueId = issueId;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public UUID getIssueId() {\n    return issueId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/remediation/aicodefix/SuggestFixResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.remediation.aicodefix;\n\nimport java.util.List;\nimport java.util.UUID;\n\npublic class SuggestFixResponse {\n  private final UUID id;\n  private final String explanation;\n  private final List<SuggestFixChangeDto> changes;\n\n  public SuggestFixResponse(UUID id, String explanation, List<SuggestFixChangeDto> changes) {\n    this.id = id;\n    this.explanation = explanation;\n    this.changes = changes;\n  }\n\n  public UUID getId() {\n    return id;\n  }\n\n  public String getExplanation() {\n    return explanation;\n  }\n\n  public List<SuggestFixChangeDto> getChanges() {\n    return changes;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/remediation/aicodefix/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.remediation.aicodefix;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/EffectiveRuleDetailsDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\nimport com.google.gson.annotations.JsonAdapter;\nimport java.util.Collection;\nimport java.util.List;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.EitherRuleDescriptionAdapterFactory;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.MQRModeDetails;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.RuleType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.StandardModeDetails;\n\npublic class EffectiveRuleDetailsDto {\n  private final String key;\n  private final String name;\n  private final Either<StandardModeDetails, MQRModeDetails> severityDetails;\n  private final Language language;\n  private final VulnerabilityProbability vulnerabilityProbability;\n  @JsonAdapter(EitherRuleDescriptionAdapterFactory.class)\n  private final Either<RuleMonolithicDescriptionDto, RuleSplitDescriptionDto> description;\n  private final Collection<EffectiveRuleParamDto> params;\n\n  public EffectiveRuleDetailsDto(String key, String name, Either<StandardModeDetails, MQRModeDetails> severityDetails,\n    Either<RuleMonolithicDescriptionDto, RuleSplitDescriptionDto> description, Collection<EffectiveRuleParamDto> params,\n    Language language, @Nullable VulnerabilityProbability vulnerabilityProbability) {\n    this.key = key;\n    this.name = name;\n    this.severityDetails = severityDetails;\n    this.language = language;\n    this.vulnerabilityProbability = vulnerabilityProbability;\n    this.description = description;\n    this.params = params;\n  }\n\n  public Either<RuleMonolithicDescriptionDto, RuleSplitDescriptionDto> getDescription() {\n    return description;\n  }\n\n  public Collection<EffectiveRuleParamDto> getParams() {\n    return params;\n  }\n\n  public String getKey() {\n    return key;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public Either<StandardModeDetails, MQRModeDetails> getSeverityDetails() {\n    return severityDetails;\n  }\n\n  @CheckForNull\n  public IssueSeverity getSeverity() {\n    return this.severityDetails.isLeft() ?\n      this.severityDetails.getLeft().getSeverity() : null;\n  }\n\n  @CheckForNull\n  public RuleType getType() {\n    return this.severityDetails.isLeft() ?\n      this.severityDetails.getLeft().getType() : null;\n  }\n\n  public List<ImpactDto> getDefaultImpacts() {\n    return this.severityDetails.isRight() ?\n      this.severityDetails.getRight().getImpacts() : List.of();\n  }\n\n  @CheckForNull\n  public CleanCodeAttribute getCleanCodeAttribute() {\n    return this.severityDetails.isRight() ?\n      this.severityDetails.getRight().getCleanCodeAttribute() : null;\n  }\n\n  public Language getLanguage() {\n    return language;\n  }\n\n  public VulnerabilityProbability getVulnerabilityProbability() {\n    return vulnerabilityProbability;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/EffectiveRuleParamDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class EffectiveRuleParamDto {\n  private final String name;\n  private final String description;\n  private final String value;\n  private final String defaultValue;\n\n  public EffectiveRuleParamDto(String name, String description, @Nullable String value, @Nullable String defaultValue) {\n    this.name = name;\n    this.description = description;\n    this.value = value;\n    this.defaultValue = defaultValue;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public String getDescription() {\n    return description;\n  }\n\n  /**\n   * Get the effective value, as would be used by the analyzer\n   */\n  @CheckForNull\n  public String getValue() {\n    return value;\n  }\n\n  /**\n   * Get the default value as defined in the rule's definition\n   */\n  @CheckForNull\n  public String getDefaultValue() {\n    return defaultValue;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/GetEffectiveRuleDetailsParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\nimport javax.annotation.Nullable;\n\nimport static java.util.Objects.requireNonNull;\n\npublic class GetEffectiveRuleDetailsParams {\n\n  private final String configurationScopeId;\n  private final String ruleKey;\n\n  @Nullable\n  private final String contextKey;\n\n  public GetEffectiveRuleDetailsParams(String configurationScopeId, String ruleKey, @Nullable String contextKey) {\n    this.configurationScopeId = requireNonNull(configurationScopeId);\n    this.ruleKey = requireNonNull(ruleKey);\n    this.contextKey = contextKey;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public String getRuleKey() {\n    return ruleKey;\n  }\n\n  @Nullable\n  public String getContextKey() {\n    return contextKey;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/GetEffectiveRuleDetailsResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\npublic class GetEffectiveRuleDetailsResponse {\n  private final EffectiveRuleDetailsDto details;\n\n  public GetEffectiveRuleDetailsResponse(EffectiveRuleDetailsDto details) {\n    this.details = details;\n  }\n\n  public EffectiveRuleDetailsDto details() {\n    return details;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/GetStandaloneRuleDescriptionParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\nimport static java.util.Objects.requireNonNull;\n\npublic class GetStandaloneRuleDescriptionParams {\n\n  private final String ruleKey;\n\n  public GetStandaloneRuleDescriptionParams(String ruleKey) {\n    this.ruleKey = requireNonNull(ruleKey);\n  }\n\n  public String getRuleKey() {\n    return ruleKey;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/GetStandaloneRuleDescriptionResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\nimport com.google.gson.annotations.JsonAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.EitherRuleDescriptionAdapterFactory;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\n\npublic class GetStandaloneRuleDescriptionResponse {\n\n  private final RuleDefinitionDto ruleDefinition;\n  @JsonAdapter(EitherRuleDescriptionAdapterFactory.class)\n  private final Either<RuleMonolithicDescriptionDto, RuleSplitDescriptionDto> description;\n\n  public GetStandaloneRuleDescriptionResponse(RuleDefinitionDto ruleDefinition, Either<RuleMonolithicDescriptionDto, RuleSplitDescriptionDto> description) {\n    this.ruleDefinition = ruleDefinition;\n    this.description = description;\n  }\n\n  public RuleDefinitionDto getRuleDefinition() {\n    return ruleDefinition;\n  }\n\n  public Either<RuleMonolithicDescriptionDto, RuleSplitDescriptionDto> getDescription() {\n    return description;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/ImpactDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SoftwareQuality;\n\npublic class ImpactDto {\n  private final SoftwareQuality softwareQuality;\n  private final ImpactSeverity impactSeverity;\n\n  public ImpactDto(SoftwareQuality softwareQuality, ImpactSeverity impactSeverity) {\n    this.softwareQuality = softwareQuality;\n    this.impactSeverity = impactSeverity;\n  }\n\n  public SoftwareQuality getSoftwareQuality() {\n    return softwareQuality;\n  }\n\n  public ImpactSeverity getImpactSeverity() {\n    return impactSeverity;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/ListAllStandaloneRulesDefinitionsResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\nimport java.util.Map;\n\npublic class ListAllStandaloneRulesDefinitionsResponse {\n\n  private final Map<String, RuleDefinitionDto> rulesByKey;\n\n  public ListAllStandaloneRulesDefinitionsResponse(Map<String, RuleDefinitionDto> rulesByKey) {\n    this.rulesByKey = rulesByKey;\n  }\n\n  public Map<String, RuleDefinitionDto> getRulesByKey() {\n    return rulesByKey;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/RuleContextualSectionDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\n\npublic class RuleContextualSectionDto {\n  private final String htmlContent;\n  private final String contextKey;\n  private final String displayName;\n\n  public RuleContextualSectionDto(String htmlContent, String contextKey, String displayName) {\n    this.htmlContent = htmlContent;\n    this.contextKey = contextKey;\n    this.displayName = displayName;\n  }\n\n  public String getHtmlContent() {\n    return htmlContent;\n  }\n\n  public String getContextKey() {\n    return contextKey;\n  }\n\n  public String getDisplayName() {\n    return displayName;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/RuleContextualSectionWithDefaultContextKeyDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\nimport java.util.List;\n\npublic class RuleContextualSectionWithDefaultContextKeyDto {\n\n  private final String defaultContextKey;\n  private final List<RuleContextualSectionDto> contextualSections;\n\n  public RuleContextualSectionWithDefaultContextKeyDto(String defaultContextKey, List<RuleContextualSectionDto> contextualSections) {\n    this.defaultContextKey = defaultContextKey;\n    this.contextualSections = contextualSections;\n  }\n\n  public String getDefaultContextKey() {\n    return defaultContextKey;\n  }\n\n  public List<RuleContextualSectionDto> getContextualSections() {\n    return contextualSections;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/RuleDefinitionDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\nimport java.util.List;\nimport java.util.Map;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\n\npublic class RuleDefinitionDto {\n  private final String key;\n  private final String name;\n  private final CleanCodeAttribute cleanCodeAttribute;\n  private final List<ImpactDto> softwareImpacts;\n  private final Language language;\n  private final Map<String, RuleParamDefinitionDto> paramsByKey;\n  private final boolean isActiveByDefault;\n\n  public RuleDefinitionDto(String key, String name, CleanCodeAttribute cleanCodeAttribute, List<ImpactDto> softwareImpacts,\n    Map<String, RuleParamDefinitionDto> paramsByKey, boolean isActiveByDefault,\n    Language language) {\n    this.key = key;\n    this.name = name;\n    this.cleanCodeAttribute = cleanCodeAttribute;\n    this.softwareImpacts = softwareImpacts;\n    this.language = language;\n    this.paramsByKey = paramsByKey;\n    this.isActiveByDefault = isActiveByDefault;\n  }\n\n  public Map<String, RuleParamDefinitionDto> getParamsByKey() {\n    return paramsByKey;\n  }\n\n  public boolean isActiveByDefault() {\n    return isActiveByDefault;\n  }\n\n  public String getKey() {\n    return key;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public CleanCodeAttribute getCleanCodeAttribute() {\n    return cleanCodeAttribute;\n  }\n\n  public List<ImpactDto> getSoftwareImpacts() {\n    return softwareImpacts;\n  }\n\n  public Language getLanguage() {\n    return language;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/RuleDescriptionTabDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\nimport com.google.gson.annotations.JsonAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.EitherRuleDescriptionTabContentAdapterFactory;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\n\npublic class RuleDescriptionTabDto {\n  private final String title;\n\n  @JsonAdapter(EitherRuleDescriptionTabContentAdapterFactory.class)\n  private final Either<RuleNonContextualSectionDto, RuleContextualSectionWithDefaultContextKeyDto> content;\n\n  public RuleDescriptionTabDto(String title, Either<RuleNonContextualSectionDto, RuleContextualSectionWithDefaultContextKeyDto> content) {\n    this.title = title;\n    this.content = content;\n  }\n\n  public String getTitle() {\n    return title;\n  }\n\n  public Either<RuleNonContextualSectionDto, RuleContextualSectionWithDefaultContextKeyDto> getContent() {\n    return content;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/RuleMonolithicDescriptionDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\npublic class RuleMonolithicDescriptionDto {\n  private final String htmlContent;\n\n  public RuleMonolithicDescriptionDto(String htmlContent) {\n    this.htmlContent = htmlContent;\n  }\n\n  /**\n   * @return the rule description content + the extended description if any\n   */\n  public String getHtmlContent() {\n    return htmlContent;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/RuleNonContextualSectionDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\npublic class RuleNonContextualSectionDto {\n  private final String htmlContent;\n\n  public RuleNonContextualSectionDto(String htmlContent) {\n    this.htmlContent = htmlContent;\n  }\n\n  public String getHtmlContent() {\n    return htmlContent;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/RuleParamDefinitionDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\nimport java.util.List;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class RuleParamDefinitionDto {\n  private final String key;\n  private final String name;\n  private final String description;\n  private final String defaultValue;\n  private final RuleParamType type;\n  private final boolean multiple;\n  private final List<String> possibleValues;\n\n  public RuleParamDefinitionDto(String key, String name, String description, @Nullable String defaultValue, RuleParamType type, boolean multiple, List<String> possibleValues) {\n    this.key = key;\n    this.name = name;\n    this.description = description;\n    this.defaultValue = defaultValue;\n    this.type = type;\n    this.multiple = multiple;\n    this.possibleValues = possibleValues;\n  }\n\n  public String getKey() {\n    return key;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public String getDescription() {\n    return description;\n  }\n\n  @CheckForNull\n  public String getDefaultValue() {\n    return defaultValue;\n  }\n\n  public RuleParamType getType() {\n    return type;\n  }\n\n  public boolean isMultiple() {\n    return multiple;\n  }\n\n  public List<String> getPossibleValues() {\n    return possibleValues;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/RuleParamType.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\npublic enum RuleParamType {\n\n  /**\n   * Keep in sync with constants in org.sonar.api.server.rule.RuleParamType\n   */\n  STRING, TEXT, BOOLEAN, INTEGER, FLOAT\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/RuleSplitDescriptionDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\nimport java.util.List;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class RuleSplitDescriptionDto {\n  private final String introductionHtmlContent;\n  private final List<RuleDescriptionTabDto> tabs;\n\n  public RuleSplitDescriptionDto(@Nullable String introductionHtmlContent, List<RuleDescriptionTabDto> tabs) {\n    this.introductionHtmlContent = introductionHtmlContent;\n    this.tabs = tabs;\n  }\n\n  @CheckForNull\n  public String getIntroductionHtmlContent() {\n    return introductionHtmlContent;\n  }\n\n  public List<RuleDescriptionTabDto> getTabs() {\n    return tabs;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/RulesRpcService.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonNotification;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonRequest;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonSegment;\n\n@JsonSegment(\"rule\")\npublic interface RulesRpcService {\n\n  /**\n   * Returns the effective details about a rule. \"Effective\" means that returned rule details will take into account\n   * the binding and the finding context.\n   * @return a completed future if the rule was found, else a failed future\n   */\n  @JsonRequest\n  CompletableFuture<GetEffectiveRuleDetailsResponse> getEffectiveRuleDetails(GetEffectiveRuleDetailsParams params);\n\n  /**\n   * Return list of all available rules for SonarLint standalone mode. Used to build the rules configuration UI.\n   * The description is not part of the response, since we usually display description one rule at a time.\n   * Use {@link RulesRpcService#getStandaloneRuleDetails(GetStandaloneRuleDescriptionParams)} to get the rule description.\n   */\n  @JsonRequest\n  CompletableFuture<ListAllStandaloneRulesDefinitionsResponse> listAllStandaloneRulesDefinitions();\n\n  /**\n   * Get rule details of a single rule. The details will include the rule description.\n   */\n  @JsonRequest\n  CompletableFuture<GetStandaloneRuleDescriptionResponse> getStandaloneRuleDetails(GetStandaloneRuleDescriptionParams params);\n\n  /**\n   * Notify the backend about changes to the standalone rule's configuration. This configuration will override defaults rule activation and parameters\n   */\n  @JsonNotification\n  void updateStandaloneRulesConfiguration(UpdateStandaloneRulesConfigurationParams params);\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/StandaloneRuleConfigDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\nimport java.util.Map;\n\npublic class StandaloneRuleConfigDto {\n\n  private final boolean isActive;\n\n  private final Map<String, String> paramValueByKey;\n\n  public StandaloneRuleConfigDto(boolean isActive, Map<String, String> paramValueByKey) {\n    this.isActive = isActive;\n    this.paramValueByKey = paramValueByKey;\n  }\n\n  public boolean isActive() {\n    return isActive;\n  }\n\n  public Map<String, String> getParamValueByKey() {\n    return paramValueByKey;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/UpdateStandaloneRulesConfigurationParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\nimport java.util.Map;\n\npublic class UpdateStandaloneRulesConfigurationParams {\n\n  private final Map<String, StandaloneRuleConfigDto> ruleConfigByKey;\n\n\n  public UpdateStandaloneRulesConfigurationParams(Map<String, StandaloneRuleConfigDto> ruleConfigByKey) {\n    this.ruleConfigByKey = ruleConfigByKey;\n  }\n\n  public Map<String, StandaloneRuleConfigDto> getRuleConfigByKey() {\n    return ruleConfigByKey;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/VulnerabilityProbability.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\npublic enum VulnerabilityProbability {\n  HIGH,\n  MEDIUM,\n  LOW\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/rules/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.rules;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/sca/ChangeDependencyRiskStatusParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.sca;\n\nimport java.util.UUID;\nimport javax.annotation.Nullable;\n\npublic class ChangeDependencyRiskStatusParams {\n  private final String configurationScopeId;\n  private final UUID dependencyRiskKey;\n  private final DependencyRiskTransition transition;\n  @Nullable\n  private final String comment;\n\n  public ChangeDependencyRiskStatusParams(String configurationScopeId, UUID dependencyRiskKey, DependencyRiskTransition transition, @Nullable String comment) {\n    this.configurationScopeId = configurationScopeId;\n    this.dependencyRiskKey = dependencyRiskKey;\n    this.transition = transition;\n    this.comment = comment;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public UUID getDependencyRiskKey() {\n    return dependencyRiskKey;\n  }\n\n  public DependencyRiskTransition getTransition() {\n    return transition;\n  }\n\n  @Nullable\n  public String getComment() {\n    return comment;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/sca/CheckDependencyRiskSupportedParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.sca;\n\npublic class CheckDependencyRiskSupportedParams {\n\n  private final String configurationScopeId;\n\n  public CheckDependencyRiskSupportedParams(String configurationScopeId) {\n    this.configurationScopeId = configurationScopeId;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/sca/CheckDependencyRiskSupportedResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.sca;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class CheckDependencyRiskSupportedResponse {\n\n  private final boolean supported;\n  private final String reason;\n\n  public CheckDependencyRiskSupportedResponse(boolean supported, @Nullable String reason) {\n    this.supported = supported;\n    this.reason = reason;\n  }\n\n  public boolean isSupported() {\n    return supported;\n  }\n\n  @CheckForNull\n  public String getReason() {\n    return reason;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/sca/DependencyRiskRpcService.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.sca;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonRequest;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonSegment;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.ListAllParams;\n\n@JsonSegment(\"dependencyRisk\")\npublic interface DependencyRiskRpcService {\n\n  /**\n   * Returns the list of dependency risks detected for the given configuration scopes.\n   */\n  @JsonRequest\n  CompletableFuture<ListAllDependencyRisksResponse> listAll(ListAllParams params);\n\n    /**\n   * <p> It changes a status of a Dependency Risk (SCA finding) that exists on the server. In detail, it is responsible for:\n   * <ul>\n   *   <li>Changes the status of a Dependency Risk (identified by {@link ChangeDependencyRiskStatusParams#getDependencyRiskKey()})</li>\n   *   <li>Updates the Dependency Risk status in the local storage</li>\n   *   <li>Calls the server to update the Dependency Risk status</li>\n   * </ul>\n   *</p>\n   * It returns a failed future if:\n   * <ul>\n   *   <li>the provided configuration scope (identified by {@link ChangeDependencyRiskStatusParams#getConfigurationScopeId()} is unknown</li>\n   *   <li>the connection bound to the configuration scope is unknown</li>\n   *   <li>the issueReleaseKey is not found in the local storage</li>\n   *   <li>the Dependency Risk is not found either on the server or in the local storage for issues</li>\n   *   <li>there is a communication problem with the server: network outage, server is down, unauthorized</li>\n   *   <li>the transition is ACCEPT, FIXED, or SAFE, but no comment is provided</li>\n   * </ul>\n   * </p>\n   */  \n  @JsonRequest\n  CompletableFuture<Void> changeStatus(ChangeDependencyRiskStatusParams params);\n\n  @JsonRequest\n  CompletableFuture<Void> openDependencyRiskInBrowser(OpenDependencyRiskInBrowserParams params);\n\n  /**\n   * Checks if the dependency risk feature is supported for the given configuration scope.\n   * Reasons for not being supported include:\n   * <ul>\n   *   <li>Invalid connection or binding</li>\n   *   <li>Not using version 2025.4 or higher</li>\n   *   <li>Not using edition Enterprise or higher</li>\n   *   <li>Not using Advanced Security (SCA not enabled)</li>\n   * </ul>\n   */\n  @JsonRequest\n  CompletableFuture<CheckDependencyRiskSupportedResponse> checkSupported(CheckDependencyRiskSupportedParams params);\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/sca/DependencyRiskTransition.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.sca;\n\npublic enum DependencyRiskTransition {\n    REOPEN, CONFIRM, ACCEPT, SAFE\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/sca/ListAllDependencyRisksResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.sca;\n\nimport java.util.List;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.DependencyRiskDto;\n\npublic class ListAllDependencyRisksResponse {\n  private final List<DependencyRiskDto> dependencyRisks;\n\n  public ListAllDependencyRisksResponse(List<DependencyRiskDto> dependencyRisks) {\n    this.dependencyRisks = dependencyRisks;\n  }\n\n  public List<DependencyRiskDto> getDependencyRisks() {\n    return dependencyRisks;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/sca/OpenDependencyRiskInBrowserParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.sca;\n\nimport java.util.UUID;\n\npublic class OpenDependencyRiskInBrowserParams {\n  private final String configScopeId;\n  private final UUID dependencyRiskKey;\n\n  public OpenDependencyRiskInBrowserParams(String configScopeId, UUID dependencyRiskKey) {\n    this.configScopeId = configScopeId;\n    this.dependencyRiskKey = dependencyRiskKey;\n  }\n\n  public String getConfigScopeId() {\n    return configScopeId;\n  }\n\n  public UUID getDependencyRiskKey() {\n    return dependencyRiskKey;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/sca/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.sca;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/telemetry/GetStatusResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry;\n\npublic class GetStatusResponse {\n\n  private final boolean enabled;\n\n\n  public GetStatusResponse(boolean enabled) {\n    this.enabled = enabled;\n  }\n\n  public boolean isEnabled() {\n    return enabled;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/telemetry/TelemetryRpcService.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonNotification;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonRequest;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonSegment;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.DidUpdateBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AcceptedBindingSuggestionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AddQuickFixAppliedForRuleParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AddReportedRulesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AnalysisDoneOnSingleLanguageParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AnalysisReportingTriggeredParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.DevNotificationsClickedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.FindingsFilteredParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.FixSuggestionResolvedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.HelpAndFeedbackClickedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.IdeLabsExternalLinkClickedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.IdeLabsFeedbackLinkClickedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.McpTransportModeUsedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.ToolCalledParams;\n\n@JsonSegment(\"telemetry\")\npublic interface TelemetryRpcService {\n\n  @JsonRequest\n  CompletableFuture<GetStatusResponse> getStatus();\n\n  @JsonNotification\n  void enableTelemetry();\n\n  @JsonNotification\n  void disableTelemetry();\n\n  /**\n   * @deprecated managed automatically when using {@link org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService#analyzeFilesAndTrack(AnalyzeFilesAndTrackParams)}\n   * it is still used by VS because of the C# analysis handled on the client side\n   */\n  @JsonNotification\n  @Deprecated(since = \"10.1\")\n  void analysisDoneOnSingleLanguage(AnalysisDoneOnSingleLanguageParams params);\n\n  /**\n   * @deprecated managed automatically when using {@link org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService#analyzeFilesAndTrack(AnalyzeFilesAndTrackParams)}\n   * it is still used by VS because of the C# analysis handled on the client side\n   */\n  @JsonNotification\n  @Deprecated(since = \"10.1\")\n  void analysisDoneOnMultipleFiles();\n\n  @JsonNotification\n  void devNotificationsClicked(DevNotificationsClickedParams params);\n\n  @JsonNotification\n  void taintVulnerabilitiesInvestigatedLocally();\n\n  @JsonNotification\n  void taintVulnerabilitiesInvestigatedRemotely();\n\n  /**\n   * @deprecated managed automatically when using {@link org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService#analyzeFilesAndTrack(AnalyzeFilesAndTrackParams)}\n   * it is still used by VS because of the C# analysis handled on the client side\n   */\n  @JsonNotification\n  @Deprecated(since = \"10.1\")\n  void addReportedRules(AddReportedRulesParams params);\n\n  @JsonNotification\n  void addQuickFixAppliedForRule(AddQuickFixAppliedForRuleParams params);\n\n  @JsonNotification\n  void helpAndFeedbackLinkClicked(HelpAndFeedbackClickedParams params);\n\n  /**\n   * To be called from SonarQube MCP Server when SQ:IDE integration is enabled and valid\n   * This is tracking if SQ:IDE integration was enabled at least once during the day\n   */\n  @JsonNotification\n  void mcpIntegrationEnabled();\n\n  /**\n   * To be called from SonarQube MCP Server after initialization\n   * This is tracking the transport type on which the MCP is running\n   */\n  @JsonNotification\n  void mcpTransportModeUsed(McpTransportModeUsedParams params);\n\n  @JsonNotification\n  void toolCalled(ToolCalledParams params);\n\n  /**\n   * Should be used to track the usage of specific types of analysis\n   * This includes analysis of VCS changed files, pre-commit analysis or all project files analysis\n   */\n  @JsonNotification\n  void analysisReportingTriggered(AnalysisReportingTriggeredParams params);\n\n  @JsonNotification\n  void fixSuggestionResolved(FixSuggestionResolvedParams params);\n\n  /**\n   * This method should be called when binding is created manually (not through binding suggestion or assistance).\n   */\n  @JsonNotification\n  void addedManualBindings();\n\n  /**\n   * This method should be called when binding is created from a suggestion and pass where the suggestion originated from\n   * (that is provided to the clients with the suggestions themselves).\n   */\n  @JsonNotification\n  void acceptedBindingSuggestion(AcceptedBindingSuggestionParams origin);\n\n  /**\n   * @deprecated avoid calling this method if possible, since it will be removed once all the clients are migrated.\n   * Rely on providing the {@link org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingMode)} and\n   * {@link org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin)} while calling the\n   * {@link org.sonarsource.sonarlint.core.rpc.protocol.backend.config.ConfigurationRpcService#didUpdateBinding(DidUpdateBindingParams)} )}\n   * within the DidUpdateBindingParams.\n   */\n  @Deprecated(forRemoval = true)\n  @JsonNotification\n  void addedImportedBindings();\n\n  /**\n   * @deprecated avoid calling this method if possible, since it will be removed once all the clients are migrated.\n   * Rely on providing the {@link org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingMode)} and\n   * {@link org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin)} while calling the\n   * {@link org.sonarsource.sonarlint.core.rpc.protocol.backend.config.ConfigurationRpcService#didUpdateBinding(DidUpdateBindingParams)} )}\n   * within the DidUpdateBindingParams.\n   */\n  @Deprecated(forRemoval = true)\n  @JsonNotification\n  void addedAutomaticBindings();\n\n  @JsonNotification\n  void taintInvestigatedLocally();\n\n  @JsonNotification\n  void taintInvestigatedRemotely();\n\n  @JsonNotification\n  void hotspotInvestigatedLocally();\n\n  @JsonNotification\n  void hotspotInvestigatedRemotely();\n\n  @JsonNotification\n  void issueInvestigatedLocally();\n\n  @JsonNotification\n  void dependencyRiskInvestigatedLocally();\n\n  @JsonNotification\n  void findingsFiltered(FindingsFilteredParams params);\n\n  @JsonNotification\n  void ideLabsExternalLinkClicked(IdeLabsExternalLinkClickedParams params);\n\n  @JsonNotification\n  void ideLabsFeedbackLinkClicked(IdeLabsFeedbackLinkClickedParams params);\n\n  /**\n   * Should be called when the user opens the \"Supported Languages\" panel.\n   */\n  @JsonNotification\n  void supportedLanguagesPanelOpened();\n\n  /**\n   * Should be called when the user clicks the \"set up connection/binding\" CTA in the \"Supported Languages\" panel.\n   */\n  @JsonNotification\n  void supportedLanguagesPanelCtaClicked();\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/telemetry/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/tracking/AffectedPackageDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class AffectedPackageDto {\n  private final String purl;\n  private final String recommendation;\n  private final RecommendationDetailsDto recommendationDetails;\n\n  public AffectedPackageDto(String purl, String recommendation, @Nullable RecommendationDetailsDto recommendationDetails) {\n    this.purl = purl;\n    this.recommendation = recommendation;\n    this.recommendationDetails = recommendationDetails;\n  }\n\n  public String getPurl() {\n    return purl;\n  }\n\n  public String getRecommendation() {\n    return recommendation;\n  }\n\n  @CheckForNull\n  public RecommendationDetailsDto getRecommendationDetails() {\n    return recommendationDetails;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/tracking/DependencyRiskDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking;\n\nimport java.util.List;\nimport java.util.UUID;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class DependencyRiskDto {\n  private final UUID id;\n  private final Type type;\n  private final Severity severity;\n  private final SoftwareQuality quality;\n  private final Status status;\n  private final String packageName;\n  private final String packageVersion;\n  @Nullable\n  private final String vulnerabilityId;\n  @Nullable\n  private final String cvssScore;\n  private final List<Transition> transitions;\n\n  public DependencyRiskDto(UUID id, Type type, Severity severity, SoftwareQuality quality, Status status, String packageName,\n    String packageVersion, @Nullable String vulnerabilityId, @Nullable String cvssScore, List<Transition> transitions) {\n    this.id = id;\n    this.type = type;\n    this.severity = severity;\n    this.quality = quality;\n    this.status = status;\n    this.packageName = packageName;\n    this.packageVersion = packageVersion;\n    this.vulnerabilityId = vulnerabilityId;\n    this.cvssScore = cvssScore;\n    this.transitions = transitions;\n  }\n\n  public UUID getId() {\n    return id;\n  }\n\n  public Type getType() {\n    return type;\n  }\n\n  public Severity getSeverity() {\n    return severity;\n  }\n\n  public SoftwareQuality getQuality() {\n    return quality;\n  }\n\n  public Status getStatus() {\n    return status;\n  }\n\n  public String getPackageName() {\n    return packageName;\n  }\n\n  public String getPackageVersion() {\n    return packageVersion;\n  }\n\n  @CheckForNull\n  public String getVulnerabilityId() {\n    return vulnerabilityId;\n  }\n\n  @CheckForNull\n  public String getCvssScore() {\n    return cvssScore;\n  }\n\n  public List<Transition> getTransitions() {\n    return transitions;\n  }\n\n  public enum Severity {\n    INFO, LOW, MEDIUM, HIGH, BLOCKER\n  }\n\n  public enum SoftwareQuality {\n    MAINTAINABILITY,\n    RELIABILITY,\n    SECURITY\n  }\n\n  public enum Type {\n    VULNERABILITY, PROHIBITED_LICENSE\n  }\n\n  public enum Status {\n    FIXED, OPEN, CONFIRM, ACCEPT, SAFE\n  }\n\n  public enum Transition {\n    CONFIRM, REOPEN, SAFE, FIXED, ACCEPT\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/tracking/ListAllParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking;\n\npublic class ListAllParams {\n  private final String configurationScopeId;\n  private final boolean shouldRefresh;\n\n  public ListAllParams(String configurationScopeId) {\n    this(configurationScopeId, false);\n  }\n\n  public ListAllParams(String configurationScopeId, boolean shouldRefresh) {\n    this.configurationScopeId = configurationScopeId;\n    this.shouldRefresh = shouldRefresh;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public boolean shouldRefresh() {\n    return shouldRefresh;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/tracking/ListAllResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking;\n\nimport java.util.List;\n\npublic class ListAllResponse {\n  private final List<TaintVulnerabilityDto> taintVulnerabilities;\n\n  public ListAllResponse(List<TaintVulnerabilityDto> taintVulnerabilities) {\n    this.taintVulnerabilities = taintVulnerabilities;\n  }\n\n  public List<TaintVulnerabilityDto> getTaintVulnerabilities() {\n    return taintVulnerabilities;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/tracking/RecommendationDetailsDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking;\n\npublic class RecommendationDetailsDto {\n  private final int impactScore;\n  private final String impactDescription;\n  private final boolean realIssue;\n  private final String falsePositiveReason;\n  private final boolean includesDev;\n  private final boolean specificMethodsAffected;\n  private final String specificMethodsDescription;\n  private final boolean otherConditions;\n  private final String otherConditionsDescription;\n  private final boolean workaroundAvailable;\n  private final String workaroundDescription;\n  private final String visibility;\n\n  private RecommendationDetailsDto(Builder builder) {\n    this.impactScore = builder.impactScore;\n    this.impactDescription = builder.impactDescription;\n    this.realIssue = builder.realIssue;\n    this.falsePositiveReason = builder.falsePositiveReason;\n    this.includesDev = builder.includesDev;\n    this.specificMethodsAffected = builder.specificMethodsAffected;\n    this.specificMethodsDescription = builder.specificMethodsDescription;\n    this.otherConditions = builder.otherConditions;\n    this.otherConditionsDescription = builder.otherConditionsDescription;\n    this.workaroundAvailable = builder.workaroundAvailable;\n    this.workaroundDescription = builder.workaroundDescription;\n    this.visibility = builder.visibility;\n  }\n\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  public int getImpactScore() {\n    return impactScore;\n  }\n\n  public String getImpactDescription() {\n    return impactDescription;\n  }\n\n  public boolean isRealIssue() {\n    return realIssue;\n  }\n\n  public String getFalsePositiveReason() {\n    return falsePositiveReason;\n  }\n\n  public boolean isIncludesDev() {\n    return includesDev;\n  }\n\n  public boolean isSpecificMethodsAffected() {\n    return specificMethodsAffected;\n  }\n\n  public String getSpecificMethodsDescription() {\n    return specificMethodsDescription;\n  }\n\n  public boolean isOtherConditions() {\n    return otherConditions;\n  }\n\n  public String getOtherConditionsDescription() {\n    return otherConditionsDescription;\n  }\n\n  public boolean isWorkaroundAvailable() {\n    return workaroundAvailable;\n  }\n\n  public String getWorkaroundDescription() {\n    return workaroundDescription;\n  }\n\n  public String getVisibility() {\n    return visibility;\n  }\n\n  public static class Builder {\n    private int impactScore;\n    private String impactDescription;\n    private boolean realIssue;\n    private String falsePositiveReason;\n    private boolean includesDev;\n    private boolean specificMethodsAffected;\n    private String specificMethodsDescription;\n    private boolean otherConditions;\n    private String otherConditionsDescription;\n    private boolean workaroundAvailable;\n    private String workaroundDescription;\n    private String visibility;\n\n    public Builder impactScore(int impactScore) {\n      this.impactScore = impactScore;\n      return this;\n    }\n\n    public Builder impactDescription(String impactDescription) {\n      this.impactDescription = impactDescription;\n      return this;\n    }\n\n    public Builder realIssue(boolean realIssue) {\n      this.realIssue = realIssue;\n      return this;\n    }\n\n    public Builder falsePositiveReason(String falsePositiveReason) {\n      this.falsePositiveReason = falsePositiveReason;\n      return this;\n    }\n\n    public Builder includesDev(boolean includesDev) {\n      this.includesDev = includesDev;\n      return this;\n    }\n\n    public Builder specificMethodsAffected(boolean specificMethodsAffected) {\n      this.specificMethodsAffected = specificMethodsAffected;\n      return this;\n    }\n\n    public Builder specificMethodsDescription(String specificMethodsDescription) {\n      this.specificMethodsDescription = specificMethodsDescription;\n      return this;\n    }\n\n    public Builder otherConditions(boolean otherConditions) {\n      this.otherConditions = otherConditions;\n      return this;\n    }\n\n    public Builder otherConditionsDescription(String otherConditionsDescription) {\n      this.otherConditionsDescription = otherConditionsDescription;\n      return this;\n    }\n\n    public Builder workaroundAvailable(boolean workaroundAvailable) {\n      this.workaroundAvailable = workaroundAvailable;\n      return this;\n    }\n\n    public Builder workaroundDescription(String workaroundDescription) {\n      this.workaroundDescription = workaroundDescription;\n      return this;\n    }\n\n    public Builder visibility(String visibility) {\n      this.visibility = visibility;\n      return this;\n    }\n\n    public RecommendationDetailsDto build() {\n      return new RecommendationDetailsDto(this);\n    }\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/tracking/TaintVulnerabilityDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.UUID;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ResolutionStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.MQRModeDetails;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.StandardModeDetails;\n\npublic class TaintVulnerabilityDto {\n  private final UUID id;\n  private final String sonarServerKey;\n  private final boolean resolved;\n  @Nullable\n  private final ResolutionStatus resolutionStatus;\n  private final String ruleKey;\n  private final String message;\n  private final Path ideFilePath;\n  private final Instant introductionDate;\n  private final Either<StandardModeDetails, MQRModeDetails> severityMode;\n  private final List<FlowDto> flows;\n  @Nullable\n  private final TextRangeWithHashDto textRange;\n  @Nullable\n  private final String ruleDescriptionContextKey;\n  private final boolean isOnNewCode;\n  private final boolean isAiCodeFixable;\n\n  public TaintVulnerabilityDto(UUID id, String sonarServerKey, boolean resolved, @Nullable ResolutionStatus resolutionStatus, String ruleKey,\n    String message, Path ideFilePath, Instant introductionDate,\n    Either<StandardModeDetails, MQRModeDetails> severityMode, List<FlowDto> flows, @Nullable TextRangeWithHashDto textRange, @Nullable String ruleDescriptionContextKey,\n    boolean isOnNewCode, boolean isAiCodeFixable) {\n    this.id = id;\n    this.sonarServerKey = sonarServerKey;\n    this.resolved = resolved;\n    this.resolutionStatus = resolutionStatus;\n    this.ruleKey = ruleKey;\n    this.message = message;\n    this.ideFilePath = ideFilePath;\n    this.introductionDate = introductionDate;\n    this.severityMode = severityMode;\n    this.flows = flows;\n    this.textRange = textRange;\n    this.ruleDescriptionContextKey = ruleDescriptionContextKey;\n    this.isOnNewCode = isOnNewCode;\n    this.isAiCodeFixable = isAiCodeFixable;\n  }\n\n  public UUID getId() {\n    return id;\n  }\n\n  public String getSonarServerKey() {\n    return sonarServerKey;\n  }\n\n  public boolean isResolved() {\n    return resolved;\n  }\n\n  public ResolutionStatus getResolutionStatus() {\n    return resolutionStatus;\n  }\n\n  public String getRuleKey() {\n    return ruleKey;\n  }\n\n  public String getMessage() {\n    return message;\n  }\n\n  public Path getIdeFilePath() {\n    return ideFilePath;\n  }\n\n  public Instant getIntroductionDate() {\n    return introductionDate;\n  }\n\n  public Either<StandardModeDetails, MQRModeDetails> getSeverityMode() {\n    return severityMode;\n  }\n\n  public List<FlowDto> getFlows() {\n    return flows;\n  }\n\n  @CheckForNull\n  public TextRangeWithHashDto getTextRange() {\n    return textRange;\n  }\n\n  @CheckForNull\n  public String getRuleDescriptionContextKey() {\n    return ruleDescriptionContextKey;\n  }\n\n  public boolean isOnNewCode() {\n    return isOnNewCode;\n  }\n\n  public boolean isAiCodeFixable() {\n    return isAiCodeFixable;\n  }\n\n  public static class FlowDto {\n    private final List<LocationDto> locations;\n\n    public FlowDto(List<LocationDto> locations) {\n      this.locations = locations;\n    }\n\n    public List<LocationDto> getLocations() {\n      return locations;\n    }\n\n    public static class LocationDto {\n      private final TextRangeWithHashDto textRange;\n      private final String message;\n      private final Path filePath;\n\n      public LocationDto(@Nullable TextRangeWithHashDto textRange, String message, @Nullable Path filePath) {\n        this.textRange = textRange;\n        this.message = message;\n        this.filePath = filePath;\n      }\n\n      @CheckForNull\n      public TextRangeWithHashDto getTextRange() {\n        return textRange;\n      }\n\n      public String getMessage() {\n        return message;\n      }\n\n      @CheckForNull\n      public Path getFilePath() {\n        return filePath;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/tracking/TaintVulnerabilityTrackingRpcService.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking;\n\nimport java.util.concurrent.CompletableFuture;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonRequest;\nimport org.eclipse.lsp4j.jsonrpc.services.JsonSegment;\n\n@JsonSegment(\"taintVulnerability\")\npublic interface TaintVulnerabilityTrackingRpcService {\n\n  /**\n   * Returns the list of taint vulnerabilities detected for the given configuration scopes.\n   */\n  @JsonRequest\n  CompletableFuture<ListAllResponse> listAll(ListAllParams params);\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/tracking/TextRangeWithHashDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking;\n\npublic class TextRangeWithHashDto {\n  private final int startLine;\n  private final int startLineOffset;\n  private final int endLine;\n  private final int endLineOffset;\n\n  private final String hash;\n\n  public TextRangeWithHashDto(int startLine, int startLineOffset, int endLine, int endLineOffset, String hash) {\n    this.startLine = startLine;\n    this.startLineOffset = startLineOffset;\n    this.endLine = endLine;\n    this.endLineOffset = endLineOffset;\n    this.hash = hash;\n  }\n\n  public int getStartLine() {\n    return startLine;\n  }\n\n  public int getStartLineOffset() {\n    return startLineOffset;\n  }\n\n  public int getEndLine() {\n    return endLine;\n  }\n\n  public int getEndLineOffset() {\n    return endLineOffset;\n  }\n\n  public String getHash() {\n    return hash;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/tracking/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/OpenUrlInBrowserParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client;\n\npublic class OpenUrlInBrowserParams {\n\n  private final String url;\n\n  public OpenUrlInBrowserParams(String url) {\n    this.url = url;\n  }\n\n  public String getUrl() {\n    return url;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/analysis/DidChangeAnalysisReadinessParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.analysis;\n\nimport java.util.Set;\n\npublic class DidChangeAnalysisReadinessParams {\n  private final Set<String> configurationScopeIds;\n  private final boolean areReadyForAnalysis;\n\n  public DidChangeAnalysisReadinessParams(Set<String> configurationScopeIds, boolean areReadyForAnalysis) {\n    this.configurationScopeIds = configurationScopeIds;\n    this.areReadyForAnalysis = areReadyForAnalysis;\n  }\n\n  public Set<String> getConfigurationScopeIds() {\n    return configurationScopeIds;\n  }\n\n  public boolean areReadyForAnalysis() {\n    return areReadyForAnalysis;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/analysis/DidDetectSecretParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.analysis;\n\npublic class DidDetectSecretParams {\n  private final String configurationScopeId;\n\n  public DidDetectSecretParams(String configurationScopeId) {\n    this.configurationScopeId = configurationScopeId;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/analysis/FileEditDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.analysis;\n\nimport java.net.URI;\nimport java.util.List;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\n\n/**\n * @deprecated since 10.2, replaced by {@link org.sonarsource.sonarlint.core.rpc.protocol.client.issue.FileEditDto}\n * See {@link org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService#analyzeFilesAndTrack(AnalyzeFilesAndTrackParams)}\n */\npublic class FileEditDto {\n  private final URI target;\n  private final List<TextEditDto> textEdits;\n\n  public FileEditDto(URI target, List<TextEditDto> textEdits) {\n    this.target = target;\n    this.textEdits = textEdits;\n  }\n\n  public URI target() {\n    return target;\n  }\n\n  public List<TextEditDto> textEdits() {\n    return textEdits;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/analysis/GetFileExclusionsParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.analysis;\n\npublic class GetFileExclusionsParams {\n  private final String configurationScopeId;\n\n  public GetFileExclusionsParams(String configurationScopeId) {\n    this.configurationScopeId = configurationScopeId;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/analysis/GetFileExclusionsResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.analysis;\n\nimport java.util.Set;\n\npublic class GetFileExclusionsResponse {\n  private final Set<String> fileExclusionPatterns;\n\n  public GetFileExclusionsResponse(Set<String> fileExclusionPatterns) {\n    this.fileExclusionPatterns = fileExclusionPatterns;\n  }\n\n  public Set<String> getFileExclusionPatterns() {\n    return fileExclusionPatterns;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/analysis/GetInferredAnalysisPropertiesParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.analysis;\n\nimport java.net.URI;\nimport java.util.List;\n\npublic class GetInferredAnalysisPropertiesParams {\n  private final String configurationScopeId;\n  private final List<URI> filesToAnalyze;\n\n  public GetInferredAnalysisPropertiesParams(String configurationScopeId, List<URI> filePathsToAnalyze) {\n    this.configurationScopeId = configurationScopeId;\n    this.filesToAnalyze = filePathsToAnalyze;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public List<URI> getFilesToAnalyze() {\n    return filesToAnalyze;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/analysis/GetInferredAnalysisPropertiesResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.analysis;\n\nimport java.util.Map;\n\npublic class GetInferredAnalysisPropertiesResponse {\n  private final Map<String, String> properties;\n\n  public GetInferredAnalysisPropertiesResponse(Map<String, String> properties) {\n    this.properties = properties;\n  }\n\n  public Map<String, String> getProperties() {\n    return properties;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/analysis/QuickFixDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.analysis;\n\nimport java.util.List;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\n\n/**\n * @deprecated since 10.2, replaced by {@link org.sonarsource.sonarlint.core.rpc.protocol.client.issue.QuickFixDto}\n * See {@link org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService#analyzeFilesAndTrack(AnalyzeFilesAndTrackParams)}\n */\n@Deprecated(since = \"10.2\")\npublic class QuickFixDto {\n\n  private final List<FileEditDto> inputFileEdits;\n  private final String message;\n\n  public QuickFixDto(List<FileEditDto> inputFileEdits, String message) {\n    this.inputFileEdits = inputFileEdits;\n    this.message = message;\n  }\n\n  public List<FileEditDto> fileEdits() {\n    return inputFileEdits;\n  }\n\n  public String message() {\n    return message;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/analysis/RawIssueDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.analysis;\n\nimport java.net.URI;\nimport java.util.List;\nimport java.util.Map;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.RuleType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;\n\n/**\n * @deprecated since 10.2, replaced by {@link org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto} and {@link org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.RaisedHotspotDto}.\n * See {@link org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService#analyzeFilesAndTrack(AnalyzeFilesAndTrackParams)}\n */\n@Deprecated(since = \"10.2\")\npublic class RawIssueDto {\n  private final IssueSeverity severity;\n  private final RuleType type;\n  private final CleanCodeAttribute cleanCodeAttribute;\n  private final Map<SoftwareQuality, ImpactSeverity> impacts;\n  private final String ruleKey;\n  private final String primaryMessage;\n  private final URI fileUri;\n  private final List<RawIssueFlowDto> flows;\n  private final List<QuickFixDto> quickFixes;\n  private final TextRangeDto textRange;\n  @Nullable\n  private final String ruleDescriptionContextKey;\n  @Nullable\n  private final VulnerabilityProbability vulnerabilityProbability;\n\n  public RawIssueDto(IssueSeverity severity, RuleType type, CleanCodeAttribute cleanCodeAttribute, Map<SoftwareQuality, ImpactSeverity> impacts, String ruleKey,\n    String primaryMessage, @Nullable URI fileUri, List<RawIssueFlowDto> flows, List<QuickFixDto> quickFixes, @Nullable TextRangeDto textRange,\n    @Nullable String ruleDescriptionContextKey, @Nullable VulnerabilityProbability vulnerabilityProbability) {\n    this.severity = severity;\n    this.type = type;\n    this.cleanCodeAttribute = cleanCodeAttribute;\n    this.impacts = impacts;\n    this.ruleKey = ruleKey;\n    this.primaryMessage = primaryMessage;\n    this.fileUri = fileUri;\n    this.flows = flows;\n    this.quickFixes = quickFixes;\n    this.textRange = textRange;\n    this.ruleDescriptionContextKey = ruleDescriptionContextKey;\n    this.vulnerabilityProbability = vulnerabilityProbability;\n  }\n\n  public IssueSeverity getSeverity() {\n    return severity;\n  }\n\n  public RuleType getType() {\n    return type;\n  }\n\n  public CleanCodeAttribute getCleanCodeAttribute() {\n    return cleanCodeAttribute;\n  }\n\n  public Map<SoftwareQuality, ImpactSeverity> getImpacts() {\n    return impacts;\n  }\n\n  public String getRuleKey() {\n    return ruleKey;\n  }\n\n  public String getPrimaryMessage() {\n    return primaryMessage;\n  }\n\n  @CheckForNull\n  public URI getFileUri() {\n    return fileUri;\n  }\n\n  public List<RawIssueFlowDto> getFlows() {\n    return flows;\n  }\n\n  public List<QuickFixDto> getQuickFixes() {\n    return quickFixes;\n  }\n\n  @CheckForNull\n  public TextRangeDto getTextRange() {\n    return textRange;\n  }\n\n  @CheckForNull\n  public String getRuleDescriptionContextKey() {\n    return ruleDescriptionContextKey;\n  }\n\n  @CheckForNull\n  public VulnerabilityProbability getVulnerabilityProbability() {\n    return vulnerabilityProbability;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/analysis/RawIssueFlowDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.analysis;\n\nimport java.util.List;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\n\n/**\n * @deprecated since 10.2, replaced by {@link org.sonarsource.sonarlint.core.rpc.protocol.client.issue.IssueFlowDto}\n * See {@link org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService#analyzeFilesAndTrack(AnalyzeFilesAndTrackParams)}\n */\n@Deprecated(since = \"10.2\")\npublic class RawIssueFlowDto {\n\n  private final List<RawIssueLocationDto> locations;\n\n  public RawIssueFlowDto(List<RawIssueLocationDto> locations) {\n    this.locations = locations;\n  }\n\n  public List<RawIssueLocationDto> getLocations() {\n    return locations;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/analysis/RawIssueLocationDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.analysis;\n\nimport java.net.URI;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;\n\n/**\n * @deprecated since 10.2, replaced by {@link org.sonarsource.sonarlint.core.rpc.protocol.client.issue.IssueLocationDto}\n * See {@link org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService#analyzeFilesAndTrack(AnalyzeFilesAndTrackParams)}\n */\n@Deprecated(since = \"10.2\")\npublic class RawIssueLocationDto {\n  private final TextRangeDto textRange;\n  private final String message;\n  private final URI fileUri;\n\n  public RawIssueLocationDto(@Nullable TextRangeDto textRange, @Nullable String message, @Nullable URI fileUri) {\n    this.textRange = textRange;\n    this.message = message;\n    this.fileUri = fileUri;\n  }\n\n  @CheckForNull\n  public TextRangeDto getTextRange() {\n    return textRange;\n  }\n\n  @CheckForNull\n  public String getMessage() {\n    return message;\n  }\n\n  @CheckForNull\n  public URI getFileUri() {\n    return fileUri;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/analysis/TextEditDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.analysis;\n\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;\n\n/**\n * @deprecated since 10.2, replaced by {@link org.sonarsource.sonarlint.core.rpc.protocol.client.issue.TextEditDto}\n * See {@link org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService#analyzeFilesAndTrack(AnalyzeFilesAndTrackParams)}\n */\n@Deprecated(since = \"10.2\")\npublic class TextEditDto {\n  private final TextRangeDto range;\n  private final String newText;\n\n  public TextEditDto(TextRangeDto range, String newText) {\n    this.range = range;\n    this.newText = newText;\n  }\n\n  public TextRangeDto range() {\n    return range;\n  }\n\n  public String newText() {\n    return newText;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/analysis/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.analysis;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/binding/AssistBindingParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.binding;\n\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin;\n\npublic class AssistBindingParams {\n  private final String connectionId;\n  private final String projectKey;\n  private final String configScopeId;\n  @Deprecated(forRemoval = true)\n  private final boolean isFromSharedConfiguration;\n  private final BindingSuggestionOrigin origin;\n\n  public AssistBindingParams(String connectionId, String projectKey, String configScopeId, BindingSuggestionOrigin origin) {\n    this.connectionId = connectionId;\n    this.projectKey = projectKey;\n    this.configScopeId = configScopeId;\n    this.isFromSharedConfiguration = origin == BindingSuggestionOrigin.SHARED_CONFIGURATION;\n    this.origin = origin;\n  }\n\n  public String getConnectionId() {\n    return connectionId;\n  }\n\n  public String getProjectKey() {\n    return projectKey;\n  }\n\n  public BindingSuggestionOrigin getOrigin() {\n    return origin;\n  }\n\n  public String getConfigScopeId() {\n    return configScopeId;\n  }\n\n  /**\n   * @deprecated Will be removed in a future version, use {@link #getOrigin()} instead.\n   */\n  @Deprecated(forRemoval = true)\n  public boolean isFromSharedConfiguration() {\n    return isFromSharedConfiguration;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/binding/AssistBindingResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.binding;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class AssistBindingResponse {\n  private final String configurationScopeId;\n\n  public AssistBindingResponse(@Nullable String configurationScopeId) {\n    this.configurationScopeId = configurationScopeId;\n  }\n\n  @CheckForNull\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/binding/GetBindingSuggestionsResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.binding;\n\nimport java.util.List;\nimport java.util.Map;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionDto;\n\npublic class GetBindingSuggestionsResponse {\n\n  private final Map<String, List<BindingSuggestionDto>> suggestions;\n\n  public GetBindingSuggestionsResponse(Map<String, List<BindingSuggestionDto>> suggestions) {\n    this.suggestions = suggestions;\n  }\n\n  public Map<String, List<BindingSuggestionDto>> getSuggestions() {\n    return suggestions;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/binding/NoBindingSuggestionFoundParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.binding;\n\npublic class NoBindingSuggestionFoundParams {\n\n  private final String projectKey;\n  private final boolean isSonarCloud;\n\n  public NoBindingSuggestionFoundParams(String projectKey, boolean isSonarCloud) {\n    this.projectKey = projectKey;\n    this.isSonarCloud = isSonarCloud;\n  }\n\n  public String getProjectKey() {\n    return projectKey;\n  }\n\n  public boolean isSonarCloud() {\n    return isSonarCloud;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/binding/SuggestBindingParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.binding;\n\nimport java.util.List;\nimport java.util.Map;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionDto;\n\npublic class SuggestBindingParams {\n\n  private final Map<String, List<BindingSuggestionDto>> suggestions;\n\n  public SuggestBindingParams(Map<String, List<BindingSuggestionDto>> suggestions) {\n    this.suggestions = suggestions;\n  }\n\n  public Map<String, List<BindingSuggestionDto>> getSuggestions() {\n    return suggestions;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/binding/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.binding;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/branch/DidChangeMatchedSonarProjectBranchParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.branch;\n\npublic class DidChangeMatchedSonarProjectBranchParams {\n  private final String configScopeId;\n  private final String newMatchedBranchName;\n\n  public DidChangeMatchedSonarProjectBranchParams(String configScopeId, String newMatchedBranchName) {\n    this.configScopeId = configScopeId;\n    this.newMatchedBranchName = newMatchedBranchName;\n  }\n\n  public String getConfigScopeId() {\n    return configScopeId;\n  }\n\n  public String getNewMatchedBranchName() {\n    return newMatchedBranchName;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/branch/MatchProjectBranchParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.branch;\n\npublic class MatchProjectBranchParams {\n  private final String configurationScopeId;\n  private final String serverBranchToMatch;\n\n  public MatchProjectBranchParams(String configurationScopeId, String branchNameToMatch) {\n    this.configurationScopeId = configurationScopeId;\n    this.serverBranchToMatch = branchNameToMatch;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public String getServerBranchToMatch() {\n    return serverBranchToMatch;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/branch/MatchProjectBranchResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.branch;\n\npublic class MatchProjectBranchResponse {\n  private final boolean isBranchMatched;\n\n  public MatchProjectBranchResponse(boolean matchedSonarBranch) {\n    this.isBranchMatched = matchedSonarBranch;\n  }\n\n  public boolean isBranchMatched() {\n    return isBranchMatched;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/branch/MatchSonarProjectBranchParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.branch;\n\nimport java.util.Set;\n\npublic class MatchSonarProjectBranchParams {\n  private final String configurationScopeId;\n  private final String mainSonarBranchName;\n  private final Set<String> allSonarBranchesNames;\n\n  public MatchSonarProjectBranchParams(String configurationScopeId, String mainSonarBranchName, Set<String> allSonarBranchesNames) {\n    this.configurationScopeId = configurationScopeId;\n    this.mainSonarBranchName = mainSonarBranchName;\n    this.allSonarBranchesNames = allSonarBranchesNames;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public String getMainSonarBranchName() {\n    return mainSonarBranchName;\n  }\n\n  public Set<String> getAllSonarBranchesNames() {\n    return allSonarBranchesNames;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/branch/MatchSonarProjectBranchResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.branch;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class MatchSonarProjectBranchResponse {\n  private final String matchedSonarBranch;\n\n  /**\n   * For some reason, it could happen that the client is not able to match the branch, so the parameter is nullable.\n   */\n  public MatchSonarProjectBranchResponse(@Nullable String matchedSonarBranch) {\n    this.matchedSonarBranch = matchedSonarBranch;\n  }\n\n  @CheckForNull\n  public String getMatchedSonarProjectBranch() {\n    return matchedSonarBranch;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/branch/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.branch;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/connection/AssistCreatingConnectionParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.connection;\n\nimport com.google.gson.annotations.JsonAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.EitherSonarQubeSonarCloudConnectionParamsAdapterFactory;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\n\npublic class AssistCreatingConnectionParams {\n  @JsonAdapter(EitherSonarQubeSonarCloudConnectionParamsAdapterFactory.class)\n  private final Either<SonarQubeConnectionParams, SonarCloudConnectionParams> connectionParams;\n\n  public AssistCreatingConnectionParams(Either<SonarQubeConnectionParams, SonarCloudConnectionParams> connectionParams) {\n    this.connectionParams = connectionParams;\n  }\n\n  public AssistCreatingConnectionParams(SonarQubeConnectionParams sonarQubeConnection) {\n    this(Either.forLeft(sonarQubeConnection));\n  }\n\n  public AssistCreatingConnectionParams(SonarCloudConnectionParams sonarCloudConnection) {\n    this(Either.forRight(sonarCloudConnection));\n  }\n\n  public Either<SonarQubeConnectionParams, SonarCloudConnectionParams> getConnectionParams() {\n    return connectionParams;\n  }\n\n  public String getTokenName() {\n    return connectionParams.isLeft() ?\n      connectionParams.getLeft().getTokenName()\n      : connectionParams.getRight().getTokenName();\n  }\n\n  public String getTokenValue() {\n    return connectionParams.isLeft() ?\n      connectionParams.getLeft().getTokenValue()\n      : connectionParams.getRight().getTokenValue();\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/connection/AssistCreatingConnectionResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.connection;\n\npublic class AssistCreatingConnectionResponse {\n  private final String newConnectionId;\n\n  public AssistCreatingConnectionResponse(String newConnectionId) {\n    this.newConnectionId = newConnectionId;\n  }\n\n  public String getNewConnectionId() {\n    return newConnectionId;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/connection/ConnectionSuggestionDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.connection;\n\nimport com.google.gson.annotations.JsonAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.EitherSonarQubeSonarCloudConnectionAdapterFactory;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\n\npublic class ConnectionSuggestionDto {\n\n  @JsonAdapter(EitherSonarQubeSonarCloudConnectionAdapterFactory.class)\n  private final Either<SonarQubeConnectionSuggestionDto, SonarCloudConnectionSuggestionDto> connectionSuggestion;\n  @Deprecated(forRemoval = true)\n  private final boolean isFromSharedConfiguration;\n\n  private final BindingSuggestionOrigin origin;\n\n  public ConnectionSuggestionDto(Either<SonarQubeConnectionSuggestionDto, SonarCloudConnectionSuggestionDto> connectionSuggestion,\n    BindingSuggestionOrigin origin) {\n    this.connectionSuggestion = connectionSuggestion;\n    this.isFromSharedConfiguration = origin == BindingSuggestionOrigin.SHARED_CONFIGURATION;\n    this.origin = origin;\n  }\n\n  public ConnectionSuggestionDto(SonarQubeConnectionSuggestionDto connection, BindingSuggestionOrigin origin) {\n    this(Either.forLeft(connection), origin);\n  }\n\n  public ConnectionSuggestionDto(SonarCloudConnectionSuggestionDto connection, BindingSuggestionOrigin origin) {\n    this(Either.forRight(connection), origin);\n  }\n\n  public Either<SonarQubeConnectionSuggestionDto, SonarCloudConnectionSuggestionDto> getConnectionSuggestion() {\n    return connectionSuggestion;\n  }\n\n  /**\n   * @deprecated avoid calling this method if possible, since it will be removed once all the clients are migrated.\n   * Rely on {@link #getOrigin()}  instead.\n   */\n  @Deprecated(forRemoval = true)\n  public boolean isFromSharedConfiguration() {\n    return isFromSharedConfiguration;\n  }\n\n  public BindingSuggestionOrigin getOrigin() {\n    return origin;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/connection/GetConnectionSuggestionsParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.connection;\n\npublic class GetConnectionSuggestionsParams {\n\n  private final String configurationScopeId;\n\n  public GetConnectionSuggestionsParams(String configurationScopeId) {\n    this.configurationScopeId = configurationScopeId;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/connection/GetCredentialsParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.connection;\n\npublic class GetCredentialsParams {\n\n  private final String connectionId;\n\n  public GetCredentialsParams(String connectionId) {\n    this.connectionId = connectionId;\n  }\n\n  public String getConnectionId() {\n    return connectionId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/connection/GetCredentialsResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.connection;\n\nimport com.google.gson.annotations.JsonAdapter;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.EitherCredentialsAdapterFactory;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\n\npublic class GetCredentialsResponse {\n\n  @JsonAdapter(EitherCredentialsAdapterFactory.class)\n  @Nullable\n  private final Either<TokenDto, UsernamePasswordDto> credentials;\n\n  public GetCredentialsResponse(@Nullable Either<TokenDto, UsernamePasswordDto> credentials) {\n    this.credentials = credentials;\n  }\n\n  public GetCredentialsResponse(TokenDto token) {\n    this(Either.forLeft(token));\n  }\n\n  public GetCredentialsResponse(UsernamePasswordDto usernamePassword) {\n    this(Either.forRight(usernamePassword));\n  }\n\n  /**\n   * @return @{@code null} if no credentials defined for this connection\n   */\n  @CheckForNull\n  public Either<TokenDto, UsernamePasswordDto> getCredentials() {\n    return credentials;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/connection/SonarCloudConnectionParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.connection;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion;\n\npublic class SonarCloudConnectionParams {\n  private final String organizationKey;\n  private final String tokenName;\n  private final String tokenValue;\n  private final SonarCloudRegion region;\n\n  public SonarCloudConnectionParams(String organizationKey, @Nullable String tokenName, @Nullable String tokenValue, SonarCloudRegion region) {\n    this.organizationKey = organizationKey;\n    this.tokenName = tokenName;\n    this.tokenValue = tokenValue;\n    this.region = region;\n  }\n\n  public String getOrganizationKey() {\n    return organizationKey;\n  }\n\n  @CheckForNull\n  public String getTokenName() {\n    return tokenName;\n  }\n\n  @CheckForNull\n  public String getTokenValue() {\n    return tokenValue;\n  }\n\n  public SonarCloudRegion getRegion() {\n    return region;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/connection/SonarCloudConnectionSuggestionDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.connection;\n\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion;\n\npublic class SonarCloudConnectionSuggestionDto {\n\n  private final String organization;\n  private final String projectKey;\n  private final SonarCloudRegion region;\n\n  public SonarCloudConnectionSuggestionDto(String organization, String projectKey, SonarCloudRegion region) {\n    this.organization = organization;\n    this.projectKey = projectKey;\n    this.region = region;\n  }\n\n  public String getOrganization() {\n    return organization;\n  }\n\n  public String getProjectKey() {\n    return projectKey;\n  }\n\n  public SonarCloudRegion getRegion() {\n    return region;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/connection/SonarQubeConnectionParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.connection;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class SonarQubeConnectionParams {\n  private final String serverUrl;\n  private final String tokenName;\n  private final String tokenValue;\n\n  public SonarQubeConnectionParams(String serverUrl, @Nullable String tokenName, @Nullable String tokenValue) {\n    this.serverUrl = serverUrl;\n    this.tokenName = tokenName;\n    this.tokenValue = tokenValue;\n  }\n\n  public String getServerUrl() {\n    return serverUrl;\n  }\n\n  @CheckForNull\n  public String getTokenName() {\n    return tokenName;\n  }\n\n  @CheckForNull\n  public String getTokenValue() {\n    return tokenValue;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/connection/SonarQubeConnectionSuggestionDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.connection;\n\npublic class SonarQubeConnectionSuggestionDto {\n\n  private final String serverUrl;\n  private final String projectKey;\n\n  public SonarQubeConnectionSuggestionDto(String serverUrl, String projectKey) {\n    this.serverUrl = serverUrl;\n    this.projectKey = projectKey;\n  }\n\n  public String getServerUrl() {\n    return serverUrl;\n  }\n\n  public String getProjectKey() {\n    return projectKey;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/connection/SuggestConnectionParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.connection;\n\nimport java.util.List;\nimport java.util.Map;\n\npublic class SuggestConnectionParams {\n\n  private final Map<String, List<ConnectionSuggestionDto>> suggestionsByConfigScopeId;\n\n  public SuggestConnectionParams(Map<String, List<ConnectionSuggestionDto>> suggestionsByConfigScopeId) {\n    this.suggestionsByConfigScopeId = suggestionsByConfigScopeId;\n  }\n\n  public Map<String, List<ConnectionSuggestionDto>> getSuggestionsByConfigScopeId() {\n    return suggestionsByConfigScopeId;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/connection/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.connection;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/embeddedserver/EmbeddedServerStartedParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.embeddedserver;\n\npublic class EmbeddedServerStartedParams {\n  private final int port;\n\n  public EmbeddedServerStartedParams(int port) {\n    this.port = port;\n  }\n\n  public int getPort() {\n    return port;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/embeddedserver/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.embeddedserver;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/event/DidReceiveServerHotspotEvent.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.event;\n\nimport java.nio.file.Path;\n\npublic class DidReceiveServerHotspotEvent {\n\n  private final String connectionId;\n  private final String sonarProjectKey;\n  private final Path ideFilePath;\n\n  public DidReceiveServerHotspotEvent(String connectionId, String sonarProjectKey, Path serverFilePath) {\n    this.connectionId = connectionId;\n    this.sonarProjectKey = sonarProjectKey;\n    this.ideFilePath = serverFilePath;\n  }\n\n  public String getConnectionId() {\n    return connectionId;\n  }\n\n  public String getSonarProjectKey() {\n    return sonarProjectKey;\n  }\n\n  public Path getIdeFilePath() {\n    return ideFilePath;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/event/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.event;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/fix/ChangesDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.fix;\n\npublic class ChangesDto {\n\n  private final LineRangeDto beforeLineRange;\n  private final String before;\n  private final String after;\n\n  public ChangesDto(LineRangeDto beforeLineRange, String before, String after) {\n    this.beforeLineRange = beforeLineRange;\n    this.before = before;\n    this.after = after;\n  }\n\n  public LineRangeDto beforeLineRange() {\n    return beforeLineRange;\n  }\n\n  public String before() {\n    return before;\n  }\n\n  public String after() {\n    return after;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/fix/FileEditDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.fix;\n\nimport java.nio.file.Path;\nimport java.util.List;\n\npublic class FileEditDto {\n\n  private final Path idePath;\n  private final List<ChangesDto> changes;\n\n  public FileEditDto(Path idePath, List<ChangesDto> changes) {\n    this.idePath = idePath;\n    this.changes = changes;\n  }\n\n  public Path idePath() {\n    return idePath;\n  }\n\n  public List<ChangesDto> changes() {\n    return changes;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/fix/FixSuggestionDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.fix;\n\npublic class FixSuggestionDto {\n\n  private final String suggestionId;\n  private final String explanation;\n  private final FileEditDto fileEdit;\n\n  public FixSuggestionDto(String suggestionId, String explanation, FileEditDto fileEdit) {\n    this.suggestionId = suggestionId;\n    this.explanation = explanation;\n    this.fileEdit = fileEdit;\n  }\n\n  public String suggestionId() {\n    return suggestionId;\n  }\n\n  public String explanation() {\n    return explanation;\n  }\n\n  public FileEditDto fileEdit() {\n    return fileEdit;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/fix/LineRangeDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.fix;\n\npublic class LineRangeDto {\n\n  private final int startLine;\n  private final int endLine;\n\n  public LineRangeDto(int startLine, int endLine) {\n    this.startLine = startLine;\n    this.endLine = endLine;\n  }\n\n  public int getStartLine() {\n    return startLine;\n  }\n\n  public int getEndLine() {\n    return endLine;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/fix/ShowFixSuggestionParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.fix;\n\npublic class ShowFixSuggestionParams {\n\n  private final String configurationScopeId;\n  private final String issueKey;\n  private final FixSuggestionDto fixSuggestion;\n\n  public ShowFixSuggestionParams(String configurationScopeId, String issueKey, FixSuggestionDto fixSuggestion) {\n    this.configurationScopeId = configurationScopeId;\n    this.issueKey = issueKey;\n    this.fixSuggestion = fixSuggestion;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public String getIssueKey() {\n    return issueKey;\n  }\n\n  public FixSuggestionDto getFixSuggestion() {\n    return fixSuggestion;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/fix/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.fix;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/fs/GetBaseDirParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.fs;\n\npublic class GetBaseDirParams {\n\n  private final String configurationScopeId;\n\n  public GetBaseDirParams(String configurationScopeId) {\n    this.configurationScopeId = configurationScopeId;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/fs/GetBaseDirResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.fs;\n\nimport java.nio.file.Path;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class GetBaseDirResponse {\n  private final Path baseDir;\n\n  public GetBaseDirResponse(@Nullable Path baseDir) {\n    this.baseDir = baseDir;\n  }\n\n  @CheckForNull\n  public Path getBaseDir() {\n    return baseDir;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/fs/ListFilesParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.fs;\n\npublic class ListFilesParams {\n  \n  private final String configScopeId;\n\n  public ListFilesParams(String configScopeId) {\n    this.configScopeId = configScopeId;\n  }\n\n  public String getConfigScopeId() {\n    return configScopeId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/fs/ListFilesResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.fs;\n\nimport java.util.List;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\n\npublic class ListFilesResponse {\n\n  private final List<ClientFileDto> files;\n\n  public ListFilesResponse(List<ClientFileDto> files) {\n    this.files = files;\n  }\n\n  public List<ClientFileDto> getFiles() {\n    return files;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/fs/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.fs;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/hotspot/HotspotDetailsDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot;\n\nimport java.nio.file.Path;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;\n\npublic class HotspotDetailsDto {\n  private final String key;\n  @Deprecated(forRemoval = true)\n  private final String message;\n  private final Path ideFilePath;\n  @Deprecated(forRemoval = true)\n  private final TextRangeDto textRange;\n  @Deprecated(forRemoval = true)\n  private final String author;\n  @Deprecated(forRemoval = true)\n  private final String status;\n  @Deprecated(forRemoval = true)\n  @Nullable\n  private final String resolution;\n  @Deprecated(forRemoval = true)\n  private final HotspotRule rule;\n  @Deprecated(forRemoval = true)\n  @Nullable\n  private final String codeSnippet;\n\n  public HotspotDetailsDto(String key, String message, Path ideFilePath, TextRangeDto textRange, String author, String status, @Nullable String resolution, HotspotRule rule,\n    @Nullable String codeSnippet) {\n    this.key = key;\n    this.message = message;\n    this.ideFilePath = ideFilePath;\n    this.textRange = textRange;\n    this.author = author;\n    this.status = status;\n    this.resolution = resolution;\n    this.rule = rule;\n    this.codeSnippet = codeSnippet;\n  }\n\n  public String getKey() {\n    return key;\n  }\n\n  public String getMessage() {\n    return message;\n  }\n\n  public Path getIdeFilePath() {\n    return ideFilePath;\n  }\n\n  public TextRangeDto getTextRange() {\n    return textRange;\n  }\n\n  public String getAuthor() {\n    return author;\n  }\n\n  public String getStatus() {\n    return status;\n  }\n\n  @Nullable\n  public String getResolution() {\n    return resolution;\n  }\n\n  public HotspotRule getRule() {\n    return rule;\n  }\n\n  @Nullable\n  public String getCodeSnippet() {\n    return codeSnippet;\n  }\n\n  public static class HotspotRule {\n    private final String key;\n    private final String name;\n    private final String securityCategory;\n    private final String vulnerabilityProbability;\n    private final String riskDescription;\n    private final String vulnerabilityDescription;\n    private final String fixRecommendations;\n\n    public HotspotRule(String key, String name, String securityCategory, String vulnerabilityProbability, String riskDescription, String vulnerabilityDescription,\n      String fixRecommendations) {\n      this.key = key;\n      this.name = name;\n      this.securityCategory = securityCategory;\n      this.vulnerabilityProbability = vulnerabilityProbability;\n      this.riskDescription = riskDescription;\n      this.vulnerabilityDescription = vulnerabilityDescription;\n      this.fixRecommendations = fixRecommendations;\n    }\n\n    public String getKey() {\n      return key;\n    }\n\n    public String getName() {\n      return name;\n    }\n\n    public String getSecurityCategory() {\n      return securityCategory;\n    }\n\n    public String getVulnerabilityProbability() {\n      return vulnerabilityProbability;\n    }\n\n    public String getRiskDescription() {\n      return riskDescription;\n    }\n\n    public String getVulnerabilityDescription() {\n      return vulnerabilityDescription;\n    }\n\n    public String getFixRecommendations() {\n      return fixRecommendations;\n    }\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/hotspot/RaiseHotspotsParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot;\n\nimport java.net.URI;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class RaiseHotspotsParams {\n  private final String configurationScopeId;\n  private final Map<URI, List<RaisedHotspotDto>> hotspotsByFileUri;\n  // true if the publication is made for streaming purposes, false if it's the final publication for a given analysis\n  private final boolean isIntermediatePublication;\n  @Nullable\n  // the ID that was provided when the analysis was triggered, or null if this publication is not a consequence of an analysis\n  private final UUID analysisId;\n\n  public RaiseHotspotsParams(String configurationScopeId, Map<URI, List<RaisedHotspotDto>> hotspotsByFileUri, boolean isIntermediatePublication, @Nullable UUID analysisId) {\n    this.configurationScopeId = configurationScopeId;\n    this.hotspotsByFileUri = hotspotsByFileUri;\n    this.isIntermediatePublication = isIntermediatePublication;\n    this.analysisId = analysisId;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public Map<URI, List<RaisedHotspotDto>> getHotspotsByFileUri() {\n    return hotspotsByFileUri;\n  }\n\n  public boolean isIntermediatePublication() {\n    return isIntermediatePublication;\n  }\n\n  @CheckForNull\n  public UUID getAnalysisId() {\n    return analysisId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/hotspot/RaisedHotspotDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot;\n\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.UUID;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.HotspotStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.IssueFlowDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.QuickFixDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedFindingDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.MQRModeDetails;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.StandardModeDetails;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;\n\npublic class RaisedHotspotDto extends RaisedFindingDto {\n\n  private final HotspotStatus status;\n  private final VulnerabilityProbability vulnerabilityProbability;\n\n  public RaisedHotspotDto(UUID id, @Nullable String serverKey, String ruleKey, String primaryMessage, Either<StandardModeDetails, MQRModeDetails> severityMode,\n    Instant introductionDate, boolean isOnNewCode, boolean resolved, @Nullable TextRangeDto textRange, List<IssueFlowDto> flows, List<QuickFixDto> quickFixes,\n    @Nullable String ruleDescriptionContextKey, VulnerabilityProbability vulnerabilityProbability, HotspotStatus status) {\n    super(id, serverKey, ruleKey, primaryMessage, severityMode, introductionDate, isOnNewCode, resolved, textRange, flows, quickFixes, ruleDescriptionContextKey);\n    this.vulnerabilityProbability = vulnerabilityProbability;\n    this.status = status;\n  }\n\n  public VulnerabilityProbability getVulnerabilityProbability() {\n    return vulnerabilityProbability;\n  }\n\n  public HotspotStatus getStatus() {\n    return status;\n  }\n\n  public RaisedHotspotDto withHotspotStatusAndResolution(HotspotStatus hotspotStatus, boolean resolved) {\n    return new RaisedHotspotDto(getId(), getServerKey(), getRuleKey(), getPrimaryMessage(), getSeverityMode(), getIntroductionDate(), isOnNewCode(), resolved, getTextRange(),\n      getFlows(), getQuickFixes(), getRuleDescriptionContextKey(), getVulnerabilityProbability(), hotspotStatus);\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/hotspot/ShowHotspotParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot;\n\npublic class ShowHotspotParams {\n  private final String configurationScopeId;\n  private final HotspotDetailsDto hotspotDetails;\n\n  public ShowHotspotParams(String configurationScopeId, HotspotDetailsDto hotspotDetails) {\n    this.configurationScopeId = configurationScopeId;\n    this.hotspotDetails = hotspotDetails;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public HotspotDetailsDto getHotspotDetails() {\n    return hotspotDetails;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/hotspot/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/http/CheckServerTrustedParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.http;\n\nimport java.util.List;\n\npublic class CheckServerTrustedParams {\n\n  /**\n   * the peer certificate chain\n   */\n  private final List<X509CertificateDto> chain;\n\n  /**\n   * the key exchange algorithm used\n   */\n  private final String authType;\n\n  public CheckServerTrustedParams(List<X509CertificateDto> chain, String authType) {\n    this.chain = chain;\n    this.authType = authType;\n  }\n\n  public List<X509CertificateDto> getChain() {\n    return chain;\n  }\n\n  public String getAuthType() {\n    return authType;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/http/CheckServerTrustedResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.http;\n\npublic class CheckServerTrustedResponse {\n\n  private final boolean trusted;\n\n  public CheckServerTrustedResponse(boolean trusted) {\n    this.trusted = trusted;\n  }\n\n  public boolean isTrusted() {\n    return trusted;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/http/GetProxyPasswordAuthenticationParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.http;\n\nimport java.net.Authenticator;\nimport java.net.InetAddress;\nimport java.net.URL;\nimport javax.annotation.Nullable;\n\n/**\n * @see Authenticator#requestPasswordAuthentication(String, InetAddress, int, String, String, String)\n *\n */\npublic class GetProxyPasswordAuthenticationParams {\n\n  private final String host;\n  private final int port;\n  private final String protocol;\n  private final String prompt;\n  private final String scheme;\n  private final URL targetHost;\n\n  public GetProxyPasswordAuthenticationParams(String host, int port, String protocol, @Nullable String prompt, @Nullable String scheme, URL targetHost) {\n    this.host = host;\n    this.port = port;\n    this.protocol = protocol;\n    this.prompt = prompt;\n    this.scheme = scheme;\n    this.targetHost = targetHost;\n  }\n\n  public String getHost() {\n    return host;\n  }\n\n  public int getPort() {\n    return port;\n  }\n\n  public String getProtocol() {\n    return protocol;\n  }\n\n  @Nullable\n  public String getPrompt() {\n    return prompt;\n  }\n\n  @Nullable\n  public String getScheme() {\n    return scheme;\n  }\n\n  public URL getTargetHost() {\n    return targetHost;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/http/GetProxyPasswordAuthenticationResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.http;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class GetProxyPasswordAuthenticationResponse {\n\n  private final String proxyUser;\n  private final String proxyPassword;\n\n  public GetProxyPasswordAuthenticationResponse(@Nullable String proxyUser, @Nullable String proxyPassword) {\n    this.proxyUser = proxyUser;\n    this.proxyPassword = proxyPassword;\n  }\n\n  @CheckForNull\n  public String getProxyUser() {\n    return proxyUser;\n  }\n\n  @CheckForNull\n  public String getProxyPassword() {\n    return proxyPassword;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/http/ProxyDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.http;\n\nimport java.net.Proxy;\n\n/**\n * Same as {@link java.net.Proxy}\n */\npublic class ProxyDto {\n\n  public static final ProxyDto NO_PROXY = new ProxyDto(Proxy.Type.DIRECT, null, 0);\n\n  private final Proxy.Type type;\n\n  private final String hostname;\n\n  private final int port;\n\n  public ProxyDto(Proxy.Type type, String hostname, int port) {\n    this.type = type;\n    this.hostname = hostname;\n    this.port = port;\n  }\n\n  public Proxy.Type getType() {\n    return type;\n  }\n\n  public String getHostname() {\n    return hostname;\n  }\n\n  public int getPort() {\n    return port;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/http/SelectProxiesParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.http;\n\nimport java.net.URI;\n\npublic class SelectProxiesParams {\n\n  private final URI uri;\n\n  public SelectProxiesParams(URI uri) {\n    this.uri = uri;\n  }\n\n  public URI getUri() {\n    return uri;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/http/SelectProxiesResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.http;\n\nimport java.util.List;\n\npublic class SelectProxiesResponse {\n\n  private final List<ProxyDto> proxies;\n\n  public SelectProxiesResponse(List<ProxyDto> proxies) {\n    this.proxies = proxies;\n  }\n\n  public List<ProxyDto> getProxies() {\n    return proxies;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/http/X509CertificateDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.http;\n\npublic class X509CertificateDto {\n\n  private final String pem;\n\n\n  public X509CertificateDto(String pem) {\n    this.pem = pem;\n  }\n\n  public String getPem() {\n    return pem;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/http/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.http;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/info/GetClientLiveInfoResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.info;\n\npublic class GetClientLiveInfoResponse {\n\n  private final String description;\n\n  /**\n   * @param description For clients that support multiple instances, the description should be specific enough to identify the instance\n   *     (example: Eclipse Workspace, IntelliJ flavor, ...). Still be careful to not expose sensitive data, as the content may be accessed externally.\n   *     This will be used to disambiguate between multiple instances of the same client for the open issue/hotspot in IDE feature.\n   */\n  public GetClientLiveInfoResponse(String description) {\n    this.description = description;\n  }\n\n  public String getDescription() {\n    return description;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/info/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.info;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/issue/FileEditDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.issue;\n\nimport java.net.URI;\nimport java.util.List;\n\npublic class FileEditDto {\n  private final URI target;\n  private final List<TextEditDto> textEdits;\n\n  public FileEditDto(URI target, List<TextEditDto> textEdits) {\n    this.target = target;\n    this.textEdits = textEdits;\n  }\n\n  public URI target() {\n    return target;\n  }\n\n  public List<TextEditDto> textEdits() {\n    return textEdits;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/issue/IssueDetailsDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.issue;\n\nimport java.nio.file.Path;\nimport java.util.List;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.FlowDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;\n\npublic class IssueDetailsDto {\n  private final String issueKey;\n  private final String ruleKey;\n  private final Path ideFilePath;\n  private final String message;\n  private final String creationDate;\n  private final String codeSnippet;\n  private final boolean isTaint;\n  private final List<FlowDto> flows;\n  private final TextRangeDto textRange;\n\n  public IssueDetailsDto(TextRangeDto textRange, String ruleKey, String issueKey,\n    Path ideFilePath, String message, String creationDate,\n    String codeSnippet, boolean isTaint, List<FlowDto> flows) {\n    this.issueKey = issueKey;\n    this.ruleKey = ruleKey;\n    this.textRange = textRange;\n    this.ideFilePath = ideFilePath;\n    this.message = message;\n    this.creationDate = creationDate;\n    this.codeSnippet = codeSnippet;\n    this.isTaint = isTaint;\n    this.flows = flows;\n  }\n\n  public TextRangeDto getTextRange() {\n    return textRange;\n  }\n\n  public String getRuleKey() {\n    return ruleKey;\n  }\n\n  public String getIssueKey() {\n    return issueKey;\n  }\n\n  public String getCreationDate() {\n    return creationDate;\n  }\n\n  public Path getIdeFilePath() {\n    return ideFilePath;\n  }\n\n  public String getCodeSnippet() {\n    return codeSnippet;\n  }\n\n  public String getMessage() {\n    return message;\n  }\n\n  public boolean isTaint() {\n    return isTaint;\n  }\n\n  public List<FlowDto> getFlows() {\n    return flows;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/issue/IssueFlowDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.issue;\n\nimport java.util.List;\n\npublic class IssueFlowDto {\n\n  private final List<IssueLocationDto> locations;\n\n  public IssueFlowDto(List<IssueLocationDto> locations) {\n    this.locations = locations;\n  }\n\n  public List<IssueLocationDto> getLocations() {\n    return locations;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/issue/IssueLocationDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.issue;\n\nimport java.net.URI;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;\n\npublic class IssueLocationDto {\n  private final TextRangeDto textRange;\n  private final String message;\n  private final URI fileUri;\n\n  public IssueLocationDto(@Nullable TextRangeDto textRange, @Nullable String message, @Nullable URI fileUri) {\n    this.textRange = textRange;\n    this.message = message;\n    this.fileUri = fileUri;\n  }\n\n  @CheckForNull\n  public TextRangeDto getTextRange() {\n    return textRange;\n  }\n\n  @CheckForNull\n  public String getMessage() {\n    return message;\n  }\n\n  @CheckForNull\n  public URI getFileUri() {\n    return fileUri;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/issue/QuickFixDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.issue;\n\nimport java.util.List;\n\npublic class QuickFixDto {\n\n  private final List<FileEditDto> inputFileEdits;\n  private final String message;\n\n  public QuickFixDto(List<FileEditDto> inputFileEdits, String message) {\n    this.inputFileEdits = inputFileEdits;\n    this.message = message;\n  }\n\n  public List<FileEditDto> fileEdits() {\n    return inputFileEdits;\n  }\n\n  public String message() {\n    return message;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/issue/RaiseIssuesParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.issue;\n\nimport java.net.URI;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class RaiseIssuesParams {\n  private final String configurationScopeId;\n  private final Map<URI, List<RaisedIssueDto>> issuesByFileUri;\n  // true if the publication is made for streaming purposes, false if it's the final publication for a given analysis\n  private final boolean isIntermediatePublication;\n  @Nullable\n  // the ID that was provided when the analysis was triggered, or null if this publication is not a consequence of an analysis\n  private final UUID analysisId;\n\n  public RaiseIssuesParams(String configurationScopeId, Map<URI, List<RaisedIssueDto>> issuesByFileUri, boolean isIntermediatePublication, @Nullable UUID analysisId) {\n    this.configurationScopeId = configurationScopeId;\n    this.issuesByFileUri = issuesByFileUri;\n    this.isIntermediatePublication = isIntermediatePublication;\n    this.analysisId = analysisId;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public Map<URI, List<RaisedIssueDto>> getIssuesByFileUri() {\n    return issuesByFileUri;\n  }\n\n  public boolean isIntermediatePublication() {\n    return isIntermediatePublication;\n  }\n\n  @CheckForNull\n  public UUID getAnalysisId() {\n    return analysisId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/issue/RaisedFindingDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.issue;\n\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.UUID;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.MQRModeDetails;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.StandardModeDetails;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;\n\npublic abstract class RaisedFindingDto {\n\n  private final UUID id;\n  @Nullable\n  private final String serverKey;\n  private final String ruleKey;\n  private final String primaryMessage;\n  private final Either<StandardModeDetails, MQRModeDetails> severityMode;\n  private final Instant introductionDate;\n  private final boolean isOnNewCode;\n  private final boolean resolved;\n  @Nullable\n  private final TextRangeDto textRange;\n  private final List<IssueFlowDto> flows;\n  private final List<QuickFixDto> quickFixes;\n  @Nullable\n  private final String ruleDescriptionContextKey;\n\n  protected RaisedFindingDto(UUID id, @Nullable String serverKey, String ruleKey, String primaryMessage, Either<StandardModeDetails, MQRModeDetails> severityMode,\n    Instant introductionDate, boolean isOnNewCode, boolean resolved, @Nullable TextRangeDto textRange, List<IssueFlowDto> flows, List<QuickFixDto> quickFixes,\n    @Nullable String ruleDescriptionContextKey) {\n    this.id = id;\n    this.serverKey = serverKey;\n    this.ruleKey = ruleKey;\n    this.primaryMessage = primaryMessage;\n    this.severityMode = severityMode;\n    this.introductionDate = introductionDate;\n    this.isOnNewCode = isOnNewCode;\n    this.resolved = resolved;\n    this.textRange = textRange;\n    this.flows = flows;\n    this.quickFixes = quickFixes;\n    this.ruleDescriptionContextKey = ruleDescriptionContextKey;\n  }\n\n  public UUID getId() {\n    return id;\n  }\n\n  @CheckForNull\n  public String getServerKey() {\n    return serverKey;\n  }\n\n  public String getRuleKey() {\n    return ruleKey;\n  }\n\n  public String getPrimaryMessage() {\n    return primaryMessage;\n  }\n\n  public Either<StandardModeDetails, MQRModeDetails> getSeverityMode() {\n    return severityMode;\n  }\n\n  public Instant getIntroductionDate() {\n    return introductionDate;\n  }\n\n  public boolean isOnNewCode() {\n    return isOnNewCode;\n  }\n\n  public boolean isResolved() {\n    return resolved;\n  }\n\n  @CheckForNull\n  public TextRangeDto getTextRange() {\n    return textRange;\n  }\n\n  public List<IssueFlowDto> getFlows() {\n    return flows;\n  }\n\n  public List<QuickFixDto> getQuickFixes() {\n    return quickFixes;\n  }\n\n  @CheckForNull\n  public String getRuleDescriptionContextKey() {\n    return ruleDescriptionContextKey;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/issue/RaisedIssueDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.issue;\n\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.UUID;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ResolutionStatus;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.ImpactDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.MQRModeDetails;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.RuleType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.StandardModeDetails;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;\n\npublic class RaisedIssueDto extends RaisedFindingDto {\n\n  private final boolean isAiCodeFixable;\n  private final ResolutionStatus resolutionStatus;\n\n  public RaisedIssueDto(UUID id, @Nullable String serverKey, String ruleKey, String primaryMessage, Either<StandardModeDetails, MQRModeDetails> severityMode,\n    Instant introductionDate, boolean isOnNewCode, boolean resolved, @Nullable TextRangeDto textRange, List<IssueFlowDto> flows, List<QuickFixDto> quickFixes,\n    @Nullable String ruleDescriptionContextKey, boolean isAiCodeFixable, @Nullable ResolutionStatus resolutionStatus) {\n    super(id, serverKey, ruleKey, primaryMessage, severityMode, introductionDate, isOnNewCode, resolved, textRange, flows, quickFixes, ruleDescriptionContextKey);\n    this.isAiCodeFixable = isAiCodeFixable;\n    this.resolutionStatus = resolutionStatus;\n  }\n\n  public boolean isAiCodeFixable() {\n    return isAiCodeFixable;\n  }\n\n  public Builder builder() {\n    return Builder.from(this);\n  }\n\n  public ResolutionStatus getResolutionStatus() {\n    return resolutionStatus;\n  }\n\n  public static class Builder {\n    private final UUID id;\n    private final String serverKey;\n    private final String ruleKey;\n    private final String primaryMessage;\n    private Either<StandardModeDetails, MQRModeDetails> severityMode;\n    private final Instant introductionDate;\n    private final boolean isOnNewCode;\n    private boolean resolved;\n    private final TextRangeDto textRange;\n    private final List<IssueFlowDto> flows;\n    private final List<QuickFixDto> quickFixes;\n    private final String ruleDescriptionContextKey;\n    private final boolean isAiCodeFixable;\n    private final ResolutionStatus resolutionStatus;\n\n    private Builder(UUID id, @Nullable String serverKey, String ruleKey, String primaryMessage, Either<StandardModeDetails, MQRModeDetails> severityMode,\n      Instant introductionDate, boolean isOnNewCode, boolean resolved, @Nullable TextRangeDto textRange, List<IssueFlowDto> flows, List<QuickFixDto> quickFixes,\n      @Nullable String ruleDescriptionContextKey, boolean isAiCodeFixable, ResolutionStatus resolutionStatus) {\n      this.id = id;\n      this.serverKey = serverKey;\n      this.ruleKey = ruleKey;\n      this.primaryMessage = primaryMessage;\n      this.severityMode = severityMode;\n      this.introductionDate = introductionDate;\n      this.isOnNewCode = isOnNewCode;\n      this.resolved = resolved;\n      this.textRange = textRange;\n      this.flows = flows;\n      this.quickFixes = quickFixes;\n      this.ruleDescriptionContextKey = ruleDescriptionContextKey;\n      this.isAiCodeFixable = isAiCodeFixable;\n      this.resolutionStatus = resolutionStatus;\n    }\n\n    public static Builder from(RaisedIssueDto dto) {\n      return new Builder(dto.getId(), dto.getServerKey(), dto.getRuleKey(), dto.getPrimaryMessage(), dto.getSeverityMode(), dto.getIntroductionDate(), dto.isOnNewCode(),\n        dto.isResolved(), dto.getTextRange(), dto.getFlows(), dto.getQuickFixes(), dto.getRuleDescriptionContextKey(), dto.isAiCodeFixable(), dto.getResolutionStatus());\n    }\n\n    public Builder withResolution(boolean resolved) {\n      this.resolved = resolved;\n      return this;\n    }\n\n    public Builder withStandardModeDetails(IssueSeverity severity, RuleType type) {\n      this.severityMode = Either.forLeft(new StandardModeDetails(severity, type));\n      return this;\n    }\n\n    public Builder withMQRModeDetails(CleanCodeAttribute cleanCodeAttribute, List<ImpactDto> impacts) {\n      this.severityMode = Either.forRight(new MQRModeDetails(cleanCodeAttribute, impacts));\n      return this;\n    }\n\n    public RaisedIssueDto buildIssue() {\n      return new RaisedIssueDto(id, serverKey, ruleKey, primaryMessage, severityMode, introductionDate, isOnNewCode, resolved, textRange, flows, quickFixes,\n        ruleDescriptionContextKey, isAiCodeFixable, resolutionStatus);\n    }\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/issue/ShowIssueParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.issue;\n\npublic class ShowIssueParams {\n\n  private final String configurationScopeId;\n  private final IssueDetailsDto issueDetails;\n\n  public ShowIssueParams(String configurationScopeId, IssueDetailsDto issueDetails) {\n    this.configurationScopeId = configurationScopeId;\n    this.issueDetails = issueDetails;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public IssueDetailsDto getIssueDetails() {\n    return issueDetails;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/issue/TextEditDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.issue;\n\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;\n\npublic class TextEditDto {\n  private final TextRangeDto range;\n  private final String newText;\n\n  public TextEditDto(TextRangeDto range, String newText) {\n    this.range = range;\n    this.newText = newText;\n  }\n\n  public TextRangeDto range() {\n    return range;\n  }\n\n  public String newText() {\n    return newText;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/issue/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.issue;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/log/LogLevel.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.log;\n\npublic enum LogLevel {\n  ERROR, WARN, INFO, DEBUG, TRACE\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/log/LogParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.log;\n\nimport java.time.Instant;\nimport java.time.ZoneId;\nimport java.time.format.DateTimeFormatter;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class LogParams {\n\n  private final LogLevel level;\n  @Nullable\n  private final String message;\n  @Nullable\n  private final String configScopeId;\n  private final String threadName;\n  private final String loggerName;\n  @Nullable\n  private final String stackTrace;\n  private final Instant loggedAt;\n  private static final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME.withZone(ZoneId.systemDefault());\n\n  public LogParams(LogLevel level, @Nullable String message, @Nullable String configScopeId, @Nullable String stackTrace, Instant loggedAt) {\n    this(level, message, configScopeId, Thread.currentThread().getName(), \"sonarlint\", stackTrace, loggedAt);\n  }\n\n  public LogParams(LogLevel level, @Nullable String message, @Nullable String configScopeId, String threadName, String loggerName, @Nullable String stackTrace, Instant loggedAt) {\n    this.level = level;\n    this.message = message;\n    this.configScopeId = configScopeId;\n    this.threadName = threadName;\n    this.loggerName = loggerName;\n    this.stackTrace = stackTrace;\n    this.loggedAt = loggedAt;\n  }\n\n  public LogLevel getLevel() {\n    return level;\n  }\n\n  @CheckForNull\n  public String getMessage() {\n    return message;\n  }\n\n  /**\n   * Some logs are specific to a certain config scope.\n   * This can be used to display the log in the appropriate window, for IDEs that support multiple windows in the same instance (like IntelliJ)\n   */\n  @CheckForNull\n  public String getConfigScopeId() {\n    return configScopeId;\n  }\n\n  public String getThreadName() {\n    return threadName;\n  }\n\n  public String getLoggerName() {\n    return loggerName;\n  }\n\n  @CheckForNull\n  public String getStackTrace() {\n    return stackTrace;\n  }\n\n  public Instant getLoggedAt() {\n    return loggedAt;\n  }\n\n  @Override\n  public String toString() {\n    var sb = new StringBuilder();\n    sb.append(\" [\");\n    sb.append(formatter.format(loggedAt));\n    sb.append(\"] [\");\n    sb.append(threadName);\n    sb.append(\"] \");\n    sb.append(level.toString());\n    sb.append(\" \");\n    sb.append(loggerName);\n    sb.append(\" - \");\n    sb.append(message);\n    if (stackTrace != null) {\n      sb.append(System.lineSeparator());\n      sb.append(stackTrace);\n    }\n    return sb.toString();\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/log/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.log;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/message/MessageActionItem.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.message;\n\n/**\n * An option to be presented to the user.\n */\npublic class MessageActionItem {\n\n  /**\n   * A unique key that identifies the option.\n   */\n  private final String key;\n  /**\n   * A text to display to the user while presenting choices.\n   */\n  private final String displayText;\n  /**\n   * Describes if the action should be somehow additionally highlighted (for example with color).\n   * There can be multiple primary actions. It doesn't mean that this option should be a default or first one.\n   */\n  private final boolean isPrimaryAction;\n\n  public MessageActionItem(String key, String displayText, boolean isPrimaryAction) {\n    this.key = key;\n    this.displayText = displayText;\n    this.isPrimaryAction = isPrimaryAction;\n  }\n\n  public String getKey() {\n    return key;\n  }\n\n  public String getDisplayText() {\n    return displayText;\n  }\n\n  public boolean isPrimaryAction() {\n    return isPrimaryAction;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/message/MessageType.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.message;\n\npublic enum MessageType {\n  ERROR, INFO, WARNING\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/message/ShowMessageParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.message;\n\n/**\n * The message contains a type and the text. The type can be one of:\n * <ul>\n *   <li>\"ERROR\"</li>\n *   <li>\"WARNING\"</li>\n *   <li>\"INFO\"</li>\n * </ul>\n */\npublic class ShowMessageParams {\n  private final MessageType type;\n  private final String text;\n\n  public ShowMessageParams(MessageType type, String text) {\n    this.type = type;\n    this.text = text;\n  }\n\n  public MessageType getType() {\n    return type;\n  }\n\n  public String getText() {\n    return text;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/message/ShowMessageRequestParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.message;\n\nimport java.util.List;\n\npublic class ShowMessageRequestParams {\n\n  private final MessageType type;\n  private final String message;\n  private final List<MessageActionItem> actions;\n\n  public ShowMessageRequestParams(MessageType type, String message, List<MessageActionItem> actions) {\n    this.type = type;\n    this.message = message;\n    this.actions = actions;\n  }\n\n  public MessageType getType() {\n    return type;\n  }\n\n  public String getMessage() {\n    return message;\n  }\n\n  public List<MessageActionItem> getActions() {\n    return actions;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/message/ShowMessageRequestResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.message;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class ShowMessageRequestResponse {\n\n  // Selected key can be null in case user explicitly dismisses the notification or does not act on it at all\n  @Nullable\n  private final String selectedKey;\n\n  // Depicts if user explicitly closed the notification or not (e.g. by clicking X). If true, selectedKey should be null.\n  private final boolean closedByUser;\n\n  public ShowMessageRequestResponse(@Nullable String selectedKey, boolean closedByUser) {\n    this.selectedKey = selectedKey;\n    this.closedByUser = closedByUser;\n  }\n\n  @CheckForNull\n  public String getSelectedKey() {\n    return selectedKey;\n  }\n\n  public boolean isClosedByUser() {\n    return closedByUser;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/message/ShowSoonUnsupportedMessageParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.message;\n\n/**\n * The one-time message should be displayed as a warning containing the following elements:\n * <ul>\n *   <li>A unique ID representing a pair of connection ID + version used by the client to remember that this specific notification has been already seen</li>\n *   <li>A configuration scope ID to know where the notification should be displayed</li>\n *   <li>The text to be displayed</li>\n * </ul>\n */\npublic class ShowSoonUnsupportedMessageParams {\n\n  private final String doNotShowAgainId;\n  private final String configurationScopeId;\n  private final String text;\n\n  public ShowSoonUnsupportedMessageParams(String doNotShowAgainId, String configurationScopeId, String text) {\n    this.doNotShowAgainId = doNotShowAgainId;\n    this.configurationScopeId = configurationScopeId;\n    this.text = text;\n  }\n\n  public String getDoNotShowAgainId() {\n    return doNotShowAgainId;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public String getText() {\n    return text;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/message/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.message;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/plugin/DidChangePluginStatusesParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.plugin;\n\nimport java.util.List;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.PluginStatusDto;\n\npublic class DidChangePluginStatusesParams {\n\n  private final String configScopeId;\n  private final List<PluginStatusDto> pluginStatuses;\n\n  public DidChangePluginStatusesParams(String configScopeId, List<PluginStatusDto> pluginStatuses) {\n    this.configScopeId = configScopeId;\n    this.pluginStatuses = pluginStatuses;\n  }\n\n  public String getConfigScopeId() {\n    return configScopeId;\n  }\n\n  public List<PluginStatusDto> getPluginStatuses() {\n    return pluginStatuses;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/plugin/DidSkipLoadingPluginParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.plugin;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\n\npublic class DidSkipLoadingPluginParams {\n  private final String configurationScopeId;\n  private final Language language;\n  private final SkipReason reason;\n  private final String minVersion;\n  private final String currentVersion;\n\n  public DidSkipLoadingPluginParams(String configurationScopeId, Language language, SkipReason reason, String minVersion, @Nullable String currentVersion) {\n    this.configurationScopeId = configurationScopeId;\n    this.language = language;\n    this.reason = reason;\n    this.minVersion = minVersion;\n    this.currentVersion = currentVersion;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public Language getLanguage() {\n    return language;\n  }\n\n  public SkipReason getReason() {\n    return reason;\n  }\n\n  public String getMinVersion() {\n    return minVersion;\n  }\n\n  @CheckForNull\n  public String getCurrentVersion() {\n    return currentVersion;\n  }\n\n  public enum SkipReason {\n    UNSATISFIED_JRE, UNSATISFIED_NODE_JS\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/plugin/DidUpdatePluginsParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.plugin;\n\npublic class DidUpdatePluginsParams {\n  private final String connectionId;\n\n  public DidUpdatePluginsParams(String connectionId) {\n    this.connectionId = connectionId;\n  }\n\n  public String getConnectionId() {\n    return connectionId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/plugin/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.plugin;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/progress/ProgressEndNotification.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.progress;\n\npublic class ProgressEndNotification {\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/progress/ProgressUpdateNotification.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.progress;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\n\npublic class ProgressUpdateNotification {\n  /**\n   * If the message is null, the previously set non-null message should be re-used by the client.\n   */\n  private final String message;\n  /**\n   * If the progress is indeterminate, pass null.\n   * If non-null is passed and the progress was indeterminate, the client should set it to determinate.\n   */\n  private final Integer percentage;\n\n  public ProgressUpdateNotification(@Nullable String message, @Nullable Integer percentage) {\n    this.message = message;\n    this.percentage = percentage;\n  }\n\n  @CheckForNull\n  public String getMessage() {\n    return message;\n  }\n\n  @CheckForNull\n  public Integer getPercentage() {\n    return percentage;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/progress/ReportProgressParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.progress;\n\nimport com.google.gson.annotations.JsonAdapter;\nimport org.sonarsource.sonarlint.core.rpc.protocol.adapter.EitherProgressNotificationAdapterFactory;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\n\npublic class ReportProgressParams {\n  /**\n   * The task ID is a unique identifier, generated by the backend that identifies a long-running task.\n   * The same ID needs to be re-used when reporting progress to the client for a given task.\n   */\n  private final String taskId;\n\n  @JsonAdapter(EitherProgressNotificationAdapterFactory.class)\n  private final Either<ProgressUpdateNotification, ProgressEndNotification> notification;\n\n  public ReportProgressParams(String taskId, ProgressUpdateNotification notification) {\n    this(taskId, Either.forLeft(notification));\n  }\n\n  public ReportProgressParams(String taskId, ProgressEndNotification notification) {\n    this(taskId, Either.forRight(notification));\n  }\n\n  public ReportProgressParams(String taskId, Either<ProgressUpdateNotification, ProgressEndNotification> notification) {\n    this.taskId = taskId;\n    this.notification = notification;\n  }\n\n  public String getTaskId() {\n    return taskId;\n  }\n\n  public Either<ProgressUpdateNotification, ProgressEndNotification> getNotification() {\n    return notification;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/progress/StartProgressParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.progress;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.progress.CancelTaskParams;\n\npublic class StartProgressParams {\n  /**\n   * The task ID is a unique identifier, generated by the backend that identifies a long-running task.\n   * The same ID needs to be re-used when reporting progress to the client for a given task.\n   * This ID can be used by the client to cancel the task by using {@link org.sonarsource.sonarlint.core.rpc.protocol.backend.progress.TaskProgressRpcService#cancelTask(CancelTaskParams)}\n   */\n  private final String taskId;\n  /**\n   * Configuration scope on which to report the progress.\n   * Could be null for a task that does not relate to a configuration scope in particular,\n   * or if several configuration scopes are involved.\n   */\n  private final String configurationScopeId;\n  private final String title;\n  private final String message;\n  private final boolean indeterminate;\n  private final boolean cancellable;\n\n  public StartProgressParams(String taskId, @Nullable String configurationScopeId, String title, @Nullable String message, boolean indeterminate,\n    boolean cancellable) {\n    this.taskId = taskId;\n    this.configurationScopeId = configurationScopeId;\n    this.title = title;\n    this.message = message;\n    this.indeterminate = indeterminate;\n    this.cancellable = cancellable;\n  }\n\n  public String getTaskId() {\n    return taskId;\n  }\n\n  @CheckForNull\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public String getTitle() {\n    return title;\n  }\n\n  @CheckForNull\n  public String getMessage() {\n    return message;\n  }\n\n  public boolean isIndeterminate() {\n    return indeterminate;\n  }\n\n  public boolean isCancellable() {\n    return cancellable;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/progress/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.progress;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/promotion/PromoteExtraEnabledLanguagesInConnectedModeParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.promotion;\n\nimport java.util.Set;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\n\npublic class PromoteExtraEnabledLanguagesInConnectedModeParams {\n\n  private final String configurationScopeId;\n  private final Set<Language> languagesToPromote;\n\n  public PromoteExtraEnabledLanguagesInConnectedModeParams(String configurationScopeId, Set<Language> languagesToPromote) {\n    this.configurationScopeId = configurationScopeId;\n    this.languagesToPromote = languagesToPromote;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public Set<Language> getLanguagesToPromote() {\n    return languagesToPromote;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/promotion/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.promotion;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/sca/DidChangeDependencyRisksParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.sca;\n\nimport java.util.List;\nimport java.util.Set;\nimport java.util.UUID;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.DependencyRiskDto;\n\npublic class DidChangeDependencyRisksParams {\n  private final String configurationScopeId;\n  private final Set<UUID> closedDependencyRiskIds;\n  private final List<DependencyRiskDto> addedDependencyRisks;\n  private final List<DependencyRiskDto> updatedDependencyRisks;\n\n  public DidChangeDependencyRisksParams(String configurationScopeId, Set<UUID> closedDependencyRiskIds, List<DependencyRiskDto> addedDependencyRisks,\n    List<DependencyRiskDto> updatedDependencyRisks) {\n    this.configurationScopeId = configurationScopeId;\n    this.closedDependencyRiskIds = closedDependencyRiskIds;\n    this.addedDependencyRisks = addedDependencyRisks;\n    this.updatedDependencyRisks = updatedDependencyRisks;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public Set<UUID> getClosedDependencyRiskIds() {\n    return closedDependencyRiskIds;\n  }\n\n  public List<DependencyRiskDto> getAddedDependencyRisks() {\n    return addedDependencyRisks;\n  }\n\n  public List<DependencyRiskDto> getUpdatedDependencyRisks() {\n    return updatedDependencyRisks;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/sca/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.sca;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/smartnotification/ShowSmartNotificationParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.smartnotification;\n\nimport java.util.Set;\n\npublic class ShowSmartNotificationParams {\n\n  private final String category;\n  private final String connectionId;\n  private final String link;\n  private final Set<String> scopeIds;\n  private final String text;\n\n  public ShowSmartNotificationParams(String text, String link,\n    Set<String> scopeIds, String category, String connectionId) {\n    this.text = text;\n    this.link = link;\n    this.scopeIds = scopeIds;\n    this.category = category;\n    this.connectionId = connectionId;\n  }\n\n  public String getCategory() {\n    return category;\n  }\n\n  public String getConnectionId() {\n    return connectionId;\n  }\n\n  public String getLink() {\n    return link;\n  }\n\n  public Set<String> getScopeIds() {\n    return scopeIds;\n  }\n\n  public String getText() {\n    return text;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/smartnotification/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.smartnotification;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/sync/DidSynchronizeConfigurationScopeParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.sync;\n\nimport java.util.Set;\n\npublic class DidSynchronizeConfigurationScopeParams {\n  private final Set<String> configurationScopeIds;\n\n  public DidSynchronizeConfigurationScopeParams(Set<String> configurationScopeIds) {\n    this.configurationScopeIds = configurationScopeIds;\n  }\n\n  public Set<String> getConfigurationScopeIds() {\n    return configurationScopeIds;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/sync/InvalidTokenParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.sync;\n\npublic class InvalidTokenParams {\n\n  String connectionId;\n\n  public InvalidTokenParams(String connectionId) {\n    this.connectionId = connectionId;\n  }\n\n  public String getConnectionId() {\n    return connectionId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/sync/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.sync;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/taint/vulnerability/DidChangeTaintVulnerabilitiesParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.taint.vulnerability;\n\nimport java.util.List;\nimport java.util.Set;\nimport java.util.UUID;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityDto;\n\npublic class DidChangeTaintVulnerabilitiesParams {\n  private final String configurationScopeId;\n  private final Set<UUID> closedTaintVulnerabilityIds;\n  private final List<TaintVulnerabilityDto> addedTaintVulnerabilities;\n  private final List<TaintVulnerabilityDto> updatedTaintVulnerabilities;\n\n  public DidChangeTaintVulnerabilitiesParams(String configurationScopeId, Set<UUID> closedTaintVulnerabilityIds, List<TaintVulnerabilityDto> addedTaintVulnerabilities,\n                                             List<TaintVulnerabilityDto> updatedTaintVulnerabilities) {\n    this.configurationScopeId = configurationScopeId;\n    this.closedTaintVulnerabilityIds = closedTaintVulnerabilityIds;\n    this.addedTaintVulnerabilities = addedTaintVulnerabilities;\n    this.updatedTaintVulnerabilities = updatedTaintVulnerabilities;\n  }\n\n  public String getConfigurationScopeId() {\n    return configurationScopeId;\n  }\n\n  public Set<UUID> getClosedTaintVulnerabilityIds() {\n    return closedTaintVulnerabilityIds;\n  }\n\n  public List<TaintVulnerabilityDto> getAddedTaintVulnerabilities() {\n    return addedTaintVulnerabilities;\n  }\n\n  public List<TaintVulnerabilityDto> getUpdatedTaintVulnerabilities() {\n    return updatedTaintVulnerabilities;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/taint/vulnerability/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.taint.vulnerability;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/telemetry/AcceptedBindingSuggestionParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry;\n\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin;\n\npublic class AcceptedBindingSuggestionParams {\n\n  private final BindingSuggestionOrigin origin;\n\n  public AcceptedBindingSuggestionParams(BindingSuggestionOrigin origin) {\n    this.origin = origin;\n  }\n\n  public BindingSuggestionOrigin getOrigin() {\n    return origin;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/telemetry/AddQuickFixAppliedForRuleParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry;\n\npublic class AddQuickFixAppliedForRuleParams {\n  private final String ruleKey;\n\n  public AddQuickFixAppliedForRuleParams(String ruleKey) {\n    this.ruleKey = ruleKey;\n  }\n\n  public String getRuleKey() {\n    return ruleKey;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/telemetry/AddReportedRulesParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry;\n\nimport java.util.Set;\n\npublic class AddReportedRulesParams {\n  private final Set<String> ruleKeys;\n\n  public AddReportedRulesParams(Set<String> ruleKeys) {\n    this.ruleKeys = ruleKeys;\n  }\n\n  public Set<String> getRuleKeys() {\n    return ruleKeys;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/telemetry/AiSuggestionSource.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry;\n\npublic enum AiSuggestionSource {\n  SONARQUBE,\n  SONARCLOUD\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/telemetry/AnalysisDoneOnSingleLanguageParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry;\n\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\n\npublic class AnalysisDoneOnSingleLanguageParams {\n  @Nullable\n  private final Language language;\n  private final int analysisTimeMs;\n\n  public AnalysisDoneOnSingleLanguageParams(@Nullable Language language, int analysisTimeMs) {\n    this.language = language;\n    this.analysisTimeMs = analysisTimeMs;\n  }\n\n  @Nullable\n  public Language getLanguage() {\n    return language;\n  }\n\n  public int getAnalysisTimeMs() {\n    return analysisTimeMs;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/telemetry/AnalysisReportingTriggeredParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry;\n\npublic class AnalysisReportingTriggeredParams {\n  private final AnalysisReportingType analysisType;\n\n  public AnalysisReportingTriggeredParams(AnalysisReportingType analysisType) {\n    this.analysisType = analysisType;\n  }\n\n  public AnalysisReportingType getAnalysisType() {\n    return analysisType;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/telemetry/AnalysisReportingType.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry;\n\npublic enum AnalysisReportingType {\n  VCS_CHANGED_ANALYSIS_TYPE(\"trigger_count_vcs_changed_files\"),\n  ALL_FILES_ANALYSIS_TYPE(\"trigger_count_all_project_files\"),\n  PRE_COMMIT_ANALYSIS_TYPE(\"trigger_count_pre_commit\"),\n  CURRENT_FILE_ANALYSIS_TYPE(\"trigger_count_current_file_ignoring_exclusions\"),\n  WHOLE_FOLDER_HOTSPOTS_SCAN_TYPE(\"trigger_count_whole_folder_hotspots_scan\");\n\n  private final String id;\n\n  AnalysisReportingType(String id) {\n    this.id = id;\n  }\n\n  public String getId() {\n    return id;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/telemetry/DevNotificationsClickedParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry;\n\npublic class DevNotificationsClickedParams {\n  private final String eventType;\n\n  public DevNotificationsClickedParams(String eventType) {\n    this.eventType = eventType;\n  }\n\n  public String getEventType() {\n    return eventType;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/telemetry/FindingsFilteredParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry;\n\npublic class FindingsFilteredParams {\n  private final String filterType;\n\n  public FindingsFilteredParams(String filterType) {\n    this.filterType = filterType;\n  }\n\n  public String getFilterType() {\n    return filterType;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/telemetry/FixSuggestionResolvedParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry;\n\nimport javax.annotation.Nullable;\n\npublic class FixSuggestionResolvedParams {\n  private final String suggestionId;\n  private final FixSuggestionStatus status;\n  @Nullable\n  private final Integer snippetIndex;\n\n  public FixSuggestionResolvedParams(String suggestionId, FixSuggestionStatus status, @Nullable Integer snippetIndex) {\n    this.suggestionId = suggestionId;\n    this.status = status;\n    this.snippetIndex = snippetIndex;\n  }\n\n  public String getSuggestionId() {\n    return suggestionId;\n  }\n\n  public FixSuggestionStatus getStatus() {\n    return status;\n  }\n\n  @Nullable\n  public Integer getSnippetIndex() {\n    return snippetIndex;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/telemetry/FixSuggestionStatus.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry;\n\npublic enum FixSuggestionStatus {\n  ACCEPTED, DECLINED\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/telemetry/HelpAndFeedbackClickedParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry;\n\npublic class HelpAndFeedbackClickedParams {\n  private final String itemId;\n\n  public HelpAndFeedbackClickedParams(String itemId) {\n    this.itemId = itemId;\n  }\n\n  public String getItemId() {\n    return itemId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/telemetry/IdeLabsExternalLinkClickedParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry;\n\npublic class IdeLabsExternalLinkClickedParams {\n\n  private final String linkId;\n\n  public IdeLabsExternalLinkClickedParams(String linkId) {\n    this.linkId = linkId;\n  }\n\n  public String getLinkId() {\n    return linkId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/telemetry/IdeLabsFeedbackLinkClickedParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry;\n\npublic class IdeLabsFeedbackLinkClickedParams {\n\n  private final String featureId;\n\n  public IdeLabsFeedbackLinkClickedParams(String featureId) {\n    this.featureId = featureId;\n  }\n\n  public String getFeatureId() {\n    return featureId;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/telemetry/McpTransportMode.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry;\n\npublic enum McpTransportMode {\n  STDIO,\n  HTTP,\n  HTTPS\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/telemetry/McpTransportModeUsedParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry;\n\npublic class McpTransportModeUsedParams {\n  private final McpTransportMode transport;\n\n  public McpTransportModeUsedParams(McpTransportMode transport) {\n    this.transport = transport;\n  }\n\n  public McpTransportMode getMcpTransportMode() {\n    return transport;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/telemetry/TelemetryClientLiveAttributesResponse.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry;\n\nimport java.util.Map;\n\n\npublic class TelemetryClientLiveAttributesResponse {\n  /**\n   * Map of additional attributes to be passed to the telemetry. Values types can be {@link String}, {@link Boolean} or {@link Number}. You can also pass a Map for nested objects.\n   */\n  private final Map<String, Object> additionalAttributes;\n\n  public TelemetryClientLiveAttributesResponse(Map<String, Object> additionalAttributes) {\n    this.additionalAttributes = additionalAttributes;\n  }\n\n  public Map<String, Object> getAdditionalAttributes() {\n    return additionalAttributes;\n  }\n\n  public boolean hasJoinedIdeLabs() {\n    return this.additionalAttributes.containsKey(\"joinedIdeLabs\") && (Boolean) this.additionalAttributes.get(\"joinedIdeLabs\");\n  }\n\n  public boolean hasEnabledIdeLabs() {\n    return this.additionalAttributes.containsKey(\"enabledIdeLabs\") && (Boolean) this.additionalAttributes.get(\"enabledIdeLabs\");\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/telemetry/ToggleIdeLabsEnablementParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry;\n\npublic class ToggleIdeLabsEnablementParams {\n\n  private final boolean newValue;\n\n  public ToggleIdeLabsEnablementParams(boolean newValue) {\n    this.newValue = newValue;\n  }\n\n  public boolean getNewValue() {\n    return newValue;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/telemetry/ToolCalledParams.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry;\n\npublic class ToolCalledParams {\n  private final String toolName;\n  private final boolean succeeded;\n\n  public ToolCalledParams(String toolName, boolean succeeded) {\n    this.toolName = toolName;\n    this.succeeded = succeeded;\n  }\n\n  public String getToolName() {\n    return toolName;\n  }\n\n  public boolean isSucceeded() {\n    return succeeded;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/telemetry/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/common/CleanCodeAttribute.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.common;\n\npublic enum CleanCodeAttribute {\n\n  CONVENTIONAL,\n  FORMATTED,\n  IDENTIFIABLE,\n\n  CLEAR,\n  COMPLETE,\n  EFFICIENT,\n  LOGICAL,\n\n  DISTINCT,\n  FOCUSED,\n  MODULAR,\n  TESTED,\n\n  LAWFUL,\n  RESPECTFUL,\n  TRUSTWORTHY\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/common/CleanCodeAttributeCategory.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.common;\n\npublic enum CleanCodeAttributeCategory {\n  ADAPTABLE,\n  CONSISTENT,\n  INTENTIONAL,\n  RESPONSIBLE\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/common/ClientFileDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.common;\n\nimport java.net.URI;\nimport java.nio.file.Path;\nimport javax.annotation.Nullable;\n\npublic class ClientFileDto {\n\n  private final URI uri;\n  private final Path ideRelativePath;\n  private final String configScopeId;\n  @Nullable\n  private final Boolean isTest;\n  @Nullable\n  private final String charset;\n  @Nullable\n  private final Path fsPath;\n  @Nullable\n  private final String content;\n  @Nullable\n  private final Language detectedLanguage;\n  private final boolean isUserDefined;\n\n  public ClientFileDto(URI uri, Path relativePath, String configScopeId, @Nullable Boolean isTest, @Nullable String charset, @Nullable Path fsPath, @Nullable String content,\n    @Nullable Language detectedLanguage, boolean isUserDefined) {\n    this.uri = uri;\n    this.ideRelativePath = relativePath;\n    this.configScopeId = configScopeId;\n    this.isTest = isTest;\n    this.charset = charset;\n    this.fsPath = fsPath;\n    this.content = content;\n    this.detectedLanguage = detectedLanguage;\n    this.isUserDefined = isUserDefined;\n  }\n\n  public URI getUri() {\n    return uri;\n  }\n\n  public Path getIdeRelativePath() {\n    return ideRelativePath;\n  }\n\n  public String getConfigScopeId() {\n    return configScopeId;\n  }\n\n  @Nullable\n  public Boolean isTest() {\n    return isTest;\n  }\n\n  public String getCharset() {\n    return charset;\n  }\n\n  public Path getFsPath() {\n    return fsPath;\n  }\n\n  @Nullable\n  public String getContent() {\n    return content;\n  }\n\n  @Nullable\n  public Language getDetectedLanguage() {\n    return detectedLanguage;\n  }\n\n  public boolean isUserDefined() {\n    return isUserDefined;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/common/Either.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.common;\n\nimport java.util.Objects;\nimport java.util.function.Function;\nimport org.eclipse.lsp4j.jsonrpc.validation.NonNull;\n\n/*\n * A class to use in place of {@link org.eclipse.lsp4j.jsonrpc.messages.Either} to stop depending on lsp4j types in API\n * and services.\n * See SLCORE-663 for details.\n */\npublic class Either<L, R> {\n\n  private final org.eclipse.lsp4j.jsonrpc.messages.Either<L, R> lsp4jEither;\n\n  public Either(org.eclipse.lsp4j.jsonrpc.messages.Either<L, R> lsp4jEither) {\n    this.lsp4jEither = lsp4jEither;\n  }\n\n  public static <L, R> Either<L, R> forLeft(@NonNull L left) {\n    return new Either<>(org.eclipse.lsp4j.jsonrpc.messages.Either.forLeft(left));\n  }\n\n  public static <L, R> Either<L, R> forRight(@NonNull R right) {\n    return new Either<>(org.eclipse.lsp4j.jsonrpc.messages.Either.forRight(right));\n  }\n\n  public boolean isLeft() {\n    return lsp4jEither.isLeft();\n  }\n\n  public boolean isRight() {\n    return lsp4jEither.isRight();\n  }\n\n  public L getLeft() {\n    return lsp4jEither.getLeft();\n  }\n\n  public R getRight() {\n    return lsp4jEither.getRight();\n  }\n\n  public <T> T map(\n    @NonNull Function<? super L, ? extends T> mapLeft,\n    @NonNull Function<? super R, ? extends T> mapRight) {\n    return lsp4jEither.map(mapLeft, mapRight);\n  }\n\n  public <R1, R2> Either<R1, R2> mapToEither(\n    @NonNull Function<? super L, ? extends R1> mapLeft,\n    @NonNull Function<? super R, ? extends R2> mapRight) {\n    return isLeft() ? Either.forLeft(mapLeft.apply(getLeft())) : Either.forRight(mapRight.apply(getRight()));\n  }\n\n  public org.eclipse.lsp4j.jsonrpc.messages.Either<L, R> getLsp4jEither() {\n    return lsp4jEither;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) return true;\n    if (o == null || getClass() != o.getClass()) return false;\n    Either<?, ?> either = (Either<?, ?>) o;\n    return Objects.equals(lsp4jEither, either.lsp4jEither);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(lsp4jEither);\n  }\n\n  @Override\n  public String toString() {\n    return lsp4jEither.toString();\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/common/FlowDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.common;\n\nimport java.util.List;\n\npublic class FlowDto {\n  private final List<LocationDto> locations;\n\n  public FlowDto(List<LocationDto> locations) {\n    this.locations = locations;\n  }\n\n  public List<LocationDto> getLocations() {\n    return locations;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/common/ImpactSeverity.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.common;\n\npublic enum ImpactSeverity {\n  INFO,\n  LOW,\n  MEDIUM,\n  HIGH,\n  BLOCKER\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/common/IssueSeverity.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.common;\n\npublic enum IssueSeverity {\n\n  INFO,\n  MINOR,\n  MAJOR,\n  CRITICAL,\n  BLOCKER\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/common/Language.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.common;\n\npublic enum Language {\n\n  ABAP,\n  ANSIBLE,\n  APEX,\n  AZURERESOURCEMANAGER,\n  C,\n  CLOUDFORMATION,\n  COBOL,\n  CPP,\n  CS,\n  CSS,\n  DOCKER,\n  GO,\n  GITHUBACTIONS,\n  HTML,\n  IPYTHON,\n  JAVA,\n  JCL,\n  JS,\n  JSON,\n  JSP,\n  KOTLIN,\n  KUBERNETES,\n  OBJC,\n  PHP,\n  PLI,\n  PLSQL,\n  PYTHON,\n  RPG,\n  RUBY,\n  SCALA,\n  SECRETS,\n  TEXT,\n  SWIFT,\n  TERRAFORM,\n  TS,\n  TSQL,\n  VBNET,\n  XML,\n  YAML\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/common/LocationDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.common;\n\nimport java.nio.file.Path;\n\npublic class LocationDto {\n  private final TextRangeDto textRange;\n  private final String message;\n  private final Path ideFilePath;\n  private final String codeSnippet;\n\n  public LocationDto(TextRangeDto textRange, String message, Path ideFilePath, String codeSnippet) {\n    this.textRange = textRange;\n    this.message = message;\n    this.ideFilePath = ideFilePath;\n    this.codeSnippet = codeSnippet;\n  }\n\n  public TextRangeDto getTextRange() {\n    return textRange;\n  }\n\n  public String getMessage() {\n    return message;\n  }\n\n  public Path getIdeFilePath() {\n    return ideFilePath;\n  }\n\n  public String getCodeSnippet() {\n    return codeSnippet;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/common/MQRModeDetails.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.common;\n\nimport java.util.List;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.ImpactDto;\n\npublic class MQRModeDetails {\n\n  private final CleanCodeAttribute cleanCodeAttribute;\n  private final List<ImpactDto> impacts;\n\n  public MQRModeDetails(CleanCodeAttribute cleanCodeAttribute, List<ImpactDto> impacts) {\n    this.cleanCodeAttribute = cleanCodeAttribute;\n    this.impacts = impacts;\n  }\n\n  public CleanCodeAttribute getCleanCodeAttribute() {\n    return cleanCodeAttribute;\n  }\n\n  public List<ImpactDto> getImpacts() {\n    return impacts;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/common/RuleType.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.common;\n\npublic enum RuleType {\n\n  CODE_SMELL,\n  BUG,\n  VULNERABILITY,\n  SECURITY_HOTSPOT\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/common/SoftwareQuality.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.common;\n\npublic enum SoftwareQuality {\n  MAINTAINABILITY,\n  RELIABILITY,\n  SECURITY\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/common/SonarCloudRegion.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.common;\n\npublic enum SonarCloudRegion {\n  EU, US\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/common/StandardModeDetails.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.common;\n\npublic class StandardModeDetails {\n\n  private final IssueSeverity severity;\n  private final RuleType type;\n\n  public StandardModeDetails(IssueSeverity severity, RuleType type) {\n    this.severity = severity;\n    this.type = type;\n  }\n\n  public IssueSeverity getSeverity() {\n    return severity;\n  }\n\n  public RuleType getType() {\n    return type;\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/common/TextRangeDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.common;\n\npublic class TextRangeDto {\n  private final int startLine;\n  private final int startLineOffset;\n  private final int endLine;\n  private final int endLineOffset;\n\n  public TextRangeDto(int startLine, int startLineOffset, int endLine, int endLineOffset) {\n    this.startLine = startLine;\n    this.startLineOffset = startLineOffset;\n    this.endLine = endLine;\n    this.endLineOffset = endLineOffset;\n  }\n\n  public int getStartLine() {\n    return startLine;\n  }\n\n  public int getStartLineOffset() {\n    return startLineOffset;\n  }\n\n  public int getEndLine() {\n    return endLine;\n  }\n\n  public int getEndLineOffset() {\n    return endLineOffset;\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/common/TokenDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.common;\n\n\nimport java.util.Objects;\n\npublic class TokenDto {\n\n  private final String token;\n\n  public TokenDto(String token) {\n    this.token = token;\n  }\n\n  public String getToken() {\n    return token;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) {\n      return true;\n    }\n    if (o == null || getClass() != o.getClass()) {\n      return false;\n    }\n    var tokenDto = (TokenDto) o;\n    return Objects.equals(token, tokenDto.token);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(token);\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/common/UsernamePasswordDto.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.common;\n\nimport java.util.Objects;\n\npublic class UsernamePasswordDto {\n  \n  private final String username;\n  private final String password;\n\n  public UsernamePasswordDto(String username, String password) {\n    this.username = username;\n    this.password = password;\n  }\n\n  public String getUsername() {\n    return username;\n  }\n\n  public String getPassword() {\n    return password;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (this == o) {\n      return true;\n    }\n    if (o == null || getClass() != o.getClass()) {\n      return false;\n    }\n    var that = (UsernamePasswordDto) o;\n    return Objects.equals(username, that.username) && Objects.equals(password, that.password);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(username, password);\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/common/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol.common;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/package-info.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.rpc.protocol;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n\n"
  },
  {
    "path": "rpc-protocol/src/test/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/config/binding/DidUpdateBindingParamsTests.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass DidUpdateBindingParamsTests {\n\n  @Test\n  void constructor_keeps_mode_and_origin_null() {\n    var dto = new BindingConfigurationDto(\"conn\", \"proj\", false);\n\n    var params = new DidUpdateBindingParams(\"scope\", dto);\n\n    assertThat(params.getConfigScopeId()).isEqualTo(\"scope\");\n    assertThat(params.getUpdatedBinding()).isEqualTo(dto);\n    assertThat(params.getBindingMode()).isNull();\n    assertThat(params.getOrigin()).isNull();\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/test/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/connection/projects/SonarProjectDtoTest.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.projects;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SonarProjectDtoTest {\n\n  @Test\n  void should_have_object_methods() {\n    var one = new SonarProjectDto(\"mySearchTerm\", \"project\");\n    var other = new SonarProjectDto(\"mySearchTerm\", \"project\");\n\n    assertThat(one)\n      .isEqualTo(other)\n      .hasSameHashCodeAs(other)\n      .hasToString(\"SonarProjectDto{key='mySearchTerm', name='project'}\");\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/test/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/initialize/InitializeParamsTests.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\nclass InitializeParamsTests {\n\n  @Test\n  void should_replace_null_collections_by_empty() {\n    var params = new InitializeParams(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, false, null, false, null);\n    assertNotNull(params.getEmbeddedPluginPaths());\n    assertNotNull(params.getConnectedModeEmbeddedPluginPathsByKey());\n    assertNotNull(params.getEnabledLanguagesInStandaloneMode());\n    assertNotNull(params.getExtraEnabledLanguagesInConnectedMode());\n    assertNotNull(params.getSonarQubeConnections());\n    assertNotNull(params.getSonarCloudConnections());\n    assertNotNull(params.getStandaloneRuleConfigByKey());\n    assertNotNull(params.getDisabledPluginKeysForAnalysis());\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/test/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/initialize/TelemetryClientConstantAttributesDtoTests.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\nclass TelemetryClientConstantAttributesDtoTests {\n\n  @Test\n  void should_replace_null_collections_by_empty() {\n    var params = new TelemetryClientConstantAttributesDto(null, null, null, null, null);\n    assertNotNull(params.getAdditionalAttributes());\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/test/java/org/sonarsource/sonarlint/core/rpc/protocol/client/binding/AssistBindingParamsTests.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.client.binding;\n\nimport org.junit.jupiter.api.Test;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass AssistBindingParamsTests {\n\n  @Test\n  void new_ctor_sets_fields_and_isFromSharedConfiguration_true_when_origin_is_shared_configuration() {\n    var params = new AssistBindingParams(\"conn1\", \"proj\", \"scope1\", BindingSuggestionOrigin.SHARED_CONFIGURATION);\n\n    assertThat(params.getConnectionId()).isEqualTo(\"conn1\");\n    assertThat(params.getProjectKey()).isEqualTo(\"proj\");\n    assertThat(params.getConfigScopeId()).isEqualTo(\"scope1\");\n    assertThat(params.isFromSharedConfiguration()).isTrue();\n  }\n\n  @Test\n  void new_ctor_sets_isFromSharedConfiguration_false_for_non_shared_origins_properties_file() {\n    var params = new AssistBindingParams(\"conn2\", \"proj2\", \"scope2\", BindingSuggestionOrigin.PROPERTIES_FILE);\n\n    assertThat(params.getConnectionId()).isEqualTo(\"conn2\");\n    assertThat(params.getProjectKey()).isEqualTo(\"proj2\");\n    assertThat(params.getConfigScopeId()).isEqualTo(\"scope2\");\n    assertThat(params.isFromSharedConfiguration()).isFalse();\n  }\n\n  @Test\n  void new_ctor_sets_isFromSharedConfiguration_false_for_non_shared_origins_project_name() {\n    var params = new AssistBindingParams(\"conn3\", \"proj3\", \"scope3\", BindingSuggestionOrigin.PROJECT_NAME);\n\n    assertThat(params.getConnectionId()).isEqualTo(\"conn3\");\n    assertThat(params.getProjectKey()).isEqualTo(\"proj3\");\n    assertThat(params.getConfigScopeId()).isEqualTo(\"scope3\");\n    assertThat(params.isFromSharedConfiguration()).isFalse();\n  }\n\n  @Test\n  void deprecated_ctor_keeps_boolean_semantics_and_sets_fields() {\n    var params = new AssistBindingParams(\"conn4\", \"proj4\", \"scope4\", BindingSuggestionOrigin.SHARED_CONFIGURATION);\n\n    assertThat(params.getConnectionId()).isEqualTo(\"conn4\");\n    assertThat(params.getProjectKey()).isEqualTo(\"proj4\");\n    assertThat(params.getConfigScopeId()).isEqualTo(\"scope4\");\n    assertThat(params.isFromSharedConfiguration()).isTrue();\n\n    var paramsFalse = new AssistBindingParams(\"conn5\", \"proj5\", \"scope5\", BindingSuggestionOrigin.PROJECT_NAME);\n    assertThat(paramsFalse.isFromSharedConfiguration()).isFalse();\n  }\n}\n"
  },
  {
    "path": "rpc-protocol/src/test/java/org/sonarsource/sonarlint/core/rpc/protocol/common/EitherTests.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.common;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass EitherTests {\n\n  @Test\n  void testToString() {\n    Either<String, Object> left = Either.forLeft(\"left\");\n    assertThat(left).hasToString(left.getLsp4jEither().toString());\n    Either<String, Object> right = Either.forLeft(\"right\");\n    assertThat(right).hasToString(right.getLsp4jEither().toString());\n  }\n\n  @Test\n  void testEquals() {\n    Either<String, Object> left = Either.forLeft(\"left\");\n    assertThat(left).isEqualTo(left)\n      .isEqualTo(Either.forLeft(\"left\"))\n      .isNotEqualTo(Either.forLeft(\"left_2\"))\n      .isNotEqualTo(null);\n  }\n\n}\n"
  },
  {
    "path": "rpc-protocol/src/test/java/org/sonarsource/sonarlint/core/rpc/protocol/common/TokenDtoTests.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.common;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass TokenDtoTests {\n\n  @Test\n  void testEqualsAndHashCode() {\n    TokenDto token = new TokenDto(\"token1\");\n    TokenDto sameToken = new TokenDto(\"token1\");\n    TokenDto differentToken = new TokenDto(\"token2\");\n\n    // Assuming that two new instances are equal\n    assertThat(token)\n      .isEqualTo(token)\n      .isEqualTo(sameToken)\n      .isNotEqualTo(differentToken)\n      .isNotEqualTo(\"token1\")\n      .hasSameHashCodeAs(sameToken)\n      .doesNotHaveSameHashCodeAs(differentToken);\n  }\n}"
  },
  {
    "path": "rpc-protocol/src/test/java/org/sonarsource/sonarlint/core/rpc/protocol/common/UsernamePasswordDtoTests.java",
    "content": "/*\n * SonarLint Core - RPC Protocol\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.rpc.protocol.common;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass UsernamePasswordDtoTests {\n\n\n  @Test\n  void testEqualsAndHashCode() {\n    var up1 = new UsernamePasswordDto(\"user1\", \"password1\");\n    var sameUp = new UsernamePasswordDto(\"user1\", \"password1\");\n    var differentUp = new UsernamePasswordDto(\"user2\", \"password1\");\n    var differentUp2 = new UsernamePasswordDto(\"user1\", \"password2\");\n\n    // Assuming that two new instances are equal\n    assertThat(up1)\n      .isEqualTo(up1)\n      .isEqualTo(sameUp)\n      .isNotEqualTo(differentUp)\n      .isNotEqualTo(differentUp2)\n      .isNotEqualTo(\"token1\")\n      .hasSameHashCodeAs(sameUp)\n      .doesNotHaveSameHashCodeAs(differentUp)\n      .doesNotHaveSameHashCodeAs(differentUp2);\n  }\n}\n"
  },
  {
    "path": "spec/README.adoc",
    "content": "= SonarLint Core documentation\n\nlink:connected_mode[Connecting with SonarQube/SonarCloud]\n\nlink:logging.adoc[Logging]\n\nlink:glossary.adoc[Glossary]\n"
  },
  {
    "path": "spec/connected_mode/README.adoc",
    "content": "[#connected_mode]\n= Connected mode\n\n== Overview\n\nlink:synchronization/README.adoc[Synchronization]\n\nlink:binding_suggestion.adoc[Binding suggestions]\n"
  },
  {
    "path": "spec/connected_mode/binding_suggestion.adoc",
    "content": "[#binding_suggestion]\n= Binding Suggestion\n\n== Overview\n\nFor xref:../glossary.adoc#connected_mode[connected mode] to work, a xref:glossary.adoc#connection[connection] should be defined to allow SonarLint to communicate with the server. Then, SonarLint needs to know which xref:glossary.adoc#sonar_project[Sonar Project] should be considered. This is the xref:glossary.adoc#binding[_binding_]. Inside IDE, there could be different bindings, for different xref:glossary.adoc#configuration_scope[_configuration scopes_].\n\nFor most users, there will be only one configured connection (to their corporate SonarQube server, or to SonarCloud). But every time they open a new project or solution, they will need to setup the binding. This process can be tedious, and easily forgotten.\n\nBinding suggestion is the feature where SonarLint assist users as much as possible to configure binding.\n\n== Triggers\n\nBinding suggestion is triggered:\n\n* when a xref:../glossary.adoc#configuration_scope[configuration scope] is added (we will look for suggestions among all connections, but only for this _configuration scope_)\n* when a xref:../glossary.adoc#connection_config[connection configuration] is added/updated (we will look for suggestions for all _configuration scopes_, but only for this connection)\n\n== Conditions\n\n1. At least one xref:../glossary.adoc#connection_config[connection configuration] is defined\n2. Only consider xref:../glossary.adoc#configuration_scope[configuration scopes] that:\n** are not bound (or maybe if we know the binding is **invalid**? #TBD#)\n** have their _bindingSuggestionDisabled_ attribute set to `false`\n\n== Algorithm\n\n=== SonarQube/SonarCloud scanner configuration file auto-detection\n\nFor each xref:../glossary.adoc#configuration_scope[configuration scope]:\n\n1. Search for the presence of files `sonar-project.properties` or `.sonarcloud.properties`. This step should be under IDE responsibility, since the configuration scope layout and filesystem is very IDE specific. The IDE should provide the list of files (with filename) and their text content.\n\n2. Extract from each file content the following properties (all are optional):\n- `sonar.projectKey`\n- `sonar.host.url`\n- `sonar.organization`\n- `sonar.region`\n\n3. For each filename + tuple of properties, try to guess if we are looking for a SonarQube or SonarCloud connection, using the following heuristic:\n- if the file is `.sonarcloud.properties`, this is SonarCloud (AutoScan)\n- if there is a `sonar.organization`, this is SonarCloud\n- if the `sonar.host.url` is equal to `https://sonarcloud.io` or one of the aliases used for tests, this is SonarCloud, any other non-empty value means it is SonarQube\n- at this stage keep properties (normally only _projectKey_) as an unknown binding clue\n\nAt the end of this step, we should have a possibly empty list of binding clues having one of those types and attributes:\n\n[%autowidth,options=\"header\"]\n|=======\n|SonarQube Binding Clue\n|projectKey: string?\n|serverUrl: string\n|=======\n\n[%autowidth,options=\"header\"]\n|=======\n|SonarCloud Binding Clue\n|projectKey: string?\n|organization: string?\n|region: string?\n|=======\n\n[%autowidth,options=\"header\"]\n|=======\n|Unknown Binding Clue\n|projectKey: string\n|=======\n\n=== Connection matching\n\nCandidates for connection matching depend on the trigger. We are not necessarily looking for binding suggestions among all configured connections.\n\nFor each binding clue, find the matching connection(s) among connection candidates.\n\nFor a _SonarQube Binding Clue_, select all SonarQube connections having the samefootnote:[determining that two URLs are pointing to the same server is tricky, so here we do at best] url.\n\nFor a _SonarCloud Binding Clue_, select all SonarCloud connections having the same _organization_. If we don't have an organization for this candidate binding, select all SonarCloud connections.\n\nFor a generic Binding Clue, select all connections.\n\nAt the end of this step, we should have each binding clue with a (possibly empty) list of matching connections.\n\n_Binding Clues_ having no matching connections should be discarded (example: the project has a `.sonarcloud.properties`, but there is no SonarCloud connection defined).\n\n=== Project matching\n\nWe should favor binding clues with a non-empty _projectKey_.\n\n==== Exact matching on projectKey\n\nFor each binding clue having a projectKey, search for a xref:../glossary.adoc#sonar_project[Sonar project] having the exact same _projectKey_ among the matched connections. If there is at least one perfect match, return the match(es).\n\n==== Matching based on configuration scope _name_\n\nIf no perfect matches were found in the previous step, then for each _binding clue_ with no _projectKey_, we should do a search among all _Sonar Projects_ of their matched connections.\nIf there were no binding clues with no _projectKey_, then simply do a search for connection candidates.\n\nThe matching is based on a scoring described in `TextSearchIndex`. If there is only one _Sonar Project_ with the highest score, then we will return this one, else if there are multiple with top score, then we will return all top candidates. Finally, if there are no matches (#FIXME# or score is too low?), then we should return `NO_MATCHES_FOUND`.\n\n\n== Outcome\n\nAt the end of the binding suggestion computation, one single notification will be sent (1 trigger -> 1 notification maximum). The notification will contain the\nbinding suggestion(s) for each configuration scopes where the computation was _executed_. For each configuration scope:\n\n1. if there is a single best suggestion:\n- the single Sonar Project suggestion + connectionId\n\n2. if there are multiple best suggestions (and less than a threshold #TBD#):\n- the list of (Sonar Project suggestions + connectionIds)\n\n3. if there are no suggestions\n- empty suggestion list (to indicate to clients that binding suggestion was attempted but could not find good results, and client should usually forward users to manual binding)\n\nConfiguration scopes that have been skipped from binding suggestion at any time in the previously described algorithm will not be listed in the notification.\n\n[#do_not_ask_again]\n== Do not ask again\n\nIt is the responsibility of the IDE to offer the \"Do not ask again\" option in the binding suggestion notification, and it should be remembered for this configuration scope in IDE settings. When the client synchronize _configuration scopes_ with the backend, the attribute _bindingSuggestionDisabled_ will be set accordingly.\n"
  },
  {
    "path": "spec/connected_mode/synchronization/README.adoc",
    "content": "[#synchronization]\n= Synchronization\n\nlink:overview.adoc[Overview]\n\nlink:pull_synchronization.adoc[Pull Synchronization]\n"
  },
  {
    "path": "spec/connected_mode/synchronization/overview.adoc",
    "content": "[#synchronization]\n= Synchronization\n\n== Overview\n\nSonarLint lets users bind their project to SonarCloud/SonarQube. This permits using the same parameters as configured on those products:\n\n* settings\n* ruleset\n* plugins\n* ...\n\nFor connected mode to work in SonarLint, the base mechanism is the synchronization of those parameters, which consists in locally storing required data from the server. There are several ways to achieve that:\n\n* xref:../pull_synchronization.adoc[pull synchronization]. SonarLint explicitly and periodically pulls data from the server\n* live synchronization. Data is updated locally via events pushed from the server (using WebSockets for SonarCloud and Server Sent Events for SonarQube)\n"
  },
  {
    "path": "spec/connected_mode/synchronization/pull_synchronization.adoc",
    "content": "[#synchronization]\n= Synchronization\n\n== Overview\n\nPull synchronization is the mechanism with which SonarLint fetches data from SonarCloud/SonarQube and store it locally for later use.\n\n== Triggers\n\nPull synchronization is triggered:\n\n* when a bound xref:../glossary.adoc#configuration_scope[configuration scope] is added\n* when binding or re-binding a xref:../glossary.adoc#configuration_scope[configuration scope]\n* when a xref:../glossary.adoc#connection_config[connection configuration] is added and has scopes bound to it (could be the case after fixing inconsistencies in some clients)\n* periodically (every hour)\n\nIn all cases, synchronization is necessary because either:\n\n* the local storage does not exist\n* it contains data for a previously configured binding\n* it is stale.\n\nA lighter version of the synchronization, involving only the last \"Branch-specific synchronization\" step can also be triggered separately:\n\n* when the VCS branch changes on the client side, and leads to a different branch match\n\n== Algorithm\n\nThere are several sub-steps during pull synchronization. For each xref:../glossary.adoc#configuration_scope[configuration scope] needing synchronization:\n\n=== Connection synchronization\n\n. Fetch and store information from the bound connection (no specific project at this step)\n.. Fetch server status and version. Stop if the server is not UP. Store the version in local storage\n.. Fetch plugins. Store only plugins that are useful\n\n=== Project synchronization\n\n. Fetch project specific information (from project key configured in the binding)\n.. Calculate paths difference between local and remote project\n... Fetch all component keys from the server project\n... Request client the list of file paths in the configuration scope\n... Match local and remote paths to calculate the project paths difference. Store them in memory\n.. Fetch and store the ruleset active on the server project\n.. Fetch and store the settings applied on the server project\n.. Fetch and store the new code definition set on the server project\n.. Fetch and store server project branches\n\n=== Branch matching\n\nAfter this previous step occurred, the list of server project branches is known. On the other hand, the client knows which branch is active on the VCS.\n\nBranch matching is the process of selecting the closest server branch that matches locally checked out branch.\n\nThe matching is made by the client on request from the backend.\n\n=== Branch-specific synchronization\n\nWhen the matching branch is known, the last step of the synchronization can occur:\n\n. Fetch and store issues\n. Fetch and store taint vulnerabilities (if supported)\n. Fetch and store Security Hotspots\n"
  },
  {
    "path": "spec/glossary.adoc",
    "content": "[glossary]\n= Glossary\n\n[glossary]\n[[active_rule]]Active Rule:: An active rule is an _instance_ of a <<rule,rule>>. An active rule may have different attributes than the rule. For example severity and parameters values can be overridden in quality profiles, so that different <<sonar_project,Sonar Projects>> can be analyzed for the same rule, but with different behavior/threshold. Active rules are part of the input for <<sensor,sensors>>.\n[[active_sonar_project_branch]]Active Sonar Project Branch:: A <<sonar_project_branch,Sonar Project Branch>> that is determined to be the active one in a given <<configuration_scope,configuration scope>>. The client is responsible for determining what the active branch is based on the current VCS branch and all <<sonar_project_branch,Sonar Project Branches>>.\n[[binding]]Binding:: In <<connected_mode>>, this is the association between a <<configuration_scope,configuration scope>> and a <<sonar_project,Sonar project>>, for a given <<connection,connection>>.\n[[binding_clue]]Binding Clue:: For a <<configuration_scope,configuration scope>>, this is any element that could help to guess what should be the <<binding,binding>> in <<connected_mode,connected mode>>. A configuration scope can have multiple binding clues, partial, or even contradictory. They will be processed during xref:connected_mode/binding_suggestion.adoc#binding_suggestion[binding suggestion computation].\n[[configuration_scope]]Configuration Scope:: Represent an abstract IDE element that can be configured, and possibly <<binding,bound>>. Usually a project (Eclipse, IntelliJ), module (IntelliJ), solution (VS, Rider), or workspace folder (VSCode).\n[[connected_mode]]Connected Mode:: The use of SonarLint associated with a SonarQube server or SonarCloud. link:connected_mode/README.adoc[More details]. See also <<standalone_mode,standalone mode>>.\n[[connection]]Connection:: A channel of communication between SonarLint and a particular SonarQube server or SonarCloud. See also <<connected_mode,connected mode>>.\n[[connection_config]]Connection Configuration:: All attributes that will be used to instantiate and operate a <<connection,connection>>. Usually provided by the IDE settings. It includes credentials, endpoint URL, timeouts, proxy...\n[[effective_rule_details]]Effective Rule Details:: <<rule_definition>> but taking into account the <<binding,binding>> and the <<finding_context,finding context>>. Some attributes can be different from the <<rule_definition, rule definition>>.\n[[finding]]Finding:: Generic concept that includes all things reported by <<sonar_analyzer,Sonar analyzers>>: issues, security hotspots, taint vulnerabilities, ... With the new Clean Code Taxonomy, security hotspots should disappear, and so everything will be an issue, so this concept might disappear.\n[[issue_matching]]Issue Matching:: Refer to the process, algorithm or xref:issue_tracking.adoc#matching_heuristic[heuristic] that we use to reconcile issues reported by an analysis with the issues previously reported and tracked. Important part of <<issue_tracking,issue tracking>>.\n[[issue_tracking]]Issue Tracking:: This is the process allowing to xref:issue_tracking.adoc#issue_tracking[track issues] over time, when the source code changes. Tracked issues have an identity, allowing to attach metadata that will be sticky between consecutive analyses.\n[[finding_context]]Finding Context:: When reporting a finding, <<sonar_analyzer,Sonar analyzers>> can optionally provide a context key. This context key will be used to adapt the <<rule_definition>> to give more specific remediation guidelines. For example, we can have framework specific context (spring vs struts).\n[[local_branch]]Local Branch:: The VCS branch that is currently checked out locally.\n[[project_key]]Project Key:: Unique identifier of a <<sonar_project,Sonar project>> in a given SonarQube or SonarCloud instance. Project key can be changed on server side (the server use uuid internally).\n[[rule_definition]]Rule Definition:: All the attributes defining a rule, as provided by <<sonar_analyzer>>, like <<rule_key,key>>, name, default severity, description, ... Some attributes of a rule definition can be overridden at different levels. For example severity and parameter values can be changed in the quality profile. The rule description can also be sensitive to the finding <<finding_context,context>>. See <<effective_rule_details>>.\n[[rule]]Rule:: A coding standard or practice which should be followed. <<sonar_analyzer,Sonar analyzers>> will analyze the user's code and report <<finding,findings>> for each rule. Each <<sonar_analyzer,Sonar analyzer>> will contribute a set of rules, identified by a unique <<rule_key,rule key>>. Rules have attached <<rule_definition,rule definition>> (name, type, tags, description, default activation...). Rules can also have parameters, with default values. On SonarQube or SonarCloud, users can create quality profiles, that are logical rule sets in which they can decide to activate some rules, and override certain rules metadata (severity, parameters, ...). <<sonar_project,Sonar projects>> are associated with a quality profile. When analyzing the code of a project, <<sensor,sensors>> are only interested in enabled rules and the effective parameter values. This is what we call <<active_rule,active rules>>.\n[[rule_key]]Rule Key:: Unique identifier of a <<rule,rule>>. The format of a rule key is always [<<rule_repo,rule_repo_key>>]:[rule_id]. For example: `java:S1423`.\n[[rule_repo]]Rule Repository:: A rule repository is a logical grouping of rules. This is not a concept exposed in SonarLint. It is mostly used to differentiate rules of the same language that are assessed by different <<sonar_analyzer, analyzers>> or <<sensor,sensors>>.\n[[sensor]]Sensor:: A Sensor is the Java interface implemented by <<sonar_analyzer,Sonar analyzers>> to analyze source files and report <<finding,findings>>.\n[[sonar_analyzer]]Sonar Analyzer:: A component responsible to analyze user's code, for one or multiple languages.\n[[sonar_project]]Sonar Project:: A project that exists on the <<sonar_server,Sonar Server>> (either SonarQube or SonarCloud). Sonar Projects are identified by a <<project_key,projectKey>>.\n[[smart_notification]]Smart Notifications:: A feature that enables the IDE to receive notifications, only available in <<connected_mode,connected mode>> with SonarQube or SonarCloud. You receive a smart notification when the quality gate status of an opened project in your IDE changes, or when an analysis raises new issues that you've introduced in an opened project.\n[[sonar_project_branch]]Sonar Project Branch:: A branch that has already been analyzed on a <<sonar_project,Sonar Project>>.\n[[sonar_server]]Sonar Server:: An instance of SonarQube or the SonarCloud service, to which SonarLint can be connected.\n[[standalone_mode]]Standalone Mode:: The use of SonarLint without a SonarQube server or SonarCloud.  See also <<connected_mode,connected mode>>.\n"
  },
  {
    "path": "spec/issue_tracking.adoc",
    "content": "[#issue_tracking]\n= Issue Tracking\n\nSonar analyzers are stateless. Or to be precise, the plugin API we use to interact with analyzers to collect issues is designed in a stateless way. Each analysis is independent of the previous one, in the sense that issues don't have an identity allowing to know that this is conceptually the same issue as the previous analysis.\nBut a big value of Sonar solution is the ability to track issues over time, especially when code changes, in order to know when the issue has been introduced, and also to attach some metadata to issues (status, comments, ...). So we need a strategy to track a set of issues, while code changes, new analysis results are available, and other external factors can affect this list of issues.\n\n=== 1. Raw issues\n\nRaw issues are the outcome of a fresh analysis. They are \"valid\" for a fixed input (source code, analysis settings, ...). Raw issues have no identity.\n\n=== 2. Tracked issues\n\nTracked issues is the collection of all issues currently \"known\" for a given analysis scope, and that we follow over time, even if the source code changes. Tracked issues have attributes.\n\n=== 3. Closed issues\n\nIssues that have been previously detected, but not anymore can either be totally removed from the collection of tracked issues, or transitioned to a \"closed\" state, and kept for a while. The benefit is that in case the issue reappear quickly (user deleted some code, but then Ctrl+Z), we can restore the previous state from the closed issue, and not classify the issues as new.\nTo avoid growing the list of closed issues indefinitely, a purge strategy should be defined.\nNB: This is currently implemented in SonarQube/SonarCLoud, but not in SonarLint.\n\n[#matching_heuristic]\n== Matching heuristic\n\nThe issue matching heuristic relies on the following attributes:\n\n* the rule key\n* line number (the first line of the primary location). May be absent for project or file-level issues.\n* a digest (aka hash) of the text content of the primary location. May be absent for project or file-level issues.\n* a digest (aka hash) of the text content of the entire line. May be absent for project or file-level issues. Deprecated in favor of the text range digest.\n* the issue message (primary message)\n* the server issue key (optional, only when matching against server issues)\n\nThose attributes will be compared between the two set of issues in multiple passes, trying first to pair issues that have the strongest similarity, then progressively relaxing the matching criteria.\n"
  },
  {
    "path": "spec/rpc/json_rpc.adoc",
    "content": "[json_rpc]\n= JSON-RPC\n\nSonarLint Core can be used as standalone process. When started, it will use standard input/output streams to communication with the client (= the IDE) using https://www.jsonrpc.org/specification[JSON-RPC].\n\nWe are in fact starting 2 JSON-RPC channels, in order to have bidirectional communication.\n\nVirtually, we have bidirectional communication:\n[ditaa]\n....\n+---------+         +----------------+\n|   IDE   | <-----> | SonarLint Core |\n+---------+         +----------------+\n....\n\nTechnically, we have two channels:\n\n* IDE to Backend\n* Backend to IDE\n\n[ditaa]\n....\n+---------+         +----------------+\n|         |  -----> |                |\n|   IDE   |         | SonarLint Core |\n|         | <-----  |                |\n+---------+         +----------------+\n....\n\nIn JSON-RPC, there is a concept of client and server. This is a bit confusing for our case, because we are creating 2 channels in both directions. In most of the cases, what we call the *client* is the IDE side, and the *server* is the SonarLint Core side.\nHowever, when dealing with some low-level JSON-RPC implementation details, be careful that the client and server can be reversed, when dealing with request and notifications sent from the backend to the IDE.\n\n== Threading model\n\nJSON-RPC specification does not tell if message should be processed sequentially or concurrently. Based on the experience of the LSP, we decided to process messages sequentially. This message processing is handled in the `org.eclipse.lsp4j.jsonrpc.json.ConcurrentMessageProcessor`, by a thread created from an ExecutorService that we provide when creating the `Launcher` in the `SonarLintRpcServerImpl`.\nSo this single thread will sequentially parse messages, and then we want to:\n\n* immediately handle cancellation notifications\n* schedule other messages (requests and regular notifications) processing in a separate executor service, to not block future cancellation notifications\n\nThis _request and notification processing_ executor service is based also on a single thread, in order to guarantee the order of the processing. We want to preserve the order between notifications, and also we want a request to start only after all previous notifications have been processed.\n\nThen requests are scheduled in a thread pool, with asynchronous results.\n\n[ditaa]\n....\n----> blocking/sync\n=---> non-blocking/async\n\n\n+----------+         +-------------------+\n| {io}     |         |                   |  If cancellation notification    /----------------\\\n| JSON-RPC |  -----> | Message processor |  ------------------------------> | Cancel request |\n|  message |         | (single thread)   |                                  \\----------------/\n+----------+         +-------------------+\n                              :\n                              |  If request or other notification\n                              |\n                              v\n                     +----------------------------+\n                     | Requests And Notifications |  If notification   /----------------------\\\n                     |     Executor Service       |  ----------------> | Execute notification |\n                     |     (single thread)        |                    \\----------------------/\n                     +----------------------------+\n                              :\n                              |  If request\n                              |\n                              v\n                     +----------------------------+\n                     | Requests Executor Service  |                    /----------------------\\\n                     |       (thread pool)        |  ----------------> | Execute request      |\n                     |                            |                    \\----------------------/\n                     +----------------------------+\n\n....\n\n\n== Cancellation\n\nCancellation is not part of JSON-RPC specification. However, it is a very important feature for SonarLint, because we want to be able to cancel a request that is taking too much time to execute, or because it is not relevant anymore to the caller.\nWe are reusing the https://microsoft.github.io/language-server-protocol/specification#cancelRequest[Cancel Request] feature of the LSP: a special notification is used to indicate that a request should be cancelled, using the request id. We are relying on cooperative cancellation. We are not trying to interrupt threads, as it might leave resources in a bad state. Requests implementation logic will need to periodically check the cancellation status, and react in the best way.\n\nMost of the time, when talking about cancellation, this is _client cancellation_: the IDE is cancelling a request that it has sent to the backend, or the backend is cancelling a request that it has sent to the IDE. However, we also have _server cancellation_: the backend is cancelling a request that it has *received* from the IDE. This is the case for example when the backend is shutting down, and it wants to cancel all pending requests.\nThe IDE can also cancel a request that it has received from the backend, like the request to assist the user to configure a binding, if the user finally decides to cancel the process.\n"
  },
  {
    "path": "spec/smart_notifications.adoc",
    "content": "[#smart_notifications]\n= Smart notifications\n\n== Overview\n\nSmart notifications, sometimes called dev notifications, allow developers using xref:glossary.adoc#connected_mode[Connected Mode] in SonarLint to receive in-IDE notifications from SonarQube or SonarCloud when:\n\n* the Quality Gate status (failed/success) of a project open in the IDE changes\n* a SonarQube or SonarCloud analysis raises new xref:glossary.adoc#finding[findings] introduced by this developer in a project open in the IDE\n\nSonarLint will poll SonarQube or SonarCloud every minute to retrieve any new notifications up to 1 day old. When the polling has been done, the timestamp of the polling date is stored and will be used next time as a reference, so the client cannot retrieve the same notification later.\n\n== Activate/deactivate notifications\n\nThe activation or deactivation of notifications must be done individually by each developer directly in SonarLint (on the IDE side). When setting up the connection to SonarQube or SonarCloud, it is possible to check a box to activate smart notifications.\n\n== Algorithm\n\n=== Retrieve and send notifications\n\n1. Retrieve all `configScopeId` per `projectKey`, for each `connectionId` where the xref:glossary.adoc#binding[binding] is valid, then for each xref:glossary.adoc#connection[connection]:\n* 2. Make sure the connection is valid and that the smart notifications are enabled\n* 3. Group all the project keys inside this connection and retrieve the last event polling timestamp stored locally for each. This enables SonarLint to request the server only once for a given connection\n* 4. Send a request to `api/developers/search_events?projects=&from=` with the list of project keys and the last event polling timestamp previously retrieved. As a result, we get a list of `ServerNotification`\n* 5. Send each notification to the client  using the method `showSmartNotification(ShowSmartNotificationParams params)`. For each notification, all the xref:glossary.adoc#configuration_scope[configuration scope ids] for the project key where this notification is linked should be gathered. This will be used on the client side to display the notification.\n\n=== Polling mechanism\n\nTo avoid retrieving already sent notifications, every 60 seconds, the backend will try to retrieve all new notifications since the last polling. This last polling information is stored as a timestamp - later used as a `ZonedDateTime` -  inside a protobuf file under `connectionId/projectKey/last_event_polling.pb`.\n\n=== Displaying notifications\n\nThis part is handled on the client side where the method `showSmartNotification(ShowSmartNotificationParams params)` should be implemented.\n\nThe `params` parameter corresponds to the list of notifications to display. Among the fields received:\n\n* the client has the responsibility to display the smart notifications on the right project(s) based on the list of `configScopeId`.\n* `category` is used for telemetry when a smart notification link is clicked\n* `connectionId` is used to know whether the smart notification is from SonarQube or SonarCloud\n* `link` to be shown within the smart notification\n* `text` is the smart notification message\n\n=== Telemetry\n\nCurrently, the telemetry is handled both on the backend side (smart notification received) and the client side (smart notification clicked).\n"
  },
  {
    "path": "test-utils/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.sonarsource.sonarlint.core</groupId>\n    <artifactId>sonarlint-core-parent</artifactId>\n    <version>11.2-SNAPSHOT</version>\n  </parent>\n  <artifactId>sonarlint-core-test-utils</artifactId>\n  <name>SonarLint Core - Test Utils</name>\n  <description>Test utils for SonarLint</description>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.code.findbugs</groupId>\n      <artifactId>jsr305</artifactId>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-rpc-java-client</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-rpc-impl</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>${project.groupId}</groupId>\n      <artifactId>sonarlint-java-client-utils</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>ch.qos.logback</groupId>\n      <artifactId>logback-classic</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-engine</artifactId>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-params</artifactId>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.assertj</groupId>\n      <artifactId>assertj-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-core</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.wiremock</groupId>\n      <artifactId>wiremock-jetty12</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.squareup.okhttp3</groupId>\n      <artifactId>mockwebserver3</artifactId>\n    </dependency>\n    <!-- For WebSockets testing -->\n    <dependency>\n      <groupId>org.apache.tomcat.embed</groupId>\n      <artifactId>tomcat-embed-websocket</artifactId>\n      <version>11.0.18</version>\n    </dependency>\n    <!-- For Plugins testing -->\n    <dependency>\n      <groupId>net.bytebuddy</groupId>\n      <artifactId>byte-buddy</artifactId>\n      <version>1.18.7</version>\n    </dependency>\n  </dependencies>\n\n  <profiles>\n    <!-- Workaround for https://issues.apache.org/jira/projects/MJAR/issues/MJAR-138 -->\n    <profile>\n      <id>conditionally-add-commons-tests-if-tests-not-skipped</id>\n      <activation>\n        <property>\n          <name>maven.test.skip</name>\n          <value>!true</value>\n        </property>\n      </activation>\n      <dependencies>\n        <dependency>\n          <groupId>${project.groupId}</groupId>\n          <artifactId>sonarlint-commons</artifactId>\n          <version>${project.version}</version>\n          <classifier>tests</classifier>\n          <type>test-jar</type>\n          <scope>test</scope>\n        </dependency>\n      </dependencies>\n    </profile>\n  </profiles>\n\n</project>\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/ProtobufUtils.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils;\n\nimport com.github.tomakehurst.wiremock.http.Body;\nimport com.github.tomakehurst.wiremock.http.ContentTypeHeader;\nimport com.google.protobuf.Message;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\n\npublic class ProtobufUtils {\n\n  private ProtobufUtils() {\n    // utility class\n  }\n\n  public static Body protobufBody(Message message) {\n    var baos = new ByteArrayOutputStream();\n    try {\n      message.writeTo(baos);\n      return Body.ofBinaryOrText(baos.toByteArray(), ContentTypeHeader.absent());\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  public static Body protobufBodyDelimited(Message... messages) {\n    var baos = new ByteArrayOutputStream();\n    try {\n      for (var message : messages) {\n        message.writeDelimitedTo(baos);\n      }\n      return Body.ofBinaryOrText(baos.toByteArray(), ContentTypeHeader.absent());\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/SonarLintBackendFixture.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.EnumSet;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Queue;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentLinkedQueue;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.function.Consumer;\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.rpc.client.ConfigScopeNotFoundException;\nimport org.sonarsource.sonarlint.core.rpc.client.SonarLintCancelChecker;\nimport org.sonarsource.sonarlint.core.rpc.client.SonarLintRpcClientDelegate;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.ConfigurationScopeDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.scope.DidAddConfigurationScopesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarCloudConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.SonarQubeConnectionConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.ClientConstantInfoDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.HttpConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.JsTsRequirementsDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.LanguageSpecificRequirements;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.SonarCloudAlternativeEnvironmentDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.SonarQubeCloudRegionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.SslConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.TelemetryClientConstantAttributesDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.TelemetryMigrationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.log.LogLevel;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.StandaloneRuleConfigDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.DependencyRiskDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.AssistBindingParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.AssistBindingResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.binding.NoBindingSuggestionFoundParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.connection.ConnectionSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.embeddedserver.EmbeddedServerStartedParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.event.DidReceiveServerHotspotEvent;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.fix.FixSuggestionDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.HotspotDetailsDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.RaisedHotspotDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.GetProxyPasswordAuthenticationResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.ProxyDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.http.X509CertificateDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.IssueDetailsDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.log.LogParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.MessageType;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.message.ShowSoonUnsupportedMessageParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.progress.ReportProgressParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.progress.StartProgressParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.sca.DidChangeDependencyRisksParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.smartnotification.ShowSmartNotificationParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.taint.vulnerability.DidChangeTaintVulnerabilitiesParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.TelemetryClientLiveAttributesResponse;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Either;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.SonarCloudRegion;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.TokenDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.UsernamePasswordDto;\nimport org.sonarsource.sonarlint.core.test.utils.plugins.Plugin;\nimport org.sonarsource.sonarlint.core.test.utils.server.ServerFixture;\nimport org.sonarsource.sonarlint.core.test.utils.storage.ConfigurationScopeStorageFixture;\nimport org.sonarsource.sonarlint.core.test.utils.storage.StorageFixture;\nimport org.sonarsource.sonarlint.core.test.utils.storage.TestDatabase;\n\nimport static java.util.Collections.emptyMap;\nimport static java.util.Collections.emptySet;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.verify;\nimport static org.sonarsource.sonarlint.core.labs.IdeLabsSpringConfig.PROPERTY_IDE_LABS_SUBSCRIPTION_URL;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.GESSIE_TELEMETRY;\nimport static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.TELEMETRY;\nimport static org.sonarsource.sonarlint.core.telemetry.TelemetrySpringConfig.PROPERTY_TELEMETRY_ENDPOINT;\nimport static org.sonarsource.sonarlint.core.telemetry.gessie.GessieSpringConfig.PROPERTY_GESSIE_ENDPOINT;\nimport static org.sonarsource.sonarlint.core.test.utils.storage.StorageFixture.newStorage;\n\npublic class SonarLintBackendFixture {\n\n  public static final String USER_AGENT_FOR_TESTS = \"SonarLintBackendFixture\";\n\n  private SonarLintBackendFixture() {\n    // utility class\n  }\n\n  public static SonarLintBackendBuilder newBackend() {\n    return newBackend(null);\n  }\n\n  public static SonarLintBackendBuilder newBackend(@Nullable Consumer<SonarLintTestRpcServer> afterStartCallback) {\n    return new SonarLintBackendBuilder(afterStartCallback);\n  }\n\n  public static SonarLintClientBuilder newFakeClient() {\n    return new SonarLintClientBuilder();\n  }\n\n  public static class SonarLintBackendBuilder {\n    private final Consumer<SonarLintTestRpcServer> afterStartCallback;\n    private final List<SonarQubeConnectionConfigurationDto> sonarQubeConnections = new ArrayList<>();\n    private final List<SonarCloudConnectionConfigurationDto> sonarCloudConnections = new ArrayList<>();\n    private final List<ConfigurationScopeDto> configurationScopes = new ArrayList<>();\n    private final List<ConfigurationScopeStorageFixture.ConfigurationScopeStorageBuilder> configurationScopeStorages = new ArrayList<>();\n    private final Set<Path> embeddedPluginPaths = new HashSet<>();\n    private final Map<String, Path> connectedModeEmbeddedPluginPathsByKey = new HashMap<>();\n    private final Set<Language> enabledLanguages = EnumSet.noneOf(Language.class);\n    private final Set<Language> extraEnabledLanguagesInConnectedMode = EnumSet.noneOf(Language.class);\n    private final Set<String> disabledPluginKeysForAnalysis = new HashSet<>();\n    private final Set<BackendCapability> backendCapabilities = EnumSet.noneOf(BackendCapability.class);\n    private Path customStorageRoot;\n    private Path customUserHome;\n    private String userAgent = USER_AGENT_FOR_TESTS;\n    private String clientName = \"SonarLint Backend Fixture\";\n\n    private final Map<String, StandaloneRuleConfigDto> standaloneConfigByKey = new HashMap<>();\n    private final List<StorageFixture.StorageBuilder> serverStorages = new ArrayList<>();\n    private boolean isFocusOnNewCode;\n\n    @Nullable\n    private String euRegionUri;\n    @Nullable\n    private String euRegionApiUri;\n    @Nullable\n    private String euRegionWebSocketUri;\n\n    @Nullable\n    private String usRegionUri;\n    @Nullable\n    private String usRegionApiUri;\n    @Nullable\n    private String usRegionWebSocketUri;\n\n    private Duration responseTimeout;\n    private Path keyStorePath;\n    private String keyStorePassword;\n    private String keyStoreType;\n    private boolean automaticAnalysisEnabled = true;\n    private TelemetryMigrationDto telemetryMigration;\n    private LanguageSpecificRequirements languageSpecificRequirements;\n    private final List<Consumer<SonarLintTestRpcServer>> beforeInitializeCallbacks = new ArrayList<>();\n    private LogLevel logLevel = LogLevel.DEBUG;\n    private String productKey = \"mediumTests\";\n\n    public SonarLintBackendBuilder(@Nullable Consumer<SonarLintTestRpcServer> afterStartCallback) {\n      this.afterStartCallback = afterStartCallback;\n    }\n\n    public SonarLintBackendBuilder withSonarQubeConnection() {\n      return withSonarQubeConnection(\"connectionId\");\n    }\n\n    public SonarLintBackendBuilder withSonarQubeConnection(String connectionId) {\n      return withSonarQubeConnection(connectionId, \"http://not-used\", true, null);\n    }\n\n    public SonarLintBackendBuilder withSonarQubeConnection(String connectionId, Consumer<StorageFixture.StorageBuilder> storageBuilder) {\n      return withSonarQubeConnection(connectionId, \"http://not-used\", true, storageBuilder);\n    }\n\n    public SonarLintBackendBuilder withSonarQubeConnection(String connectionId, String serverUrl) {\n      return withSonarQubeConnection(connectionId, serverUrl, true, null);\n    }\n\n    public SonarLintBackendBuilder withSonarQubeConnection(String connectionId, String serverUrl, Consumer<StorageFixture.StorageBuilder> storageBuilder) {\n      return withSonarQubeConnection(connectionId, serverUrl, true, storageBuilder);\n    }\n\n    public SonarLintBackendBuilder withSonarQubeConnection(String connectionId, ServerFixture.Server server) {\n      return withSonarQubeConnection(connectionId, server.baseUrl(), true, null);\n    }\n\n    public SonarLintBackendBuilder withSonarQubeConnection(String connectionId, ServerFixture.Server server, Consumer<StorageFixture.StorageBuilder> storageBuilder) {\n      return withSonarQubeConnection(connectionId, server.baseUrl(), true, storageBuilder);\n    }\n\n    public SonarLintBackendBuilder withSonarQubeConnectionAndNotifications(String connectionId, String serverUrl) {\n      return withSonarQubeConnection(connectionId, serverUrl, false, null);\n    }\n\n    public SonarLintBackendBuilder withSonarQubeConnectionAndNotifications(String connectionId, String serverUrl, Consumer<StorageFixture.StorageBuilder> storageBuilder) {\n      return withSonarQubeConnection(connectionId, serverUrl, false, storageBuilder);\n    }\n\n    public SonarLintBackendBuilder withSonarCloudConnectionAndNotifications(String connectionId, String organisationKey, Consumer<StorageFixture.StorageBuilder> storageBuilder) {\n      return withSonarCloudConnection(connectionId, organisationKey, false, storageBuilder);\n    }\n\n    private SonarLintBackendBuilder withSonarQubeConnection(String connectionId, String serverUrl, boolean disableNotifications,\n      @Nullable Consumer<StorageFixture.StorageBuilder> storageBuilder) {\n      if (storageBuilder != null) {\n        var storage = newStorage(connectionId);\n        storageBuilder.accept(storage);\n        serverStorages.add(storage);\n      }\n      sonarQubeConnections.add(new SonarQubeConnectionConfigurationDto(connectionId, serverUrl, disableNotifications));\n      return this;\n    }\n\n    // use only when the storage needs to be present but not the corresponding connection\n    public SonarLintBackendBuilder withStorage(String connectionId, Consumer<StorageFixture.StorageBuilder> storageBuilder) {\n      var storage = newStorage(connectionId);\n      storageBuilder.accept(storage);\n      serverStorages.add(storage);\n      return this;\n    }\n\n    public SonarLintBackendBuilder withSonarQubeCloudEuRegionUri(String euRegionUri) {\n      this.euRegionUri = euRegionUri;\n      return this;\n    }\n\n    public SonarLintBackendBuilder withSonarQubeCloudEuRegionApiUri(String euRegionApiUri) {\n      this.euRegionApiUri = euRegionApiUri;\n      return this;\n    }\n\n    public SonarLintBackendBuilder withSonarQubeCloudEuRegionWebSocketUri(String euRegionWebSocketUri) {\n      this.euRegionWebSocketUri = euRegionWebSocketUri;\n      return this;\n    }\n\n    public SonarLintBackendBuilder withSonarQubeCloudUsRegionUri(String usRegionUri) {\n      this.usRegionUri = usRegionUri;\n      return this;\n    }\n\n    public SonarLintBackendBuilder withSonarQubeCloudUsRegionApiUri(String usRegionApiUri) {\n      this.usRegionApiUri = usRegionApiUri;\n      return this;\n    }\n\n    public SonarLintBackendBuilder withSonarQubeCloudUsRegionWebSocketUri(String usRegionWebSocketUri) {\n      this.usRegionWebSocketUri = usRegionWebSocketUri;\n      return this;\n    }\n\n    public SonarLintBackendBuilder withSonarCloudConnection(String connectionId, String organizationKey, boolean disableNotifications,\n      @Nullable Consumer<StorageFixture.StorageBuilder> storageBuilder) {\n      return withSonarCloudConnection(connectionId, organizationKey, SonarCloudRegion.EU.name(), disableNotifications, storageBuilder);\n    }\n\n    public SonarLintBackendBuilder withSonarCloudConnection(String connectionId, String organizationKey, String region, boolean disableNotifications,\n      @Nullable Consumer<StorageFixture.StorageBuilder> storageBuilder) {\n      if (storageBuilder != null) {\n        var storage = newStorage(connectionId);\n        storageBuilder.accept(storage);\n        serverStorages.add(storage);\n      }\n      sonarCloudConnections.add(new SonarCloudConnectionConfigurationDto(connectionId, organizationKey,\n        SonarCloudRegion.valueOf(region), disableNotifications));\n      return this;\n    }\n\n    public SonarLintBackendBuilder withSonarCloudConnection(String connectionId, String organizationKey, boolean disableNotifications,\n      @Nullable Consumer<StorageFixture.StorageBuilder> storageBuilder, SonarCloudRegion region) {\n      if (storageBuilder != null) {\n        var storage = newStorage(connectionId);\n        storageBuilder.accept(storage);\n        serverStorages.add(storage);\n      }\n      sonarCloudConnections.add(new SonarCloudConnectionConfigurationDto(connectionId, organizationKey,\n        region, disableNotifications));\n      return this;\n    }\n\n    public SonarLintBackendBuilder withSonarCloudConnection(String connectionId) {\n      return withSonarCloudConnection(connectionId, \"orgKey\");\n    }\n\n    public SonarLintBackendBuilder withSonarCloudConnection(String connectionId, Consumer<StorageFixture.StorageBuilder> storageBuilder) {\n      return withSonarCloudConnection(connectionId, \"orgKey\", true, storageBuilder);\n    }\n\n    public SonarLintBackendBuilder withSonarCloudConnection(String connectionId, String organizationKey) {\n      return withSonarCloudConnection(connectionId, organizationKey, true, null);\n    }\n\n    public SonarLintBackendBuilder withSonarCloudConnection(String connectionId, String organizationKey, String region) {\n      return withSonarCloudConnection(connectionId, organizationKey, region, true, null);\n    }\n\n    public SonarLintBackendBuilder withUnboundConfigScope(String configurationScopeId) {\n      return withUnboundConfigScope(configurationScopeId, configurationScopeId);\n    }\n\n    public SonarLintBackendBuilder withUnboundConfigScope(String configurationScopeId, String name) {\n      return withUnboundConfigScope(configurationScopeId, name, null);\n    }\n\n    public SonarLintBackendBuilder withUnboundConfigScope(String configurationScopeId, String name, @Nullable String parentId) {\n      return withConfigScope(configurationScopeId, name, parentId, new BindingConfigurationDto(null, null, false));\n    }\n\n    public SonarLintBackendBuilder withBoundConfigScope(String configurationScopeId, String connectionId, String projectKey) {\n      return withConfigScope(configurationScopeId, configurationScopeId, null, new BindingConfigurationDto(connectionId, projectKey, false));\n    }\n\n    public SonarLintBackendBuilder withBoundConfigScope(String configurationScopeId, String connectionId, String projectKey,\n      Consumer<ConfigurationScopeStorageFixture.ConfigurationScopeStorageBuilder> storageBuilder) {\n      return withConfigScope(configurationScopeId, configurationScopeId, null, new BindingConfigurationDto(connectionId, projectKey, false), storageBuilder);\n    }\n\n    public SonarLintBackendBuilder withChildConfigScope(String configurationScopeId, @Nullable String parentScopeId) {\n      return withConfigScope(configurationScopeId, configurationScopeId, parentScopeId, new BindingConfigurationDto(null, null, false));\n    }\n\n    public SonarLintBackendBuilder withConfigScope(String configurationScopeId, String name, @Nullable String parentScopeId, BindingConfigurationDto bindingConfiguration) {\n      return withConfigScope(configurationScopeId, name, parentScopeId, bindingConfiguration, null);\n    }\n\n    public SonarLintBackendBuilder withConfigScope(String configurationScopeId, String name, @Nullable String parentScopeId, BindingConfigurationDto bindingConfiguration,\n      @Nullable Consumer<ConfigurationScopeStorageFixture.ConfigurationScopeStorageBuilder> storageBuilder) {\n      if (storageBuilder != null) {\n        var builder = ConfigurationScopeStorageFixture.newBuilder(configurationScopeId);\n        storageBuilder.accept(builder);\n        configurationScopeStorages.add(builder);\n      }\n      configurationScopes.add(new ConfigurationScopeDto(configurationScopeId, parentScopeId, true, name, bindingConfiguration));\n      return this;\n    }\n\n    public SonarLintBackendBuilder withStandaloneEmbeddedPluginAndEnabledLanguage(Plugin plugin) {\n      var builder = withStandaloneEmbeddedPlugin(plugin);\n      for (var language : plugin.getLanguages()) {\n        builder = builder.withEnabledLanguageInStandaloneMode(language);\n      }\n      return builder;\n    }\n\n    public SonarLintBackendBuilder withStandaloneEmbeddedPlugin(Plugin plugin) {\n      return withStandaloneEmbeddedPlugin(plugin.getPath());\n    }\n\n    public SonarLintBackendBuilder withStandaloneEmbeddedPlugin(Path pluginPath) {\n      this.embeddedPluginPaths.add(pluginPath);\n      return this;\n    }\n\n    public SonarLintBackendBuilder withConnectedEmbeddedPluginAndEnabledLanguage(Plugin plugin) {\n      this.embeddedPluginPaths.add(plugin.getPath());\n      this.connectedModeEmbeddedPluginPathsByKey.put(plugin.getPluginKey(), plugin.getPath());\n      var builder = this;\n      for (var language : plugin.getLanguages()) {\n        builder = builder.withEnabledLanguageInStandaloneMode(language);\n      }\n      return builder;\n    }\n\n    public SonarLintBackendBuilder withEnabledLanguageInStandaloneMode(Language language) {\n      this.enabledLanguages.add(language);\n      return this;\n    }\n\n    public SonarLintBackendBuilder withExtraEnabledLanguagesInConnectedMode(Language language) {\n      this.extraEnabledLanguagesInConnectedMode.add(language);\n      return this;\n    }\n\n    public SonarLintBackendBuilder withDisabledPluginsForAnalysis(String pluginKey) {\n      this.disabledPluginKeysForAnalysis.add(pluginKey);\n      return this;\n    }\n\n    public SonarLintBackendBuilder withBackendCapability(BackendCapability... capabilities) {\n      this.backendCapabilities.addAll(Arrays.asList(capabilities));\n      return this;\n    }\n\n    public SonarLintBackendBuilder withStandaloneRuleConfig(String ruleKey, boolean isActive, Map<String, String> params) {\n      this.standaloneConfigByKey.put(ruleKey, new StandaloneRuleConfigDto(isActive, params));\n      return this;\n    }\n\n    public SonarLintBackendBuilder withStorageRoot(Path storageRoot) {\n      this.customStorageRoot = storageRoot;\n      return this;\n    }\n\n    public SonarLintBackendBuilder withUserHome(Path userHome) {\n      this.customUserHome = userHome;\n      return this;\n    }\n\n    public SonarLintBackendBuilder withUserAgent(String userAgent) {\n      this.userAgent = userAgent;\n      return this;\n    }\n\n    public SonarLintBackendBuilder withClientName(String clientName) {\n      this.clientName = clientName;\n      return this;\n    }\n\n    public SonarLintBackendBuilder withFocusOnNewCode() {\n      isFocusOnNewCode = true;\n      return this;\n    }\n\n    public SonarLintBackendBuilder withClientNodeJsPath(Path clientNodeJsPath) {\n      languageSpecificRequirements = new LanguageSpecificRequirements(new JsTsRequirementsDto(clientNodeJsPath, null), false);\n      return this;\n    }\n\n    public SonarLintBackendBuilder withEslintBridgeServerBundlePath(Path eslintBridgeServerBundlePath) {\n      languageSpecificRequirements = new LanguageSpecificRequirements(new JsTsRequirementsDto(null, eslintBridgeServerBundlePath), false);\n      return this;\n    }\n\n    public SonarLintBackendBuilder withNoLanguageSpecificRequirements() {\n      languageSpecificRequirements = null;\n      return this;\n    }\n\n    public SonarLintBackendBuilder withHttpResponseTimeout(Duration responseTimeout) {\n      this.responseTimeout = responseTimeout;\n      return this;\n    }\n\n    public SonarLintBackendBuilder withKeyStore(Path keyStorePath, String password, String storeType) {\n      this.keyStorePath = keyStorePath;\n      this.keyStorePassword = password;\n      this.keyStoreType = storeType;\n      return this;\n    }\n\n    public SonarLintBackendBuilder withTelemetryEnabled() {\n      return withTelemetryEnabled(\"http://telemetryEndpoint.sonarlint\");\n    }\n\n    public SonarLintBackendBuilder withTelemetryEnabled(String endpointUrl) {\n      this.backendCapabilities.add(TELEMETRY);\n      System.setProperty(PROPERTY_TELEMETRY_ENDPOINT, endpointUrl);\n      return this;\n    }\n\n    public SonarLintBackendBuilder withGessieTelemetryEnabled(String endpointUrl) {\n      this.backendCapabilities.add(GESSIE_TELEMETRY);\n      System.setProperty(PROPERTY_GESSIE_ENDPOINT, endpointUrl);\n      return this;\n    }\n\n    public SonarLintBackendBuilder withIdeLabsSubscriptionUrl(String ideLabsSubscriptionUrl) {\n      System.setProperty(PROPERTY_IDE_LABS_SUBSCRIPTION_URL, ideLabsSubscriptionUrl);\n      return this;\n    }\n\n    public SonarLintBackendBuilder withAutomaticAnalysisEnabled(boolean enabled) {\n      this.automaticAnalysisEnabled = enabled;\n      return this;\n    }\n\n    public SonarLintBackendBuilder withTelemetryMigration(TelemetryMigrationDto telemetryMigration) {\n      this.telemetryMigration = telemetryMigration;\n      return this;\n    }\n\n    public SonarLintBackendBuilder withLogLevel(LogLevel logLevel) {\n      this.logLevel = logLevel;\n      return this;\n    }\n\n    public SonarLintBackendBuilder withProductKey(String productKey) {\n      this.productKey = productKey;\n      return this;\n    }\n\n    public SonarLintBackendBuilder beforeInitialize(Consumer<SonarLintTestRpcServer> backendConsumer) {\n      this.beforeInitializeCallbacks.add(backendConsumer);\n      return this;\n    }\n\n    public SonarLintTestRpcServer start(SonarLintRpcClientDelegate client) {\n      var sonarlintUserHome = customUserHome == null ? tempDirectory(\"slUserHome\") : customUserHome;\n      var workDir = tempDirectory(\"work\");\n      var storageParentPath = customStorageRoot == null ? tempDirectory(\"storage\") : customStorageRoot.getParent();\n      var storageRoot = storageParentPath.resolve(\"storage\");\n      try {\n        var database = new TestDatabase(storageRoot);\n        serverStorages.forEach(storage -> storage.populate(storageParentPath, database));\n        if (!configurationScopeStorages.isEmpty()) {\n          configurationScopeStorages.forEach(storage -> storage.populate(storageRoot, database));\n        }\n        database.shutdown();\n        var sonarLintBackend = new SonarLintTestRpcServer(client);\n        beforeInitializeCallbacks.forEach(callback -> callback.accept(sonarLintBackend));\n        var telemetryInitDto = new TelemetryClientConstantAttributesDto(productKey, productKey,\n          \"1.2.3\", \"4.5.6\", emptyMap());\n        var clientInfo = new ClientConstantInfoDto(clientName, userAgent);\n\n        // If more regions are added in the future, extend this by adding a new entry set and add the fields / methods above!\n        var sonarCloudAlternativeEnvironment = new SonarCloudAlternativeEnvironmentDto(Map.of(\n          SonarCloudRegion.EU,\n          new SonarQubeCloudRegionDto(createUriFromString(euRegionUri), createUriFromString(euRegionApiUri), createUriFromString(euRegionWebSocketUri)),\n          SonarCloudRegion.US,\n          new SonarQubeCloudRegionDto(createUriFromString(usRegionUri), createUriFromString(usRegionApiUri), createUriFromString(usRegionWebSocketUri))));\n\n        var sslConfiguration = new SslConfigurationDto(null, null, null, keyStorePath, keyStorePassword, keyStoreType);\n        var httpConfiguration = new HttpConfigurationDto(sslConfiguration, null, null, null, responseTimeout);\n        sonarLintBackend\n          .initialize(new InitializeParams(clientInfo, telemetryInitDto, httpConfiguration,\n            sonarCloudAlternativeEnvironment,\n            backendCapabilities,\n            storageRoot, workDir, embeddedPluginPaths, connectedModeEmbeddedPluginPathsByKey,\n            enabledLanguages, extraEnabledLanguagesInConnectedMode, disabledPluginKeysForAnalysis, sonarQubeConnections, sonarCloudConnections, sonarlintUserHome.toString(),\n            standaloneConfigByKey, isFocusOnNewCode, languageSpecificRequirements, automaticAnalysisEnabled, telemetryMigration, logLevel))\n          .get();\n        sonarLintBackend.getConfigurationService().didAddConfigurationScopes(new DidAddConfigurationScopesParams(configurationScopes));\n        if (afterStartCallback != null) {\n          afterStartCallback.accept(sonarLintBackend);\n        }\n        return sonarLintBackend;\n      } catch (Exception e) {\n        throw new IllegalStateException(\"Cannot initialize the backend\", e);\n      }\n    }\n\n    private static URI createUriFromString(@Nullable String uri) {\n      return uri == null ? null : URI.create(uri);\n    }\n\n    private static Path tempDirectory(String prefix) {\n      try {\n        return Files.createTempDirectory(prefix);\n      } catch (IOException e) {\n        throw new RuntimeException(e);\n      }\n    }\n\n    public SonarLintTestRpcServer start() {\n      return start(newFakeClient().build());\n    }\n  }\n\n  public static class SonarLintClientBuilder {\n    private final Map<String, Either<TokenDto, UsernamePasswordDto>> credentialsByConnectionId = new HashMap<>();\n    private boolean printLogsToStdOut;\n    private final Map<String, Path> baseDirsByConfigScope = new HashMap<>();\n    private final Map<String, List<ClientFileDto>> initialFilesByConfigScope = new HashMap<>();\n    private final Map<String, String> matchedBranchPerScopeId = new HashMap<>();\n    private final Map<String, Set<String>> fileExclusionsByConfigScope = new HashMap<>();\n\n    public SonarLintClientBuilder withCredentials(String connectionId, String user, String password) {\n      credentialsByConnectionId.put(connectionId, Either.forRight(new UsernamePasswordDto(user, password)));\n      return this;\n    }\n\n    public SonarLintClientBuilder withToken(String connectionId, String token) {\n      credentialsByConnectionId.put(connectionId, Either.forLeft(new TokenDto(token)));\n      return this;\n    }\n\n    public SonarLintClientBuilder withFileExclusions(String connectionId, Set<String> fileExclusions) {\n      fileExclusionsByConfigScope.put(connectionId, fileExclusions);\n      return this;\n    }\n\n    public FakeSonarLintRpcClient build() {\n      return spy(new FakeSonarLintRpcClient(credentialsByConnectionId, printLogsToStdOut, matchedBranchPerScopeId, baseDirsByConfigScope, initialFilesByConfigScope,\n        fileExclusionsByConfigScope));\n    }\n\n    public SonarLintClientBuilder printLogsToStdOut() {\n      this.printLogsToStdOut = true;\n      return this;\n    }\n\n    public SonarLintClientBuilder withMatchedBranch(String configurationScopeId, String matchedBranchName) {\n      matchedBranchPerScopeId.put(configurationScopeId, matchedBranchName);\n      return this;\n    }\n\n    public SonarLintClientBuilder withInitialFs(String configScopeId, List<ClientFileDto> clientFileDtos) {\n      initialFilesByConfigScope.put(configScopeId, clientFileDtos);\n      return this;\n    }\n\n    public SonarLintClientBuilder withInitialFs(String configScopeId, Path baseDir, List<ClientFileDto> clientFileDtos) {\n      initialFilesByConfigScope.put(configScopeId, clientFileDtos);\n      baseDirsByConfigScope.put(configScopeId, baseDir);\n      return this;\n    }\n  }\n\n  public static class FakeSonarLintRpcClient implements SonarLintRpcClientDelegate {\n    private final Queue<ShowSoonUnsupportedMessageParams> soonUnsupportedMessagesToShow = new ConcurrentLinkedQueue<>();\n    private final Queue<ShowSmartNotificationParams> smartNotificationsToShow = new ConcurrentLinkedQueue<>();\n    private final Map<String, ProgressReport> progressReportsByTaskId = new ConcurrentHashMap<>();\n    private final Set<String> synchronizedConfigScopeIds = new HashSet<>();\n    private final Map<String, List<ConnectionSuggestionDto>> suggestionsByConfigScope = new HashMap<>();\n    private final Map<String, Either<TokenDto, UsernamePasswordDto>> credentialsByConnectionId;\n    private final boolean printLogsToStdOut;\n    private final Queue<LogParams> logs = new ConcurrentLinkedQueue<>();\n    private final List<DidChangeTaintVulnerabilitiesParams> taintVulnerabilityChanges = new CopyOnWriteArrayList<>();\n    private final List<DidChangeDependencyRisksParams> dependencyRiskChanges = new CopyOnWriteArrayList<>();\n    private final Map<String, String> matchedBranchPerScopeId;\n    private final Map<String, Path> baseDirsByConfigScope;\n    private final Map<String, Set<String>> fileExclusionsByConfigScope;\n    private final Map<String, List<ClientFileDto>> initialFilesByConfigScope;\n    Map<String, Map<URI, List<RaisedIssueDto>>> raisedIssuesByScopeId = new HashMap<>();\n    Map<String, Map<URI, List<RaisedHotspotDto>>> raisedHotspotsByScopeId = new HashMap<>();\n    Map<String, Map<String, String>> inferredAnalysisPropertiesByScopeId = new HashMap<>();\n    Map<String, Boolean> analysisReadinessPerScopeId = new HashMap<>();\n    Map<String, Integer> invalidTokenNotificationsCountPerConnectionId = new HashMap<>();\n    int embeddedServerStartedPort = -1;\n\n    public FakeSonarLintRpcClient(Map<String, Either<TokenDto, UsernamePasswordDto>> credentialsByConnectionId, boolean printLogsToStdOut,\n      Map<String, String> matchedBranchPerScopeId, Map<String, Path> baseDirsByConfigScope, Map<String, List<ClientFileDto>> initialFilesByConfigScope,\n      Map<String, Set<String>> fileExclusionsByConfigScope) {\n      this.credentialsByConnectionId = credentialsByConnectionId;\n      this.printLogsToStdOut = printLogsToStdOut;\n      this.matchedBranchPerScopeId = matchedBranchPerScopeId;\n      this.baseDirsByConfigScope = baseDirsByConfigScope;\n      this.initialFilesByConfigScope = initialFilesByConfigScope;\n      this.fileExclusionsByConfigScope = fileExclusionsByConfigScope;\n    }\n\n    @Override\n    public void showSoonUnsupportedMessage(ShowSoonUnsupportedMessageParams params) {\n      soonUnsupportedMessagesToShow.add(params);\n    }\n\n    @Override\n    public void showSmartNotification(ShowSmartNotificationParams params) {\n      smartNotificationsToShow.add(params);\n    }\n\n    @Override\n    public String getClientLiveDescription() {\n      return \"\";\n    }\n\n    @Override\n    public void showHotspot(String configurationScopeId, HotspotDetailsDto hotspotDetails) {\n      // no-op\n    }\n\n    @Override\n    public void showIssue(String configurationScopeId, IssueDetailsDto issueDetails) {\n      // no-op\n    }\n\n    @Override\n    public void showFixSuggestion(String configurationScopeId, String issueKey, FixSuggestionDto fixSuggestion) {\n      // no-op\n    }\n\n    @Override\n    public AssistCreatingConnectionResponse assistCreatingConnection(AssistCreatingConnectionParams params, SonarLintCancelChecker cancelChecker) {\n      throw new CancellationException(\"Not stubbed in medium tests\");\n    }\n\n    @Override\n    public AssistBindingResponse assistBinding(AssistBindingParams params, SonarLintCancelChecker cancelChecker) {\n      throw new CancellationException(\"Not stubbed in medium tests\");\n    }\n\n    @Override\n    public void startProgress(StartProgressParams params) throws UnsupportedOperationException {\n      progressReportsByTaskId.put(params.getTaskId(),\n        new ProgressReport(params.getConfigurationScopeId(), params.getTitle(), params.getMessage(), params.isIndeterminate(), params.isCancellable()));\n    }\n\n    @Override\n    public void reportProgress(ReportProgressParams params) {\n      var progressReport = progressReportsByTaskId.computeIfAbsent(params.getTaskId(), k -> {\n        throw new IllegalStateException(\"Cannot update a progress that has not been started before\");\n      });\n      var notification = params.getNotification();\n      if (notification.isLeft()) {\n        var updateNotification = notification.getLeft();\n        progressReport.addStep(new ProgressStep(updateNotification.getMessage(), updateNotification.getPercentage()));\n      } else {\n        progressReport.complete();\n      }\n    }\n\n    @Override\n    public void invalidToken(String connectionId) {\n      invalidTokenNotificationsCountPerConnectionId.merge(connectionId, 1, Integer::sum);\n    }\n\n    public Map<String, ProgressReport> getProgressReportsByTaskId() {\n      return progressReportsByTaskId;\n    }\n\n    @Override\n    public void didSynchronizeConfigurationScopes(Set<String> configurationScopeIds) {\n      synchronizedConfigScopeIds.addAll(configurationScopeIds);\n    }\n\n    public Set<String> getSynchronizedConfigScopeIds() {\n      return synchronizedConfigScopeIds;\n    }\n\n    public Map<String, List<ConnectionSuggestionDto>> getSuggestionsByConfigScope() {\n      return suggestionsByConfigScope;\n    }\n\n    @Override\n    public Either<TokenDto, UsernamePasswordDto> getCredentials(String connectionId) {\n      return credentialsByConnectionId.getOrDefault(connectionId, Either.forLeft(new TokenDto(\"token\")));\n    }\n\n    @Override\n    public List<ProxyDto> selectProxies(URI uri) {\n      return List.of();\n    }\n\n    @Override\n    public GetProxyPasswordAuthenticationResponse getProxyPasswordAuthentication(String host, int port, String protocol, String prompt, String scheme, URL targetHost) {\n      return null;\n    }\n\n    @Override\n    public boolean checkServerTrusted(List<X509CertificateDto> chain, String authType) {\n      return false;\n    }\n\n    public void setToken(String connectionId, String token) {\n      credentialsByConnectionId.put(connectionId, Either.forLeft(new TokenDto(token)));\n    }\n\n    @Override\n    public void didReceiveServerHotspotEvent(DidReceiveServerHotspotEvent params) {\n      // no-op\n    }\n\n    @Override\n    public String matchSonarProjectBranch(String configurationScopeId, String mainBranchName, Set<String> allBranchesNames, SonarLintCancelChecker cancelChecker) {\n      if (matchedBranchPerScopeId.containsKey(configurationScopeId)) {\n        return matchedBranchPerScopeId.get(configurationScopeId);\n      }\n      return mainBranchName;\n    }\n\n    @Override\n    public void didChangeMatchedSonarProjectBranch(String configScopeId, String newMatchedBranchName) {\n      // no-op\n\n    }\n\n    @Override\n    public void suggestBinding(Map<String, List<BindingSuggestionDto>> suggestionsByConfigScope) {\n      // no-op\n\n    }\n\n    @Override\n    public void suggestConnection(Map<String, List<ConnectionSuggestionDto>> suggestionsByConfigScope) {\n      this.suggestionsByConfigScope.putAll(suggestionsByConfigScope);\n    }\n\n    @Override\n    public void openUrlInBrowser(URL url) {\n      // no-op\n\n    }\n\n    @Override\n    public void showMessage(MessageType type, String text) {\n      // no-op\n\n    }\n\n    @Override\n    public TelemetryClientLiveAttributesResponse getTelemetryLiveAttributes() {\n      return new TelemetryClientLiveAttributesResponse(emptyMap());\n    }\n\n    @Override\n    public void didChangeTaintVulnerabilities(String configurationScopeId, Set<UUID> closedTaintVulnerabilityIds, List<TaintVulnerabilityDto> addedTaintVulnerabilities,\n      List<TaintVulnerabilityDto> updatedTaintVulnerabilities) {\n      this.taintVulnerabilityChanges\n        .add(new DidChangeTaintVulnerabilitiesParams(configurationScopeId, closedTaintVulnerabilityIds, addedTaintVulnerabilities, updatedTaintVulnerabilities));\n    }\n\n    @Override\n    public void didChangeDependencyRisks(String configurationScopeId, Set<UUID> closedDependencyRiskIds, List<DependencyRiskDto> addedDependencyRisks,\n      List<DependencyRiskDto> updatedDependencyRisks) {\n      this.dependencyRiskChanges\n        .add(new DidChangeDependencyRisksParams(configurationScopeId, closedDependencyRiskIds, addedDependencyRisks, updatedDependencyRisks));\n    }\n\n    @Override\n    public void log(LogParams params) {\n      this.logs.add(params);\n      if (printLogsToStdOut) {\n        System.out.println((params.getThreadName() != null ? (\"[\" + params.getThreadName() + \"] \") : \"\") + params.getLevel() + \" \"\n          + (params.getConfigScopeId() != null ? (\"[\" + params.getConfigScopeId() + \"] \") : \"\") + params.getMessage());\n      }\n    }\n\n    @Override\n    public Set<String> getFileExclusions(String configurationScopeId) {\n      return fileExclusionsByConfigScope.getOrDefault(configurationScopeId, emptySet());\n    }\n\n    @Override\n    public Path getBaseDir(String configurationScopeId) {\n      return baseDirsByConfigScope.get(configurationScopeId);\n    }\n\n    @Override\n    public List<ClientFileDto> listFiles(String configScopeId) {\n      return initialFilesByConfigScope.getOrDefault(configScopeId, List.of());\n    }\n\n    @Override\n    public void didChangeAnalysisReadiness(Set<String> configurationScopeIds, boolean areReadyForAnalysis) {\n      configurationScopeIds.forEach(scopeId -> analysisReadinessPerScopeId.put(scopeId, areReadyForAnalysis));\n    }\n\n    public boolean isAnalysisReadyForScope(String configurationScopeId) {\n      return analysisReadinessPerScopeId.getOrDefault(configurationScopeId, false);\n    }\n\n    @Override\n    public void raiseIssues(String configurationScopeId, Map<URI, List<RaisedIssueDto>> issuesByFileUri, boolean isIntermediatePublication, @Nullable UUID analysisId) {\n      raisedIssuesByScopeId.put(configurationScopeId, issuesByFileUri);\n    }\n\n    @Override\n    public void raiseHotspots(String configurationScopeId, Map<URI, List<RaisedHotspotDto>> hotspotsByFileUri, boolean isIntermediatePublication,\n      @org.jetbrains.annotations.Nullable UUID analysisId) {\n      raisedHotspotsByScopeId.put(configurationScopeId, hotspotsByFileUri);\n    }\n\n    @Override\n    public Map<String, String> getInferredAnalysisProperties(String configurationScopeId, List<URI> filePathsToAnalyze) throws ConfigScopeNotFoundException {\n      return inferredAnalysisPropertiesByScopeId.getOrDefault(configurationScopeId, new HashMap<>());\n    }\n\n    @Override\n    public void embeddedServerStarted(EmbeddedServerStartedParams params) {\n      this.embeddedServerStartedPort = params.getPort();\n    }\n\n    public void setInferredAnalysisProperties(String configurationScopeId, Map<String, String> inferredAnalysisProperties) {\n      inferredAnalysisPropertiesByScopeId.put(configurationScopeId, inferredAnalysisProperties);\n    }\n\n    public Map<URI, List<RaisedIssueDto>> getRaisedIssuesForScopeId(String configurationScopeId) {\n      return raisedIssuesByScopeId.getOrDefault(configurationScopeId, Map.of());\n    }\n\n    public List<RaisedIssueDto> getRaisedIssuesForScopeIdAsList(String configurationScopeId) {\n      return raisedIssuesByScopeId.getOrDefault(configurationScopeId, Map.of()).values().stream().flatMap(Collection::stream)\n        .toList();\n    }\n\n    public Map<URI, List<RaisedHotspotDto>> getRaisedHotspotsForScopeId(String configurationScopeId) {\n      return raisedHotspotsByScopeId.getOrDefault(configurationScopeId, Map.of());\n    }\n\n    public List<RaisedHotspotDto> getRaisedHotspotsForScopeIdAsList(String configurationScopeId) {\n      return raisedHotspotsByScopeId.getOrDefault(configurationScopeId, Map.of()).values().stream().flatMap(Collection::stream)\n        .toList();\n    }\n\n    public void cleanRaisedIssues() {\n      raisedIssuesByScopeId.clear();\n      raisedHotspotsByScopeId.clear();\n    }\n\n    public void cleanRaisedHotspots() {\n      raisedHotspotsByScopeId.clear();\n    }\n\n    public Queue<ShowSmartNotificationParams> getSmartNotificationsToShow() {\n      return smartNotificationsToShow;\n    }\n\n    public Queue<LogParams> getLogs() {\n      return logs;\n    }\n\n    public List<String> getLogMessages() {\n      return logs.stream().map(LogParams::getMessage).toList();\n    }\n\n    @Override\n    public void noBindingSuggestionFound(NoBindingSuggestionFoundParams params) {\n    }\n\n    public List<DidChangeTaintVulnerabilitiesParams> getTaintVulnerabilityChanges() {\n      return taintVulnerabilityChanges;\n    }\n\n    public List<DidChangeDependencyRisksParams> getDependencyRiskChanges() {\n      return dependencyRiskChanges;\n    }\n\n    public void clearLogs() {\n      logs.clear();\n    }\n\n    public void waitForSynchronization() {\n      verify(this, timeout(5000)).didSynchronizeConfigurationScopes(any());\n    }\n\n    public Integer getConnectionIdsWithInvalidToken(String connectionId) {\n      return invalidTokenNotificationsCountPerConnectionId.getOrDefault(connectionId, 0);\n    }\n\n    public int getEmbeddedServerPort() {\n      return embeddedServerStartedPort;\n    }\n\n    public static class ProgressReport {\n      @CheckForNull\n      private final String configurationScopeId;\n      private final String title;\n      @CheckForNull\n      private final String startingMessage;\n      private final boolean indeterminate;\n      private final boolean cancellable;\n      private final Collection<ProgressStep> steps;\n      private boolean complete;\n\n      private ProgressReport(@Nullable String configurationScopeId, String title, @Nullable String startingMessage, boolean indeterminate, boolean cancellable) {\n        this(configurationScopeId, title, startingMessage, indeterminate, cancellable, new ArrayList<>(), false);\n      }\n\n      public ProgressReport(@Nullable String configurationScopeId, String title, @Nullable String startingMessage, boolean indeterminate, boolean cancellable,\n        Collection<ProgressStep> steps, boolean complete) {\n        this.configurationScopeId = configurationScopeId;\n        this.title = title;\n        this.startingMessage = startingMessage;\n        this.indeterminate = indeterminate;\n        this.cancellable = cancellable;\n        this.steps = steps;\n        this.complete = complete;\n      }\n\n      private void addStep(ProgressStep step) {\n        steps.add(step);\n      }\n\n      @CheckForNull\n      public String getConfigurationScopeId() {\n        return configurationScopeId;\n      }\n\n      public String getTitle() {\n        return title;\n      }\n\n      @CheckForNull\n      public String getStartingMessage() {\n        return startingMessage;\n      }\n\n      public boolean isIndeterminate() {\n        return indeterminate;\n      }\n\n      public boolean isCancellable() {\n        return cancellable;\n      }\n\n      public Collection<ProgressStep> getSteps() {\n        return steps;\n      }\n\n      public void complete() {\n        this.complete = true;\n      }\n\n      public boolean isComplete() {\n        return complete;\n      }\n\n      @Override\n      public boolean equals(Object o) {\n        if (this == o)\n          return true;\n        if (o == null || getClass() != o.getClass())\n          return false;\n        ProgressReport that = (ProgressReport) o;\n        return indeterminate == that.indeterminate && cancellable == that.cancellable && complete == that.complete\n          && Objects.equals(configurationScopeId, that.configurationScopeId) && title.equals(that.title) && Objects.equals(startingMessage, that.startingMessage)\n          && steps.equals(that.steps);\n      }\n\n      @Override\n      public int hashCode() {\n        return Objects.hash(configurationScopeId, title, startingMessage, indeterminate, cancellable, steps, complete);\n      }\n\n      @Override\n      public String toString() {\n        return \"ProgressReport{\" +\n          \"configurationScopeId='\" + configurationScopeId + '\\'' +\n          \", title='\" + title + '\\'' +\n          \", startingMessage='\" + startingMessage + '\\'' +\n          \", indeterminate=\" + indeterminate +\n          \", cancellable=\" + cancellable +\n          \", steps=\" + steps +\n          \", complete=\" + complete +\n          '}';\n      }\n    }\n\n    public record ProgressStep(@Nullable String message, @Nullable Integer percentage) {\n    }\n  }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/SonarLintTestRpcServer.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils;\n\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.ToNumberPolicy;\nimport java.io.IOException;\nimport java.io.PipedInputStream;\nimport java.io.PipedOutputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.OffsetDateTime;\nimport java.util.Base64;\nimport java.util.concurrent.CompletableFuture;\nimport org.jetbrains.annotations.NotNull;\nimport org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabase;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.plugin.PluginRpcService;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.LocalOnlyIssuesRepository;\nimport org.sonarsource.sonarlint.core.rpc.client.ClientJsonRpcLauncher;\nimport org.sonarsource.sonarlint.core.rpc.client.SonarLintRpcClientDelegate;\nimport org.sonarsource.sonarlint.core.rpc.impl.BackendJsonRpcLauncher;\nimport org.sonarsource.sonarlint.core.rpc.impl.SonarLintRpcServerImpl;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.AiAgentRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalysisRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.binding.BindingRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.branch.SonarProjectBranchRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.config.ConfigurationRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.ConnectionRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.dogfooding.DogfoodingRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.file.FileRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.HotspotRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.IssueRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.labs.IdeLabsRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.log.LogRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.newcode.NewCodeRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.progress.TaskProgressRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.remediation.aicodefix.AiCodeFixRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RulesRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.DependencyRiskRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.TelemetryRpcService;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityTrackingRpcService;\nimport org.sonarsource.sonarlint.core.storage.StorageService;\nimport org.sonarsource.sonarlint.core.commons.storage.adapter.LocalDateAdapter;\nimport org.sonarsource.sonarlint.core.commons.storage.adapter.LocalDateTimeAdapter;\nimport org.sonarsource.sonarlint.core.commons.storage.adapter.OffsetDateTimeAdapter;\nimport org.sonarsource.sonarlint.core.telemetry.TelemetryLocalStorage;\n\nimport static java.util.Objects.requireNonNull;\n\npublic class SonarLintTestRpcServer implements SonarLintRpcServer {\n  private final SonarLintRpcServer serverUsingRpc;\n  private final SonarLintRpcServerImpl serverUsingJava;\n  private final BackendJsonRpcLauncher serverLauncher;\n  private final ClientJsonRpcLauncher clientLauncher;\n  private final JsonRpcSpyOutputStream clientToServerOutputStream;\n  private final PipedInputStream clientToServerInputStream;\n  private final PipedOutputStream serverToClientOutputStream;\n  private final JsonRpcSpyInputStream serverToClientInputStream;\n  private Path userHome;\n  private Path workDir;\n  private Path storageRoot;\n  private String productKey;\n\n  public SonarLintTestRpcServer(SonarLintRpcClientDelegate client) throws IOException {\n    clientToServerOutputStream = new JsonRpcSpyOutputStream();\n    clientToServerInputStream = new PipedInputStream(clientToServerOutputStream);\n\n    serverToClientOutputStream = new PipedOutputStream();\n    serverToClientInputStream = new JsonRpcSpyInputStream(serverToClientOutputStream);\n\n    this.serverLauncher = new BackendJsonRpcLauncher(clientToServerInputStream, serverToClientOutputStream);\n    this.clientLauncher = new ClientJsonRpcLauncher(serverToClientInputStream, clientToServerOutputStream, client);\n    this.serverUsingRpc = clientLauncher.getServerProxy();\n    this.serverUsingJava = serverLauncher.getServer();\n  }\n\n  @Override\n  public CompletableFuture<Void> initialize(InitializeParams params) {\n    this.productKey = params.getTelemetryConstantAttributes().getProductKey();\n    this.userHome = Path.of(requireNonNull(params.getSonarlintUserHome()));\n    this.workDir = requireNonNull(params.getWorkDir());\n    this.storageRoot = requireNonNull(params.getStorageRoot());\n    return serverUsingRpc.initialize(params);\n  }\n\n  @Override\n  public ConnectionRpcService getConnectionService() {\n    return serverUsingRpc.getConnectionService();\n  }\n\n  @Override\n  public ConfigurationRpcService getConfigurationService() {\n    return serverUsingRpc.getConfigurationService();\n  }\n\n  @Override\n  public FileRpcService getFileService() {\n    return serverUsingRpc.getFileService();\n  }\n\n  @Override\n  public RulesRpcService getRulesService() {\n    return serverUsingRpc.getRulesService();\n  }\n\n  @Override\n  public BindingRpcService getBindingService() {\n    return serverUsingRpc.getBindingService();\n  }\n\n  @Override\n  public HotspotRpcService getHotspotService() {\n    return serverUsingRpc.getHotspotService();\n  }\n\n  @Override\n  public TelemetryRpcService getTelemetryService() {\n    return serverUsingRpc.getTelemetryService();\n  }\n\n  @Override\n  public AnalysisRpcService getAnalysisService() {\n    return serverUsingRpc.getAnalysisService();\n  }\n\n  @Override\n  public SonarProjectBranchRpcService getSonarProjectBranchService() {\n    return serverUsingRpc.getSonarProjectBranchService();\n  }\n\n  @Override\n  public IssueRpcService getIssueService() {\n    return serverUsingRpc.getIssueService();\n  }\n\n  @Override\n  public DependencyRiskRpcService getDependencyRiskService() {\n    return serverUsingRpc.getDependencyRiskService();\n  }\n\n  @Override\n  public NewCodeRpcService getNewCodeService() {\n    return serverUsingRpc.getNewCodeService();\n  }\n\n  @Override\n  public TaintVulnerabilityTrackingRpcService getTaintVulnerabilityTrackingService() {\n    return serverUsingRpc.getTaintVulnerabilityTrackingService();\n  }\n\n  @Override\n  public DogfoodingRpcService getDogfoodingService() {\n    return serverUsingRpc.getDogfoodingService();\n  }\n\n  @Override\n  public AiCodeFixRpcService getAiCodeFixRpcService() {\n    return serverUsingRpc.getAiCodeFixRpcService();\n  }\n\n  @Override\n  public TaskProgressRpcService getTaskProgressRpcService() {\n    return serverUsingRpc.getTaskProgressRpcService();\n  }\n\n  @Override\n  public AiAgentRpcService getAiAgentService() {\n    return serverUsingJava.getAiAgentService();\n  }\n\n  @Override\n  public LogRpcService getLogService() {\n    return serverUsingRpc.getLogService();\n  }\n\n  @Override\n  public IdeLabsRpcService getIdeLabsService() {\n    return serverUsingRpc.getIdeLabsService();\n  }\n\n  @Override\n  public PluginRpcService getPluginService() {\n    return serverUsingRpc.getPluginService();\n  }\n\n  public Path getWorkDir() {\n    return workDir;\n  }\n\n  public Path getUserHome() {\n    return userHome;\n  }\n\n  public Path getStorageRoot() {\n    return storageRoot;\n  }\n\n  public Path telemetryFilePath() {\n    return userHome.resolve(\"telemetry\").resolve(productKey).resolve(\"usage\");\n  }\n\n  public TelemetryLocalStorage telemetryFileContent() {\n    try {\n      return readTelemetryFile(telemetryFilePath());\n    } catch (IOException e) {\n      // use this exception type to allow retries with awaitility's untilAsserted method\n      throw new AssertionError(\"Failed to read telemetry file\", e);\n    }\n  }\n\n  private static TelemetryLocalStorage readTelemetryFile(Path path) throws IOException {\n    var fileContent = Files.readString(path, StandardCharsets.UTF_8);\n    var decoded = new String(Base64.getDecoder().decode(fileContent), StandardCharsets.UTF_8);\n    var gson = new GsonBuilder()\n      .registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter().nullSafe())\n      .registerTypeAdapter(LocalDate.class, new LocalDateAdapter().nullSafe())\n      .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter().nullSafe())\n      .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)\n      .create();\n    return gson.fromJson(decoded, TelemetryLocalStorage.class);\n  }\n\n  public LocalOnlyIssuesRepository getLocalOnlyIssuesRepository() {\n    return serverUsingJava.getLocalOnlyIssuesRepository();\n  }\n\n  public StorageService getIssueStorageService() {\n    return serverUsingJava.getIssueStorageService();\n  }\n\n  public SonarLintDatabase getSonarLintDatabase() {\n    return serverUsingJava.getDatabase();\n  }\n\n  @Override\n  public CompletableFuture<Void> shutdown() {\n    try {\n      serverUsingRpc.shutdown().get();\n    } catch (Exception e) {\n      throw new IllegalStateException(e);\n    } finally {\n      try {\n        serverLauncher.close();\n      } catch (Exception e) {\n        e.printStackTrace(System.err);\n      }\n      try {\n        clientLauncher.close();\n      } catch (Exception e) {\n        e.printStackTrace(System.err);\n      }\n    }\n    try {\n      this.clientToServerOutputStream.close();\n      this.serverToClientOutputStream.close();\n      this.clientToServerInputStream.close();\n      this.serverToClientInputStream.close();\n    } catch (Exception e) {\n      e.printStackTrace(System.err);\n    }\n    return CompletableFuture.completedFuture(null);\n  }\n\n  public int getEmbeddedServerPort() {\n    return serverUsingJava.getEmbeddedServerPort();\n  }\n\n  private static class JsonRpcSpyInputStream extends PipedInputStream {\n\n    public JsonRpcSpyInputStream(PipedOutputStream outputStream) throws IOException {\n      super(outputStream);\n    }\n\n    @Override\n    public synchronized int read(byte[] b, int off, int len) throws IOException {\n      int readLength = super.read(b, off, len);\n      if (readLength > 0) {\n        System.out.println(\"<-- \" + new String(b, off, readLength, StandardCharsets.UTF_8) + \"\\n\");\n      }\n      return readLength;\n    }\n  }\n\n  private static class JsonRpcSpyOutputStream extends PipedOutputStream {\n    private final StringBuilder mem = new StringBuilder();\n    private int nextContentSize = -1;\n\n    @Override\n    public void write(@NotNull byte[] b) throws IOException {\n      var content = new String(b, StandardCharsets.UTF_8);\n      mem.append(content);\n      flushIfNeeded(content);\n      super.write(b);\n    }\n\n    private void flushIfNeeded(String b) {\n      int cr = mem.indexOf(\"\\r\\n\");\n      if (cr != -1 && nextContentSize < 0) {\n        var contentLength = mem.substring(0, cr);\n        mem.replace(0, cr + 2, \"\");\n        nextContentSize = Integer.parseInt(contentLength.substring(\"Content-Length: \".length()));\n      }\n      if (nextContentSize > 0 && mem.length() >= nextContentSize + 2) {\n        var content = b.trim();\n        var bytes = mem.toString().getBytes(StandardCharsets.UTF_8);\n        var relevantBytes = new byte[nextContentSize];\n        System.arraycopy(bytes, 0, relevantBytes, 0, nextContentSize);\n        // Because of non-ASCII characters, a character might be longer than one byte, which makes Content-Length irrelevant\n        // As a workaround, we can directly extract the String from the byte array\n        var relevantString = new String(relevantBytes, StandardCharsets.UTF_8);\n\n        mem.replace(0, relevantString.length() + 2, \"\");\n        nextContentSize = -1;\n        System.out.println(\"--> \" + content + \"\\n\");\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/junit5/SonarLintTest.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.junit5;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\n\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.METHOD)\n@Test\n@ExtendWith(SonarLintTestHarness.class)\npublic @interface SonarLintTest {\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/junit5/SonarLintTestHarness.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.junit5;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.CompletionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport org.junit.jupiter.api.extension.AfterAllCallback;\nimport org.junit.jupiter.api.extension.AfterEachCallback;\nimport org.junit.jupiter.api.extension.BeforeAllCallback;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.api.extension.ParameterContext;\nimport org.junit.jupiter.api.extension.support.TypeBasedParameterResolver;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintBackendFixture;\nimport org.sonarsource.sonarlint.core.test.utils.SonarLintTestRpcServer;\nimport org.sonarsource.sonarlint.core.test.utils.server.ServerFixture;\n\npublic class SonarLintTestHarness extends TypeBasedParameterResolver<SonarLintTestHarness> implements BeforeAllCallback, AfterEachCallback, AfterAllCallback {\n  private static final Logger LOG = Logger.getLogger(SonarLintTestHarness.class.getName());\n  private static final long SHUTDOWN_TIMEOUT_SECONDS = 10;\n\n  private final List<SonarLintTestRpcServer> backends = new ArrayList<>();\n  private final List<ServerFixture.Server> servers = new ArrayList<>();\n  private boolean isStatic;\n\n  @Override\n  public SonarLintTestHarness resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {\n    return this;\n  }\n\n  @Override\n  public void beforeAll(ExtensionContext context) {\n    isStatic = true;\n  }\n\n  @Override\n  public void afterAll(ExtensionContext context) {\n    if (isStatic) {\n      shutdownAll();\n    }\n  }\n\n  @Override\n  public void afterEach(ExtensionContext context) {\n    if (!isStatic) {\n      shutdownAll();\n    }\n  }\n\n  private void shutdownAll() {\n    // Shutdown backends with timeout and exception handling\n    for (SonarLintTestRpcServer backend : backends) {\n      doShutdown(backend);\n    }\n    backends.clear();\n    // Shutdown servers with exception handling\n    for (ServerFixture.Server server : servers) {\n      try {\n        server.shutdown();\n      } catch (Exception e) {\n        // Log and continue with next server\n        LOG.log(Level.WARNING, \"Failed to shutdown server\", e);\n      }\n    }\n    servers.clear();\n  }\n\n  private static void doShutdown(SonarLintTestRpcServer backend) {\n    try {\n      CompletableFuture<Void> future = backend.shutdown();\n      future.orTimeout(SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS)\n        .exceptionally(ex -> {\n          LOG.log(Level.WARNING, \"Error shutting down backend\", ex);\n          return null;\n        })\n        .join();\n    } catch (CompletionException | IllegalStateException e) {\n      // Log and continue with next backend\n      LOG.log(Level.WARNING, \"Failed to shutdown backend\", e);\n    }\n  }\n\n  public SonarLintBackendFixture.SonarLintBackendBuilder newBackend() {\n    return SonarLintBackendFixture.newBackend(backends::add);\n  }\n\n  public SonarLintBackendFixture.SonarLintClientBuilder newFakeClient() {\n    return SonarLintBackendFixture.newFakeClient();\n  }\n\n  public ServerFixture.SonarQubeServerBuilder newFakeSonarQubeServer() {\n    return ServerFixture.newSonarQubeServer(servers::add);\n  }\n\n  public ServerFixture.SonarQubeServerBuilder newFakeSonarQubeServer(String version) {\n    return ServerFixture.newSonarQubeServer(servers::add, version);\n  }\n\n  public ServerFixture.SonarQubeCloudBuilder newFakeSonarCloudServer() {\n    return ServerFixture.newSonarCloudServer(servers::add);\n  }\n\n  public void addBackend(SonarLintTestRpcServer backend) {\n    backends.add(backend);\n  }\n\n  public void addServer(ServerFixture.Server server) {\n    servers.add(server);\n  }\n\n  public List<SonarLintTestRpcServer> getBackends() {\n    return backends;\n  }\n\n  public List<ServerFixture.Server> getServers() {\n    return servers;\n  }\n\n  public void shutdown(SonarLintTestRpcServer backend) {\n    if (backends.remove(backend)) {\n      doShutdown(backend);\n    }\n  }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/junit5/package-info.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.test.utils.junit5;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/package-info.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.test.utils;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/plugins/Plugin.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.plugins;\n\nimport java.nio.file.Path;\nimport java.util.Set;\nimport org.sonarsource.sonarlint.core.commons.api.SonarLanguage;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.Language;\n\npublic class Plugin {\n  private final String key;\n  private final Set<Language> languages;\n  private final Path path;\n  private final String version;\n  private final String hash;\n\n  private static String getPluginKeyFromLanguage(Language language) {\n    return SonarLanguage.valueOf(language.name()).getPlugin().getKey();\n  }\n\n  public Plugin(Language language, Path path, String version, String hash) {\n    this(Set.of(language), path, version, hash);\n  }\n\n  public Plugin(String key, Language language, Path path, String version, String hash) {\n    this(key, Set.of(language), path, version, hash);\n  }\n\n  public Plugin(Set<Language> languages, Path path, String version, String hash) {\n    this(getPluginKeyFromLanguage(languages.iterator().next()), languages, path, version, hash);\n  }\n\n  public Plugin(String key, Set<Language> languages, Path path, String version, String hash) {\n    this.key = key;\n    this.languages = languages;\n    this.path = path;\n    this.version = version;\n    this.hash = hash;\n  }\n\n  public Set<Language> getLanguages() {\n    return languages;\n  }\n\n  public String getPluginKey() {\n    return key;\n  }\n\n  public Path getPath() {\n    return path;\n  }\n\n  public String getVersion() {\n    return version;\n  }\n\n  public String getHash() {\n    return hash;\n  }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/plugins/SonarPluginBuilder.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.plugins;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.jar.Attributes;\nimport java.util.jar.Manifest;\nimport net.bytebuddy.ByteBuddy;\nimport org.jetbrains.annotations.NotNull;\nimport org.sonar.api.batch.sensor.Sensor;\nimport org.sonar.api.server.rule.RulesDefinition;\nimport org.sonarsource.sonarlint.core.test.utils.plugins.src.DefaultPlugin;\nimport org.sonarsource.sonarlint.core.test.utils.plugins.src.DefaultRulesDefinition;\nimport org.sonarsource.sonarlint.core.test.utils.plugins.src.DefaultSensor;\n\npublic class SonarPluginBuilder {\n\n  /**\n   * @param pluginKey a plugin key from the whitelist, see {@link org.sonarsource.sonarlint.core.commons.api.SonarLanguage#containsPlugin(String)}\n   */\n  public static SonarPluginBuilder newSonarPlugin(String pluginKey) {\n    return new SonarPluginBuilder(pluginKey);\n  }\n\n  private Class<? extends Sensor> sensorClass = DefaultSensor.class;\n\n  private Class<? extends RulesDefinition> rulesDefinitionClass = DefaultRulesDefinition.class;\n  private final String pluginKey;\n\n  private SonarPluginBuilder(String pluginKey) {\n    this.pluginKey = pluginKey;\n  }\n\n  public SonarPluginBuilder withSensor(Class<? extends Sensor> sensorClass) {\n    this.sensorClass = sensorClass;\n    return this;\n  }\n\n  public SonarPluginBuilder withRulesDefinition(Class<? extends RulesDefinition> rulesDefinitionClass) {\n    this.rulesDefinitionClass = rulesDefinitionClass;\n    return this;\n  }\n\n  public Path generate(Path folder) {\n    var pluginPath = folder.resolve(\"my.jar\");\n    try (var pluginClass = new ByteBuddy()\n      .redefine(DefaultPlugin.class)\n      .make();\n      var sensorType = new ByteBuddy()\n        .redefine(sensorClass)\n        .name(DefaultSensor.class.getName())\n        .make();\n      var rulesDefinitionType = new ByteBuddy()\n        .redefine(rulesDefinitionClass)\n        .name(DefaultRulesDefinition.class.getName())\n        .make()) {\n      pluginClass.toJar(pluginPath.toFile(), generateManifest());\n      sensorType.inject(pluginPath.toFile());\n      rulesDefinitionType.inject(pluginPath.toFile());\n    } catch (IOException exception) {\n      throw new IllegalStateException(\"Error when generating the plugin\", exception);\n    }\n\n    return pluginPath;\n  }\n\n  @NotNull\n  private Manifest generateManifest() {\n    var manifest = new Manifest();\n    manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, \"1.0\");\n    manifest.getMainAttributes().put(new Attributes.Name(\"SonarLint-Supported\"), \"true\");\n    manifest.getMainAttributes().put(new Attributes.Name(\"Plugin-Class\"), DefaultPlugin.class.getName());\n    manifest.getMainAttributes().put(new Attributes.Name(\"Plugin-Key\"), pluginKey);\n    manifest.getMainAttributes().put(new Attributes.Name(\"Plugin-Version\"), \"10.0.0\");\n    return manifest;\n  }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/plugins/package-info.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.test.utils.plugins;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/plugins/src/DefaultPlugin.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.plugins.src;\n\nimport org.sonar.api.Plugin;\n\npublic class DefaultPlugin implements Plugin {\n  @Override\n  public void define(Context context) {\n    context.addExtension(DefaultRulesDefinition.class);\n    context.addExtension(DefaultSensor.class);\n  }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/plugins/src/DefaultRulesDefinition.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.plugins.src;\n\nimport org.sonar.api.server.rule.RulesDefinition;\n\npublic class DefaultRulesDefinition implements RulesDefinition {\n  @Override\n  public void define(Context context) {\n    // no implementation needed for the current tests\n  }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/plugins/src/DefaultSensor.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.plugins.src;\n\nimport org.sonar.api.batch.sensor.Sensor;\nimport org.sonar.api.batch.sensor.SensorContext;\nimport org.sonar.api.batch.sensor.SensorDescriptor;\n\npublic class DefaultSensor implements Sensor {\n  @Override\n  public void describe(SensorDescriptor descriptor) {\n    // no implementation needed for the current tests\n\n  }\n\n  @Override\n  public void execute(SensorContext context) {\n    // no implementation needed for the current tests\n  }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/plugins/src/package-info.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.test.utils.plugins.src;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/server/ServerFixture.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.server;\n\nimport com.fasterxml.jackson.annotation.JsonAutoDetect;\nimport com.fasterxml.jackson.annotation.PropertyAccessor;\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.github.tomakehurst.wiremock.WireMockServer;\nimport com.github.tomakehurst.wiremock.matching.AnythingPattern;\nimport com.google.protobuf.Message;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.time.ZoneId;\nimport java.time.ZoneOffset;\nimport java.time.format.DateTimeFormatter;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.function.Consumer;\nimport java.util.function.UnaryOperator;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport javax.annotation.Nullable;\nimport org.apache.commons.lang3.StringUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.sonar.scanner.protocol.Constants;\nimport org.sonar.scanner.protocol.input.ScannerInput;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.RuleKey;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.Version;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.commons.api.TextRange;\nimport org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;\nimport org.sonarsource.sonarlint.core.serverapi.UrlUtils;\nimport org.sonarsource.sonarlint.core.serverapi.hotspot.HotspotApi;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarcloud.ws.Organizations;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Common;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Components;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Hotspots;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Issues;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Measures;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.ProjectBranches;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Qualityprofiles;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Rules;\nimport org.sonarsource.sonarlint.core.serverapi.proto.sonarqube.ws.Settings;\nimport org.sonarsource.sonarlint.core.serverconnection.aicodefix.AiCodeFixFeatureEnablement;\nimport org.sonarsource.sonarlint.core.test.utils.plugins.Plugin;\nimport org.sonarsource.sonarlint.core.test.utils.server.sse.SSEServer;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.equalTo;\nimport static com.github.tomakehurst.wiremock.client.WireMock.get;\nimport static com.github.tomakehurst.wiremock.client.WireMock.jsonResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.post;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlMatching;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;\nimport static java.util.stream.Collectors.groupingBy;\nimport static java.util.stream.Collectors.mapping;\nimport static java.util.stream.Collectors.toList;\nimport static java.util.stream.Collectors.toMap;\nimport static org.sonarsource.sonarlint.core.serverapi.UrlUtils.urlEncode;\nimport static org.sonarsource.sonarlint.core.serverapi.rules.RulesApi.TAINT_REPOS_BY_LANGUAGE;\nimport static org.sonarsource.sonarlint.core.test.utils.ProtobufUtils.protobufBody;\nimport static org.sonarsource.sonarlint.core.test.utils.ProtobufUtils.protobufBodyDelimited;\n\npublic class ServerFixture {\n  public static SonarQubeServerBuilder newSonarQubeServer() {\n    return newSonarQubeServer((Consumer<Server>) null);\n  }\n\n  public static SonarQubeServerBuilder newSonarQubeServer(@Nullable Consumer<Server> onStart) {\n    return newSonarQubeServer(onStart, \"99.9\");\n  }\n\n  public static SonarQubeServerBuilder newSonarQubeServer(String version) {\n    return newSonarQubeServer(null, version);\n  }\n\n  public static SonarQubeServerBuilder newSonarQubeServer(@Nullable Consumer<Server> onStart, String version) {\n    return new SonarQubeServerBuilder(onStart, ServerKind.SONARQUBE, version);\n  }\n\n  public static SonarQubeCloudBuilder newSonarCloudServer() {\n    return newSonarCloudServer(null);\n  }\n\n  public static SonarQubeCloudBuilder newSonarCloudServer(@Nullable Consumer<Server> onStart) {\n    return new SonarQubeCloudBuilder(onStart, ServerKind.SONARCLOUD, \"0.0.0\");\n  }\n\n  private enum ServerKind {\n    SONARQUBE, SONARCLOUD\n  }\n\n  public enum ServerStatus {\n    UP, DOWN\n  }\n\n  public abstract static class AbstractServerBuilder<T extends AbstractServerBuilder<T>> {\n    private final Consumer<Server> onStart;\n    private final ServerKind serverKind;\n    private final String version;\n    protected final Map<String, SonarQubeCloudBuilder.SonarQubeCloudOrganizationBuilder> organizationsByKey = new HashMap<>();\n    protected final Map<String, ServerProjectBuilder> projectByProjectKey = new HashMap<>();\n    protected final Map<String, ServerQualityProfileBuilder> qualityProfilesByKey = new HashMap<>();\n    private final Map<String, ServerPluginBuilder> pluginsByKey = new HashMap<>();\n    private ServerStatus serverStatus = ServerStatus.UP;\n    private ResponseCodesBuilder responseCodes = new ResponseCodesBuilder();\n    protected boolean serverSentEventsEnabled;\n    protected Set<String> aiCodeFixSupportedRules = Set.of();\n    protected Set<String> features = new HashSet<>();\n    private final List<SmartNotifications> smartNotifications = new ArrayList<>();\n    private final Map<String, String> globalSettings = new HashMap<>();\n    protected DopTranslationBuilder dopTranslation = new DopTranslationBuilder();\n\n    protected AbstractServerBuilder(@Nullable Consumer<Server> onStart, ServerKind serverKind, @Nullable String version) {\n      this.onStart = onStart;\n      this.serverKind = serverKind;\n      this.version = version;\n    }\n\n    public T withStatus(ServerStatus status) {\n      serverStatus = status;\n      return (T) this;\n    }\n\n    public T withGlobalSetting(String key, String value) {\n      globalSettings.put(key, value);\n      return (T) this;\n    }\n\n    public T withPlugin(Plugin testPlugin) {\n      return withPlugin(testPlugin.getPluginKey(), plugin -> plugin.withJarPath(testPlugin.getPath()).withHash(testPlugin.getHash()));\n    }\n\n    public T withPlugin(String pluginKey, UnaryOperator<ServerPluginBuilder> pluginBuilder) {\n      var builder = new ServerPluginBuilder();\n      this.pluginsByKey.put(pluginKey, pluginBuilder.apply(builder));\n      return (T) this;\n    }\n\n    public T withAiCodeFixSupportedRules(Set<String> supportedRules) {\n      this.aiCodeFixSupportedRules = supportedRules;\n      return (T) this;\n    }\n\n    public T withResponseCodes(UnaryOperator<ResponseCodesBuilder> responseCodes) {\n      this.responseCodes = new ResponseCodesBuilder();\n      responseCodes.apply(this.responseCodes);\n      return (T) this;\n    }\n\n    public T withSmartNotifications(List<String> projects, String events) {\n      this.smartNotifications.add(new SmartNotifications(projects, events));\n      return (T) this;\n    }\n\n    public T withDopTranslation(UnaryOperator<DopTranslationBuilder> dopTranslationBuilder) {\n      this.dopTranslation = dopTranslationBuilder.apply(new DopTranslationBuilder());\n      return (T) this;\n    }\n\n    public static class DopTranslationBuilder {\n      private final Map<String, ProjectBinding> projectBindings = new HashMap<>();\n\n      public DopTranslationBuilder withProjectBinding(String repositoryUrl, String projectId, String projectKey) {\n        this.projectBindings.put(repositoryUrl, new ProjectBinding(projectId, projectKey));\n        return this;\n      }\n\n      record ProjectBinding(String projectId, String projectKey) {\n      }\n\n    }\n\n    record SmartNotifications(List<String> projects, String events) {\n    }\n\n    public Server start() {\n      var server = new Server(serverKind, serverStatus, version, organizationsByKey, projectByProjectKey, pluginsByKey, qualityProfilesByKey, responseCodes.build(),\n        aiCodeFixSupportedRules, serverSentEventsEnabled, features, smartNotifications, globalSettings, dopTranslation);\n      server.start();\n      if (onStart != null) {\n        onStart.accept(server);\n      }\n      return server;\n    }\n\n    public static class ServerPluginBuilder {\n      private String hash = \"hash\";\n      private Path jarPath;\n      private boolean sonarLintSupported = true;\n\n      public ServerPluginBuilder withHash(String hash) {\n        this.hash = hash;\n        return this;\n      }\n\n      public ServerPluginBuilder withJarPath(Path jarPath) {\n        this.jarPath = jarPath;\n        return this;\n      }\n\n      public ServerPluginBuilder withSonarLintSupported(boolean sonarLintSupported) {\n        this.sonarLintSupported = sonarLintSupported;\n        return this;\n      }\n    }\n\n    public static class AiCodeFixFeatureBuilder {\n      private boolean organizationEligible = true;\n      private AiCodeFixFeatureEnablement enablement = AiCodeFixFeatureEnablement.DISABLED;\n      private List<String> enabledProjectKeys;\n\n      public AiCodeFixFeatureBuilder organizationEligible(boolean organizationEligible) {\n        this.organizationEligible = organizationEligible;\n        return this;\n      }\n\n      public AiCodeFixFeatureBuilder disabled() {\n        this.enablement = AiCodeFixFeatureEnablement.DISABLED;\n        return this;\n      }\n\n      public AiCodeFixFeatureBuilder enabledForProjects(String projectKey) {\n        this.enablement = AiCodeFixFeatureEnablement.ENABLED_FOR_SOME_PROJECTS;\n        this.enabledProjectKeys = List.of(projectKey);\n        return this;\n      }\n\n      public AiCodeFixFeatureBuilder enabledForAllProjects() {\n        this.enablement = AiCodeFixFeatureEnablement.ENABLED_FOR_ALL_PROJECTS;\n        this.enabledProjectKeys = null;\n        return this;\n      }\n\n      public AiCodeFixFeature build() {\n        return new AiCodeFixFeature(enablement);\n      }\n    }\n\n    public static class ResponseCodesBuilder {\n      private int statusCode = 200;\n      private int issueTransitionStatusCode = 200;\n      private int addCommentStatusCode = 200;\n\n      public ResponseCodesBuilder withStatusCode(int statusCode) {\n        this.statusCode = statusCode;\n        return this;\n      }\n\n      public ResponseCodesBuilder withIssueTransitionStatusCode(int issueTransitionStatusCode) {\n        this.issueTransitionStatusCode = issueTransitionStatusCode;\n        return this;\n      }\n\n      public ResponseCodesBuilder withAddCommentStatusCode(int addCommentStatusCode) {\n        this.addCommentStatusCode = addCommentStatusCode;\n        return this;\n      }\n\n      public ResponseCodes build() {\n        return new ResponseCodes(this.statusCode, this.issueTransitionStatusCode, this.addCommentStatusCode);\n      }\n    }\n\n    public record AiCodeFixFeature(AiCodeFixFeatureEnablement enablement) {\n    }\n\n    public record ResponseCodes(int statusCode, int issueTransitionStatusCode, int addCommentStatusCode) {\n    }\n\n    public static class ServerProjectBuilder {\n      private final String organizationKey;\n      private final Map<String, ServerProjectBranchBuilder> branchesByName = new HashMap<>();\n      private String mainBranchName = \"main\";\n      private final Map<String, ServerProjectPullRequestBuilder> pullRequestsByName = new HashMap<>();\n      private final List<String> qualityProfileKeys = new ArrayList<>();\n      private final List<String> relativeFilePaths = new ArrayList<>();\n      private String name = \"MyProject\";\n      private String projectName;\n      private AiCodeFixSuggestionBuilder aiCodeFixSuggestion;\n      private boolean aiCodeFixEnabled;\n      private final DopTranslationBuilder dopTranslation;\n      private final String projectKey;\n      private String projectId;\n\n      private ServerProjectBuilder() {\n        this(null, null, null);\n      }\n\n      private ServerProjectBuilder(@Nullable String organizationKey, @Nullable DopTranslationBuilder dopTranslation, @Nullable String projectKey) {\n        this.organizationKey = organizationKey;\n        this.dopTranslation = dopTranslation;\n        this.projectKey = projectKey;\n        this.branchesByName.put(mainBranchName, new ServerProjectBranchBuilder());\n      }\n\n      public ServerProjectBuilder withMainBranch(String branchName) {\n        this.mainBranchName = branchName;\n        return withBranch(branchName, builder -> builder);\n      }\n\n      public ServerProjectBuilder withBranch(String branchName) {\n        return withBranch(branchName, builder -> builder);\n      }\n\n      public ServerProjectBuilder withProjectName(String projectName) {\n        this.projectName = projectName;\n        return this;\n      }\n\n      public ServerProjectBuilder withBranch(@Nullable String branchName, UnaryOperator<ServerProjectBranchBuilder> branchBuilder) {\n        var builder = new ServerProjectBranchBuilder();\n        this.branchesByName.put(branchName, branchBuilder.apply(builder));\n        return this;\n      }\n\n      public ServerProjectBuilder withPullRequest(String pullRequestNumber, UnaryOperator<ServerProjectPullRequestBuilder> pullRequestBuilder) {\n        var builder = new ServerProjectPullRequestBuilder();\n        this.pullRequestsByName.put(pullRequestNumber, pullRequestBuilder.apply(builder));\n        return this;\n      }\n\n      public ServerProjectBuilder withDefaultBranch(UnaryOperator<ServerProjectBranchBuilder> branchBuilder) {\n        return withBranch(null, branchBuilder);\n      }\n\n      public ServerProjectBuilder withName(String name) {\n        this.name = name;\n        return this;\n      }\n\n      public ServerProjectBuilder withQualityProfile(String qualityProfileKey) {\n        this.qualityProfileKeys.add(qualityProfileKey);\n        return this;\n      }\n\n      public ServerProjectBuilder withFile(String relativeFilePath) {\n        this.relativeFilePaths.add(relativeFilePath);\n        return this;\n      }\n\n      public ServerProjectBuilder withAiCodeFixSuggestion(UnaryOperator<AiCodeFixSuggestionBuilder> aiCodeFixSuggestionBuilder) {\n        this.aiCodeFixSuggestion = new AiCodeFixSuggestionBuilder();\n        aiCodeFixSuggestionBuilder.apply(aiCodeFixSuggestion);\n        return this;\n      }\n\n      public ServerProjectBuilder withAiCodeFixEnabled(boolean enabled) {\n        this.aiCodeFixEnabled = enabled;\n        return this;\n      }\n\n      public ServerProjectBuilder withId(UUID id) {\n        this.projectId = id.toString();\n        return this;\n      }\n\n      public ServerProjectBuilder withBinding(String repositoryUrl) {\n        if (this.projectId == null) {\n          throw new IllegalStateException(\"withBinding() requires project id to be set via withId(UUID) beforehand\");\n        }\n        this.dopTranslation.withProjectBinding(repositoryUrl, this.projectId, this.projectKey);\n        return this;\n      }\n\n      public record ServerDependencyRisk(\n        String id,\n        String type,\n        String severity,\n        String quality,\n        String status,\n        String packageName,\n        String packageVersion,\n        @Nullable String vulnerabilityId,\n        @Nullable String cvssScore,\n        List<String> transitions) {\n      }\n\n      public static class ServerProjectBranchBuilder {\n        protected final Collection<ServerHotspot> hotspots = new ArrayList<>();\n        protected final Collection<ServerIssue> issues = new ArrayList<>();\n        private final Collection<ServerIssue> taintIssues = new ArrayList<>();\n        private final Collection<ServerDependencyRisk> dependencyRisks = new ArrayList<>();\n        protected final Map<String, ServerSourceFileBuilder> sourceFileByComponentKey = new HashMap<>();\n\n        public ServerProjectBranchBuilder withHotspot(String hotspotKey) {\n          return withHotspot(hotspotKey, UnaryOperator.identity());\n        }\n\n        public ServerProjectBranchBuilder withHotspot(String hotspotKey, UnaryOperator<HotspotBuilder> hotspotBuilder) {\n          var builder = new HotspotBuilder();\n          hotspotBuilder.apply(builder);\n          this.hotspots.add(builder.build(hotspotKey));\n          return this;\n        }\n\n        public ServerProjectBranchBuilder withIssue(String issueKey) {\n          return withIssue(issueKey, \"ruleKey\", \"message\", \"author\", \"filePath\", \"OPEN\", null, Instant.now(),\n            new TextRange(1, 2, 3, 4));\n        }\n\n        public ServerProjectBranchBuilder withIssue(String issueKey, String ruleKey, String message, String author, String filePath,\n          String status, @Nullable String resolution, Instant creationDate, TextRange textRange) {\n          this.issues.add(new ServerIssue(issueKey, ruleKey, message, author, filePath, status, resolution, creationDate, textRange, RuleType.BUG));\n          return this;\n        }\n\n        public ServerProjectBranchBuilder withIssue(String issueKey, String ruleKey, String message, String author, String filePath,\n          String hash, Constants.Severity severity, RuleType ruleType, String status, String resolution, Instant creationDate, TextRange textRange) {\n          this.issues.add(new ServerIssue(issueKey, ruleKey, message, author, filePath, status, resolution, creationDate, textRange, ruleType, hash, severity));\n          return this;\n        }\n\n        public ServerProjectBranchBuilder withIssue(String issueKey, String ruleKey, String message, String author, String filePath,\n          String hash, Constants.Severity severity, RuleType ruleType, String status, String resolution, Instant creationDate, TextRange textRange,\n          Map<SoftwareQuality, ImpactSeverity> impacts) {\n          this.issues.add(new ServerIssue(issueKey, ruleKey, message, author, filePath, status, resolution, creationDate, textRange, ruleType, hash, severity, impacts));\n          return this;\n        }\n\n        public ServerProjectBranchBuilder withSourceFile(String componentKey, UnaryOperator<ServerSourceFileBuilder> sourceFileBuilder) {\n          var builder = new ServerSourceFileBuilder();\n          this.sourceFileByComponentKey.put(componentKey, sourceFileBuilder.apply(builder));\n          return this;\n        }\n\n        public ServerProjectBranchBuilder withTaintIssue(String issueKey, String ruleKey, String message, String author, String filePath,\n          String status, String resolution, Instant introductionDate, TextRange textRange, RuleType ruleType) {\n          this.taintIssues.add(new ServerIssue(issueKey, ruleKey, message, author, filePath, status, resolution, introductionDate, textRange, ruleType));\n          return this;\n        }\n\n        public ServerProjectBranchBuilder withDependencyRisk(ServerDependencyRisk dependencyRisk) {\n          this.dependencyRisks.add(dependencyRisk);\n          return this;\n        }\n\n        private static class ServerHotspot {\n          private final String hotspotKey;\n          private final String ruleKey;\n          private final String message;\n          private final String author;\n          private final String filePath;\n          private final HotspotReviewStatus status;\n          private final TextRange textRange;\n          private final boolean canChangeStatus;\n          private final Instant creationDate;\n          private final VulnerabilityProbability vulnerabilityProbability;\n\n          private ServerHotspot(String hotspotKey, String ruleKey, String message, String author, String filePath, HotspotReviewStatus status, TextRange textRange,\n            boolean canChangeStatus, Instant creationDate, VulnerabilityProbability vulnerabilityProbability) {\n            this.hotspotKey = hotspotKey;\n            this.ruleKey = ruleKey;\n            this.message = message;\n            this.author = author;\n            this.filePath = filePath;\n            this.status = status;\n            this.textRange = textRange;\n            this.canChangeStatus = canChangeStatus;\n            this.creationDate = creationDate;\n            this.vulnerabilityProbability = vulnerabilityProbability;\n          }\n\n          public String getFilePath() {\n            return filePath;\n          }\n        }\n\n        protected static class ServerIssue {\n          private final String issueKey;\n          private final String ruleKey;\n          private final String message;\n          private final String author;\n          private final String filePath;\n          private final String status;\n          private final String resolution;\n          private final Instant introductionDate;\n          private final TextRange textRange;\n          private final RuleType ruleType;\n          private String hash;\n          private Constants.Severity severity;\n          private boolean manualSeverity = false;\n          private Map<SoftwareQuality, ImpactSeverity> impacts;\n\n          private ServerIssue(String issueKey, String ruleKey, String message, String author, String filePath, String status,\n            String resolution, Instant introductionDate, TextRange textRange, RuleType ruleType, String hash, Constants.Severity severity,\n            Map<SoftwareQuality, ImpactSeverity> impacts) {\n            this(issueKey, ruleKey, message, author, filePath, status, resolution, introductionDate, textRange, ruleType, hash, severity);\n            this.impacts = impacts;\n          }\n\n          private ServerIssue(String issueKey, String ruleKey, String message, String author, String filePath, String status,\n            @Nullable String resolution, Instant introductionDate, TextRange textRange, RuleType ruleType, String hash, Constants.Severity severity) {\n            this(issueKey, ruleKey, message, author, filePath, status, resolution, introductionDate, textRange, ruleType);\n            this.hash = hash;\n            this.severity = severity;\n            this.manualSeverity = true;\n          }\n\n          private ServerIssue(String issueKey, String ruleKey, String message, String author, String filePath, String status,\n            @Nullable String resolution, Instant introductionDate, TextRange textRange, RuleType ruleType) {\n            this.issueKey = issueKey;\n            this.ruleKey = ruleKey;\n            this.message = message;\n            this.author = author;\n            this.filePath = filePath;\n            this.status = status;\n            this.resolution = resolution;\n            this.introductionDate = introductionDate;\n            this.textRange = textRange;\n            this.ruleType = ruleType;\n            this.hash = \"hash\";\n            this.severity = Constants.Severity.BLOCKER;\n            this.impacts = Collections.emptyMap();\n          }\n\n          public String getFilePath() {\n            return filePath;\n          }\n\n          public boolean isResolved() {\n            return StringUtils.isNotEmpty(resolution);\n          }\n        }\n      }\n\n      public static class AiCodeFixSuggestionBuilder {\n        private UUID id = UUID.randomUUID();\n        private String explanation = \"default\";\n        private final List<AiCodeFixChange> changes = new ArrayList<>();\n\n        public AiCodeFixSuggestionBuilder withId(UUID id) {\n          this.id = id;\n          return this;\n        }\n\n        public AiCodeFixSuggestionBuilder withExplanation(String explanation) {\n          this.explanation = explanation;\n          return this;\n        }\n\n        public AiCodeFixSuggestionBuilder withChange(int startLine, int endLine, String newCode) {\n          this.changes.add(new AiCodeFixChange(startLine, endLine, newCode));\n          return this;\n        }\n\n        public AiCodeFix build() {\n          return new AiCodeFix(id, explanation, changes);\n        }\n      }\n\n      public record AiCodeFix(UUID id, String explanation, List<AiCodeFixChange> changes) {\n      }\n\n      public record AiCodeFixChange(int startLine, int endLine, String newCode) {\n\n      }\n\n      public static class ServerProjectPullRequestBuilder extends ServerProjectBranchBuilder {\n      }\n\n      public static class ServerSourceFileBuilder {\n        private String code;\n\n        public ServerSourceFileBuilder withCode(String sourceCode) {\n          this.code = sourceCode;\n          return this;\n        }\n      }\n\n      public static class HotspotBuilder {\n        private String ruleKey = \"ruleKey\";\n        private String message = \"message\";\n        private String author = \"author\";\n        private String filePath = \"filePath\";\n        private HotspotReviewStatus reviewStatus = HotspotReviewStatus.TO_REVIEW;\n        private TextRange textRange = new TextRange(1, 2, 3, 4);\n        private boolean canChangeStatus = true;\n        private Instant creationDate = Instant.now();\n        private VulnerabilityProbability vulnerabilityProbability = VulnerabilityProbability.MEDIUM;\n\n        public HotspotBuilder withRuleKey(String ruleKey) {\n          this.ruleKey = ruleKey;\n          return this;\n        }\n\n        public HotspotBuilder withMessage(String message) {\n          this.message = message;\n          return this;\n        }\n\n        public HotspotBuilder withAuthor(String author) {\n          this.author = author;\n          return this;\n        }\n\n        public HotspotBuilder withFilePath(String filePath) {\n          this.filePath = filePath;\n          return this;\n        }\n\n        public HotspotBuilder withStatus(HotspotReviewStatus status) {\n          this.reviewStatus = status;\n          return this;\n        }\n\n        public HotspotBuilder withTextRange(TextRange textRange) {\n          this.textRange = textRange;\n          return this;\n        }\n\n        public HotspotBuilder withoutStatusChangePermission() {\n          this.canChangeStatus = false;\n          return this;\n        }\n\n        public HotspotBuilder withCreationDate(Instant creationDate) {\n          this.creationDate = creationDate;\n          return this;\n        }\n\n        public HotspotBuilder withVulnerabilityProbability(VulnerabilityProbability vulnerabilityProbability) {\n          this.vulnerabilityProbability = vulnerabilityProbability;\n          return this;\n        }\n\n        public ServerProjectBranchBuilder.ServerHotspot build(String hotspotKey) {\n          return new ServerProjectBranchBuilder.ServerHotspot(hotspotKey, ruleKey, message, author, filePath, reviewStatus, textRange, canChangeStatus, creationDate,\n            vulnerabilityProbability);\n        }\n      }\n    }\n\n    public static class ServerQualityProfileBuilder {\n      private final String organizationKey;\n      private String languageKey = \"lang\";\n      private final Map<String, ServerActiveRuleBuilder> activeRulesByKey = new HashMap<>();\n\n      public ServerQualityProfileBuilder(@Nullable String organizationKey) {\n        this.organizationKey = organizationKey;\n      }\n\n      public ServerQualityProfileBuilder withLanguage(String languageKey) {\n        this.languageKey = languageKey;\n        return this;\n      }\n\n      public ServerQualityProfileBuilder withActiveRule(String ruleKey, UnaryOperator<ServerActiveRuleBuilder> activeRuleBuilder) {\n        var builder = new ServerActiveRuleBuilder();\n        this.activeRulesByKey.put(ruleKey, activeRuleBuilder.apply(builder));\n        return this;\n      }\n\n      public static class ServerActiveRuleBuilder {\n        private IssueSeverity issueSeverity = IssueSeverity.CRITICAL;\n\n        public ServerActiveRuleBuilder withSeverity(IssueSeverity issueSeverity) {\n          this.issueSeverity = issueSeverity;\n          return this;\n        }\n      }\n    }\n\n  }\n\n  public static class SonarQubeServerBuilder extends AbstractServerBuilder<SonarQubeServerBuilder> {\n\n    public SonarQubeServerBuilder(@org.jetbrains.annotations.Nullable Consumer<Server> onStart, ServerKind serverKind, @Nullable String version) {\n      super(onStart, serverKind, version);\n    }\n\n    public SonarQubeServerBuilder withProject(String projectKey) {\n      return withProject(projectKey, UnaryOperator.identity());\n    }\n\n    public SonarQubeServerBuilder withProject(String projectKey, UnaryOperator<ServerProjectBuilder> projectBuilder) {\n      var builder = new ServerProjectBuilder(null, this.dopTranslation, projectKey);\n      this.projectByProjectKey.put(projectKey, projectBuilder.apply(builder));\n      return this;\n    }\n\n    public SonarQubeServerBuilder withQualityProfile(String qualityProfileKey) {\n      return withQualityProfile(qualityProfileKey, UnaryOperator.identity());\n    }\n\n    public SonarQubeServerBuilder withQualityProfile(String qualityProfileKey, UnaryOperator<ServerQualityProfileBuilder> qualityProfileBuilder) {\n      var builder = new ServerQualityProfileBuilder(null);\n      this.qualityProfilesByKey.put(qualityProfileKey, qualityProfileBuilder.apply(builder));\n      return this;\n    }\n\n    public SonarQubeServerBuilder withServerSentEventsEnabled() {\n      this.serverSentEventsEnabled = true;\n      return this;\n    }\n\n    public SonarQubeServerBuilder withFeature(String featureName) {\n      this.features.add(featureName);\n      return this;\n    }\n  }\n\n  public static class SonarQubeCloudBuilder extends AbstractServerBuilder<SonarQubeCloudBuilder> {\n\n    public SonarQubeCloudBuilder(@org.jetbrains.annotations.Nullable Consumer<Server> onStart, ServerKind serverKind, @Nullable String version) {\n      super(onStart, serverKind, version);\n    }\n\n    public SonarQubeCloudBuilder withOrganization(String organizationKey) {\n      return withOrganization(organizationKey, UnaryOperator.identity());\n    }\n\n    public SonarQubeCloudBuilder withOrganization(String organizationKey, UnaryOperator<SonarQubeCloudOrganizationBuilder> organizationBuilder) {\n      var builder = new SonarQubeCloudOrganizationBuilder(organizationKey, qualityProfilesByKey, projectByProjectKey, this.dopTranslation);\n      this.organizationsByKey.put(organizationKey, organizationBuilder.apply(builder));\n      return this;\n    }\n\n    public static class SonarQubeCloudOrganizationBuilder {\n      private final String organizationKey;\n      private final Map<String, ServerQualityProfileBuilder> qualityProfilesByKey;\n      private final Map<String, ServerProjectBuilder> projectByProjectKey;\n      public String name = \"OrgName\";\n      public String description = \"OrgDescription\";\n      private final String id = UUID.randomUUID().toString();\n      private final UUID uuidV4 = UUID.randomUUID();\n      private AbstractServerBuilder.AiCodeFixFeatureBuilder aiCodeFixFeature = new AiCodeFixFeatureBuilder();\n      private boolean isCurrentUserMember = true;\n      private final DopTranslationBuilder dopTranslation;\n\n      public SonarQubeCloudOrganizationBuilder(String organizationKey, Map<String, ServerQualityProfileBuilder> qualityProfilesByKey,\n        Map<String, ServerProjectBuilder> projectByProjectKey, DopTranslationBuilder dopTranslation) {\n        this.organizationKey = organizationKey;\n        this.qualityProfilesByKey = qualityProfilesByKey;\n        this.projectByProjectKey = projectByProjectKey;\n        this.dopTranslation = dopTranslation;\n      }\n\n      public SonarQubeCloudOrganizationBuilder withQualityProfile(String qualityProfileKey, UnaryOperator<ServerQualityProfileBuilder> qualityProfileBuilder) {\n        var builder = new ServerQualityProfileBuilder(organizationKey);\n        this.qualityProfilesByKey.put(qualityProfileKey, qualityProfileBuilder.apply(builder));\n        return this;\n      }\n\n      public SonarQubeCloudOrganizationBuilder withProject(String projectKey) {\n        return withProject(projectKey, UnaryOperator.identity());\n      }\n\n      public SonarQubeCloudOrganizationBuilder withProject(String projectKey, UnaryOperator<ServerProjectBuilder> projectBuilder) {\n        var builder = new ServerProjectBuilder(organizationKey, dopTranslation, projectKey);\n        this.projectByProjectKey.put(projectKey, projectBuilder.apply(builder));\n        return this;\n      }\n\n      public SonarQubeCloudOrganizationBuilder withAiCodeFixFeature(UnaryOperator<AiCodeFixFeatureBuilder> aiCodeFixBuilder) {\n        this.aiCodeFixFeature = new AiCodeFixFeatureBuilder();\n        aiCodeFixBuilder.apply(aiCodeFixFeature);\n        return this;\n      }\n\n      public SonarQubeCloudOrganizationBuilder withCurrentUserMember(boolean isCurrentUserMember) {\n        this.isCurrentUserMember = isCurrentUserMember;\n        return this;\n      }\n    }\n  }\n\n  public static class Server {\n\n    private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern(\"yyyy-MM-dd'T'HH:mm:ssZ\").withZone(ZoneId.from(ZoneOffset.UTC));\n    public static final String API_COMPONENTS_SHOW_PROTOBUF_COMPONENT = \"/api/components/show.protobuf?component=\";\n    public static final String CONTENT_TYPE = \"Content-Type\";\n    public static final String APPLICATION_JSON = \"application/json\";\n\n    private final WireMockServer mockServer = new WireMockServer(options().dynamicPort());\n\n    private final ServerKind serverKind;\n    private final ServerStatus serverStatus;\n    @Nullable\n    private final Version version;\n    private final Map<String, SonarQubeCloudBuilder.SonarQubeCloudOrganizationBuilder> organizationsByKey;\n    private final Map<String, AbstractServerBuilder.ServerProjectBuilder> projectsByProjectKey;\n    private final Map<String, AbstractServerBuilder.ServerPluginBuilder> pluginsByKey;\n    private final Map<String, AbstractServerBuilder.ServerQualityProfileBuilder> qualityProfilesByKey;\n    private final AbstractServerBuilder.ResponseCodes responseCodes;\n    private final List<AbstractServerBuilder.SmartNotifications> smartNotifications;\n    private final Map<String, String> globalSettings;\n    private final Set<String> aiCodeFixSupportedRules;\n    private final boolean serverSentEventsEnabled;\n    private final AbstractServerBuilder.DopTranslationBuilder dopTranslation;\n    private final Set<String> features;\n    private SSEServer sseServer;\n\n    public Server(ServerKind serverKind, ServerStatus serverStatus, @Nullable String version,\n      Map<String, SonarQubeCloudBuilder.SonarQubeCloudOrganizationBuilder> organizationsByKey, Map<String, AbstractServerBuilder.ServerProjectBuilder> projectsByProjectKey,\n      Map<String, AbstractServerBuilder.ServerPluginBuilder> pluginsByKey, Map<String, AbstractServerBuilder.ServerQualityProfileBuilder> qualityProfilesByKey,\n      AbstractServerBuilder.ResponseCodes responseCodes, Set<String> aiCodeFixSupportedRules, boolean serverSentEventsEnabled, Set<String> features,\n      List<AbstractServerBuilder.SmartNotifications> smartNotifications, Map<String, String> globalSettings, AbstractServerBuilder.DopTranslationBuilder dopTranslation) {\n      this.serverKind = serverKind;\n      this.serverStatus = serverStatus;\n      this.version = version != null ? Version.create(version) : null;\n      this.organizationsByKey = organizationsByKey;\n      this.projectsByProjectKey = projectsByProjectKey;\n      this.pluginsByKey = pluginsByKey;\n      this.qualityProfilesByKey = qualityProfilesByKey;\n      this.responseCodes = responseCodes;\n      this.aiCodeFixSupportedRules = aiCodeFixSupportedRules;\n      this.serverSentEventsEnabled = serverSentEventsEnabled;\n      this.features = features;\n      this.smartNotifications = smartNotifications;\n      this.globalSettings = globalSettings;\n      this.dopTranslation = dopTranslation;\n    }\n\n    public void start() {\n      mockServer.start();\n      if (serverSentEventsEnabled) {\n        sseServer = new SSEServer();\n        sseServer.start();\n      }\n      registerWebApiResponses();\n    }\n\n    private void registerWebApiResponses() {\n      registerSystemApiResponses();\n      if (serverStatus == ServerStatus.UP) {\n        registerPluginsApiResponses();\n        registerQualityProfilesApiResponses();\n        registerRulesApiResponses();\n        registerProjectBranchesApiResponses();\n        registerHotspotsApiResponses();\n        registerIssuesApiResponses();\n        registerSourceApiResponses();\n        registerDevelopersApiResponses();\n        registerMeasuresApiResponses();\n        registerComponentsApiResponses();\n        registerSettingsApiResponses();\n        registerComponentApiResponses();\n        registerFixSuggestionsApiResponses();\n        registerOrganizationApiResponses();\n        registerPushApiResponses();\n        registerFeaturesApiResponses();\n        registerScaApiResponses();\n        registerUsersApiResponses();\n        registerDopTranslationApiResponses();\n      }\n    }\n\n    private void registerComponentApiResponses() {\n      projectsByProjectKey.forEach((projectKey, project) -> {\n        if (project.projectName != null) {\n          mockServer.stubFor(get(API_COMPONENTS_SHOW_PROTOBUF_COMPONENT + UrlUtils.urlEncode(projectKey))\n            .willReturn(aResponse().withResponseBody(protobufBody(\n              Components.ShowWsResponse.newBuilder()\n                .setComponent(Components.Component.newBuilder().setKey(projectKey).setName(project.projectName).build()).build()))));\n        }\n      });\n    }\n\n    public void registerSystemApiResponses() {\n      // API is public, so it can't return 401 or 403 status\n      var statusesToSkip = Set.of(401, 403);\n      var status = statusesToSkip.contains(responseCodes.statusCode) ? 200 : responseCodes.statusCode;\n      mockServer.stubFor(get(\"/api/system/status\")\n        .willReturn(aResponse().withStatus(status).withBody(\"{\\\"id\\\": \\\"20160308094653\\\",\\\"version\\\": \\\"\" + version + \"\\\",\\\"status\\\": \" +\n          \"\\\"\" + serverStatus + \"\\\"}\")));\n    }\n\n    private void registerPluginsApiResponses() {\n      registerPluginsInstalledResponses();\n      registerPluginsDownloadResponses();\n    }\n\n    private void registerPluginsInstalledResponses() {\n      mockServer.stubFor(get(\"/api/plugins/installed\")\n        .willReturn(aResponse().withStatus(responseCodes.statusCode).withBody(\"{\\\"plugins\\\": [\" +\n          pluginsByKey.entrySet().stream().map(\n            entry -> {\n              var pluginKey = entry.getKey();\n              return \"{\\\"key\\\": \\\"\" + pluginKey + \"\\\", \" +\n                \"\\\"hash\\\": \\\"\" + entry.getValue().hash + \"\\\", \" +\n                \"\\\"filename\\\": \\\"\" + entry.getValue().jarPath.getFileName() + \"\\\", \" +\n                \"\\\"sonarLintSupported\\\": \" + entry.getValue().sonarLintSupported + \"}\";\n            })\n            .collect(Collectors.joining(\", \"))\n          + \"]}\")));\n    }\n\n    private void registerPluginsDownloadResponses() {\n      pluginsByKey.forEach((pluginKey, plugin) -> {\n        try {\n          var pluginContent = Files.exists(plugin.jarPath) ? Files.readAllBytes(plugin.jarPath) : new byte[0];\n          mockServer.stubFor(get(\"/api/plugins/download?plugin=\" + pluginKey)\n            .willReturn(aResponse().withStatus(responseCodes.statusCode).withBody(pluginContent)));\n        } catch (IOException e) {\n          throw new RuntimeException(e);\n        }\n      });\n    }\n\n    private void registerQualityProfilesApiResponses() {\n      projectsByProjectKey.forEach((projectKey, project) -> {\n        var urlBuilder = new StringBuilder(\"/api/qualityprofiles/search.protobuf?project=\" + projectKey);\n        if (project.organizationKey != null) {\n          urlBuilder.append(\"&organization=\").append(project.organizationKey);\n        }\n        mockServer.stubFor(get(urlBuilder.toString())\n          .willReturn(aResponse().withStatus(responseCodes.statusCode).withResponseBody(protobufBody(Qualityprofiles.SearchWsResponse.newBuilder().addAllProfiles(\n            project.qualityProfileKeys.stream().map(qualityProfileKey -> {\n              var qualityProfile = qualityProfilesByKey.get(qualityProfileKey);\n              return Qualityprofiles.SearchWsResponse.QualityProfile.newBuilder()\n                .setKey(qualityProfileKey)\n                .setLanguage(qualityProfile.languageKey)\n                .setLanguageName(qualityProfile.languageKey)\n                .setName(\"Quality Profile\")\n                .setRulesUpdatedAt(Instant.now().toString())\n                .setUserUpdatedAt(Instant.now().toString())\n                .setIsDefault(true)\n                .setActiveRuleCount(qualityProfile.activeRulesByKey.size())\n                .build();\n            }).toList()).build()))));\n      });\n    }\n\n    private void registerRulesApiResponses() {\n      qualityProfilesByKey.forEach((qualityProfileKey, qualityProfile) -> {\n        var url = \"/api/rules/search.protobuf?qprofile=\" + qualityProfileKey;\n        if (qualityProfile.organizationKey != null) {\n          url += \"&organization=\" + qualityProfile.organizationKey;\n        }\n        url += \"&activation=true&f=templateKey,actives&types=CODE_SMELL,BUG,VULNERABILITY,SECURITY_HOTSPOT&s=key&ps=500&p=1\";\n        mockServer.stubFor(get(url)\n          .willReturn(aResponse().withStatus(responseCodes.statusCode).withResponseBody(protobufBody(Rules.SearchResponse.newBuilder()\n            .addAllRules(qualityProfile.activeRulesByKey.entrySet().stream().map(entry -> Rules.Rule.newBuilder()\n              .setKey(entry.getKey())\n              .setSeverity(entry.getValue().issueSeverity.name())\n              .build()).toList())\n            .setActives(Rules.Actives.newBuilder()\n              .putAllActives(qualityProfile.activeRulesByKey.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> Rules.ActiveList.newBuilder()\n                .addActiveList(Rules.Active.newBuilder().setSeverity(entry.getValue().issueSeverity.name()).build()).build())))\n              .build())\n            .setPaging(Common.Paging.newBuilder().setTotal(qualityProfile.activeRulesByKey.size()).build())\n            .build()))));\n      });\n      var taintActiveRulesByKey = qualityProfilesByKey.values().stream().flatMap(qp -> qp.activeRulesByKey.entrySet().stream())\n        .filter(entry -> TAINT_REPOS_BY_LANGUAGE.containsValue(entry.getKey())).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));\n      qualityProfilesByKey.values().stream().map(qp -> qp.organizationKey).collect(Collectors.toSet()).forEach(organizationKey -> {\n        var url = \"/api/rules/search.protobuf?repositories=roslyn.sonaranalyzer.security.cs,javasecurity,jssecurity,phpsecurity,pythonsecurity,tssecurity\";\n        if (organizationKey != null) {\n          url += \"&organization=\" + organizationKey;\n        }\n        url += \"&f=repo&s=key&ps=500&p=1\";\n        mockServer.stubFor(get(url)\n          .willReturn(aResponse().withStatus(responseCodes.statusCode).withResponseBody(protobufBody(Rules.SearchResponse.newBuilder()\n            .addAllRules(taintActiveRulesByKey.entrySet().stream().map(entry -> Rules.Rule.newBuilder()\n              .setKey(entry.getKey())\n              .setSeverity(entry.getValue().issueSeverity.name())\n              .build()).toList())\n            .setActives(Rules.Actives.newBuilder()\n              .putAllActives(taintActiveRulesByKey.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> Rules.ActiveList.newBuilder()\n                .addActiveList(Rules.Active.newBuilder().setSeverity(entry.getValue().issueSeverity.name()).build()).build())))\n              .build())\n            .setPaging(Common.Paging.newBuilder().setTotal(taintActiveRulesByKey.size()).build())\n            .build()))));\n      });\n\n      var activeRules = qualityProfilesByKey.values().stream().flatMap(qp -> qp.activeRulesByKey.entrySet().stream()).toList();\n      activeRules.forEach(entry -> {\n        var ruleKey = entry.getKey();\n        var rule = entry.getValue();\n        var rulesShowUrl = \"/api/rules/show.protobuf?key=\" + ruleKey;\n        mockServer.stubFor(get(rulesShowUrl)\n          .willReturn(aResponse().withStatus(responseCodes.statusCode).withResponseBody(protobufBody(Rules.ShowResponse.newBuilder()\n            .setRule(Rules.Rule.newBuilder()\n              .setKey(ruleKey)\n              .setName(\"fakeName\")\n              .setLang(\"java\")\n              .setHtmlNote(\"htmlNote\")\n              .setDescriptionSections(Rules.Rule.DescriptionSections.newBuilder().build())\n              .setCleanCodeAttribute(Common.CleanCodeAttribute.CONVENTIONAL)\n              .setEducationPrinciples(Rules.Rule.EducationPrinciples.newBuilder().build())\n              .setSeverity(rule.issueSeverity.name())\n              .setType(Common.RuleType.BUG)\n              .setHtmlDesc(\"htmlDesc\")\n              .setImpacts(Rules.Rule.Impacts.newBuilder().build())\n              .build())\n            .build()))));\n      });\n    }\n\n    private void registerProjectBranchesApiResponses() {\n      projectsByProjectKey.forEach((projectKey, project) -> mockServer.stubFor(get(\"/api/project_branches/list.protobuf?project=\" + projectKey)\n        .willReturn(aResponse().withStatus(responseCodes.statusCode).withResponseBody(protobufBody(ProjectBranches.ListWsResponse.newBuilder()\n          .addAllBranches(project.branchesByName.keySet().stream()\n            .filter(Objects::nonNull)\n            .map(branchName -> ProjectBranches.Branch.newBuilder().setName(branchName).setIsMain(project.mainBranchName.equals(branchName)).setType(Common.BranchType.LONG).build())\n            .toList())\n          .build())))));\n    }\n\n    private void registerHotspotsApiResponses() {\n      if (version != null && version.satisfiesMinRequirement(HotspotApi.MIN_SQ_VERSION_SUPPORTING_PULL)) {\n        registerApiHotspotsPullResponses();\n      } else {\n        registerApiHotspotSearchResponses();\n      }\n      registerHotspotsShowApiResponses();\n      registerHotspotsStatusChangeApiResponses();\n    }\n\n    private void registerApiHotspotSearchResponses() {\n      projectsByProjectKey.forEach((projectKey, project) -> project.branchesByName.forEach((branchName, branch) -> {\n        var messagesPerFilePath = branch.hotspots.stream()\n          .collect(groupingBy(AbstractServerBuilder.ServerProjectBuilder.ServerProjectBranchBuilder.ServerHotspot::getFilePath,\n            mapping(hotspot -> {\n              var builder = Hotspots.SearchWsResponse.Hotspot.newBuilder()\n                .setKey(hotspot.hotspotKey)\n                .setComponent(projectKey + \":\" + hotspot.filePath)\n                .setRuleKey(hotspot.ruleKey)\n                .setMessage(hotspot.message)\n                .setTextRange(Common.TextRange.newBuilder()\n                  .setStartLine(hotspot.textRange.getStartLine())\n                  .setStartOffset(hotspot.textRange.getStartLineOffset())\n                  .setEndLine(hotspot.textRange.getEndLine())\n                  .setEndOffset(hotspot.textRange.getEndLineOffset())\n                  .build())\n                .setCreationDate(DateTimeFormatter.ofPattern(\"yyyy-MM-dd'T'HH:mm:ssZ\").withZone(ZoneId.systemDefault()).format(hotspot.creationDate))\n                .setStatus(hotspot.status == HotspotReviewStatus.TO_REVIEW ? \"TO_REVIEW\" : \"REVIEWED\")\n                .setResolution(\"\")\n                .setVulnerabilityProbability(hotspot.vulnerabilityProbability.toString())\n                .setAssignee(hotspot.author);\n              if (hotspot.status != HotspotReviewStatus.TO_REVIEW) {\n                builder = builder.setResolution(hotspot.status.toString());\n              }\n              return builder.build();\n            }, toList())));\n        var branchParameter = branchName == null ? \"\" : (\"&branch=\" + urlEncode(branchName));\n        messagesPerFilePath.forEach((filePath,\n          messages) -> mockServer.stubFor(get(\"/api/hotspots/search.protobuf?projectKey=\" + projectKey + \"&files=\" + urlEncode(filePath) + branchParameter + \"&ps=500&p=1\")\n            .willReturn(aResponse().withResponseBody(protobufBody(Hotspots.SearchWsResponse.newBuilder()\n              .addComponents(Hotspots.Component.newBuilder().setPath(filePath).setKey(projectKey + \":\" + filePath).build())\n              .addAllHotspots(messages)\n              .setPaging(Common.Paging.newBuilder().setTotal(messages.size()).build())\n              .build())))));\n        var allMessages = messagesPerFilePath.values().stream().flatMap(Collection::stream).toList();\n        mockServer.stubFor(get(\"/api/hotspots/search.protobuf?projectKey=\" + projectKey + branchParameter + \"&ps=500&p=1\")\n          .willReturn(aResponse().withResponseBody(protobufBody(Hotspots.SearchWsResponse.newBuilder()\n            .addAllComponents(messagesPerFilePath.keySet().stream().map(filePath -> Hotspots.Component.newBuilder().setPath(filePath).setKey(projectKey + \":\" + filePath).build())\n              .toList())\n            .setPaging(Common.Paging.newBuilder().setTotal(allMessages.size()).build())\n            .addAllHotspots(allMessages).build()))));\n      }));\n    }\n\n    private static String componentKeyParams(String projectKey) {\n      return \"&componentKeys=\" + projectKey + \"&components=\" + projectKey;\n    }\n\n    private void registerSearchIssueApiResponses() {\n      projectsByProjectKey.forEach((projectKey, project) -> {\n        project.pullRequestsByName.forEach((pullRequestName, pullRequest) -> {\n          var issuesPerFilePath = getIssuesPerFilePath(projectKey, pullRequest);\n\n          var allIssues = issuesPerFilePath.values().stream().flatMap(Collection::stream).toList();\n\n          allIssues.forEach(issue -> {\n            var searchUrl = \"/api/issues/search.protobuf?issues=\".concat(urlEncode(issue.getKey()))\n              .concat(componentKeyParams(projectKey))\n              .concat(\"&ps=1&p=1\")\n              .concat(\"&pullRequest=\").concat(pullRequestName);\n            mockServer.stubFor(get(searchUrl)\n              .willReturn(aResponse().withResponseBody(protobufBody(Issues.SearchWsResponse.newBuilder()\n                .addIssues(\n                  Issues.Issue.newBuilder()\n                    .setKey(issue.getKey()).setRule(issue.getRule()).setCreationDate(issue.getCreationDate()).setMessage(issue.getMessage())\n                    .setTextRange(issue.getTextRange()).setComponent(issue.getComponent())\n                    .addAllImpacts(issue.getImpactsList().stream().map(i -> Common.Impact.newBuilder()\n                      .setSoftwareQuality(i.getSoftwareQuality())\n                      .setSeverity(i.getSeverity())\n                      .build()).toList())\n                    .build())\n                .addAllComponents(\n                  issuesPerFilePath.keySet().stream().map(issues -> Issues.Component.newBuilder().setPath(issues).setKey(projectKey + \":\" + issues).build()).toList())\n                .setRules(Issues.SearchWsResponse.newBuilder().getRulesBuilder().addRules(Common.Rule.newBuilder().setKey(issue.getRule()).build()))\n                .setPaging(Common.Paging.newBuilder().setTotal(1).build())\n                .build()))));\n          });\n        });\n        project.branchesByName.forEach((branchName, branch) -> {\n          var issuesPerFilePath = getIssuesPerFilePath(projectKey, branch);\n\n          var allIssues = issuesPerFilePath.values().stream().flatMap(Collection::stream).toList();\n\n          allIssues.forEach(issue -> {\n            var searchUrl = \"/api/issues/search.protobuf?issues=\".concat(urlEncode(issue.getKey()))\n              .concat(componentKeyParams(projectKey))\n              .concat(\"&ps=1&p=1\")\n              .concat(\"&branch=\").concat(branchName);\n            mockServer.stubFor(get(searchUrl)\n              .willReturn(aResponse().withResponseBody(protobufBody(Issues.SearchWsResponse.newBuilder()\n                .addIssues(\n                  Issues.Issue.newBuilder()\n                    .setKey(issue.getKey()).setRule(issue.getRule()).setCreationDate(issue.getCreationDate()).setMessage(issue.getMessage())\n                    .setTextRange(issue.getTextRange()).setComponent(issue.getComponent()).setType(issue.getType()).build())\n                .addAllComponents(\n                  issuesPerFilePath.keySet().stream().map(issues -> Issues.Component.newBuilder().setPath(issues).setKey(projectKey + \":\" + issues).build()).toList())\n                .setRules(Issues.SearchWsResponse.newBuilder().getRulesBuilder().addRules(Common.Rule.newBuilder().setKey(issue.getRule()).build()))\n                .setPaging(Common.Paging.newBuilder().setTotal(1).build())\n                .build()))));\n          });\n\n          var vulnerabilities = allIssues.stream().filter(issue -> issue.getType() == Common.RuleType.VULNERABILITY).toList();\n          var searchUrl = \"/api/issues/search.protobuf?statuses=OPEN,CONFIRMED,REOPENED,RESOLVED&types=VULNERABILITY\"\n            + componentKeyParams(projectKey) + \"&rules=&branch=\" + branchName\n            + \"&ps=500&p=1\";\n          mockServer.stubFor(get(searchUrl)\n            .willReturn(aResponse().withResponseBody(protobufBody(Issues.SearchWsResponse.newBuilder()\n              .addAllIssues(\n                vulnerabilities.stream().map(vulnerability -> Issues.Issue.newBuilder()\n                  .setKey(vulnerability.getKey()).setRule(vulnerability.getRule()).setCreationDate(vulnerability.getCreationDate()).setMessage(vulnerability.getMessage())\n                  .setTextRange(vulnerability.getTextRange()).setComponent(vulnerability.getComponent()).setType(vulnerability.getType()).build()).toList())\n              .addAllComponents(\n                issuesPerFilePath.keySet().stream().map(issues -> Issues.Component.newBuilder().setPath(issues).setKey(projectKey + \":\" + issues).build()).toList())\n              .setPaging(Common.Paging.newBuilder().setTotal(vulnerabilities.size()).build())\n              .build()))));\n        });\n      });\n    }\n\n    @NotNull\n    private static Map<String, List<Issues.Issue>> getIssuesPerFilePath(String projectKey,\n      AbstractServerBuilder.ServerProjectBuilder.ServerProjectBranchBuilder pullRequestOrBranch) {\n      return Stream.concat(pullRequestOrBranch.issues.stream(), pullRequestOrBranch.taintIssues.stream())\n        .collect(groupingBy(AbstractServerBuilder.ServerProjectBuilder.ServerProjectBranchBuilder.ServerIssue::getFilePath,\n          mapping(issue -> Issues.Issue.newBuilder()\n            .setKey(issue.issueKey)\n            .setComponent(projectKey + \":\" + issue.filePath)\n            .setRule(issue.ruleKey)\n            .setMessage(issue.message)\n            .setTextRange(Common.TextRange.newBuilder()\n              .setStartLine(issue.textRange.getStartLine())\n              .setStartOffset(issue.textRange.getStartLineOffset())\n              .setEndLine(issue.textRange.getEndLine())\n              .setEndOffset(issue.textRange.getEndLineOffset())\n              .build())\n            .setCreationDate(DATETIME_FORMATTER.format(issue.introductionDate))\n            .setStatus(issue.status)\n            .setAssignee(issue.author)\n            .setType(Common.RuleType.valueOf(issue.ruleType.name()))\n            .addAllImpacts(issue.impacts.entrySet().stream().map(i -> Common.Impact.newBuilder()\n              .setSoftwareQuality(Common.SoftwareQuality.valueOf(i.getKey().name()))\n              .setSeverity(Common.ImpactSeverity.valueOf(i.getValue().name()))\n              .build()).toList())\n            .build(), toList())));\n    }\n\n    private void registerApiHotspotsPullResponses() {\n      projectsByProjectKey.forEach((projectKey, project) -> project.branchesByName.forEach((branchName, branch) -> {\n        var branchParameter = branchName == null ? \"\" : (\"&branchName=\" + branchName);\n        var timestamp = Hotspots.HotspotPullQueryTimestamp.newBuilder().setQueryTimestamp(123L).build();\n        var hotspotsArray = branch.hotspots.stream().map(hotspot -> Hotspots.HotspotLite.newBuilder()\n          .setKey(hotspot.hotspotKey)\n          .setRuleKey(hotspot.ruleKey)\n          .setVulnerabilityProbability(hotspot.vulnerabilityProbability.name())\n          .setMessage(hotspot.message)\n          .setFilePath(hotspot.filePath)\n          .setTextRange(Hotspots.TextRange.newBuilder()\n            .setStartLine(hotspot.textRange.getStartLine())\n            .setStartLineOffset(hotspot.textRange.getStartLineOffset())\n            .setEndLine(hotspot.textRange.getEndLine())\n            .setEndLineOffset(hotspot.textRange.getEndLineOffset())\n            .setHash(\"hash\"))\n          .setCreationDate(123_456_789L)\n          .build()).toArray(Hotspots.HotspotLite[]::new);\n        var messages = new Message[hotspotsArray.length + 1];\n        messages[0] = timestamp;\n        System.arraycopy(hotspotsArray, 0, messages, 1, hotspotsArray.length);\n        var response = aResponse().withResponseBody(protobufBodyDelimited(messages));\n        mockServer.stubFor(\n          get(urlMatching(\"\\\\Q/api/hotspots/pull?projectKey=\" + projectKey + branchParameter + \"\\\\E(&languages=.*)?(\\\\Q&changedSince=\" + timestamp.getQueryTimestamp() + \"\\\\E)?\"))\n            .willReturn(response));\n      }));\n    }\n\n    private void registerHotspotsShowApiResponses() {\n      projectsByProjectKey.forEach((projectKey, project) -> project.branchesByName.forEach((branchName, branch) -> branch.hotspots.forEach(hotspot -> {\n        var textRange = hotspot.textRange;\n        var reviewStatus = hotspot.status;\n        var status = reviewStatus.isReviewed() ? \"REVIEWED\" : \"TO_REVIEW\";\n        var builder = Hotspots.ShowWsResponse.newBuilder()\n          .setMessage(hotspot.message)\n          .setComponent(Hotspots.Component.newBuilder().setPath(hotspot.filePath).setKey(projectKey + \":\" + hotspot.filePath))\n          .setTextRange(Common.TextRange.newBuilder().setStartLine(textRange.getStartLine()).setStartOffset(textRange.getStartLineOffset()).setEndLine(textRange.getEndLine())\n            .setEndOffset(textRange.getEndLineOffset()).build())\n          .setAuthor(hotspot.author)\n          .setStatus(status)\n          .setCanChangeStatus(hotspot.canChangeStatus)\n          .setRule(Hotspots.Rule.newBuilder().setKey(hotspot.ruleKey)\n            .setName(\"name\")\n            .setSecurityCategory(\"category\")\n            .setVulnerabilityProbability(\"HIGH\")\n            .setRiskDescription(\"risk\")\n            .setVulnerabilityDescription(\"vulnerability\")\n            .setFixRecommendations(\"fix\")\n            .build());\n        if (reviewStatus.isReviewed()) {\n          builder = builder.setResolution(reviewStatus.name());\n        }\n        mockServer.stubFor(get(\"/api/hotspots/show.protobuf?hotspot=\" + hotspot.hotspotKey)\n          .willReturn(aResponse().withResponseBody(protobufBody(builder.build()))));\n      })));\n    }\n\n    private void registerHotspotsStatusChangeApiResponses() {\n      mockServer.stubFor(post(\"/api/hotspots/change_status\").willReturn(aResponse().withStatus(responseCodes.statusCode)));\n    }\n\n    private void registerIssuesApiResponses() {\n      if (version != null) {\n        registerApiIssuesPullResponses();\n        registerApiIssuesPullTaintResponses();\n      } else {\n        registerBatchIssuesResponses();\n      }\n      registerIssuesStatusChangeApiResponses();\n      registerAddIssueCommentApiResponses();\n      registerSearchIssueApiResponses();\n      if (version != null && version.satisfiesMinRequirement(Version.create(\"10.2\"))) {\n        registerIssueAnticipateTransitionResponses();\n      }\n    }\n\n    private void registerBatchIssuesResponses() {\n      projectsByProjectKey.forEach((projectKey, project) -> project.branchesByName.forEach((branchName, branch) -> {\n        var allBranchIssues = new ArrayList<Message>();\n        branch.issues.stream().collect(groupingBy(i -> i.filePath)).forEach((filePath, issues) -> {\n          var messages = issues.stream().map(issue -> {\n            var ruleKey = RuleKey.parse(issue.ruleKey);\n            var serverIssue = ScannerInput.ServerIssue.newBuilder()\n              .setKey(issue.issueKey)\n              .setRuleRepository(ruleKey.repository())\n              .setRuleKey(ruleKey.rule())\n              .setChecksum(issue.hash)\n              .setMsg(issue.message)\n              .setLine(issue.textRange.getStartLine())\n              .setCreationDate(issue.introductionDate.toEpochMilli())\n              .setPath(issue.filePath)\n              .setType(issue.ruleType.name())\n              .setManualSeverity(issue.manualSeverity)\n              .setSeverity(issue.severity)\n              .build();\n            allBranchIssues.add(serverIssue);\n            return serverIssue;\n          }).toArray(Message[]::new);\n          var branchParameter = branchName == null ? \"\" : \"&branch=\" + urlEncode(branchName);\n          mockServer.stubFor(get(\"/batch/issues?key=\" + urlEncode(projectKey + ':' + filePath) + branchParameter)\n            .willReturn(aResponse().withResponseBody(protobufBodyDelimited(messages))));\n        });\n        var branchParameter = branchName == null ? \"\" : \"&branch=\" + urlEncode(branchName);\n        mockServer.stubFor(get(\"/batch/issues?key=\" + urlEncode(projectKey) + branchParameter)\n          .willReturn(aResponse().withResponseBody(protobufBodyDelimited(allBranchIssues.toArray(new Message[0])))));\n      }));\n    }\n\n    private void registerIssuesStatusChangeApiResponses() {\n      mockServer.stubFor(post(\"/api/issues/do_transition\").willReturn(aResponse().withStatus(responseCodes.issueTransitionStatusCode)));\n    }\n\n    private void registerAddIssueCommentApiResponses() {\n      mockServer.stubFor(post(\"/api/issues/add_comment\").willReturn(aResponse().withStatus(responseCodes.addCommentStatusCode)));\n    }\n\n    private void registerApiIssuesPullResponses() {\n      projectsByProjectKey.forEach((projectKey, project) -> project.branchesByName.forEach((branchName, branch) -> {\n        var branchParameter = branchName == null ? \"\" : (\"&branchName=\" + branchName);\n        var timestamp = Issues.IssuesPullQueryTimestamp.newBuilder().setQueryTimestamp(123L).build();\n        var issuesArray = branch.issues.stream().map(issue -> Issues.IssueLite.newBuilder()\n          .setKey(issue.issueKey)\n          .setRuleKey(issue.ruleKey)\n          .setType(Common.RuleType.BUG)\n          .setUserSeverity(Common.Severity.MAJOR)\n          .setMainLocation(Issues.Location.newBuilder().setFilePath(issue.filePath).setMessage(issue.message)\n            .setTextRange(Issues.TextRange.newBuilder()\n              .setStartLine(issue.textRange.getStartLine())\n              .setStartLineOffset(issue.textRange.getStartLineOffset())\n              .setEndLine(issue.textRange.getEndLine())\n              .setEndLineOffset(issue.textRange.getEndLineOffset())\n              .setHash(\"hash\")))\n          .setCreationDate(123_456_789L)\n          .addAllImpacts(issue.impacts.entrySet().stream().map(i -> Common.Impact.newBuilder()\n            .setSoftwareQuality(Common.SoftwareQuality.valueOf(i.getKey().name()))\n            .setSeverity(Common.ImpactSeverity.valueOf(i.getValue().name()))\n            .build()).toList())\n          .build()).toArray(Issues.IssueLite[]::new);\n        var messages = new Message[issuesArray.length + 1];\n        messages[0] = timestamp;\n        System.arraycopy(issuesArray, 0, messages, 1, issuesArray.length);\n        var response = aResponse().withResponseBody(protobufBodyDelimited(messages));\n        mockServer.stubFor(\n          get(urlMatching(\"\\\\Q/api/issues/pull?projectKey=\" + projectKey + branchParameter + \"\\\\E(&languages=.*)?(\\\\Q&changedSince=\" + timestamp.getQueryTimestamp() + \"\\\\E)?\"))\n            .willReturn(response));\n      }));\n    }\n\n    private void registerApiIssuesPullTaintResponses() {\n      projectsByProjectKey.forEach((projectKey, project) -> project.branchesByName.forEach((branchName, branch) -> {\n        var branchParameter = branchName == null ? \"\" : (\"&branchName=\" + branchName);\n        var timestamp = Issues.TaintVulnerabilityPullQueryTimestamp.newBuilder().setQueryTimestamp(123L).build();\n        var issuesArray = branch.taintIssues.stream().map(issue -> Issues.TaintVulnerabilityLite.newBuilder()\n          .setKey(issue.issueKey)\n          .setResolved(issue.isResolved())\n          .setRuleKey(issue.ruleKey)\n          .setType(Common.RuleType.BUG)\n          .setSeverity(Common.Severity.MAJOR)\n          .setMainLocation(Issues.Location.newBuilder().setFilePath(issue.filePath).setMessage(issue.message)\n            .setTextRange(Issues.TextRange.newBuilder()\n              .setStartLine(issue.textRange.getStartLine())\n              .setStartLineOffset(issue.textRange.getStartLineOffset())\n              .setEndLine(issue.textRange.getEndLine())\n              .setEndLineOffset(issue.textRange.getEndLineOffset())\n              .setHash(\"hash\")))\n          .setCreationDate(issue.introductionDate.toEpochMilli())\n          .build()).toArray(Issues.TaintVulnerabilityLite[]::new);\n        var messages = new Message[issuesArray.length + 1];\n        messages[0] = timestamp;\n        System.arraycopy(issuesArray, 0, messages, 1, issuesArray.length);\n        var response = aResponse().withResponseBody(protobufBodyDelimited(messages));\n        mockServer.stubFor(get(\n          urlMatching(\"\\\\Q/api/issues/pull_taint?projectKey=\" + projectKey + branchParameter + \"\\\\E(&languages=.*)?(\\\\Q&changedSince=\" + timestamp.getQueryTimestamp() + \"\\\\E)?\"))\n            .willReturn(response));\n      }));\n    }\n\n    private void registerIssueAnticipateTransitionResponses() {\n      mockServer.stubFor(post(\"/api/issues/anticipated_transitions?projectKey=projectKey\").willReturn(aResponse().withStatus(responseCodes.statusCode)));\n    }\n\n    private void registerSourceApiResponses() {\n      projectsByProjectKey.forEach((projectKey, project) -> project.pullRequestsByName.forEach((pullRequestName, pullRequest) -> pullRequest.sourceFileByComponentKey\n        .forEach((componentKey, sourceFile) -> mockServer\n          .stubFor(get(\"/api/sources/raw?key=\" + urlEncode(componentKey) + \"&pullRequest=\" + urlEncode(pullRequestName)).willReturn(aResponse().withBody(sourceFile.code))))));\n\n      projectsByProjectKey.forEach((projectKey, project) -> project.branchesByName.forEach((branchName, branch) -> {\n        if (branchName != null) {\n          branch.sourceFileByComponentKey\n            .forEach((componentKey, sourceFile) -> mockServer\n              .stubFor(get(\"/api/sources/raw?key=\" + urlEncode(componentKey) + \"&branch=\" + urlEncode(branchName)).willReturn(aResponse().withBody(sourceFile.code))));\n        }\n      }));\n\n      projectsByProjectKey.forEach((projectKey, project) -> project.branchesByName.forEach((branchName, branch) -> branch.sourceFileByComponentKey\n        .forEach((componentKey, sourceFile) -> mockServer.stubFor(get(\"/api/sources/raw?key=\" + urlEncode(componentKey)).willReturn(aResponse().withBody(sourceFile.code))))));\n    }\n\n    private void registerDevelopersApiResponses() {\n      mockServer.stubFor(get(\"/api/developers/search_events?projects=&from=\").willReturn(aResponse().withStatus(responseCodes.statusCode)));\n      smartNotifications.forEach(sn -> {\n        var projects = sn.projects.stream().map(UrlUtils::urlEncode).collect(Collectors.joining(\",\"));\n        mockServer.stubFor(get(urlMatching(\"\\\\Q/api/developers/search_events?projects=\" + projects + \"\\\\E(&from=.*)\"))\n          .willReturn(aResponse().withBody(sn.events).withStatus(responseCodes.statusCode)));\n      });\n    }\n\n    private void registerMeasuresApiResponses() {\n      var periodFieldName = this.serverKind == ServerKind.SONARCLOUD ? \"periods\" : \"period\";\n      projectsByProjectKey.forEach((projectKey, project) -> {\n        var uriPath = \"/api/measures/component.protobuf?additionalFields=\" + periodFieldName + \"&metricKeys=projects&component=\" + projectKey;\n        mockServer.stubFor(get(uriPath)\n          .willReturn(aResponse().withResponseBody(protobufBody(\n            // TODO Override with whatever is set on the branch fixture?\n            Measures.ComponentWsResponse.newBuilder()\n              .setComponent(Measures.Component.newBuilder()\n                .setKey(projectKey)\n                .setQualifier(\"TRK\")\n                .build())\n              .setPeriod(Measures.Period.newBuilder()\n                .setMode(\"PREVIOUS_VERSION\")\n                .setDate(\"2023-08-29T09:37:59+0000\")\n                .setParameter(\"9.2\")\n                .build())\n              .build()))));\n      });\n    }\n\n    private void registerComponentsApiResponses() {\n      registerComponentsSearchApiResponses();\n      registerComponentsShowApiResponses();\n      registerComponentsTreeApiResponses();\n\n      projectsByProjectKey.forEach((projectKey, project) -> {\n        var organizationKey = project.organizationKey;\n        if (organizationKey == null) {\n          return;\n        }\n        var projectName = project.name;\n        if (projectName == null) {\n          return;\n        }\n\n        dopTranslation.projectBindings.entrySet().stream()\n          .filter(e -> projectKey.equals(e.getValue().projectKey()))\n          .forEach(e -> {\n            var projectBinding = e.getValue();\n            var endpoint = \"/api/components/search_projects?projectIds=\" + projectBinding.projectId() + \"&organization=\" + organizationKey;\n            var body = \"\"\"\n              {\"components\":[{\"key\":\"%s\",\"name\":\"%s\"}]}\n              \"\"\".formatted(projectKey, projectName);\n            mockServer.stubFor(get(urlEqualTo(endpoint))\n              .willReturn(aResponse()\n                .withStatus(responseCodes.statusCode)\n                .withHeader(CONTENT_TYPE, APPLICATION_JSON)\n                .withBody(body)));\n          });\n      });\n    }\n\n    private void registerComponentsSearchApiResponses() {\n      var projectsByOrganizationKey = projectsByProjectKey.entrySet().stream()\n        .collect(groupingBy(e -> Optional.ofNullable(e.getValue().organizationKey), HashMap::new, toList()));\n      if (projectsByOrganizationKey.isEmpty()) {\n        projectsByOrganizationKey.put(Optional.empty(), List.of());\n      }\n      organizationsByKey.forEach((organizationKey, organization) -> {\n        if (!projectsByOrganizationKey.containsKey(Optional.of(organizationKey))) {\n          projectsByOrganizationKey.put(Optional.of(organizationKey), List.of());\n        }\n      });\n      projectsByOrganizationKey\n        .forEach((organizationKey, projects) -> {\n          var url = \"/api/components/search.protobuf?qualifiers=TRK\";\n          if (organizationKey.isPresent()) {\n            url += \"&organization=\" + organizationKey.get();\n          }\n          url += \"&ps=500&p=1\";\n          mockServer.stubFor(get(url)\n            .willReturn(aResponse().withResponseBody(protobufBody(Components.SearchWsResponse.newBuilder()\n              .addAllComponents(projects.stream().map(entry -> Components.Component.newBuilder().setKey(entry.getKey()).setName(entry.getValue().name).build())\n                .toList())\n              .setPaging(Common.Paging.newBuilder().setTotal(projectsByProjectKey.size()).build())\n              .build()))));\n        });\n    }\n\n    private void registerComponentsShowApiResponses() {\n      projectsByProjectKey.forEach((projectKey, project) -> {\n        var url = API_COMPONENTS_SHOW_PROTOBUF_COMPONENT + projectKey;\n        var projectComponent = projectsByProjectKey.entrySet().stream().filter(e -> e.getKey().equals(projectKey))\n          .map(entry -> Components.Component.newBuilder()\n            .setKey(entry.getKey())\n            .setName(entry.getValue().name)\n            .setIsAiCodeFixEnabled(project.aiCodeFixEnabled)\n            .build())\n          .findFirst().get();\n        mockServer.stubFor(get(url)\n          .willReturn(aResponse().withResponseBody(protobufBody(Components.ShowWsResponse.newBuilder()\n            .setComponent(projectComponent)\n            .build()))));\n      });\n    }\n\n    private void registerComponentsTreeApiResponses() {\n      projectsByProjectKey.forEach((projectKey, project) -> {\n        var url = \"/api/components/tree.protobuf?qualifiers=FIL,UTS&component=\" + projectKey;\n        if (project.organizationKey != null) {\n          url += \"&organization=\" + project.organizationKey;\n        }\n        url += \"&ps=500&p=1\";\n        mockServer.stubFor(get(url)\n          .willReturn(aResponse().withResponseBody(protobufBody(Components.TreeWsResponse.newBuilder()\n            .addAllComponents(\n              project.relativeFilePaths.stream().map(relativePath -> Components.Component.newBuilder().setKey(projectKey + \":\" + relativePath).build()).toList())\n            .setPaging(Common.Paging.newBuilder().setTotal(project.relativeFilePaths.size()).build())\n            .build()))));\n      });\n    }\n\n    private void registerSettingsApiResponses() {\n      projectsByProjectKey.forEach((projectKey, project) -> mockServer.stubFor(get(\"/api/settings/values.protobuf?component=\" + projectKey)\n        .willReturn(aResponse().withResponseBody(protobufBody(Settings.ValuesWsResponse.newBuilder().build())))));\n\n      var settingsBuilder = Settings.ValuesWsResponse.newBuilder()\n        .addSettings(Settings.Setting.newBuilder()\n          .setKey(\"sonar.earlyAccess.misra.enabled\")\n          .setValue(\"false\"));\n      var mqrModeAvailable = this.version != null && this.version.compareToIgnoreQualifier(Version.create(\"10.8\")) >= 0;\n      if (mqrModeAvailable) {\n        settingsBuilder\n          .addSettings(Settings.Setting.newBuilder()\n            .setKey(\"sonar.multi-quality-mode.enabled\")\n            .setValue(\"true\"));\n      }\n      settingsBuilder.addAllSettings(globalSettings.entrySet().stream().map(entry -> Settings.Setting.newBuilder()\n        .setKey(entry.getKey())\n        .setValue(entry.getValue()).build()).toList());\n      mockServer.stubFor(get(\"/api/settings/values.protobuf\")\n        .willReturn(aResponse().withResponseBody(protobufBody(settingsBuilder.build()))));\n    }\n\n    private void registerFixSuggestionsApiResponses() {\n      projectsByProjectKey.forEach((projectKey, project) -> {\n        try {\n          if (project.aiCodeFixSuggestion != null) {\n            mockServer.stubFor(post(serverKind == ServerKind.SONARCLOUD ? \"/fix-suggestions/ai-suggestions\" : \"/api/v2/fix-suggestions/ai-suggestions\")\n              .willReturn(jsonResponse(\n                new ObjectMapper().setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY).writeValueAsString(project.aiCodeFixSuggestion.build()),\n                responseCodes.statusCode)));\n          }\n        } catch (JsonProcessingException e) {\n          throw new IllegalArgumentException(e);\n        }\n      });\n\n      mockServer.stubFor(get(serverKind == ServerKind.SONARCLOUD ? \"/fix-suggestions/supported-rules\" : \"/api/v2/fix-suggestions/supported-rules\")\n        .willReturn(jsonResponse(\"{\\\"rules\\\": [\" + String.join(\", \", aiCodeFixSupportedRules.stream().map(rule -> \"\\\"\" + rule + \"\\\"\").toList()) + \"]}\", responseCodes.statusCode)));\n      organizationsByKey.forEach((organizationKey, organization) -> {\n        var enabledProjectKeys = organization.aiCodeFixFeature.enabledProjectKeys == null ? null\n          : (\"[\" + String.join(\", \", organization.aiCodeFixFeature.enabledProjectKeys) + \"]\");\n        // this payload will change in the future and will need to update the fixture to have up to date payload example\n        var aiCodeFix = \"\\\"aiCodeFix\\\": {\" +\n          \"\\\"enablement\\\": \\\"\" + organization.aiCodeFixFeature.enablement.name() + \"\\\",\" +\n          \"\\\"enabledProjectKeys\\\":\" + enabledProjectKeys + \",\" +\n          \"\\\"organizationEligible\\\": \" + organization.aiCodeFixFeature.organizationEligible +\n          \"}\";\n        var response = \"{\\\"enablement\\\": \\\"\" + organization.aiCodeFixFeature.enablement.name() + \"\\\", \\\"organizationEligible\\\": \"\n          + organization.aiCodeFixFeature.organizationEligible + \",  \\\"enabledProjectKeys\\\": \" + enabledProjectKeys + \",\" + aiCodeFix + \"}\";\n        mockServer.stubFor(get(\"/fix-suggestions/organization-configs/\" + organization.id)\n          .willReturn(jsonResponse(response, responseCodes.statusCode)));\n      });\n    }\n\n    private void registerOrganizationApiResponses() {\n      organizationsByKey\n        .forEach((organizationKey, organization) -> mockServer.stubFor(get(\"/organizations/organizations?organizationKey=\" + organizationKey + \"&excludeEligibility=true\")\n          .willReturn(jsonResponse(\"[{\\\"id\\\": \\\"\" + organization.id + \"\\\", \\\"uuidV4\\\": \\\"\" + organization.uuidV4 + \"\\\"}]\", responseCodes.statusCode))));\n      mockServer.stubFor(get(\"/api/organizations/search.protobuf?member=true&ps=500&p=1\")\n        .willReturn(aResponse().withStatus(responseCodes.statusCode).withResponseBody(protobufBody(Organizations.SearchWsResponse.newBuilder()\n          .addAllOrganizations(organizationsByKey.entrySet().stream()\n            .filter(e -> e.getValue().isCurrentUserMember)\n            .map(e -> Organizations.Organization.newBuilder()\n              .setKey(e.getKey())\n              .setName(e.getValue().name)\n              .setDescription(e.getValue().description)\n              .build())\n            .toList())\n          .setPaging(Common.Paging.newBuilder().setTotal(organizationsByKey.size()).build())\n          .build()))));\n    }\n\n    private void registerPushApiResponses() {\n      if (!serverSentEventsEnabled) {\n        return;\n      }\n      // wiremock does not support SSE, so we redirect to our custom SSE server\n      mockServer.stubFor(get(urlPathEqualTo(\"/api/push/sonarlint_events\"))\n        .withQueryParam(\"projectKeys\", equalTo(String.join(\",\", projectsByProjectKey.keySet())))\n        .withQueryParam(\"languages\", new AnythingPattern())\n        .willReturn(aResponse()\n          .withStatus(302)\n          .withHeader(\"Location\", sseServer.getUrl())));\n    }\n\n    private void registerFeaturesApiResponses() {\n      mockServer.stubFor(get(\"/api/features/list\")\n        .willReturn(jsonResponse(\n          \"[\" + String.join(\", \", features.stream().map(feature -> \"\\\"\" + feature + \"\\\"\").toList()) + \"]\",\n          responseCodes.statusCode)));\n    }\n\n    private void registerScaApiResponses() {\n      projectsByProjectKey.forEach((projectKey, project) -> project.branchesByName.forEach((branchName, branch) -> {\n        var dependencyRisksJson = branch.dependencyRisks.stream()\n          .map(issue -> String.format(\"\"\"\n            {\n              \"key\": \"%s\",\n              \"type\": \"%s\",\n              \"severity\": \"%s\",\n              \"quality\": \"%s\",\n              \"status\": \"%s\",\n              \"release\": {\n                \"packageName\": \"%s\",\n                \"version\": \"%s\"\n              },\n              \"vulnerabilityId\": \"%s\",\n              \"cvssScore\": \"%s\",\n              \"transitions\": [%s]\n            }\n            \"\"\", issue.id(), issue.type(), issue.severity(), issue.quality(), issue.status(), issue.packageName(), issue.packageVersion(), issue.vulnerabilityId(),\n            issue.cvssScore(), String.join(\", \", issue.transitions())))\n          .collect(Collectors.joining(\",\"));\n\n        var responseJson = String.format(\"\"\"\n          {\n            \"issuesReleases\": [%s],\n            \"page\": {\n              \"pageIndex\": 1,\n              \"pageSize\": %d,\n              \"total\": %d\n            }\n          }\n          \"\"\", dependencyRisksJson, branch.dependencyRisks.size(), branch.dependencyRisks.size());\n\n        var prefix = serverKind == ServerKind.SONARCLOUD ? \"\" : \"/api/v2\";\n        mockServer.stubFor(get(prefix + \"/sca/issues-releases?projectKey=\" + projectKey + \"&branchKey=\" + branchName + \"&pageSize=500&pageIndex=1\")\n          .willReturn(jsonResponse(responseJson, responseCodes.statusCode)));\n        mockServer.stubFor(post(prefix + \"/sca/issues-releases/change-status\")\n          .willReturn(aResponse().withStatus(200)));\n      }));\n    }\n\n    private void registerUsersApiResponses() {\n      mockServer.stubFor(get(\"/api/users/current\")\n        .willReturn(jsonResponse(\"{\\\"isLoggedIn\\\": true, \\\"id\\\": \\\"11111111-1111-1111-1111-111111111111\\\", \\\"login\\\": \\\"user\\\"}\", 200)));\n    }\n\n    public void pushEvent(String eventPayload) {\n      if (!serverSentEventsEnabled) {\n        throw new IllegalStateException(\"Please use withServerSentEventsEnabled() first\");\n      }\n      sseServer.sendEventToAllClients(eventPayload);\n    }\n\n    public void shutdown() {\n      mockServer.stop();\n      if (sseServer != null) {\n        sseServer.stop();\n      }\n    }\n\n    public String baseUrl() {\n      return mockServer.url(\"\");\n    }\n\n    public String url(String path) {\n      return mockServer.url(path);\n    }\n\n    public WireMockServer getMockServer() {\n      return mockServer;\n    }\n\n    private void registerDopTranslationApiResponses() {\n      dopTranslation.projectBindings.forEach((repositoryUrl, projectBinding) -> {\n        var encodedUrl = UrlUtils.urlEncode(repositoryUrl);\n        if (serverKind == ServerKind.SONARCLOUD) {\n          var endpoint = \"/dop-translation/project-bindings?url=\" + encodedUrl;\n          var responseBody = \"\"\"\n            {\"bindings\":[{\"projectId\":\"%s\"}]}\n            \"\"\".formatted(projectBinding.projectId());\n          mockServer.stubFor(get(urlEqualTo(endpoint))\n            .willReturn(aResponse()\n              .withStatus(200)\n              .withHeader(CONTENT_TYPE, APPLICATION_JSON)\n              .withBody(responseBody)));\n        } else {\n          var endpoint = \"/api/v2/dop-translation/project-bindings?repositoryUrl=\" + encodedUrl;\n          var responseBody = \"\"\"\n            {\"projectBindings\":[{\"projectId\":\"%s\",\"projectKey\":\"%s\"}]}\n            \"\"\".formatted(projectBinding.projectId(), projectBinding.projectKey());\n          mockServer.stubFor(get(urlEqualTo(endpoint))\n            .willReturn(aResponse()\n              .withStatus(200)\n              .withHeader(CONTENT_TYPE, APPLICATION_JSON)\n              .withBody(responseBody)));\n        }\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/server/package-info.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.test.utils.server;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/server/sse/SSEServer.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.server.sse;\n\nimport java.io.File;\nimport org.apache.catalina.LifecycleException;\nimport org.apache.catalina.startup.Tomcat;\n\npublic class SSEServer {\n\n  public static final int DEFAULT_PORT = 54321;\n  private Tomcat tomcat;\n  private SSEServlet sseServlet;\n\n  public void start() {\n    try {\n      var baseDir = new File(\"\").getAbsoluteFile().getParentFile().getPath();\n      tomcat = new Tomcat();\n      tomcat.setBaseDir(baseDir);\n      tomcat.setPort(DEFAULT_PORT);\n      var context = tomcat.addContext(\"\", baseDir);\n      sseServlet = new SSEServlet();\n      Tomcat.addServlet(context, \"sse\", sseServlet).addMapping(\"/\");\n      // needed to start the endpoint\n      tomcat.getConnector();\n      tomcat.start();\n    } catch (LifecycleException e) {\n      throw new IllegalStateException(e);\n    }\n  }\n\n  public void stop() {\n    try {\n      tomcat.stop();\n      tomcat.destroy();\n    } catch (LifecycleException e) {\n      throw new IllegalStateException(e);\n    }\n  }\n\n  public String getUrl() {\n    return \"http://localhost:\" + DEFAULT_PORT;\n  }\n\n  public void sendEventToAllClients(String eventPayload) {\n    sseServlet.sendEventToAllClients(eventPayload);\n  }\n\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/server/sse/SSEServlet.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.server.sse;\n\nimport jakarta.servlet.AsyncContext;\nimport jakarta.servlet.Servlet;\nimport jakarta.servlet.ServletConfig;\nimport jakarta.servlet.ServletRequest;\nimport jakarta.servlet.ServletResponse;\nimport jakarta.servlet.annotation.WebServlet;\nimport jakarta.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.List;\n\n@WebServlet(asyncSupported = true)\npublic class SSEServlet implements Servlet {\n\n  private final List<AsyncContext> asyncContexts = new ArrayList<>();\n  private final List<String> pendingEvents = new ArrayList<>();\n\n  @Override\n  public synchronized void service(ServletRequest request, ServletResponse response) throws IOException {\n    var asyncContext = request.startAsync();\n    asyncContext.setTimeout(0);\n    asyncContexts.add(asyncContext);\n    setHeadersForResponse((HttpServletResponse) response);\n    sendPendingEventsIfNeeded(asyncContext);\n  }\n\n  private static void setHeadersForResponse(HttpServletResponse response) throws IOException {\n    response.setStatus(HttpServletResponse.SC_OK);\n    response.setCharacterEncoding(StandardCharsets.UTF_8.name());\n    response.setContentType(\"text/event-stream\");\n    // By adding this header, and not closing the connection,\n    // we disable HTTP chunking, and we can use write()+flush()\n    // to send data in the text/event-stream protocol\n    response.setHeader(\"Connection\", \"close\");\n    response.flushBuffer();\n  }\n\n  private void sendPendingEventsIfNeeded(AsyncContext asyncContext) {\n    if (!pendingEvents.isEmpty()) {\n      pendingEvents.forEach(event -> sendEventToClient(asyncContext, event));\n      pendingEvents.clear();\n    }\n  }\n\n  public synchronized void sendEventToAllClients(String eventPayload) {\n    if (asyncContexts.isEmpty()) {\n      pendingEvents.add(eventPayload);\n    } else {\n      asyncContexts.forEach(asyncContext -> sendEventToClient(asyncContext, eventPayload));\n    }\n  }\n\n  private static void sendEventToClient(AsyncContext asyncContext, String eventPayload) {\n    try {\n      var outputStream = asyncContext.getResponse().getOutputStream();\n      outputStream.write(eventPayload.getBytes(StandardCharsets.UTF_8));\n      outputStream.flush();\n    } catch (IOException e) {\n      throw new IllegalStateException(\"Cannot send event to client\", e);\n    }\n\n  }\n\n  @Override\n  public void init(ServletConfig config) {\n    // no-op\n  }\n\n  @Override\n  public ServletConfig getServletConfig() {\n    return null;\n  }\n\n  @Override\n  public String getServletInfo() {\n    return \"Server Sent Event servlet\";\n  }\n\n  @Override\n  public void destroy() {\n    // no-op\n  }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/server/sse/package-info.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.test.utils.server.sse;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/server/websockets/ContextListener.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.server.websockets;\n\nimport jakarta.servlet.ServletContextEvent;\nimport org.apache.tomcat.websocket.server.WsContextListener;\nimport org.apache.tomcat.websocket.server.WsServerContainer;\n\nimport static org.apache.tomcat.websocket.server.Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE;\n\npublic class ContextListener extends WsContextListener {\n  @Override\n  public void contextInitialized(final ServletContextEvent sce) {\n    super.contextInitialized(sce);\n\n    sce.getServletContext().addListener(new RequestListener());\n    var sc = (WsServerContainer) sce.getServletContext().getAttribute(SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);\n    try {\n      sc.addEndpoint(WebSocketEndpoint.class);\n    } catch (jakarta.websocket.DeploymentException e) {\n      throw new RuntimeException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/server/websockets/RequestListener.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.server.websockets;\n\nimport jakarta.servlet.ServletRequestEvent;\nimport jakarta.servlet.ServletRequestListener;\nimport jakarta.servlet.http.HttpServletRequest;\n\npublic class RequestListener implements ServletRequestListener {\n  @Override\n  public void requestInitialized(ServletRequestEvent sre) {\n    // needed to force creation of the WebSocket session for handshake\n    ((HttpServletRequest) sre.getServletRequest()).getSession();\n  }\n\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/server/websockets/ServletAwareConfig.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.server.websockets;\n\nimport jakarta.servlet.http.HttpSession;\nimport jakarta.websocket.HandshakeResponse;\nimport jakarta.websocket.server.HandshakeRequest;\nimport jakarta.websocket.server.ServerEndpointConfig;\n\nimport static org.sonarsource.sonarlint.core.test.utils.server.websockets.WebSocketEndpoint.WS_REQUEST_KEY;\nimport static org.sonarsource.sonarlint.core.test.utils.server.websockets.WebSocketServer.CONNECTION_REPOSITORY_ATTRIBUTE_KEY;\n\npublic class ServletAwareConfig extends ServerEndpointConfig.Configurator {\n  @Override\n  public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {\n    var webSocketRequest = new WebSocketRequest(request.getHeaders().get(\"Authorization\").get(0), request.getHeaders().get(\"User-Agent\").get(0));\n    config.getUserProperties().put(WS_REQUEST_KEY, webSocketRequest);\n    config.getUserProperties().put(CONNECTION_REPOSITORY_ATTRIBUTE_KEY, getWebSocketConnectionRepository(request));\n    super.modifyHandshake(config, request, response);\n  }\n\n  private static WebSocketConnectionRepository getWebSocketConnectionRepository(HandshakeRequest request) {\n    HttpSession httpSession = (HttpSession) request.getHttpSession();\n    return (WebSocketConnectionRepository) httpSession.getServletContext().getAttribute(CONNECTION_REPOSITORY_ATTRIBUTE_KEY);\n  }\n\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/server/websockets/WebSocketConnection.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.server.websockets;\n\nimport jakarta.websocket.Session;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\npublic class WebSocketConnection {\n  private final WebSocketRequest request;\n  private boolean isOpened = true;\n  private final List<String> receivedMessages = new CopyOnWriteArrayList<>();\n  private Throwable throwable;\n  private final Session session;\n\n  public WebSocketConnection(WebSocketRequest request, Session session) {\n    this.request = request;\n    this.session = session;\n  }\n\n  public String getAuthorizationHeader() {\n    return request.getAuthorizationHeader();\n  }\n\n  public String getUserAgent() {\n    return request.getUserAgent();\n  }\n\n  public boolean isOpened() {\n    return isOpened;\n  }\n\n  public List<String> getReceivedMessages() {\n    return receivedMessages;\n  }\n\n  public void addReceivedMessage(String message) {\n    receivedMessages.add(message);\n  }\n\n  void setIsClosed() {\n    isOpened = false;\n  }\n\n  public void setIsError(Throwable throwable) {\n    this.throwable = throwable;\n  }\n\n  public void sendMessage(String message) {\n    if (session == null) {\n      throw new IllegalStateException(\"Cannot send a message, session is null\");\n    }\n    if (!isOpened) {\n      throw new IllegalStateException(\"Cannot send a message, the WebSocket is not opened\");\n    }\n    if (throwable != null) {\n      throw new IllegalStateException(\"Cannot send a message, the WebSocket previously errored\", throwable);\n    }\n    try {\n      session.getBasicRemote().sendText(message);\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  public void close() {\n    if (session != null) {\n      try {\n        session.close();\n      } catch (IOException e) {\n        throw new RuntimeException(e);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/server/websockets/WebSocketConnectionRepository.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.server.websockets;\n\nimport java.util.List;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\npublic class WebSocketConnectionRepository {\n  private final List<WebSocketConnection> connections = new CopyOnWriteArrayList<>();\n\n  public void add(WebSocketConnection webSocketConnection) {\n    connections.add(webSocketConnection);\n  }\n\n  public List<WebSocketConnection> getConnections() {\n    return connections;\n  }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/server/websockets/WebSocketEndpoint.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.server.websockets;\n\nimport jakarta.websocket.OnClose;\nimport jakarta.websocket.OnError;\nimport jakarta.websocket.OnMessage;\nimport jakarta.websocket.OnOpen;\nimport jakarta.websocket.Session;\nimport jakarta.websocket.server.ServerEndpoint;\n\nimport static org.sonarsource.sonarlint.core.test.utils.server.websockets.WebSocketServer.CONNECTION_REPOSITORY_ATTRIBUTE_KEY;\n\n@ServerEndpoint(value = \"/endpoint\", configurator = ServletAwareConfig.class)\npublic class WebSocketEndpoint {\n  public static final String WS_REQUEST_KEY = \"wsRequest\";\n  private WebSocketConnection connection;\n\n  @OnOpen\n  public void onOpen(final Session session) {\n    connection = createWsConnection(session);\n  }\n\n  @OnMessage\n  public void handleTextMessage(Session session, String message) {\n    System.out.println(\"Message received by web socket server: \" + message);\n    connection.addReceivedMessage(message);\n  }\n\n  @OnClose\n  public void onClose(final Session session) {\n    connection.setIsClosed();\n  }\n\n  @OnError\n  public void onError(final Session session, final Throwable throwable) {\n    connection.setIsError(throwable);\n  }\n\n  private static WebSocketConnection createWsConnection(Session session) {\n    var connectionRepository = getWebSocketConnectionRepository(session);\n    var webSocketRequest = (WebSocketRequest) session.getUserProperties().get(WS_REQUEST_KEY);\n    var webSocketConnection = new WebSocketConnection(webSocketRequest, session);\n    connectionRepository.add(webSocketConnection);\n    return webSocketConnection;\n  }\n\n  private static WebSocketConnectionRepository getWebSocketConnectionRepository(Session session) {\n    return (WebSocketConnectionRepository) session.getUserProperties().get(CONNECTION_REPOSITORY_ATTRIBUTE_KEY);\n  }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/server/websockets/WebSocketRequest.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.server.websockets;\n\npublic class WebSocketRequest {\n  private final String authorizationHeader;\n  private final String userAgent;\n\n  public WebSocketRequest(String authorizationHeader, String userAgent) {\n    this.authorizationHeader = authorizationHeader;\n    this.userAgent = userAgent;\n  }\n\n  public String getAuthorizationHeader() {\n    return authorizationHeader;\n  }\n\n  public String getUserAgent() {\n    return userAgent;\n  }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/server/websockets/WebSocketServer.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.server.websockets;\n\nimport java.io.File;\nimport java.util.List;\nimport org.apache.catalina.LifecycleException;\nimport org.apache.catalina.servlets.DefaultServlet;\nimport org.apache.catalina.startup.Tomcat;\n\npublic class WebSocketServer {\n\n  public static final int DEFAULT_PORT = 54321;\n  public static final String CONNECTION_REPOSITORY_ATTRIBUTE_KEY = \"connectionRepository\";\n  private Tomcat tomcat;\n  private WebSocketConnectionRepository connectionRepository;\n  private final int port;\n\n  public WebSocketServer(int port) {\n    this.port = port;\n  }\n\n  public WebSocketServer() {\n    this(DEFAULT_PORT);\n  }\n\n  public void start() {\n    try {\n      var baseDir = new File(\"\").getAbsoluteFile().getParentFile().getPath();\n      tomcat = new Tomcat();\n      tomcat.setBaseDir(baseDir);\n      tomcat.setPort(port);\n      var context = tomcat.addContext(\"\", baseDir);\n      connectionRepository = new WebSocketConnectionRepository();\n      context.getServletContext().setAttribute(CONNECTION_REPOSITORY_ATTRIBUTE_KEY, connectionRepository);\n      context.addApplicationListener(ContextListener.class.getName());\n      Tomcat.addServlet(context, \"dummy\", new DefaultServlet()).addMapping(\"/\");\n      // needed to start the endpoint\n      tomcat.getConnector();\n      tomcat.start();\n    } catch (LifecycleException e) {\n      throw new IllegalStateException(e);\n    }\n  }\n\n  public void stop() {\n    try {\n      tomcat.stop();\n      tomcat.destroy();\n    } catch (LifecycleException e) {\n      throw new IllegalStateException(e);\n    }\n  }\n\n  public String getUrl() {\n    return \"ws://localhost:\" + port + \"/endpoint\";\n  }\n\n  public List<WebSocketConnection> getConnections() {\n    return connectionRepository.getConnections();\n  }\n\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/server/websockets/package-info.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.test.utils.server.websockets;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/storage/AiCodeFixFixtures.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.storage;\n\nimport java.util.List;\nimport java.util.Set;\nimport org.sonarsource.sonarlint.core.serverconnection.aicodefix.AiCodeFix;\nimport org.sonarsource.sonarlint.core.serverconnection.aicodefix.AiCodeFixRepository;\nimport org.sonarsource.sonarlint.core.serverconnection.aicodefix.AiCodeFixFeatureEnablement;\n\npublic class AiCodeFixFixtures {\n  private AiCodeFixFixtures() {\n    // utility class\n  }\n\n  public static class Builder {\n    private final String connectionId;\n    private Set<String> supportedRules = Set.of();\n    private boolean organizationEligible = true;\n    private AiCodeFixFeatureEnablement enablement = AiCodeFixFeatureEnablement.DISABLED;\n    private List<String> enabledProjectKeys = List.of();\n\n    public Builder(String connectionId) {\n      this.connectionId = connectionId;\n    }\n\n    public Builder withSupportedRules(Set<String> supportedRules) {\n      this.supportedRules = supportedRules;\n      return this;\n    }\n\n    public Builder organizationEligible(boolean organizationEligible) {\n      this.organizationEligible = organizationEligible;\n      return this;\n    }\n\n    public Builder disabled() {\n      this.enablement = AiCodeFixFeatureEnablement.DISABLED;\n      return this;\n    }\n\n    public Builder enabledForProjects(String projectKey) {\n      this.enablement = AiCodeFixFeatureEnablement.ENABLED_FOR_SOME_PROJECTS;\n      this.enabledProjectKeys = List.of(projectKey);\n      return this;\n    }\n\n    public Builder enabledForAllProjects() {\n      this.enablement = AiCodeFixFeatureEnablement.ENABLED_FOR_ALL_PROJECTS;\n      return this;\n    }\n\n    public void populate(TestDatabase database) {\n      var aiCodeFixRepository = new AiCodeFixRepository(database.dsl());\n      aiCodeFixRepository\n        .upsert(new AiCodeFix(connectionId, supportedRules, organizationEligible, AiCodeFix.Enablement.valueOf(enablement.name()), Set.copyOf(enabledProjectKeys)));\n    }\n  }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/storage/ConfigurationScopeStorageFixture.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.storage;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.UUID;\nimport java.util.stream.Collectors;\nimport jetbrains.exodus.entitystore.PersistentEntityStores;\nimport jetbrains.exodus.env.Environments;\nimport jetbrains.exodus.util.CompressBackupUtil;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.LocalOnlyIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.LocalOnlyIssuesRepository;\nimport org.sonarsource.sonarlint.core.local.only.IssueStatusBinding;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.InstantBinding;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.UuidBinding;\n\nimport static java.util.Objects.requireNonNull;\n\npublic class ConfigurationScopeStorageFixture {\n  public static ConfigurationScopeStorageBuilder newBuilder(String configScopeId) {\n    return new ConfigurationScopeStorageBuilder(configScopeId);\n  }\n\n  public static class ConfigurationScopeStorageBuilder {\n    private final List<LocalOnlyIssue> localOnlyIssues = new ArrayList<>();\n    private final String configScopeId;\n    private boolean usingXodus;\n\n    public ConfigurationScopeStorageBuilder(String configScopeId) {\n      this.configScopeId = configScopeId;\n    }\n\n    public ConfigurationScopeStorageBuilder usingXodus() {\n      this.usingXodus = true;\n      return this;\n    }\n\n    public ConfigurationScopeStorageBuilder withLocalOnlyIssue(LocalOnlyIssue issue) {\n      localOnlyIssues.add(issue);\n      return this;\n    }\n\n    public void populate(Path storageRoot, TestDatabase database) {\n      populateDatabase(database);\n      if (!usingXodus) {\n        return;\n      }\n      var xodusTempDbPath = storageRoot.resolve(\"xodus_temp_db\");\n      var xodusBackupPath = storageRoot.resolve(\"local_only_issue_backup.tar.gz\");\n      try {\n        Files.createDirectories(xodusBackupPath.getParent());\n      } catch (IOException e) {\n        throw new IllegalStateException(\"Unable to create the Xodus backup parent folders\", e);\n      }\n      var environment = Environments.newInstance(xodusTempDbPath.toAbsolutePath().toFile());\n      var entityStore = PersistentEntityStores.newInstance(environment);\n      entityStore.executeInTransaction(txn -> {\n        entityStore.registerCustomPropertyType(txn, Instant.class, new InstantBinding());\n        entityStore.registerCustomPropertyType(txn, UUID.class, new UuidBinding());\n        entityStore.registerCustomPropertyType(txn, IssueStatus.class, new IssueStatusBinding());\n        var scopeEntity = txn.newEntity(\"Scope\");\n        localOnlyIssues.stream()\n          .collect(Collectors.groupingBy(LocalOnlyIssue::getServerRelativePath))\n          .forEach((filePath, issues) -> {\n            var fileEntity = txn.newEntity(\"File\");\n            issues.forEach(issue -> {\n              var issueEntity = txn.newEntity(\"Issue\");\n              issueEntity.setProperty(\"uuid\", issue.getId());\n              issueEntity.setProperty(\"ruleKey\", issue.getRuleKey());\n              issueEntity.setBlobString(\"message\", issue.getMessage());\n              var resolution = requireNonNull(issue.getResolution());\n              issueEntity.setProperty(\"resolvedStatus\", resolution.getStatus());\n              issueEntity.setProperty(\"resolvedDate\", resolution.getResolutionDate());\n              var comment = resolution.getComment();\n              if (comment != null) {\n                issueEntity.setBlobString(\"comment\", comment);\n              }\n              var textRange = issue.getTextRangeWithHash();\n              var lineWithHash = issue.getLineWithHash();\n              if (textRange != null) {\n                issueEntity.setProperty(\"startLine\", textRange.getStartLine());\n                issueEntity.setProperty(\"startLineOffset\", textRange.getStartLineOffset());\n                issueEntity.setProperty(\"endLine\", textRange.getEndLine());\n                issueEntity.setProperty(\"endLineOffset\", textRange.getEndLineOffset());\n                issueEntity.setProperty(\"rangeHash\", textRange.getHash());\n              }\n              if (lineWithHash != null) {\n                issueEntity.setProperty(\"lineHash\", lineWithHash.getHash());\n              }\n\n              issueEntity.setLink(\"file\", fileEntity);\n              fileEntity.addLink(\"issues\", issueEntity);\n            });\n\n            scopeEntity.setProperty(\"name\", configScopeId);\n            fileEntity.setProperty(\"path\", filePath.toString());\n            scopeEntity.addLink(\"files\", fileEntity);\n          });\n      });\n      try {\n        CompressBackupUtil.backup(entityStore, xodusBackupPath.toFile(), false);\n      } catch (Exception e) {\n        throw new IllegalStateException(\"Unable to backup server issue database\", e);\n      }\n    }\n\n    private void populateDatabase(TestDatabase database) {\n      if (!usingXodus) {\n        var localOnlyIssuesRepository = new LocalOnlyIssuesRepository(database.dsl());\n        localOnlyIssues.forEach(issue -> localOnlyIssuesRepository.storeLocalOnlyIssue(configScopeId, issue));\n      }\n    }\n  }\n\n  private ConfigurationScopeStorageFixture() {\n    // utility class\n  }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/storage/ProjectStorageFixture.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.storage;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport java.time.ZonedDateTime;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport javax.annotation.Nullable;\nimport jetbrains.exodus.entitystore.Entity;\nimport jetbrains.exodus.entitystore.PersistentEntityStores;\nimport jetbrains.exodus.entitystore.StoreTransaction;\nimport jetbrains.exodus.env.Environments;\nimport jetbrains.exodus.util.CompressBackupUtil;\nimport org.apache.commons.io.FileUtils;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.serverapi.hotspot.ServerHotspot;\nimport org.sonarsource.sonarlint.core.serverapi.util.ProtobufUtil;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.RangeLevelServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerDependencyRisk;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerTaintIssue;\nimport org.sonarsource.sonarlint.core.serverconnection.proto.Sonarlint;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.HotspotReviewStatusBinding;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.InstantBinding;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.IssueSeverityBinding;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.IssueStatusBinding;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.IssueTypeBinding;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ProjectStoragePaths;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ProtobufFileUtil;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ServerFindingRepository;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.UuidBinding;\n\nimport static org.apache.commons.lang3.StringUtils.trimToEmpty;\n\npublic class ProjectStorageFixture {\n\n  public static class ProjectStorageBuilder {\n    private final String connectionId;\n    private final String projectKey;\n    private final List<RuleSetBuilder> ruleSets = new ArrayList<>();\n    private final List<BranchBuilder> branches = new ArrayList<>();\n    private final Map<String, String> projectSettings = new HashMap<>();\n    private ZonedDateTime lastSmartNotificationPoll;\n    private Sonarlint.NewCodeDefinition newCodeDefinition;\n    private boolean shouldThrowOnReadLastEvenPollingTime = false;\n\n    public ProjectStorageBuilder(String connectionId, String projectKey) {\n      this.connectionId = connectionId;\n      this.projectKey = projectKey;\n    }\n\n    public ProjectStorageBuilder withRuleSet(String languageKey, Consumer<RuleSetBuilder> consumer) {\n      var ruleSetBuilder = new RuleSetBuilder(languageKey);\n      consumer.accept(ruleSetBuilder);\n      ruleSets.add(ruleSetBuilder);\n      return this;\n    }\n\n    public ProjectStorageBuilder withNewCodeDefinition(Sonarlint.NewCodeDefinition newCodeDefinition) {\n      this.newCodeDefinition = newCodeDefinition;\n      return this;\n    }\n\n    public ProjectStorageBuilder withSetting(String key, String value) {\n      projectSettings.put(key, value);\n      return this;\n    }\n\n    public ProjectStorageBuilder withMainBranch(Consumer<BranchBuilder> consumer) {\n      return withMainBranch(\"main\", consumer);\n    }\n\n    public ProjectStorageBuilder withMainBranch(String name) {\n      return withMainBranch(name, branch -> {\n      });\n    }\n\n    public ProjectStorageBuilder withMainBranch(String name, Consumer<BranchBuilder> consumer) {\n      var branchBuilder = new BranchBuilder(name, true);\n      consumer.accept(branchBuilder);\n      branches.add(branchBuilder);\n      return this;\n    }\n\n    public ProjectStorageBuilder withNonMainBranch(String name) {\n      var branchBuilder = new BranchBuilder(name, false);\n      branches.add(branchBuilder);\n      return this;\n    }\n\n    public ProjectStorageBuilder withLastSmartNotificationPoll(ZonedDateTime dateTime) {\n      this.lastSmartNotificationPoll = dateTime;\n      return this;\n    }\n\n    /**\n     * It writes an illegal content to the last_event_polling.pb file,\n     * that leads to StorageException being thrown on the file read.\n     * It emulates the situation when this file is not accessible during sync.\n     */\n    public ProjectStorageBuilder shouldThrowOnReadLastEvenPollingTime() {\n      this.shouldThrowOnReadLastEvenPollingTime = true;\n      return this;\n    }\n\n    void populate(Path projectsRootPath, TestDatabase database) {\n      var projectFolder = projectsRootPath.resolve(ProjectStoragePaths.encodeForFs(projectKey));\n      try {\n        FileUtils.forceMkdir(projectFolder.toFile());\n      } catch (IOException e) {\n        throw new IllegalStateException(e);\n      }\n\n      createAnalyzerConfig(projectFolder);\n      createSmartNotificationPoll(projectFolder);\n      createServerBranches(projectFolder);\n      createFindings(projectFolder);\n      createNewCodeDefinition(projectFolder);\n\n      populateDatabase(database);\n    }\n\n    private void createNewCodeDefinition(Path projectFolder) {\n      if (newCodeDefinition != null) {\n        ProtobufFileUtil.writeToFile(newCodeDefinition, projectFolder.resolve(\"new_code_definition.pb\"));\n      }\n    }\n\n    private void createSmartNotificationPoll(Path projectFolder) {\n      if (shouldThrowOnReadLastEvenPollingTime) {\n        try {\n          FileUtils.write(projectFolder.resolve(\"last_event_polling.pb\").toFile(), \"illegal content\", StandardCharsets.UTF_8);\n        } catch (IOException e) {\n          throw new IllegalStateException(\"Unable to create the last smart notification poll file\", e);\n        }\n      } else if (lastSmartNotificationPoll != null) {\n        var lastPoll = Sonarlint.LastEventPolling.newBuilder()\n          .setLastEventPolling(lastSmartNotificationPoll.toInstant().toEpochMilli())\n          .build();\n        ProtobufFileUtil.writeToFile(lastPoll, projectFolder.resolve(\"last_event_polling.pb\"));\n      }\n    }\n\n    private void createAnalyzerConfig(Path projectFolder) {\n      Map<String, Sonarlint.RuleSet> protoRuleSets = new HashMap<>();\n      ruleSets.forEach(ruleSet -> {\n        var ruleSetBuilder = Sonarlint.RuleSet.newBuilder();\n        ruleSet.activeRules.forEach(activeRule -> ruleSetBuilder.addRule(Sonarlint.RuleSet.ActiveRule.newBuilder()\n          .setRuleKey(activeRule.ruleKey)\n          .setSeverity(activeRule.severity)\n          .setTemplateKey(trimToEmpty(activeRule.templateKey))\n          .putAllParams(activeRule.params)\n          .build()));\n        protoRuleSets.put(ruleSet.languageKey, ruleSetBuilder.build());\n      });\n      var analyzerConfiguration = Sonarlint.AnalyzerConfiguration.newBuilder()\n        .putAllSettings(projectSettings)\n        .putAllRuleSetsByLanguageKey(protoRuleSets).build();\n      ProtobufFileUtil.writeToFile(analyzerConfiguration, projectFolder.resolve(\"analyzer_config.pb\"));\n    }\n\n    private void createServerBranches(Path projectFolder) {\n      if (branches.isEmpty()) {\n        return;\n      }\n      var projectBranches = Sonarlint.ProjectBranches.newBuilder()\n        .setMainBranchName(\n          branches.stream().filter(branch -> branch.isMain).map(branch -> branch.name).findFirst().orElseThrow(() -> new IllegalArgumentException(\"No main branch defined\")))\n        .addAllBranchName(branches.stream().map(branch -> branch.name).toList())\n        .build();\n      ProtobufFileUtil.writeToFile(projectBranches, projectFolder.resolve(\"project_branches.pb\"));\n    }\n\n    private void createFindings(Path projectFolder) {\n      if (branches.isEmpty()) {\n        return;\n      }\n      var xodusTempDbPath = projectFolder.resolve(\"xodus_temp_db\");\n      var xodusBackupPath = projectFolder.resolve(\"issues\").resolve(\"backup.tar.gz\");\n      try {\n        Files.createDirectories(xodusBackupPath.getParent());\n      } catch (IOException e) {\n        throw new IllegalStateException(\"Unable to create the Xodus backup parent folders\", e);\n      }\n      var environment = Environments.newInstance(xodusTempDbPath.toAbsolutePath().toFile());\n      var entityStore = PersistentEntityStores.newInstance(environment);\n      entityStore.executeInTransaction(txn -> {\n        entityStore.registerCustomPropertyType(txn, IssueSeverity.class, new IssueSeverityBinding());\n        entityStore.registerCustomPropertyType(txn, RuleType.class, new IssueTypeBinding());\n        entityStore.registerCustomPropertyType(txn, Instant.class, new InstantBinding());\n        entityStore.registerCustomPropertyType(txn, HotspotReviewStatus.class, new HotspotReviewStatusBinding());\n        entityStore.registerCustomPropertyType(txn, UUID.class, new UuidBinding());\n        entityStore.registerCustomPropertyType(txn, IssueStatus.class, new IssueStatusBinding());\n        branches.forEach(branch -> {\n          var branchEntity = txn.newEntity(\"Branch\");\n          branchEntity.setProperty(\"name\", branch.name);\n          var issuesByFilePath = branch.serverIssues.stream()\n            .map(ServerIssueFixtures.ServerIssueBuilder::build)\n            .collect(Collectors.groupingBy(ServerIssueFixtures.ServerIssue::filePath));\n          var taintIssuesByFilePath = branch.serverTaintIssues.stream()\n            .map(ServerTaintIssueFixtures.ServerTaintIssueBuilder::build)\n            .collect(Collectors.groupingBy(ServerTaintIssueFixtures.ServerTaintIssue::filePath));\n          var hotspotsByFilePath = branch.serverHotspots.stream()\n            .map(ServerSecurityHotspotFixture.ServerSecurityHotspotBuilder::build)\n            .collect(Collectors.groupingBy(ServerSecurityHotspotFixture.ServerHotspot::filePath));\n          Stream.of(issuesByFilePath, taintIssuesByFilePath, hotspotsByFilePath)\n            .flatMap(map -> map.keySet().stream())\n            .toList()\n            .forEach(filePath -> {\n              var fileEntity = txn.newEntity(\"File\");\n              fileEntity.setProperty(\"path\", filePath);\n              branchEntity.addLink(\"files\", fileEntity);\n              issuesByFilePath.getOrDefault(filePath, Collections.emptyList())\n                .forEach(issue -> linkIssueEntity(txn, issue, fileEntity));\n\n              taintIssuesByFilePath.getOrDefault(filePath, Collections.emptyList())\n                .forEach(taint -> linkTaintEntity(txn, taint, fileEntity, branchEntity));\n\n              hotspotsByFilePath.getOrDefault(filePath, Collections.emptyList())\n                .forEach(hotspot -> linkHotshotEntity(txn, hotspot, fileEntity));\n            });\n\n          branch.serverDependencyRisks.stream()\n            .map(ServerDependencyRiskFixtures.ServerDependencyRiskBuilder::build)\n            .forEach(dependencyRisk -> linkDependencyRiskEntity(txn, dependencyRisk, branchEntity));\n        });\n      });\n      try {\n        CompressBackupUtil.backup(entityStore, xodusBackupPath.toFile(), false);\n      } catch (Exception e) {\n        throw new IllegalStateException(\"Unable to backup server issue database\", e);\n      }\n    }\n\n    private static void linkIssueEntity(StoreTransaction txn, ServerIssueFixtures.ServerIssue issue, Entity fileEntity) {\n      var issueEntity = txn.newEntity(\"Issue\");\n      issueEntity.setProperty(\"key\", issue.key());\n      issueEntity.setProperty(\"type\", issue.ruleType());\n      issueEntity.setProperty(\"resolved\", issue.resolved());\n      if (issue.resolutionStatus() != null) {\n        issueEntity.setProperty(\"resolutionStatus\", issue.resolutionStatus());\n      }\n      issueEntity.setProperty(\"ruleKey\", issue.ruleKey());\n      issueEntity.setBlobString(\"message\", issue.message());\n      issueEntity.setProperty(\"creationDate\", issue.introductionDate());\n      var userSeverity = issue.userSeverity();\n      if (userSeverity != null) {\n        issueEntity.setProperty(\"userSeverity\", userSeverity);\n      }\n      if (issue.lineNumber() != null && issue.lineHash() != null) {\n        issueEntity.setBlobString(\"lineHash\", issue.lineHash());\n        issueEntity.setProperty(\"startLine\", issue.lineNumber());\n      } else if (issue.textRangeWithHash() != null) {\n        var textRange = issue.textRangeWithHash();\n        issueEntity.setProperty(\"startLine\", textRange.getStartLine());\n        issueEntity.setProperty(\"startLineOffset\", textRange.getStartLineOffset());\n        issueEntity.setProperty(\"endLine\", textRange.getEndLine());\n        issueEntity.setProperty(\"endLineOffset\", textRange.getEndLineOffset());\n        issueEntity.setBlobString(\"rangeHash\", textRange.getHash());\n      }\n      issueEntity.setBlob(\"impacts\", toProtoImpacts(issue.impacts()));\n\n      issueEntity.setLink(\"file\", fileEntity);\n      fileEntity.addLink(\"issues\", issueEntity);\n    }\n\n    private static void linkTaintEntity(StoreTransaction txn, ServerTaintIssueFixtures.ServerTaintIssue taint, Entity fileEntity, Entity branchEntity) {\n      var taintIssueEntity = txn.newEntity(\"TaintIssue\");\n      taintIssueEntity.setProperty(\"id\", UUID.randomUUID());\n      taintIssueEntity.setProperty(\"key\", taint.key());\n      taintIssueEntity.setProperty(\"type\", taint.type());\n      taintIssueEntity.setProperty(\"resolved\", taint.resolved());\n      if (taint.resolutionStatus() != null) {\n        taintIssueEntity.setProperty(\"resolutionStatus\", taint.resolutionStatus());\n      }\n      taintIssueEntity.setProperty(\"ruleKey\", taint.ruleKey());\n      taintIssueEntity.setBlobString(\"message\", taint.message());\n      taintIssueEntity.setProperty(\"creationDate\", taint.creationDate());\n      taintIssueEntity.setProperty(\"severity\", taint.severity());\n      if (taint.textRange() != null) {\n        var textRange = taint.textRange();\n        taintIssueEntity.setProperty(\"startLine\", textRange.getStartLine());\n        taintIssueEntity.setProperty(\"startLineOffset\", textRange.getStartLineOffset());\n        taintIssueEntity.setProperty(\"endLine\", textRange.getEndLine());\n        taintIssueEntity.setProperty(\"endLineOffset\", textRange.getEndLineOffset());\n        taintIssueEntity.setBlobString(\"rangeHash\", textRange.getHash());\n      }\n      taintIssueEntity.setBlob(\"flows\", toProtoFlows(taint.flows()));\n      if (taint.ruleDescriptionContextKey() != null) {\n        taintIssueEntity.setProperty(\"ruleDescriptionContextKey\", taint.ruleDescriptionContextKey());\n      }\n      if (taint.cleanCodeAttribute() != null) {\n        taintIssueEntity.setProperty(\"cleanCodeAttribute\", taint.cleanCodeAttribute().name());\n      }\n      taintIssueEntity.setBlob(\"impacts\", toProtoImpacts(taint.impacts()));\n\n      taintIssueEntity.setLink(\"file\", fileEntity);\n      fileEntity.addLink(\"taintIssues\", taintIssueEntity);\n      branchEntity.addLink(\"taintIssues\", taintIssueEntity);\n      taintIssueEntity.setLink(\"branch\", branchEntity);\n    }\n\n    public static InputStream toProtoFlows(List<ServerTaintIssue.Flow> flows) {\n      var buffer = new ByteArrayOutputStream();\n      ProtobufUtil.writeMessages(buffer, flows.stream().map(ProjectStorageBuilder::toProtoFlow).toList());\n      return new ByteArrayInputStream(buffer.toByteArray());\n    }\n\n    public static InputStream toProtoImpacts(Map<SoftwareQuality, ImpactSeverity> impacts) {\n      var buffer = new ByteArrayOutputStream();\n      ProtobufUtil.writeMessages(buffer, impacts.entrySet().stream().map(ProjectStorageBuilder::toProtoImpact).toList());\n      return new ByteArrayInputStream(buffer.toByteArray());\n    }\n\n    private static Sonarlint.Flow toProtoFlow(ServerTaintIssue.Flow javaFlow) {\n      var flowBuilder = Sonarlint.Flow.newBuilder();\n      javaFlow.locations().forEach(l -> flowBuilder.addLocation(toProtoLocation(l)));\n      return flowBuilder.build();\n    }\n\n    private static Sonarlint.Impact toProtoImpact(Map.Entry<SoftwareQuality, ImpactSeverity> impact) {\n      return Sonarlint.Impact.newBuilder()\n        .setSoftwareQuality(impact.getKey().name())\n        .setSeverity(impact.getValue().name())\n        .build();\n    }\n\n    private static Sonarlint.Location toProtoLocation(ServerTaintIssue.ServerIssueLocation l) {\n      var location = Sonarlint.Location.newBuilder();\n      var filePath = l.filePath();\n      if (filePath != null) {\n        location.setFilePath(filePath.toString());\n      }\n      location.setMessage(l.message());\n      var textRange = l.textRange();\n      if (textRange != null) {\n        location.setTextRange(Sonarlint.TextRange.newBuilder()\n          .setStartLine(textRange.getStartLine())\n          .setStartLineOffset(textRange.getStartLineOffset())\n          .setEndLine(textRange.getEndLine())\n          .setEndLineOffset(textRange.getEndLineOffset())\n          .setHash(textRange.getHash()));\n      }\n      return location.build();\n    }\n\n    private static void linkHotshotEntity(StoreTransaction txn, ServerSecurityHotspotFixture.ServerHotspot hotspot, Entity fileEntity) {\n      var hotspotEntity = txn.newEntity(\"Hotspot\");\n      hotspotEntity.setProperty(\"key\", hotspot.key());\n      hotspotEntity.setProperty(\"ruleKey\", hotspot.ruleKey());\n      hotspotEntity.setBlobString(\"message\", hotspot.message());\n      hotspotEntity.setProperty(\"creationDate\", hotspot.introductionDate());\n      var textRange = hotspot.textRangeWithHash();\n      hotspotEntity.setProperty(\"startLine\", textRange.getStartLine());\n      hotspotEntity.setProperty(\"startLineOffset\", textRange.getStartLineOffset());\n      hotspotEntity.setProperty(\"endLine\", textRange.getEndLine());\n      hotspotEntity.setProperty(\"endLineOffset\", textRange.getEndLineOffset());\n      hotspotEntity.setBlobString(\"rangeHash\", textRange.getHash());\n\n      hotspotEntity.setProperty(\"status\", hotspot.status());\n      hotspotEntity.setProperty(\"vulnerabilityProbability\", hotspot.vulnerabilityProbability().toString());\n      if (hotspot.assignee() != null) {\n        hotspotEntity.setProperty(\"assignee\", hotspot.assignee());\n      }\n\n      hotspotEntity.setLink(\"file\", fileEntity);\n      fileEntity.addLink(\"hotspots\", hotspotEntity);\n    }\n\n    private static void linkDependencyRiskEntity(StoreTransaction txn, ServerDependencyRisk dependencyRisk, Entity branchEntity) {\n      var dependencyRiskEntity = txn.newEntity(\"DependencyRisk\");\n      dependencyRiskEntity.setProperty(\"key\", dependencyRisk.key().toString());\n      dependencyRiskEntity.setProperty(\"type\", dependencyRisk.type().name());\n      dependencyRiskEntity.setProperty(\"severity\", dependencyRisk.severity().name());\n      dependencyRiskEntity.setProperty(\"quality\", dependencyRisk.quality().name());\n      dependencyRiskEntity.setProperty(\"status\", dependencyRisk.status().name());\n      dependencyRiskEntity.setProperty(\"packageName\", dependencyRisk.packageName());\n      dependencyRiskEntity.setProperty(\"packageVersion\", dependencyRisk.packageVersion());\n      if (dependencyRisk.vulnerabilityId() != null) {\n        dependencyRiskEntity.setProperty(\"vulnerabilityId\", dependencyRisk.vulnerabilityId());\n      }\n      if (dependencyRisk.cvssScore() != null) {\n        dependencyRiskEntity.setProperty(\"cvssScore\", dependencyRisk.cvssScore());\n      }\n      dependencyRiskEntity.setProperty(\"transitions\", dependencyRisk.transitions().stream()\n        .map(Enum::name)\n        .collect(Collectors.joining(\",\")));\n\n      branchEntity.addLink(\"dependencyRisks\", dependencyRiskEntity);\n      dependencyRiskEntity.setLink(\"branch\", branchEntity);\n    }\n\n    public void populateDatabase(TestDatabase database) {\n      var serverFindingRepository = new ServerFindingRepository(database.dsl(), connectionId, projectKey);\n      branches.forEach(branch -> {\n\n        serverFindingRepository.replaceAllIssuesOfBranch(branch.name,\n          branch.serverIssues.stream().map(ServerIssueFixtures.ServerIssueBuilder::build).<ServerIssue<?>>map(issue -> new RangeLevelServerIssue(\n            UUID.randomUUID(),\n            issue.key(),\n            issue.resolved(),\n            issue.resolutionStatus(),\n            issue.ruleKey(),\n            issue.message(),\n            Paths.get(issue.filePath()),\n            issue.introductionDate(),\n            issue.userSeverity(),\n            issue.ruleType(),\n            issue.textRangeWithHash(),\n            issue.impacts())).toList(),\n          Set.of());\n\n        serverFindingRepository.replaceAllHotspotsOfBranch(branch.name,\n          branch.serverHotspots.stream().map(ServerSecurityHotspotFixture.ServerSecurityHotspotBuilder::build).map(hotspot -> new ServerHotspot(\n            UUID.randomUUID(),\n            hotspot.key(),\n            hotspot.ruleKey(),\n            hotspot.message(),\n            Paths.get(hotspot.filePath()),\n            hotspot.textRangeWithHash(),\n            hotspot.introductionDate(),\n            hotspot.status(),\n            hotspot.vulnerabilityProbability(),\n            hotspot.assignee())).toList(),\n          Set.of());\n\n        serverFindingRepository.replaceAllTaintsOfBranch(branch.name,\n          branch.serverTaintIssues.stream().map(ServerTaintIssueFixtures.ServerTaintIssueBuilder::build).map(i -> new ServerTaintIssue(\n            i.id(),\n            i.key(),\n            i.resolved(),\n            i.resolutionStatus(),\n            i.ruleKey(),\n            i.message(),\n            Paths.get(i.filePath()),\n            i.creationDate(),\n            i.severity(),\n            i.type(),\n            i.textRange(),\n            i.ruleDescriptionContextKey(),\n            i.cleanCodeAttribute(),\n            i.impacts(),\n            List.of())).toList(),\n          Set.of());\n\n        serverFindingRepository.replaceAllDependencyRisksOfBranch(branch.name,\n          branch.serverDependencyRisks.stream().map(ServerDependencyRiskFixtures.ServerDependencyRiskBuilder::build).map(i -> new ServerDependencyRisk(\n            i.key(),\n            i.type(),\n            i.severity(),\n            i.quality(),\n            i.status(),\n            i.packageName(),\n            i.packageVersion(),\n            i.vulnerabilityId(),\n            i.cvssScore(),\n            i.transitions())).toList());\n      });\n    }\n\n    public static class RuleSetBuilder {\n      private final String languageKey;\n      private final List<ActiveRule> activeRules = new ArrayList<>();\n\n      public RuleSetBuilder(String languageKey) {\n        this.languageKey = languageKey;\n      }\n\n      public RuleSetBuilder withActiveRule(String ruleKey, String severity) {\n        return withActiveRule(ruleKey, severity, Map.of());\n      }\n\n      public RuleSetBuilder withActiveRule(String ruleKey, String severity, Map<String, String> params) {\n        activeRules.add(new ActiveRule(ruleKey, severity, null, params));\n        return this;\n      }\n\n      public RuleSetBuilder withCustomActiveRule(String ruleKey, String templateKey, String severity, Map<String, String> params) {\n        activeRules.add(new ActiveRule(ruleKey, severity, templateKey, params));\n        return this;\n      }\n    }\n\n    public static class BranchBuilder {\n      private final List<ServerIssueFixtures.ServerIssueBuilder> serverIssues = new ArrayList<>();\n      private final List<ServerTaintIssueFixtures.ServerTaintIssueBuilder> serverTaintIssues = new ArrayList<>();\n      private final List<ServerDependencyRiskFixtures.ServerDependencyRiskBuilder> serverDependencyRisks = new ArrayList<>();\n      private final List<ServerSecurityHotspotFixture.ServerSecurityHotspotBuilder> serverHotspots = new ArrayList<>();\n      private final String name;\n      private final boolean isMain;\n\n      public BranchBuilder(String name, boolean isMain) {\n        this.name = name;\n        this.isMain = isMain;\n      }\n\n      public BranchBuilder withIssue(ServerIssueFixtures.ServerIssueBuilder serverIssueBuilder) {\n        serverIssues.add(serverIssueBuilder);\n        return this;\n      }\n\n      public BranchBuilder withTaintIssue(ServerTaintIssueFixtures.ServerTaintIssueBuilder serverTaintIssueBuilder) {\n        serverTaintIssues.add(serverTaintIssueBuilder);\n        return this;\n      }\n\n      public BranchBuilder withDependencyRisk(ServerDependencyRiskFixtures.ServerDependencyRiskBuilder serverDependencyRiskBuilder) {\n        serverDependencyRisks.add(serverDependencyRiskBuilder);\n        return this;\n      }\n\n      public BranchBuilder withHotspot(ServerSecurityHotspotFixture.ServerSecurityHotspotBuilder serverHotspotBuilder) {\n        serverHotspots.add(serverHotspotBuilder);\n        return this;\n      }\n    }\n\n    private record ActiveRule(String ruleKey, String severity, @Nullable String templateKey, Map<String, String> params) {\n    }\n  }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/storage/ServerDependencyRiskFixtures.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.storage;\n\nimport java.util.List;\nimport java.util.UUID;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.serverconnection.issues.ServerDependencyRisk;\n\npublic class ServerDependencyRiskFixtures {\n\n  private ServerDependencyRiskFixtures() {\n    // utility class\n  }\n\n  public static ServerDependencyRiskBuilder aServerDependencyRisk() {\n    return new ServerDependencyRiskBuilder();\n  }\n\n  public static class ServerDependencyRiskBuilder {\n    private UUID key = UUID.randomUUID();\n    private ServerDependencyRisk.Type type = ServerDependencyRisk.Type.VULNERABILITY;\n    private ServerDependencyRisk.Severity severity = ServerDependencyRisk.Severity.HIGH;\n    private ServerDependencyRisk.SoftwareQuality quality = ServerDependencyRisk.SoftwareQuality.SECURITY;\n    private ServerDependencyRisk.Status status = ServerDependencyRisk.Status.OPEN;\n    private String packageName = \"com.example.vulnerable\";\n    private String packageVersion = \"1.0.0\";\n    @Nullable\n    private String vulnerabilityId = null;\n    @Nullable\n    private String cvssScore = null;\n    private List<ServerDependencyRisk.Transition> transitions = List.of(ServerDependencyRisk.Transition.CONFIRM, ServerDependencyRisk.Transition.ACCEPT);\n\n    public ServerDependencyRiskBuilder withKey(UUID key) {\n      this.key = key;\n      return this;\n    }\n\n    public ServerDependencyRiskBuilder withType(ServerDependencyRisk.Type type) {\n      this.type = type;\n      return this;\n    }\n\n    public ServerDependencyRiskBuilder withSeverity(ServerDependencyRisk.Severity severity) {\n      this.severity = severity;\n      return this;\n    }\n\n    public ServerDependencyRiskBuilder withQuality(ServerDependencyRisk.SoftwareQuality quality) {\n      this.quality = quality;\n      return this;\n    }\n\n    public ServerDependencyRiskBuilder withStatus(ServerDependencyRisk.Status status) {\n      this.status = status;\n      return this;\n    }\n\n    public ServerDependencyRiskBuilder withPackageName(String packageName) {\n      this.packageName = packageName;\n      return this;\n    }\n\n    public ServerDependencyRiskBuilder withPackageVersion(String packageVersion) {\n      this.packageVersion = packageVersion;\n      return this;\n    }\n\n    public ServerDependencyRiskBuilder withVulnerabilityId(String vulnerabilityId) {\n      this.vulnerabilityId = vulnerabilityId;\n      return this;\n    }\n\n    public ServerDependencyRiskBuilder withCvssScore(String cvssScore) {\n      this.cvssScore = cvssScore;\n      return this;\n    }\n\n    public ServerDependencyRiskBuilder withTransitions(List<ServerDependencyRisk.Transition> transitions) {\n      this.transitions = transitions;\n      return this;\n    }\n\n    public ServerDependencyRisk build() {\n      return new ServerDependencyRisk(key, type, severity, quality, status, packageName, packageVersion,\n        vulnerabilityId, cvssScore, transitions);\n    }\n  }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/storage/ServerIssueFixtures.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.storage;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.Collections;\nimport java.util.Map;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\n\npublic class ServerIssueFixtures {\n  public static ServerIssueBuilder aServerIssue(String key) {\n    return new ServerIssueBuilder(key);\n  }\n\n  public static LegacyServerIssueBuilder aLegacyServerIssue(String key) {\n    return new LegacyServerIssueBuilder(key);\n  }\n\n  public static class ServerIssueBuilder extends AbstractServerIssueBuilder<ServerIssueBuilder> {\n    private TextRangeWithHash textRangeWithHash = new TextRangeWithHash(1, 2, 3, 4, \"rangeHash\");\n    private String ruleKey = \"ruleKey\";\n\n    private String filePath = \"file/path\";\n    private String message = \"message\";\n\n    public ServerIssueBuilder(String key) {\n      super(key);\n    }\n\n    public ServerIssueBuilder withRuleKey(String ruleKey) {\n      this.ruleKey = ruleKey;\n      return this;\n    }\n\n    public ServerIssueBuilder withTextRange(TextRangeWithHash textRange) {\n      this.textRangeWithHash = textRange;\n      return this;\n    }\n\n    public ServerIssueBuilder withFilePath(String filePath) {\n      this.filePath = filePath;\n      return this;\n    }\n\n    public ServerIssueBuilder withMessage(String message) {\n      this.message = message;\n      return this;\n    }\n\n    public ServerIssue build() {\n      return new ServerIssue(key, resolved, resolutionStatus, ruleKey,\n        message, Path.of(filePath).toString(), introductionDate, issueSeverity, ruleType,\n        textRangeWithHash, null, null, impacts);\n    }\n  }\n\n  public static class LegacyServerIssueBuilder extends AbstractServerIssueBuilder<LegacyServerIssueBuilder> {\n    private int lineNumber = 1;\n    private String lineHash = \"lineHash\";\n\n    public LegacyServerIssueBuilder(String key) {\n      super(key);\n    }\n\n    public LegacyServerIssueBuilder withLine(int number, String hash) {\n      this.lineNumber = number;\n      this.lineHash = hash;\n      return this;\n    }\n\n    public ServerIssue build() {\n      return new ServerIssue(key, resolved, resolutionStatus, \"ruleKey\", \"message\", Path.of(\"file/path\").toString(), introductionDate, issueSeverity, ruleType,\n        null, lineNumber, lineHash, impacts);\n    }\n  }\n\n  public abstract static class AbstractServerIssueBuilder<T extends AbstractServerIssueBuilder<T>> {\n    protected final String key;\n    protected boolean resolved = false;\n    protected IssueStatus resolutionStatus;\n    protected Instant introductionDate = Instant.now();\n    protected RuleType ruleType = RuleType.BUG;\n    protected IssueSeverity issueSeverity;\n    protected Map<SoftwareQuality, ImpactSeverity> impacts = Collections.emptyMap();\n\n    protected AbstractServerIssueBuilder(String key) {\n      this.key = key;\n    }\n\n    public T withIntroductionDate(Instant introductionDate) {\n      this.introductionDate = introductionDate;\n      return (T) this;\n    }\n\n    public T resolved(IssueStatus resolutionStatus) {\n      this.resolved = true;\n      this.resolutionStatus = resolutionStatus;\n      return (T) this;\n    }\n\n    public T open() {\n      this.resolved = false;\n      resolutionStatus = null;\n      return (T) this;\n    }\n\n    public T withType(RuleType ruleType) {\n      this.ruleType = ruleType;\n      return (T) this;\n    }\n\n    public T withSeverity(IssueSeverity issueSeverity) {\n      this.issueSeverity = issueSeverity;\n      return (T) this;\n    }\n\n    public T withImpacts(Map<SoftwareQuality, ImpactSeverity> impacts) {\n      this.impacts = impacts;\n      return (T) this;\n    }\n  }\n\n  public record ServerIssue(String key, boolean resolved, IssueStatus resolutionStatus, String ruleKey, String message, String filePath, Instant introductionDate,\n                            IssueSeverity userSeverity, RuleType ruleType, @Nullable TextRangeWithHash textRangeWithHash, @Nullable Integer lineNumber, @Nullable String lineHash,\n                            Map<SoftwareQuality, ImpactSeverity> impacts) { }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/storage/ServerSecurityHotspotFixture.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.storage;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.HotspotReviewStatus;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\n\npublic class ServerSecurityHotspotFixture {\n  public static ServerSecurityHotspotBuilder aServerHotspot(String key) {\n    return new ServerSecurityHotspotBuilder(key);\n  }\n\n  public static class ServerSecurityHotspotBuilder {\n    private final String key;\n    private Instant introductionDate = Instant.now();\n    private HotspotReviewStatus status = HotspotReviewStatus.TO_REVIEW;\n    private VulnerabilityProbability vulnerabilityProbability = VulnerabilityProbability.MEDIUM;\n    private String assignee;\n    private TextRangeWithHash textRangeWithHash = new TextRangeWithHash(1, 2, 3, 4, \"rangeHash\");\n    private String ruleKey = \"ruleKey\";\n    private String filePath = Path.of(\"file/path\").toString();\n    private String message = \"message\";\n\n    public ServerSecurityHotspotBuilder(String key) {\n      this.key = key;\n    }\n\n    public ServerSecurityHotspotBuilder withRuleKey(String ruleKey) {\n      this.ruleKey = ruleKey;\n      return this;\n    }\n\n    public ServerSecurityHotspotBuilder withTextRange(TextRangeWithHash textRange) {\n      this.textRangeWithHash = textRange;\n      return this;\n    }\n\n    public ServerSecurityHotspotBuilder withIntroductionDate(Instant introductionDate) {\n      this.introductionDate = introductionDate;\n      return this;\n    }\n\n    public ServerSecurityHotspotBuilder withStatus(HotspotReviewStatus status) {\n      this.status = status;\n      return this;\n    }\n\n    public ServerSecurityHotspotBuilder withVulnerabilityProbability(VulnerabilityProbability vulnerabilityProbability) {\n      this.vulnerabilityProbability = vulnerabilityProbability;\n      return this;\n    }\n\n    public ServerSecurityHotspotBuilder withAssignee(String assignee) {\n      this.assignee = assignee;\n      return this;\n    }\n\n    public ServerSecurityHotspotBuilder withFilePath(String filePath) {\n      this.filePath = filePath;\n      return this;\n    }\n\n    public ServerSecurityHotspotBuilder withMessage(String message) {\n      this.message = message;\n      return this;\n    }\n\n    public ServerHotspot build() {\n      return new ServerHotspot(key, ruleKey, message, filePath, introductionDate, null, textRangeWithHash, status, vulnerabilityProbability, assignee);\n    }\n  }\n\n  public record ServerHotspot(String key, String ruleKey, String message, String filePath, Instant introductionDate, @Nullable IssueSeverity userSeverity,\n    TextRangeWithHash textRangeWithHash, HotspotReviewStatus status, VulnerabilityProbability vulnerabilityProbability, String assignee) {\n  }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/storage/ServerTaintIssueFixtures.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.storage;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport javax.annotation.Nullable;\nimport org.sonarsource.sonarlint.core.commons.CleanCodeAttribute;\nimport org.sonarsource.sonarlint.core.commons.ImpactSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueSeverity;\nimport org.sonarsource.sonarlint.core.commons.IssueStatus;\nimport org.sonarsource.sonarlint.core.commons.RuleType;\nimport org.sonarsource.sonarlint.core.commons.SoftwareQuality;\nimport org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash;\n\nimport static org.sonarsource.sonarlint.core.serverconnection.issues.ServerTaintIssue.*;\n\npublic class ServerTaintIssueFixtures {\n\n  public static ServerTaintIssueBuilder aServerTaintIssue(String key) {\n    return new ServerTaintIssueBuilder(key);\n  }\n\n  public static class ServerTaintIssueBuilder extends AbstractServerTaintIssueBuilder<ServerTaintIssueBuilder> {\n    private TextRangeWithHash textRangeWithHash = new TextRangeWithHash(1, 2, 3, 4, \"rangeHash\");\n    private String ruleKey = \"ruleKey\";\n    private String filePath = \"file/path\";\n\n    public ServerTaintIssueBuilder(String key) {\n      super(key);\n    }\n\n    public ServerTaintIssueBuilder withRuleKey(String ruleKey) {\n      this.ruleKey = ruleKey;\n      return this;\n    }\n\n    public ServerTaintIssueBuilder withFilePath(String filePath) {\n      this.filePath = filePath;\n      return this;\n    }\n\n    public ServerTaintIssueBuilder withTextRange(TextRangeWithHash textRange) {\n      this.textRangeWithHash = textRange;\n      return this;\n    }\n\n    public ServerTaintIssue build() {\n      return new ServerTaintIssue(UUID.randomUUID(), key, resolved, resolutionStatus, ruleKey, \"message\", Path.of(filePath).toString(), introductionDate,\n        issueSeverity, ruleType, new ArrayList<>(), textRangeWithHash, \"contextKey\", CleanCodeAttribute.CONVENTIONAL,\n        Map.of(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.MEDIUM));\n    }\n  }\n\n  public abstract static class AbstractServerTaintIssueBuilder<T extends AbstractServerTaintIssueBuilder<T>> {\n    protected final String key;\n    protected boolean resolved = false;\n    protected IssueStatus resolutionStatus;\n    protected Instant introductionDate = Instant.now();\n    protected RuleType ruleType = RuleType.BUG;\n    protected IssueSeverity issueSeverity = IssueSeverity.MINOR;\n\n    protected AbstractServerTaintIssueBuilder(String key) {\n      this.key = key;\n    }\n\n    public T withIntroductionDate(Instant introductionDate) {\n      this.introductionDate = introductionDate;\n      return (T) this;\n    }\n\n    public T resolvedWithStatus(IssueStatus resolutionStatus) {\n      this.resolved = true;\n      this.resolutionStatus = resolutionStatus;\n      return (T) this;\n    }\n\n    public T open() {\n      this.resolved = false;\n      resolutionStatus = null;\n      return (T) this;\n    }\n\n    public T withType(RuleType ruleType) {\n      this.ruleType = ruleType;\n      return (T) this;\n    }\n\n    public T withSeverity(IssueSeverity issueSeverity) {\n      this.issueSeverity = issueSeverity;\n      return (T) this;\n    }\n\n  }\n\n  public record ServerTaintIssue(UUID id, String key, boolean resolved, IssueStatus resolutionStatus, String ruleKey, String message,\n                                 String filePath, Instant creationDate, IssueSeverity severity, RuleType type, List<Flow> flows,\n                                 @Nullable TextRangeWithHash textRange, @Nullable String ruleDescriptionContextKey, @Nullable CleanCodeAttribute cleanCodeAttribute,\n                                 Map<SoftwareQuality, ImpactSeverity> impacts) { }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/storage/StorageFixture.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.storage;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Consumer;\nimport org.apache.commons.io.FileUtils;\nimport org.sonarsource.sonarlint.core.serverapi.features.Feature;\nimport org.sonarsource.sonarlint.core.serverconnection.proto.Sonarlint;\nimport org.sonarsource.sonarlint.core.serverconnection.storage.ProtobufFileUtil;\n\nimport static org.sonarsource.sonarlint.core.serverconnection.storage.ProjectStoragePaths.encodeForFs;\n\npublic class StorageFixture {\n  public static StorageBuilder newStorage(String connectionId) {\n    return new StorageBuilder(connectionId);\n  }\n\n  public static class StorageBuilder {\n    private final String connectionId;\n    private final List<Feature> supportedFeatures = new ArrayList<>();\n    private final List<Plugin> plugins = new ArrayList<>();\n    private final List<ProjectStorageFixture.ProjectStorageBuilder> projectBuilders = new ArrayList<>();\n    private AiCodeFixFixtures.Builder aiCodeFixBuilder;\n    private String serverVersion;\n    private Map<String, String> globalSettings;\n\n    private StorageBuilder(String connectionId) {\n      this.connectionId = connectionId;\n    }\n\n    public StorageBuilder withGlobalSettings(Map<String, String> globalSettings) {\n      this.globalSettings = globalSettings;\n      return this;\n    }\n\n    public StorageBuilder withServerVersion(String serverVersion) {\n      this.serverVersion = serverVersion;\n      return this;\n    }\n\n    public StorageBuilder withServerFeature(Feature feature) {\n      this.supportedFeatures.add(feature);\n      return this;\n    }\n\n    public StorageBuilder withPlugins(org.sonarsource.sonarlint.core.test.utils.plugins.Plugin... plugins) {\n      var builder = this;\n      for (org.sonarsource.sonarlint.core.test.utils.plugins.Plugin plugin : plugins) {\n        builder = builder.withPlugin(plugin);\n      }\n      return builder;\n    }\n\n    public StorageBuilder withPlugin(org.sonarsource.sonarlint.core.test.utils.plugins.Plugin plugin) {\n      return withPlugin(plugin.getPluginKey(), plugin.getPath(), plugin.getHash());\n    }\n\n    public StorageBuilder withPlugin(String key, Path jarPath, String hash) {\n      plugins.add(new Plugin(jarPath, jarPath.getFileName().toString(), hash, key));\n      return this;\n    }\n\n    public StorageBuilder withProject(String projectKey, Consumer<ProjectStorageFixture.ProjectStorageBuilder> consumer) {\n      var builder = new ProjectStorageFixture.ProjectStorageBuilder(connectionId, projectKey);\n      consumer.accept(builder);\n      projectBuilders.add(builder);\n      return this;\n    }\n\n    public StorageBuilder withProject(String projectKey) {\n      return withProject(projectKey, builder -> {\n      });\n    }\n\n    public StorageBuilder withAiCodeFixSettings(Consumer<AiCodeFixFixtures.Builder> consumer) {\n      var builder = new AiCodeFixFixtures.Builder(this.connectionId);\n      consumer.accept(builder);\n      aiCodeFixBuilder = builder;\n      return this;\n    }\n\n    public String getConnectionId() {\n      return connectionId;\n    }\n\n    public void populate(Path rootPath, TestDatabase database) {\n      var storagePath = rootPath.resolve(\"storage\");\n      var connectionStorage = storagePath.resolve(encodeForFs(connectionId));\n      var pluginsFolderPath = connectionStorage.resolve(\"plugins\");\n      var projectsFolderPath = connectionStorage.resolve(\"projects\");\n      try {\n        FileUtils.forceMkdir(pluginsFolderPath.toFile());\n      } catch (IOException e) {\n        throw new IllegalStateException(e);\n      }\n\n      createServerInfo(connectionStorage);\n\n      createPlugins(pluginsFolderPath);\n      createPluginReferences(pluginsFolderPath);\n\n      projectBuilders.forEach(project -> project.populate(projectsFolderPath, database));\n      if (aiCodeFixBuilder != null) {\n        aiCodeFixBuilder.populate(database);\n      }\n    }\n\n    private void createServerInfo(Path connectionStorage) {\n      if (serverVersion != null || globalSettings != null || !supportedFeatures.isEmpty()) {\n        var version = serverVersion == null ? \"0.0.0\" : serverVersion;\n        var settings = globalSettings == null ? Map.<String, String>of() : globalSettings;\n        ProtobufFileUtil.writeToFile(Sonarlint.ServerInfo.newBuilder()\n          .setVersion(version)\n          .addAllSupportedFeatures(supportedFeatures.stream().map(Feature::getKey).toList())\n          .putAllGlobalSettings(settings)\n          .build(),\n          connectionStorage.resolve(\"server_info.pb\"));\n      }\n    }\n\n    private void createPlugins(Path pluginsFolderPath) {\n      plugins.forEach(plugin -> {\n        var pluginPath = pluginsFolderPath.resolve(plugin.jarName);\n        try {\n          Files.copy(plugin.path, pluginPath);\n        } catch (IOException e) {\n          throw new IllegalStateException(\"Cannot copy plugin \" + plugin.jarName, e);\n        }\n      });\n    }\n\n    private void createPluginReferences(Path pluginsFolderPath) {\n      var builder = Sonarlint.PluginReferences.newBuilder();\n      plugins.forEach(plugin -> builder.putPluginsByKey(plugin.key, Sonarlint.PluginReferences.PluginReference.newBuilder()\n        .setFilename(plugin.jarName)\n        .setHash(plugin.hash)\n        .setKey(plugin.key)\n        .build()));\n      ProtobufFileUtil.writeToFile(builder.build(), pluginsFolderPath.resolve(\"plugin_references.pb\"));\n    }\n\n    private record Plugin(Path path, String jarName, String hash, String key) {\n    }\n  }\n\n  private StorageFixture() {\n    // utility class\n  }\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/storage/TestDatabase.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils.storage;\n\nimport java.nio.file.Path;\nimport org.flywaydb.core.Flyway;\nimport org.h2.jdbcx.JdbcConnectionPool;\nimport org.jooq.DSLContext;\nimport org.jooq.SQLDialect;\nimport org.jooq.conf.Settings;\nimport org.jooq.impl.DSL;\nimport org.jooq.impl.DefaultConfiguration;\nimport org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabase;\nimport org.sonarsource.sonarlint.core.serverconnection.FileUtils;\n\n/**\n * Having a separate database entrypoint than the production one allows for more flexibility for tests (e.g. setup DB in an unexpected state, or test migrations).\n */\npublic class TestDatabase {\n  private final JdbcConnectionPool dataSource;\n  private final DSLContext dsl;\n\n  public TestDatabase(Path storageRoot) {\n    var baseDir = storageRoot.resolve(\"h2\");\n    FileUtils.mkdirs(baseDir);\n    var dbBasePath = baseDir.resolve(SonarLintDatabase.SQ_IDE_DB_FILENAME).toAbsolutePath();\n    this.dataSource = JdbcConnectionPool.create(\"jdbc:h2:\" + dbBasePath, \"sa\", \"\");\n\n    var flyway = Flyway.configure()\n      .dataSource(this.dataSource)\n      .locations(\"classpath:db/migration\")\n      .defaultSchema(\"PUBLIC\")\n      .schemas(\"PUBLIC\")\n      .createSchemas(true)\n      .baselineOnMigrate(true)\n      .failOnMissingLocations(false)\n      .load();\n    // this might throw but it's fine. If migrations fail, we want to fail starting the backend\n    flyway.migrate();\n\n    System.setProperty(\"org.jooq.no-tips\", \"true\");\n    System.setProperty(\"org.jooq.no-logo\", \"true\");\n    var jooqConfig = new DefaultConfiguration()\n      .set(this.dataSource)\n      .set(SQLDialect.H2)\n      .set(new Settings().withExecuteLogging(false));\n    this.dsl = DSL.using(jooqConfig);\n  }\n\n  public DSLContext dsl() {\n    return dsl;\n  }\n\n  public void shutdown() {\n    dataSource.dispose();\n  }\n\n}\n"
  },
  {
    "path": "test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/storage/package-info.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n@ParametersAreNonnullByDefault\npackage org.sonarsource.sonarlint.core.test.utils.storage;\n\nimport javax.annotation.ParametersAreNonnullByDefault;\n"
  },
  {
    "path": "test-utils/src/test/java/org/sonarsource/sonarlint/core/test/utils/SonarLintTestRpcServerTest.java",
    "content": "/*\n * SonarLint Core - Test Utils\n * Copyright (C) SonarSource Sàrl\n * mailto:info AT sonarsource DOT com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\npackage org.sonarsource.sonarlint.core.test.utils;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Set;\nimport java.util.concurrent.CompletableFuture;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.sonarsource.sonarlint.core.rpc.client.ClientJsonRpcLauncher;\nimport org.sonarsource.sonarlint.core.rpc.client.SonarLintRpcClientDelegate;\nimport org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.ClientConstantInfoDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.HttpConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.SslConfigurationDto;\nimport org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.TelemetryClientConstantAttributesDto;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass SonarLintTestRpcServerTest {\n\n  @Test\n  void it_should_throw_an_assertion_exception_when_telemetry_file_does_not_exist(@TempDir Path userHome) throws IOException {\n    var clientLauncher = mock(ClientJsonRpcLauncher.class);\n    var rpcServer = mock(SonarLintRpcServer.class);\n    when(rpcServer.initialize(any())).thenReturn(CompletableFuture.completedFuture(null));\n    when(clientLauncher.getServerProxy()).thenReturn(rpcServer);\n    var sonarLintTestRpcServer = new SonarLintTestRpcServer(mock(SonarLintRpcClientDelegate.class));\n    sonarLintTestRpcServer\n      .initialize(\n        new InitializeParams(new ClientConstantInfoDto(\"\", \"\"), new TelemetryClientConstantAttributesDto(\"product\", null, null, null, null),\n          new HttpConfigurationDto(new SslConfigurationDto(null, null, null, null, null, null), null, null, null, null), null, Set.of(), Paths.get(\"\"), Paths.get(\"\"), null, null,\n          null, null, null, null, null, userHome.toString(), null, false, null, false, null))\n      .join();\n\n    var throwable = catchThrowable(sonarLintTestRpcServer::telemetryFileContent);\n\n    assertThat(throwable).isInstanceOf(AssertionError.class);\n  }\n\n}\n"
  },
  {
    "path": "third-party-licenses.sh",
    "content": "#!/bin/sh\nmvn org.codehaus.mojo:license-maven-plugin:aggregate-add-third-party -Dlicense.includedScopes=compile -pl sonar-application -am\n\ncat target/generated-sources/license/THIRD-PARTY.txt\n"
  }
]