├── .github └── workflows │ ├── ci.yaml │ └── release.yaml ├── .gitignore ├── .gitlab-ci.yml ├── .travis.yml ├── CHANGELOG ├── LICENSE.md ├── README.md ├── doc ├── builds.jpg ├── codequality.png ├── comment_commits.jpg ├── comment_line.jpg ├── quality_gate.png ├── sast.png ├── sonar_admin_settings.jpg ├── sonar_global.jpg ├── sonar_inline.jpg └── sonar_project_settings.jpg ├── gitlab_settings.xml ├── images ├── new.png ├── rule.png ├── severity-blocker.png ├── severity-critical.png ├── severity-info.png ├── severity-major.png ├── severity-minor.png └── sonarqube_avatar.png ├── pom.xml ├── src ├── main │ ├── java │ │ └── com │ │ │ └── talanlabs │ │ │ └── sonar │ │ │ └── plugins │ │ │ └── gitlab │ │ │ ├── AbstractCommentBuilder.java │ │ │ ├── BuildInitState.java │ │ │ ├── CommitFacade.java │ │ │ ├── CommitPublishPostJob.java │ │ │ ├── GitLabApiV4Wrapper.java │ │ │ ├── GitLabPlugin.java │ │ │ ├── GitLabPluginConfiguration.java │ │ │ ├── GlobalCommentBuilder.java │ │ │ ├── IGitLabApiWrapper.java │ │ │ ├── InlineCommentBuilder.java │ │ │ ├── IssueComparator.java │ │ │ ├── MarkDownUtils.java │ │ │ ├── MessageHelper.java │ │ │ ├── PatchUtils.java │ │ │ ├── Reporter.java │ │ │ ├── ReporterBuilder.java │ │ │ ├── SonarFacade.java │ │ │ ├── api │ │ │ ├── GitLabAPIMergeRequestDiscussionExt.java │ │ │ ├── GitlabDiscussionStatus.java │ │ │ ├── GitlabNote.java │ │ │ └── GitlabPosition.java │ │ │ ├── freemarker │ │ │ ├── AbstractIssuesTemplateMethodModelEx.java │ │ │ ├── AbstractQualityGateConditionsTemplateMethodModelEx.java │ │ │ ├── AbstractSeverityTemplateMethodModelEx.java │ │ │ ├── EmojiSeverityTemplateMethodModelEx.java │ │ │ ├── ImageSeverityTemplateMethodModelEx.java │ │ │ ├── IssueCountTemplateMethodModelEx.java │ │ │ ├── IssuesTemplateMethodModelEx.java │ │ │ ├── PrintTemplateMethodModelEx.java │ │ │ ├── QualityGateConditionCountTemplateMethodModelEx.java │ │ │ ├── QualityGateConditionsTemplateMethodModelEx.java │ │ │ └── RuleLinkTemplateMethodModelEx.java │ │ │ └── models │ │ │ ├── Issue.java │ │ │ ├── JsonMode.java │ │ │ ├── QualityGate.java │ │ │ ├── QualityGateFailMode.java │ │ │ ├── ReportIssue.java │ │ │ ├── Rule.java │ │ │ └── StatusNotificationsMode.java │ └── resources │ │ └── org │ │ └── sonar │ │ └── l10n │ │ └── gitlab.properties └── test │ ├── java │ └── com │ │ └── talanlabs │ │ └── sonar │ │ └── plugins │ │ └── gitlab │ │ ├── CommitFacadeTest.java │ │ ├── CommitPublishPostJobTest.java │ │ ├── GetProjectV4Test.java │ │ ├── GitLabApiV4WrapperTest.java │ │ ├── GitLabPluginConfigurationTest.java │ │ ├── GitLabPluginTest.java │ │ ├── GlobalCommentBuilderTest.java │ │ ├── GlobalTemplateTest.java │ │ ├── InlineCommentBuilderTest.java │ │ ├── InlineTemplateTest.java │ │ ├── IssueComparatorTest.java │ │ ├── PatchUtilsTest.java │ │ ├── ReporterBuilderTest.java │ │ ├── ReporterTest.java │ │ ├── SonarFacadeTest.java │ │ ├── Utils.java │ │ └── freemarker │ │ ├── EmojiSeverityTemplateMethodModelExTest.java │ │ ├── ImageSeverityTemplateMethodModelExTest.java │ │ ├── IssueCountTemplateMethodModelExTest.java │ │ ├── IssuesTemplateMethodModelExTest.java │ │ ├── PrintTemplateMethodModelExTest.java │ │ ├── QualityGateConditionCountTemplateMethodModelExTest.java │ │ ├── QualityGateConditionsTemplateMethodModelExTest.java │ │ └── RuleLinkTemplateMethodModelExTest.java │ └── resources │ └── report-task.txt └── templates ├── global ├── all-issues.md ├── default-image.md ├── default.md └── french.md └── inline ├── default-image.md └── default.md /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 4 | env: 5 | # https://github.com/actions/virtual-environments/issues/1499 6 | MAVEN_CLI_OPTS: '-ntp --batch-mode --errors --fail-at-end --show-version -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120' 7 | 8 | on: 9 | pull_request: 10 | push: 11 | branches: 12 | - master 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Setup java 21 | uses: actions/setup-java@v2 22 | with: 23 | java-version: 21 24 | distribution: "temurin" 25 | 26 | - name: mvn clean verify 27 | run: mvn ${MAVEN_CLI_OPTS} clean verify 28 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | 7 | env: 8 | # https://github.com/actions/virtual-environments/issues/1499 9 | MAVEN_CLI_OPTS: '-ntp --batch-mode --errors --fail-at-end --show-version -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120' 10 | 11 | name: Release 12 | 13 | jobs: 14 | create_release: 15 | name: Create Release 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Setup java 21 | uses: actions/setup-java@v2 22 | with: 23 | java-version: 21 24 | distribution: "temurin" 25 | 26 | - name: mvn clean verify package 27 | run: mvn ${MAVEN_CLI_OPTS} clean verify package -Dsources.skip 28 | 29 | - name: Get tag version 30 | id: get_version 31 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} 32 | 33 | - name: Release 34 | uses: softprops/action-gh-release@v1 35 | if: startsWith(github.ref, 'refs/tags/') 36 | with: 37 | tag_name: ${{ github.ref }} 38 | files: target/sonar-gitlab-plugin-*.jar 39 | draft: false 40 | prerelease: false 41 | body: "" 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .classpath 3 | .project 4 | .settings/ 5 | 6 | # Intellij 7 | .idea/ 8 | *.iml 9 | *.iws 10 | 11 | # Mac 12 | .DS_Store 13 | 14 | # Maven 15 | log/ 16 | target/ 17 | pom.xml.versionsBackup 18 | 19 | # SVN 20 | .svn/ 21 | 22 | # Gradle 23 | build/ 24 | .gradle/ 25 | 26 | # Log 27 | *.log -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: maven:3.3.3-jdk-8 2 | 3 | workflow: 4 | rules: 5 | - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' 6 | - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push" 7 | when: never 8 | - if: $CI_COMMIT_BRANCH 9 | 10 | stages: 11 | - build 12 | - publish 13 | - test 14 | - deploy 15 | 16 | build_job: 17 | stage: build 18 | script: 19 | - mvn --quiet clean package 20 | cache: 21 | key: maven 22 | paths: 23 | - .m2/repository 24 | artifacts: 25 | paths: 26 | - target/*.jar 27 | tags: 28 | - docker 29 | 30 | publish_job: 31 | stage: publish 32 | dependencies: 33 | - build_job 34 | artifacts: 35 | paths: 36 | - ci_settings.xml 37 | script: 38 | - | 39 | jarfile=$(ls target/sonar-gitlab-plugin*.jar) 40 | mvn deploy:deploy-file -s gitlab_settings.xml -DpomFile=pom.xml \ 41 | -Dfile=${jarfile} \ 42 | -DrepositoryId=gitlab-maven \ 43 | -Durl=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/maven 44 | 45 | test_sonar_preview_job: 46 | stage: test 47 | except: 48 | - master 49 | - tags 50 | script: 51 | - mvn --batch-mode verify org.sonarsource.scanner.maven:sonar-maven-plugin:3.4.0.905:sonar -Dsonar.host.url=$SONAR_URL -Dsonar.login=$SONAR_LOGIN -Dsonar.analysis.mode=preview -Dsonar.gitlab.commit_sha=$CI_COMMIT_SHA -Dsonar.gitlab.ref_name=$CI_COMMIT_REF_NAME -Dsonar.gitlab.project_id=$CI_PROJECT_ID 52 | tags: 53 | - docker 54 | 55 | test_sonar_feature_job: 56 | stage: test 57 | except: 58 | - master 59 | - tags 60 | script: 61 | - mvn --batch-mode verify org.sonarsource.scanner.maven:sonar-maven-plugin:3.4.0.905:sonar -Dsonar.host.url=$SONAR_OFF_URL -Dsonar.login=$SONAR_OFF_LOGIN -Dsonar.branch.name=$CI_COMMIT_REF_NAME 62 | tags: 63 | - docker 64 | 65 | test_sonar_job: 66 | stage: test 67 | only: 68 | - master 69 | script: 70 | - mvn --batch-mode verify org.sonarsource.scanner.maven:sonar-maven-plugin:3.4.0.905:sonar -Dsonar.host.url=$SONAR_URL -Dsonar.login=$SONAR_LOGIN 71 | - mvn --batch-mode verify org.sonarsource.scanner.maven:sonar-maven-plugin:3.4.0.905:sonar -Dsonar.host.url=$SONAR_OFF_URL -Dsonar.login=$SONAR_OFF_LOGIN 72 | tags: 73 | - docker -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | sudo: false 3 | jdk: 4 | - oraclejdk8 5 | 6 | script: 7 | - mvn --batch-mode clean verify -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | V 1.7.0: 2 | - SonarQube API 5.4 3 | - Java 7 4 | - Order settings 5 | 6 | V 1.6.6: 7 | - Fix url image 8 | 9 | V 1.6.0: 10 | - Sort issues in Markdown for not reported on diff 11 | 12 | v 1.5.0: 13 | - Remove CheckMode -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. -------------------------------------------------------------------------------- /doc/builds.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javamachr/sonar-gitlab-plugin/618abacd5c256fbd2af6e89ec82fe29f481400ad/doc/builds.jpg -------------------------------------------------------------------------------- /doc/codequality.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javamachr/sonar-gitlab-plugin/618abacd5c256fbd2af6e89ec82fe29f481400ad/doc/codequality.png -------------------------------------------------------------------------------- /doc/comment_commits.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javamachr/sonar-gitlab-plugin/618abacd5c256fbd2af6e89ec82fe29f481400ad/doc/comment_commits.jpg -------------------------------------------------------------------------------- /doc/comment_line.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javamachr/sonar-gitlab-plugin/618abacd5c256fbd2af6e89ec82fe29f481400ad/doc/comment_line.jpg -------------------------------------------------------------------------------- /doc/quality_gate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javamachr/sonar-gitlab-plugin/618abacd5c256fbd2af6e89ec82fe29f481400ad/doc/quality_gate.png -------------------------------------------------------------------------------- /doc/sast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javamachr/sonar-gitlab-plugin/618abacd5c256fbd2af6e89ec82fe29f481400ad/doc/sast.png -------------------------------------------------------------------------------- /doc/sonar_admin_settings.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javamachr/sonar-gitlab-plugin/618abacd5c256fbd2af6e89ec82fe29f481400ad/doc/sonar_admin_settings.jpg -------------------------------------------------------------------------------- /doc/sonar_global.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javamachr/sonar-gitlab-plugin/618abacd5c256fbd2af6e89ec82fe29f481400ad/doc/sonar_global.jpg -------------------------------------------------------------------------------- /doc/sonar_inline.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javamachr/sonar-gitlab-plugin/618abacd5c256fbd2af6e89ec82fe29f481400ad/doc/sonar_inline.jpg -------------------------------------------------------------------------------- /doc/sonar_project_settings.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javamachr/sonar-gitlab-plugin/618abacd5c256fbd2af6e89ec82fe29f481400ad/doc/sonar_project_settings.jpg -------------------------------------------------------------------------------- /gitlab_settings.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | gitlab-maven 6 | 7 | 8 | 9 | Job-Token 10 | ${CI_JOB_TOKEN} 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /images/new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javamachr/sonar-gitlab-plugin/618abacd5c256fbd2af6e89ec82fe29f481400ad/images/new.png -------------------------------------------------------------------------------- /images/rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javamachr/sonar-gitlab-plugin/618abacd5c256fbd2af6e89ec82fe29f481400ad/images/rule.png -------------------------------------------------------------------------------- /images/severity-blocker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javamachr/sonar-gitlab-plugin/618abacd5c256fbd2af6e89ec82fe29f481400ad/images/severity-blocker.png -------------------------------------------------------------------------------- /images/severity-critical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javamachr/sonar-gitlab-plugin/618abacd5c256fbd2af6e89ec82fe29f481400ad/images/severity-critical.png -------------------------------------------------------------------------------- /images/severity-info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javamachr/sonar-gitlab-plugin/618abacd5c256fbd2af6e89ec82fe29f481400ad/images/severity-info.png -------------------------------------------------------------------------------- /images/severity-major.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javamachr/sonar-gitlab-plugin/618abacd5c256fbd2af6e89ec82fe29f481400ad/images/severity-major.png -------------------------------------------------------------------------------- /images/severity-minor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javamachr/sonar-gitlab-plugin/618abacd5c256fbd2af6e89ec82fe29f481400ad/images/severity-minor.png -------------------------------------------------------------------------------- /images/sonarqube_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javamachr/sonar-gitlab-plugin/618abacd5c256fbd2af6e89ec82fe29f481400ad/images/sonarqube_avatar.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.sonarsource.parent 8 | parent 9 | 83.0.0.2369 10 | 11 | com.talanlabs 12 | sonar-gitlab-plugin 13 | 2025.01 14 | SonarQube :: GitLab Plugin 15 | GitLab Plugin for Reporting 16 | sonar-plugin 17 | https://github.com/javamachr/sonar-gitlab-plugin 18 | 19 | 17 20 | 17 21 | 17 22 | 1.5.0 23 | 2.3.31 24 | 1.9 25 | 26 | gabriel.allaigre@gmail.com 27 | Talanlabs 28 | 29 | 25.1.0.102122 30 | 11.0.0.2664 31 | GitLab 32 | com.talanlabs.sonar.plugins.gitlab.GitLabPlugin 33 | 34 | 35 | sonar-gitlab 36 | 37 | ${project.groupId}:${project.artifactId}:jar 38 | 39 | 0.8.9 40 | ${project.build.directory}/coverage-reports/jacoco-ut.exec 41 | 42 | ${jacoco.ut.execution.data.file} 43 | UTF-8 44 | 45 | 2016 46 | 47 | Talanlabs 48 | http://www.talanlabs.com 49 | 50 | 51 | 52 | GNU LGPL 3 53 | http://www.gnu.org/licenses/lgpl.txt 54 | repo 55 | 56 | 57 | 58 | 59 | Gabriel Allaigre 60 | gabriel.allaigre@gmail.com 61 | 62 | 63 | 64 | 65 | Johno Crawford 66 | johno.crawford@gmail.com 67 | 68 | 69 | frol2103 70 | 71 | 72 | Sylvain Desgrais 73 | 74 | 75 | Thibaud Lepretre 76 | 77 | 78 | Alex Krevenya 79 | 80 | 81 | Jérôme BAROTIN 82 | 83 | 84 | Eugene Dubrovka 85 | 86 | 87 | David Marin Vaquero 88 | 89 | 90 | Jakub Vavrik 91 | 92 | 93 | 94 | https://github.com/javamachr/sonar-gitlab-plugin 95 | scm:git:https://github.com/javamachr/sonar-gitlab-plugin.git 96 | 97 | 98 | gitlab-ci 99 | https://gitlab.talanlabs.com/gabriel-allaigre/sonar-gitlab-plugin/builds 100 | 101 | 102 | github 103 | https://github.com/javamachr/sonar-gitlab-plugin/issues 104 | 105 | 106 | 107 | jitpack.io 108 | https://jitpack.io 109 | 110 | 111 | 112 | 113 | 114 | org.jacoco 115 | jacoco-maven-plugin 116 | ${jacoco-maven-plugin.version} 117 | 118 | 119 | pre-unit-test 120 | 121 | prepare-agent 122 | 123 | 124 | ${jacoco.ut.execution.data.file} 125 | 126 | 127 | 128 | post-unit-test 129 | test 130 | 131 | report 132 | 133 | 134 | ${jacoco.ut.execution.data.file} 135 | ${project.reporting.outputDirectory}/jacoco-ut 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | org.sonarsource.api.plugin 145 | sonar-plugin-api 146 | ${sonar-api.version} 147 | provided 148 | 149 | 150 | org.sonarsource.sonarqube 151 | sonar-plugin-api-impl 152 | ${sonar.version} 153 | test 154 | 155 | 156 | org.sonarsource.sonarqube 157 | sonar-ws 158 | ${sonar.version} 159 | 160 | 161 | com.google.code.findbugs 162 | jsr305 163 | 3.0.2 164 | provided 165 | 166 | 167 | com.github.gabrie-allaigre 168 | java-gitlab-api 169 | ${java-gitlab-api.version} 170 | 171 | 172 | org.freemarker 173 | freemarker 174 | ${freemarker.version} 175 | 176 | 177 | org.apache.commons 178 | commons-text 179 | ${commons-text.version} 180 | 181 | 182 | com.google.guava 183 | guava 184 | 33.4.0-jre 185 | 186 | 187 | 188 | junit 189 | junit 190 | 4.13.2 191 | test 192 | 193 | 194 | org.assertj 195 | assertj-core 196 | 3.27.3 197 | test 198 | 199 | 200 | org.mockito 201 | mockito-core 202 | 5.15.2 203 | test 204 | 205 | 206 | org.hamcrest 207 | hamcrest-core 208 | 209 | 210 | 211 | 212 | com.squareup.okhttp3 213 | mockwebserver 214 | 4.9.3 215 | test 216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/AbstractCommentBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.freemarker.*; 20 | import com.talanlabs.sonar.plugins.gitlab.models.ReportIssue; 21 | import freemarker.template.Configuration; 22 | import freemarker.template.Template; 23 | import freemarker.template.TemplateException; 24 | import freemarker.template.TemplateExceptionHandler; 25 | import org.apache.commons.text.StringEscapeUtils; 26 | import org.sonar.api.batch.rule.Severity; 27 | import org.sonar.api.utils.MessageException; 28 | import org.sonar.api.utils.log.Logger; 29 | import org.sonar.api.utils.log.Loggers; 30 | import org.sonarqube.ws.Common; 31 | 32 | import java.io.IOException; 33 | import java.io.StringWriter; 34 | import java.util.Arrays; 35 | import java.util.HashMap; 36 | import java.util.List; 37 | import java.util.Map; 38 | 39 | public abstract class AbstractCommentBuilder { 40 | 41 | private static final Logger LOG = Loggers.get(AbstractCommentBuilder.class); 42 | 43 | protected final GitLabPluginConfiguration gitLabPluginConfiguration; 44 | protected final String revision; 45 | protected final List reportIssues; 46 | protected final MarkDownUtils markDownUtils; 47 | private final String templateName; 48 | private final String template; 49 | 50 | AbstractCommentBuilder(GitLabPluginConfiguration gitLabPluginConfiguration, String revision, List reportIssues, MarkDownUtils markDownUtils, 51 | String templateName, String template) { 52 | super(); 53 | 54 | this.gitLabPluginConfiguration = gitLabPluginConfiguration; 55 | this.revision = revision; 56 | this.reportIssues = reportIssues; 57 | this.markDownUtils = markDownUtils; 58 | this.templateName = templateName; 59 | this.template = template; 60 | } 61 | 62 | public String buildForMarkdown() { 63 | if (template != null && !template.isEmpty()) { 64 | return buildFreemarkerComment(); 65 | } 66 | return buildDefaultComment(); 67 | } 68 | 69 | private String buildFreemarkerComment() { 70 | Configuration cfg = new Configuration(Configuration.VERSION_2_3_31); 71 | cfg.setDefaultEncoding("UTF-8"); 72 | cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); 73 | cfg.setLogTemplateExceptions(false); 74 | 75 | try (StringWriter sw = new StringWriter()) { 76 | new Template(templateName, template, cfg).process(createContext(), sw); 77 | return StringEscapeUtils.unescapeHtml4(sw.toString()); 78 | } catch (IOException | TemplateException e) { 79 | LOG.error("Failed to create template {}", templateName, e); 80 | throw MessageException.of("Failed to create template " + templateName); 81 | } 82 | } 83 | 84 | protected Map createContext() { 85 | Map root = new HashMap<>(); 86 | // Config 87 | root.put("projectId", gitLabPluginConfiguration.projectId()); 88 | root.put("commitSHA", gitLabPluginConfiguration.commitSHA()); 89 | root.put("refName", gitLabPluginConfiguration.refName()); 90 | root.put("url", gitLabPluginConfiguration.url()); 91 | root.put("maxGlobalIssues", gitLabPluginConfiguration.maxGlobalIssues()); 92 | root.put("maxBlockerIssuesGate", gitLabPluginConfiguration.maxBlockerIssuesGate()); 93 | root.put("maxCriticalIssuesGate", gitLabPluginConfiguration.maxCriticalIssuesGate()); 94 | root.put("maxMajorIssuesGate", gitLabPluginConfiguration.maxMajorIssuesGate()); 95 | root.put("maxMinorIssuesGate", gitLabPluginConfiguration.maxMinorIssuesGate()); 96 | root.put("maxInfoIssuesGate", gitLabPluginConfiguration.maxInfoIssuesGate()); 97 | root.put("disableIssuesInline", !gitLabPluginConfiguration.tryReportIssuesInline()); 98 | root.put("disableGlobalComment", !gitLabPluginConfiguration.disableGlobalComment()); 99 | root.put("onlyIssueFromCommitFile", gitLabPluginConfiguration.onlyIssueFromCommitFile()); 100 | root.put("commentNoIssue", gitLabPluginConfiguration.commentNoIssue()); 101 | root.put("sonarUrl", gitLabPluginConfiguration.baseUrl()); 102 | root.put("publishMode", true); 103 | // Report 104 | root.put("revision", revision); 105 | Arrays.stream(Severity.values()).forEach(severity -> root.put(severity.name(), severity)); 106 | root.put("issueCount", new IssueCountTemplateMethodModelEx(reportIssues)); 107 | root.put("issues", new IssuesTemplateMethodModelEx(reportIssues)); 108 | root.put("print", new PrintTemplateMethodModelEx(markDownUtils)); 109 | root.put("emojiSeverity", new EmojiSeverityTemplateMethodModelEx(markDownUtils)); 110 | root.put("imageSeverity", new ImageSeverityTemplateMethodModelEx(markDownUtils)); 111 | root.put("ruleLink", new RuleLinkTemplateMethodModelEx(gitLabPluginConfiguration)); 112 | Arrays.stream(Common.RuleType.values()).forEach(type -> root.put(type.name(), type.name())); 113 | return root; 114 | } 115 | 116 | protected abstract String buildDefaultComment(); 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/BuildInitState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab; 18 | 19 | public enum BuildInitState { 20 | 21 | PENDING("pending"), RUNNING("running"); 22 | 23 | private final String meaning; 24 | 25 | BuildInitState(String meaning) { 26 | this.meaning = meaning; 27 | } 28 | 29 | public static BuildInitState of(String meaning) { 30 | for (BuildInitState m : values()) { 31 | if (m.meaning.equals(meaning)) { 32 | return m; 33 | } 34 | } 35 | return null; 36 | } 37 | 38 | public String getMeaning() { 39 | return meaning; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/CommitFacade.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab; 18 | 19 | import com.google.common.annotations.VisibleForTesting; 20 | import com.talanlabs.sonar.plugins.gitlab.models.JsonMode; 21 | import org.sonar.api.scan.filesystem.PathResolver; 22 | import org.sonar.api.scanner.ScannerSide; 23 | import org.sonar.api.utils.MessageException; 24 | import org.sonar.api.utils.log.Logger; 25 | import org.sonar.api.utils.log.Loggers; 26 | 27 | import java.io.File; 28 | import java.io.IOException; 29 | import java.io.UnsupportedEncodingException; 30 | import java.net.URLEncoder; 31 | import java.nio.file.Files; 32 | import java.nio.file.Paths; 33 | import java.nio.file.StandardOpenOption; 34 | import javax.annotation.CheckForNull; 35 | import javax.annotation.Nullable; 36 | 37 | /** 38 | * Facade for all WS interaction with GitLab. 39 | */ 40 | @ScannerSide 41 | public class CommitFacade { 42 | 43 | private static final Logger LOG = Loggers.get(CommitFacade.class); 44 | 45 | private static final String CODECLIMATE_JSON_NAME = "gl-code-quality-report.json"; 46 | private static final String SAST_JSON_NAME = "gl-sast-report.json"; 47 | 48 | private final GitLabPluginConfiguration gitLabPluginConfiguration; 49 | private final String ruleUrlPrefix; 50 | private File gitBaseDir; 51 | 52 | private IGitLabApiWrapper gitLabWrapper; 53 | 54 | public CommitFacade(GitLabPluginConfiguration gitLabPluginConfiguration) { 55 | this.gitLabPluginConfiguration = gitLabPluginConfiguration; 56 | 57 | this.ruleUrlPrefix = gitLabPluginConfiguration.baseUrl(); 58 | 59 | if (GitLabPlugin.V4_API_VERSION.equals(gitLabPluginConfiguration.apiVersion())) { 60 | this.gitLabWrapper = new GitLabApiV4Wrapper(gitLabPluginConfiguration); 61 | } 62 | } 63 | 64 | @VisibleForTesting 65 | void setGitLabWrapper(IGitLabApiWrapper gitLabWrapper) { 66 | this.gitLabWrapper = gitLabWrapper; 67 | } 68 | 69 | public static String encodeForUrl(String url) { 70 | try { 71 | return URLEncoder.encode(url, "UTF-8"); 72 | } catch (UnsupportedEncodingException e) { 73 | throw new IllegalStateException("Encoding not supported", e); 74 | } 75 | } 76 | 77 | public void init(File projectBaseDir) { 78 | initGitBaseDir(projectBaseDir); 79 | 80 | gitLabWrapper.init(); 81 | } 82 | 83 | void initGitBaseDir(File projectBaseDir) { 84 | File detectedGitBaseDir = findGitBaseDir(projectBaseDir); 85 | if (detectedGitBaseDir == null) { 86 | LOG.debug("Unable to find Git root directory. Is " + projectBaseDir + " part of a Git repository?"); 87 | setGitBaseDir(projectBaseDir); 88 | } else { 89 | setGitBaseDir(detectedGitBaseDir); 90 | } 91 | } 92 | 93 | private File findGitBaseDir(@Nullable File baseDir) { 94 | if (baseDir == null) { 95 | return null; 96 | } 97 | if (new File(baseDir, ".git").exists()) { 98 | this.gitBaseDir = baseDir; 99 | return baseDir; 100 | } 101 | return findGitBaseDir(baseDir.getParentFile()); 102 | } 103 | 104 | void setGitBaseDir(File gitBaseDir) { 105 | this.gitBaseDir = gitBaseDir; 106 | } 107 | 108 | public boolean hasSameCommitCommentsForFile(String revision, File file, Integer lineNumber, String body) { 109 | String path = getPath(file); 110 | return gitLabWrapper.hasSameCommitCommentsForFile(revision, path, lineNumber, body); 111 | } 112 | 113 | /** 114 | * Author Email is access only for admin gitlab user but search work for all users 115 | */ 116 | public String getUsernameForRevision(String revision) { 117 | return gitLabWrapper.getUsernameForRevision(revision); 118 | } 119 | 120 | public void createOrUpdateSonarQubeStatus(String status, String statusDescription) { 121 | gitLabWrapper.createOrUpdateSonarQubeStatus(status, statusDescription); 122 | } 123 | 124 | public boolean hasFile(File file) { 125 | String path = getPath(file); 126 | return gitLabWrapper.hasFile(path); 127 | } 128 | 129 | public String getRevisionForLine(File file, int lineNumber) { 130 | String path = getPath(file); 131 | return gitLabWrapper.getRevisionForLine(file, path, lineNumber); 132 | } 133 | 134 | @CheckForNull 135 | public String getGitLabUrl(@Nullable String revision, @Nullable File file, @Nullable Integer issueLine) { 136 | if (file != null) { 137 | String path = getPath(file); 138 | return gitLabWrapper.getGitLabUrl(revision, path, issueLine); 139 | } 140 | return null; 141 | } 142 | 143 | @CheckForNull 144 | public String getSrc(@Nullable File file) { 145 | if (file != null) { 146 | return getPath(file); 147 | } 148 | return null; 149 | } 150 | 151 | public void createOrUpdateReviewComment(String revision, File file, Integer line, String body) { 152 | String fullPath = getPath(file); 153 | gitLabWrapper.createOrUpdateReviewComment(revision, fullPath, line, body); 154 | } 155 | 156 | String getPath(File file) { 157 | String prefix = gitLabPluginConfiguration.prefixDirectory() != null ? gitLabPluginConfiguration.prefixDirectory() : ""; 158 | return prefix + new PathResolver().relativePath(gitBaseDir, file); 159 | } 160 | 161 | public void addGlobalComment(String comment) { 162 | gitLabWrapper.addGlobalComment(comment); 163 | } 164 | 165 | public String getRuleLink(String ruleKey) { 166 | return ruleUrlPrefix + "coding_rules#rule_key=" + encodeForUrl(ruleKey); 167 | } 168 | 169 | public void writeJsonFile(String json) { 170 | String name = null; 171 | if (gitLabPluginConfiguration.jsonMode().equals(JsonMode.CODECLIMATE)) { 172 | name = CODECLIMATE_JSON_NAME; 173 | } else if (gitLabPluginConfiguration.jsonMode().equals(JsonMode.SAST)) { 174 | name = SAST_JSON_NAME; 175 | } 176 | if (name != null) { 177 | File file = new File(gitBaseDir, name); 178 | try { 179 | Files.write(Paths.get(file.getAbsolutePath()), json.getBytes(), StandardOpenOption.CREATE); 180 | } catch (IOException e) { 181 | throw MessageException.of("Failed to write file " + file, e); 182 | } 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/CommitPublishPostJob.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.models.Issue; 20 | import com.talanlabs.sonar.plugins.gitlab.models.QualityGate; 21 | import com.talanlabs.sonar.plugins.gitlab.models.StatusNotificationsMode; 22 | import org.jetbrains.annotations.NotNull; 23 | import org.sonar.api.batch.postjob.PostJob; 24 | import org.sonar.api.batch.postjob.PostJobContext; 25 | import org.sonar.api.batch.postjob.PostJobDescriptor; 26 | import org.sonar.api.utils.MessageException; 27 | import org.sonar.api.utils.log.Logger; 28 | import org.sonar.api.utils.log.Loggers; 29 | 30 | import java.io.File; 31 | import java.util.List; 32 | 33 | /** 34 | * Compute comments to be added on the commit on preview or issue mode only 35 | */ 36 | public class CommitPublishPostJob implements PostJob { 37 | 38 | private static final Logger LOG = Loggers.get(CommitPublishPostJob.class); 39 | private static final String SONAR_PROJECT_BASE_DIR = "sonar.projectBaseDir"; 40 | private static final String SONAR_WORKING_DIRECTORY = "sonar.working.directory"; 41 | 42 | private final GitLabPluginConfiguration gitLabPluginConfiguration; 43 | private final SonarFacade sonarFacade; 44 | private final CommitFacade commitFacade; 45 | private final ReporterBuilder reporterBuilder; 46 | 47 | public CommitPublishPostJob(GitLabPluginConfiguration gitLabPluginConfiguration, SonarFacade sonarFacade, CommitFacade commitFacade, ReporterBuilder reporterBuilder) { 48 | this.gitLabPluginConfiguration = gitLabPluginConfiguration; 49 | this.sonarFacade = sonarFacade; 50 | this.commitFacade = commitFacade; 51 | this.reporterBuilder = reporterBuilder; 52 | } 53 | 54 | @Override 55 | public void describe(PostJobDescriptor descriptor) { 56 | descriptor.name("GitLab Commit Issue Publisher") 57 | .requireProperty(GitLabPlugin.GITLAB_URL, GitLabPlugin.GITLAB_USER_TOKEN, GitLabPlugin.GITLAB_PROJECT_ID, GitLabPlugin.GITLAB_COMMIT_SHA, GitLabPlugin.GITLAB_REF_NAME, 58 | SONAR_PROJECT_BASE_DIR, SONAR_WORKING_DIRECTORY); 59 | } 60 | 61 | @Override 62 | public void execute(@NotNull PostJobContext context) { 63 | LOG.info("Will execute CommitPublishPostJob of GitlabPlugin."); 64 | try { 65 | if (!gitLabPluginConfiguration.isEnabled()) { 66 | LOG.info("GitLab plugin is disabled"); 67 | return; 68 | } else { 69 | LOG.info("GitLab plugin is enabled"); 70 | } 71 | File baseDir = fileFromProperty(context, SONAR_PROJECT_BASE_DIR); 72 | if (baseDir == null) { 73 | throw MessageException.of("SonarQube failed because sonar.projectBaseDir is null"); 74 | } 75 | File workDir = fileFromProperty(context, SONAR_WORKING_DIRECTORY); 76 | if (workDir == null) { 77 | throw MessageException.of("SonarQube failed because sonar.working.directory is null"); 78 | } 79 | sonarFacade.init(baseDir, workDir); 80 | commitFacade.init(baseDir); 81 | 82 | if (StatusNotificationsMode.COMMIT_STATUS.equals(gitLabPluginConfiguration.statusNotificationsMode())) { 83 | commitFacade.createOrUpdateSonarQubeStatus(gitLabPluginConfiguration.buildInitState().getMeaning(), "SonarQube analysis in progress"); 84 | } 85 | QualityGate qualityGate; 86 | List issues; 87 | qualityGate = sonarFacade.loadQualityGate(); 88 | issues = sonarFacade.getNewIssues(); 89 | 90 | Reporter report = reporterBuilder.build(qualityGate, issues); 91 | notification(report); 92 | 93 | if(gitLabPluginConfiguration.failOnQualityGate() && QualityGate.Status.ERROR.equals(qualityGate.getStatus())) 94 | { 95 | throw MessageException.of("Quality Gate failed. Exiting scan with failure."); 96 | } 97 | 98 | } catch (MessageException e) { 99 | StatusNotificationsMode i = gitLabPluginConfiguration.statusNotificationsMode(); 100 | if (i == StatusNotificationsMode.COMMIT_STATUS) { 101 | commitFacade.createOrUpdateSonarQubeStatus(MessageHelper.FAILED_GITLAB_STATUS, MessageHelper.sonarQubeFailed(e.getMessage())); 102 | } 103 | 104 | throw e; 105 | } catch (Exception e) { 106 | StatusNotificationsMode i = gitLabPluginConfiguration.statusNotificationsMode(); 107 | if (i == StatusNotificationsMode.COMMIT_STATUS) { 108 | commitFacade.createOrUpdateSonarQubeStatus(MessageHelper.FAILED_GITLAB_STATUS, MessageHelper.sonarQubeFailed(e.getMessage())); 109 | } 110 | 111 | throw MessageException.of(MessageHelper.sonarQubeFailed(e.getMessage()), e); 112 | } 113 | } 114 | 115 | private File fileFromProperty(PostJobContext context, String property) { 116 | String value = context.config().get(property).orElse(null); 117 | return value != null ? new File(value) : null; 118 | } 119 | 120 | private void notification(Reporter report) { 121 | String status = report.getStatus(); 122 | String statusDescription = report.getStatusDescription(); 123 | String message = String.format("Report status=%s, desc=%s", status, statusDescription); 124 | 125 | switch (gitLabPluginConfiguration.statusNotificationsMode()) { 126 | case COMMIT_STATUS: 127 | notificationCommitStatus(status, statusDescription, message); 128 | break; 129 | case EXIT_CODE: 130 | notificationCommitStatus(status, message); 131 | break; 132 | case NOTHING: 133 | LOG.info(message); 134 | break; 135 | } 136 | } 137 | 138 | private void notificationCommitStatus(String status, String statusDescription, String message) { 139 | LOG.info(message); 140 | commitFacade.createOrUpdateSonarQubeStatus(status, statusDescription); 141 | } 142 | 143 | private void notificationCommitStatus(String status, String message) { 144 | if (MessageHelper.FAILED_GITLAB_STATUS.equals(status)) { 145 | throw MessageException.of(message); 146 | } else { 147 | LOG.info(message); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/GlobalCommentBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.freemarker.QualityGateConditionCountTemplateMethodModelEx; 20 | import com.talanlabs.sonar.plugins.gitlab.freemarker.QualityGateConditionsTemplateMethodModelEx; 21 | import com.talanlabs.sonar.plugins.gitlab.models.Issue; 22 | import com.talanlabs.sonar.plugins.gitlab.models.QualityGate; 23 | import com.talanlabs.sonar.plugins.gitlab.models.ReportIssue; 24 | import org.sonar.api.batch.rule.Severity; 25 | 26 | import java.util.*; 27 | 28 | public class GlobalCommentBuilder extends AbstractCommentBuilder { 29 | 30 | private final String author; 31 | private final QualityGate qualityGate; 32 | private final Reporter reporter; 33 | 34 | public GlobalCommentBuilder(GitLabPluginConfiguration gitLabPluginConfiguration, String author, QualityGate qualityGate, Reporter reporter, MarkDownUtils markDownUtils) { 35 | super(gitLabPluginConfiguration, gitLabPluginConfiguration.commitSHA().get(0), reporter.getReportIssues(), markDownUtils, "global", gitLabPluginConfiguration.globalTemplate()); 36 | 37 | this.author = author; 38 | this.qualityGate = qualityGate; 39 | this.reporter = reporter; 40 | } 41 | 42 | @Override 43 | protected Map createContext() { 44 | Map root = super.createContext(); 45 | root.put("author", author); 46 | Arrays.stream(QualityGate.Status.values()).forEach(status -> root.put(status.name(), status)); 47 | root.put("qualityGate", createQualityGateContext()); 48 | return root; 49 | } 50 | 51 | private Map createQualityGateContext() { 52 | if (qualityGate == null) { 53 | return null; 54 | } 55 | Map context = new HashMap<>(); 56 | context.put("status", qualityGate.getStatus()); 57 | context.put("conditions", new QualityGateConditionsTemplateMethodModelEx(qualityGate.getConditions() != null ? qualityGate.getConditions() : Collections.emptyList())); 58 | context.put("conditionCount", new QualityGateConditionCountTemplateMethodModelEx(qualityGate.getConditions() != null ? qualityGate.getConditions() : Collections.emptyList())); 59 | return context; 60 | } 61 | 62 | @Override 63 | protected String buildDefaultComment() { 64 | StringBuilder sb = new StringBuilder(); 65 | 66 | appendQualityGate(sb); 67 | 68 | appendIssues(sb); 69 | 70 | return sb.toString(); 71 | } 72 | 73 | private void appendQualityGate(StringBuilder sb) { 74 | if (qualityGate != null) { 75 | sb.append("SonarQube analysis indicates that quality gate is ").append(toStatusText(qualityGate.getStatus())).append(".\n"); 76 | 77 | if (qualityGate.getConditions() != null) { 78 | qualityGate.getConditions().forEach(c -> appendCondition(sb, c)); 79 | } 80 | 81 | sb.append("\n"); 82 | } 83 | } 84 | 85 | private void appendCondition(StringBuilder sb, QualityGate.Condition condition) { 86 | sb.append("* ").append(condition.getMetricName()).append(" is ").append(toStatusText(condition.getStatus())).append(": Actual value ").append(condition.getActual()); 87 | if (QualityGate.Status.WARN.equals(condition.getStatus())) { 88 | sb.append(" ").append(condition.getSymbol()).append(" ").append(condition.getWarning()); 89 | } else if (QualityGate.Status.ERROR.equals(condition.getStatus())) { 90 | sb.append(" ").append(condition.getSymbol()).append(" ").append(condition.getError()); 91 | } 92 | sb.append("\n"); 93 | } 94 | 95 | private String toStatusText(QualityGate.Status status) { 96 | switch (status) { 97 | case OK: 98 | return "passed"; 99 | case WARN: 100 | return "warning"; 101 | case ERROR: 102 | return "failed"; 103 | case NONE: 104 | return "none"; 105 | } 106 | return "unknown"; 107 | } 108 | 109 | private void appendIssues(StringBuilder sb) { 110 | int newIssues = reporter.getIssueCount(); 111 | if (newIssues == 0) { 112 | sb.append("SonarQube analysis reported no issues.\n"); 113 | } else { 114 | boolean hasInlineIssues = newIssues > reporter.getNotReportedIssueCount(); 115 | boolean extraIssuesTruncated = reporter.getNotReportedIssueCount() > gitLabPluginConfiguration.maxGlobalIssues(); 116 | sb.append("SonarQube analysis reported ").append(newIssues).append(" issue").append(newIssues > 1 ? "s" : "").append("\n"); 117 | 118 | appendSummaryBySeverity(sb); 119 | 120 | if (gitLabPluginConfiguration.tryReportIssuesInline() && hasInlineIssues) { 121 | sb.append("\nWatch the comments in this conversation to review them.\n"); 122 | } 123 | 124 | if (reporter.getNotReportedIssueCount() > 0) { 125 | appendExtraIssues(sb, hasInlineIssues, extraIssuesTruncated); 126 | } 127 | } 128 | } 129 | 130 | private void appendSummaryBySeverity(StringBuilder sb) { 131 | Reporter.SEVERITIES.forEach(severity -> appendNewIssues(sb, severity)); 132 | } 133 | 134 | private void appendNewIssues(StringBuilder builder, Severity severity) { 135 | int issueCount = reporter.getIssueCountForSeverity(severity); 136 | if (issueCount > 0) { 137 | builder.append("* ").append(markDownUtils.getEmojiForSeverity(severity)).append(" ").append(issueCount).append(" ").append(severity.name().toLowerCase(Locale.ENGLISH)).append("\n"); 138 | } 139 | } 140 | 141 | private void appendExtraIssues(StringBuilder builder, boolean hasInlineIssues, boolean extraIssuesTruncated) { 142 | if (gitLabPluginConfiguration.tryReportIssuesInline()) { 143 | if (hasInlineIssues || extraIssuesTruncated) { 144 | int extraCount; 145 | builder.append("\n#### "); 146 | if (reporter.getNotReportedIssueCount() <= gitLabPluginConfiguration.maxGlobalIssues()) { 147 | extraCount = reporter.getNotReportedIssueCount(); 148 | } else { 149 | extraCount = gitLabPluginConfiguration.maxGlobalIssues(); 150 | builder.append("Top "); 151 | } 152 | builder.append(extraCount).append(" extra issue").append(extraCount > 1 ? "s" : "").append("\n"); 153 | } 154 | builder.append( 155 | "\nNote: The following issues were found on lines that were not modified in the commit. " + "Because these issues can't be reported as line comments, they are summarized here:\n"); 156 | } else if (extraIssuesTruncated) { 157 | builder.append("\n#### Top ").append(gitLabPluginConfiguration.maxGlobalIssues()).append(" issue").append(gitLabPluginConfiguration.maxGlobalIssues() > 1 ? "s" : "").append("\n"); 158 | } 159 | 160 | builder.append("\n"); 161 | 162 | appendSeverities(builder); 163 | } 164 | 165 | private void appendSeverities(StringBuilder builder) { 166 | int notReportedDisplayedIssueCount = 0; 167 | int reportedIssueCount = 0; 168 | 169 | for (Severity severity : Reporter.SEVERITIES) { 170 | List reportIssues = reporter.getNotReportedOnDiffReportIssueForSeverity(severity); 171 | if (reportIssues != null && !reportIssues.isEmpty()) { 172 | for (ReportIssue reportIssue : reportIssues) { 173 | notReportedDisplayedIssueCount += appendIssue(builder, reportIssue, reportedIssueCount); 174 | reportedIssueCount++; 175 | } 176 | } 177 | } 178 | 179 | appendMore(builder, notReportedDisplayedIssueCount); 180 | } 181 | 182 | private int appendIssue(StringBuilder builder, ReportIssue reportIssue, int reportedIssueCount) { 183 | Issue issue = reportIssue.getIssue(); 184 | if (reportedIssueCount < gitLabPluginConfiguration.maxGlobalIssues()) { 185 | builder.append("1. ").append(markDownUtils.printIssue(issue.getSeverity(), issue.getMessage(), reportIssue.getRuleLink(), reportIssue.getUrl(), issue.getComponentKey())) 186 | .append("\n"); 187 | return 0; 188 | } else { 189 | return 1; 190 | } 191 | } 192 | 193 | private void appendMore(StringBuilder builder, int notReportedDisplayedIssueCount) { 194 | if (notReportedDisplayedIssueCount > 0) { 195 | builder.append("* ... ").append(notReportedDisplayedIssueCount).append(" more\n"); 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/IGitLabApiWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab; 18 | 19 | import java.io.File; 20 | import java.util.Objects; 21 | import javax.annotation.CheckForNull; 22 | import javax.annotation.Nullable; 23 | 24 | public interface IGitLabApiWrapper { 25 | 26 | void init(); 27 | 28 | String getUsernameForRevision(String revision); 29 | 30 | void createOrUpdateSonarQubeStatus(String status, String statusDescription); 31 | 32 | boolean hasFile(String path); 33 | 34 | String getRevisionForLine(File file, String path, int lineNumber); 35 | 36 | boolean hasSameCommitCommentsForFile(String revision, String path, Integer lineNumber, String body); 37 | 38 | @CheckForNull 39 | String getGitLabUrl(@Nullable String revision, String path, @Nullable Integer issueLine); 40 | 41 | void createOrUpdateReviewComment(String revision, String fullPath, Integer line, String body); 42 | 43 | void addGlobalComment(String comment); 44 | 45 | class Line { 46 | 47 | private final Integer number; 48 | 49 | private final String content; 50 | 51 | Line(Integer number, String content) { 52 | this.number = number; 53 | this.content = content; 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return "Line{" + "number=" + number + 59 | ", content='" + content + '\'' + 60 | '}'; 61 | } 62 | 63 | @Override 64 | public boolean equals(Object o) { 65 | if (this == o) { 66 | return true; 67 | } 68 | if (o == null || getClass() != o.getClass()) { 69 | return false; 70 | } 71 | Line line = (Line) o; 72 | return Objects.equals(number, line.number) && 73 | Objects.equals(content, line.content); 74 | } 75 | 76 | @Override 77 | public int hashCode() { 78 | return Objects.hash(number, content); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/InlineCommentBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.models.ReportIssue; 20 | 21 | import java.util.List; 22 | import java.util.Map; 23 | import java.util.stream.Collectors; 24 | 25 | public class InlineCommentBuilder extends AbstractCommentBuilder { 26 | 27 | private final Integer lineNumber; 28 | private final String author; 29 | 30 | public InlineCommentBuilder(GitLabPluginConfiguration gitLabPluginConfiguration, String revision, String author, Integer lineNumber, List reportIssues, 31 | MarkDownUtils markDownUtils) { 32 | super(gitLabPluginConfiguration, revision, reportIssues, markDownUtils, "inline", gitLabPluginConfiguration.inlineTemplate()); 33 | 34 | this.lineNumber = lineNumber; 35 | this.author = author; 36 | } 37 | 38 | @Override 39 | protected String buildDefaultComment() { 40 | String msg = reportIssues.stream() 41 | .map(reportIssue -> markDownUtils.printIssue(reportIssue.getIssue().getSeverity(), reportIssue.getIssue().getMessage(), reportIssue.getRuleLink(), null, null)) 42 | .map(reportIssue -> reportIssues.size() > 1 ? "* " + reportIssue : reportIssue) 43 | .collect(Collectors.joining("\n")); 44 | if (gitLabPluginConfiguration.pingUser() && author != null) { 45 | if (reportIssues.size() > 1) { 46 | msg += "\n\n"; 47 | } 48 | msg += " @" + author; 49 | } 50 | return msg; 51 | } 52 | 53 | @Override 54 | protected Map createContext() { 55 | Map root = super.createContext(); 56 | root.put("lineNumber", lineNumber); 57 | root.put("author", author); 58 | return root; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/IssueComparator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.models.Issue; 20 | import org.sonar.api.batch.rule.Severity; 21 | 22 | import java.util.Comparator; 23 | import java.util.Objects; 24 | import javax.annotation.Nullable; 25 | 26 | public final class IssueComparator implements Comparator { 27 | private static int compareComponentKeyAndLine(Issue left, Issue right) { 28 | if (!left.getComponentKey().equals(right.getComponentKey())) { 29 | return left.getComponentKey().compareTo(right.getComponentKey()); 30 | } 31 | return compareInt(left.getLine(), right.getLine()); 32 | } 33 | 34 | private static int compareSeverity(Severity leftSeverity, Severity rightSeverity) { 35 | if (leftSeverity.ordinal() > rightSeverity.ordinal()) { 36 | // Display higher severity first. Relies on Severity.ALL to be sorted by severity. 37 | return -1; 38 | } else { 39 | return 1; 40 | } 41 | } 42 | 43 | private static int compareInt(@Nullable Integer leftLine, @Nullable Integer rightLine) { 44 | if (Objects.equals(leftLine, rightLine)) { 45 | return 0; 46 | } else if (leftLine == null) { 47 | return -1; 48 | } else if (rightLine == null) { 49 | return 1; 50 | } else { 51 | return leftLine.compareTo(rightLine); 52 | } 53 | } 54 | 55 | @Override 56 | public int compare(@Nullable Issue left, @Nullable Issue right) { 57 | // Most severe issues should be displayed first. 58 | if (left == right) { 59 | return 0; 60 | } 61 | if (left == null) { 62 | return 1; 63 | } 64 | if (right == null) { 65 | return -1; 66 | } 67 | if (Objects.equals(left.getSeverity(), right.getSeverity())) { 68 | // When severity is the same, sort by component key to at least group issues from 69 | // the same file together. 70 | return compareComponentKeyAndLine(left, right); 71 | } 72 | return compareSeverity(left.getSeverity(), right.getSeverity()); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/MarkDownUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab; 18 | 19 | import org.sonar.api.batch.rule.Severity; 20 | import org.sonar.api.scanner.ScannerSide; 21 | 22 | import javax.annotation.Nullable; 23 | 24 | @ScannerSide 25 | public class MarkDownUtils { 26 | 27 | private static final String IMAGES_ROOT_URL = "https://github.com/gabrie-allaigre/sonar-gitlab-plugin/raw/master/images/"; 28 | 29 | public String getEmojiForSeverity(Severity severity) { 30 | switch (severity) { 31 | case BLOCKER: 32 | return ":no_entry:"; 33 | case CRITICAL: 34 | return ":no_entry_sign:"; 35 | case MAJOR: 36 | return ":warning:"; 37 | case MINOR: 38 | return ":arrow_down_small:"; 39 | case INFO: 40 | return ":information_source:"; 41 | default: 42 | return ":grey_question:"; 43 | } 44 | } 45 | 46 | public String getImageForSeverity(Severity severity) { 47 | return "![" + severity + "](" + IMAGES_ROOT_URL + "severity-" + severity.name().toLowerCase() + ".png)"; 48 | } 49 | 50 | public String printIssue(Severity severity, String message, String ruleLink, @Nullable String url, @Nullable String componentKey) { 51 | StringBuilder sb = new StringBuilder(); 52 | sb.append(getEmojiForSeverity(severity)).append(" "); 53 | if (url != null) { 54 | sb.append("[").append(message).append("]").append("(").append(url).append(")"); 55 | } else { 56 | sb.append(message); 57 | if (componentKey != null) { 58 | sb.append(" ").append("(").append(componentKey).append(")"); 59 | } 60 | } 61 | sb.append(" ").append("[:blue_book:](").append(ruleLink).append(")"); 62 | return sb.toString(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/MessageHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab; 18 | 19 | public class MessageHelper { 20 | 21 | public static final String FAILED_GITLAB_STATUS = "failed"; 22 | public static final String SUCCESS_GITLAB_STATUS = "success"; 23 | 24 | private MessageHelper() { 25 | // Nothing 26 | } 27 | 28 | public static String sonarQubeFailed(String message) { 29 | return String.format("SonarQube failed to complete the review of this commit: %s", message); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/PatchUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab; 18 | 19 | import java.util.HashSet; 20 | import java.util.Set; 21 | import java.util.regex.Matcher; 22 | import java.util.regex.Pattern; 23 | 24 | public class PatchUtils { 25 | 26 | // http://en.wikipedia.org/wiki/Diff_utility#Unified_format 27 | private static final Pattern PATCH_PATTERN = Pattern.compile("@@\\p{Space}-[0-9]+(?:,[0-9]+)?\\p{Space}\\+([0-9]+)(?:,[0-9]+)?\\p{Space}@@.*"); 28 | 29 | private PatchUtils() { 30 | // Nothing 31 | } 32 | 33 | public static Set getPositionsFromPatch(String patch) { 34 | Set positions = new HashSet<>(); 35 | 36 | int currentLine = -1; 37 | for (String line : patch.split("[\\r\\n]+")) { 38 | if (line.startsWith("@")) { 39 | Matcher matcher = PATCH_PATTERN.matcher(line); 40 | if (!matcher.matches()) { 41 | throw new IllegalStateException("Unable to parse line:\n\t" + line + "\nFull patch: \n\t" + patch); 42 | } 43 | currentLine = Integer.parseInt(matcher.group(1)); 44 | } else if (line.startsWith("+")) { 45 | positions.add(new IGitLabApiWrapper.Line(currentLine, line.replaceFirst("\\+", ""))); 46 | currentLine++; 47 | } else if (line.startsWith(" ")) { 48 | // Can't comment line if not addition or deletion due to following bug 49 | // https://gitlab.com/gitlab-org/gitlab-ce/issues/26606 50 | currentLine++; 51 | } 52 | } 53 | 54 | return positions; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/ReporterBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.models.*; 20 | import org.sonar.api.ExtensionPoint; 21 | import org.sonar.api.batch.rule.Severity; 22 | import org.sonar.api.scanner.ScannerSide; 23 | import org.sonar.api.utils.log.Logger; 24 | import org.sonar.api.utils.log.Loggers; 25 | 26 | import java.io.File; 27 | import java.util.Collections; 28 | import java.util.Comparator; 29 | import java.util.List; 30 | import java.util.Map; 31 | import java.util.stream.Stream; 32 | 33 | @ScannerSide 34 | @ExtensionPoint 35 | public class ReporterBuilder { 36 | 37 | private static final Logger LOG = Loggers.get(ReporterBuilder.class); 38 | 39 | private static final Comparator ISSUE_COMPARATOR = new IssueComparator(); 40 | 41 | private final GitLabPluginConfiguration gitLabPluginConfiguration; 42 | private final SonarFacade sonarFacade; 43 | private final CommitFacade commitFacade; 44 | private final MarkDownUtils markDownUtils; 45 | 46 | public ReporterBuilder(GitLabPluginConfiguration gitLabPluginConfiguration, SonarFacade sonarFacade, CommitFacade commitFacade, MarkDownUtils markDownUtils) { 47 | this.gitLabPluginConfiguration = gitLabPluginConfiguration; 48 | this.sonarFacade = sonarFacade; 49 | this.commitFacade = commitFacade; 50 | this.markDownUtils = markDownUtils; 51 | } 52 | 53 | /** 54 | * Build a reporter for issues 55 | * 56 | * @param qualityGate Quality Gate only for publish mode 57 | * @param issues issues 58 | * @return a reporter 59 | */ 60 | public Reporter build(QualityGate qualityGate, List issues) { 61 | Reporter report = new Reporter(gitLabPluginConfiguration); 62 | 63 | report.setQualityGate(qualityGate); 64 | 65 | processIssues(report, issues); 66 | 67 | if (gitLabPluginConfiguration.tryReportIssuesInline() && report.hasFileLine()) { 68 | updateReviewComments(report); 69 | } 70 | 71 | if (!gitLabPluginConfiguration.disableGlobalComment() && isGlobalComments(qualityGate, report)) { 72 | updateGlobalComments(qualityGate, report); 73 | } 74 | 75 | if (!gitLabPluginConfiguration.jsonMode().equals(JsonMode.NONE)) { 76 | String json = report.buildJson(); 77 | commitFacade.writeJsonFile(json); 78 | } 79 | 80 | return report; 81 | } 82 | 83 | private boolean isGlobalComments(QualityGate qualityGate, Reporter report) { 84 | return gitLabPluginConfiguration.commentNoIssue() || report.hasIssue() || (qualityGate != null && !QualityGate.Status.OK.equals(qualityGate.getStatus())); 85 | } 86 | 87 | private void processIssues(Reporter report, List issues) { 88 | getStreamIssue(issues).sorted(ISSUE_COMPARATOR).forEach(i -> processIssue(report, i)); 89 | } 90 | 91 | private Stream getStreamIssue(List issues) { 92 | return issues.stream().filter(p -> gitLabPluginConfiguration.allIssues() || p.isNewIssue()).filter(i -> { 93 | if (gitLabPluginConfiguration.onlyIssueFromCommitLine()) { 94 | return onlyIssueFromCommitLine(i); 95 | } 96 | return !gitLabPluginConfiguration.onlyIssueFromCommitFile() || i.getFile() == null || commitFacade.hasFile(i.getFile()); 97 | }); 98 | } 99 | 100 | private boolean onlyIssueFromCommitLine(Issue issue) { 101 | boolean hasFile = issue.getFile() != null && commitFacade.hasFile(issue.getFile()); 102 | return hasFile && issue.getLine() != null && commitFacade.getRevisionForLine(issue.getFile(), issue.getLine()) != null; 103 | } 104 | 105 | private void processIssue(Reporter report, Issue issue) { 106 | boolean reportedInline = false; 107 | 108 | String revision = null; 109 | if (issue.getFile() != null && issue.getLine() != null) { 110 | revision = commitFacade.getRevisionForLine(issue.getFile(), issue.getLine()); 111 | reportedInline = gitLabPluginConfiguration.tryReportIssuesInline() && revision != null; 112 | } 113 | LOG.debug("Revision for issue {} {} {}", issue, revision, reportedInline); 114 | LOG.debug("file {} {}", issue.getFile(), issue.getLine()); 115 | 116 | String url = commitFacade.getGitLabUrl(revision, issue.getFile(), issue.getLine()); 117 | String src = commitFacade.getSrc(issue.getFile()); 118 | String ruleLink = commitFacade.getRuleLink(issue.getRuleKey()); 119 | 120 | if (toSeverityNum(issue.getSeverity()) >= toSeverityNum(gitLabPluginConfiguration.issueFilter())) { 121 | Rule rule = null; 122 | if (gitLabPluginConfiguration.loadRule()) { 123 | rule = sonarFacade.getRule(issue.getRuleKey()); 124 | } 125 | 126 | report.process(issue, rule, revision, url, src, ruleLink, reportedInline); 127 | } 128 | } 129 | 130 | private int toSeverityNum(Severity severity) { 131 | switch (severity) { 132 | case INFO: 133 | return 0; 134 | case MINOR: 135 | return 1; 136 | case MAJOR: 137 | return 2; 138 | case CRITICAL: 139 | return 3; 140 | case BLOCKER: 141 | return 4; 142 | } 143 | return 0; 144 | } 145 | 146 | private void updateReviewComments(Reporter report) { 147 | LOG.info("Will try to update review comments."); 148 | for (Map.Entry>>> entry : report.getFileLineMap().entrySet()) { 149 | String revision = entry.getKey(); 150 | 151 | String username = commitFacade.getUsernameForRevision(revision); 152 | 153 | for (Map.Entry>> entryPerFile : entry.getValue().entrySet()) { 154 | updateReviewComments(revision, username, entryPerFile.getKey(), entryPerFile.getValue()); 155 | } 156 | } 157 | } 158 | 159 | private void updateReviewComments(String revision, String username, File file, Map> linePerIssuesMap) { 160 | for (Map.Entry> entryPerLine : linePerIssuesMap.entrySet()) { 161 | updateReviewComments(revision, username, file, entryPerLine.getKey(), entryPerLine.getValue()); 162 | } 163 | } 164 | 165 | private void updateReviewComments(String revision, String username, File file, Integer lineNumber, List reportIssues) { 166 | LOG.debug("updateReviewComments {} {}", revision, reportIssues); 167 | if (gitLabPluginConfiguration.uniqueIssuePerInline()) { 168 | for (ReportIssue reportIssue : reportIssues) { 169 | updateReviewCommentsPerInline(revision, username, file, lineNumber, Collections.singletonList(reportIssue)); 170 | } 171 | } else { 172 | updateReviewCommentsPerInline(revision, username, file, lineNumber, reportIssues); 173 | } 174 | } 175 | 176 | private void updateReviewCommentsPerInline(String revision, String username, File file, Integer lineNumber, List reportIssues) { 177 | String body = new InlineCommentBuilder(gitLabPluginConfiguration, revision, username, lineNumber, reportIssues, markDownUtils).buildForMarkdown(); 178 | if (body != null && !body.trim().isEmpty()) { 179 | boolean exists = commitFacade.hasSameCommitCommentsForFile(revision, file, lineNumber, body); 180 | if (!exists) { 181 | commitFacade.createOrUpdateReviewComment(revision, file, lineNumber, body); 182 | } 183 | } 184 | } 185 | 186 | private void updateGlobalComments(QualityGate qualityGate, Reporter report) { 187 | String username = commitFacade.getUsernameForRevision(gitLabPluginConfiguration.commitSHA().get(0)); 188 | String body = new GlobalCommentBuilder(gitLabPluginConfiguration, username, qualityGate, report, markDownUtils).buildForMarkdown(); 189 | if (body != null && !body.trim().isEmpty()) { 190 | commitFacade.addGlobalComment(body); 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/api/GitLabAPIMergeRequestDiscussionExt.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.api; 18 | 19 | import com.talanlabs.gitlab.api.Paged; 20 | import com.talanlabs.gitlab.api.v4.GitLabAPI; 21 | import com.talanlabs.gitlab.api.v4.Pagination; 22 | import com.talanlabs.gitlab.api.v4.http.Query; 23 | import com.talanlabs.gitlab.api.v4.utils.QueryHelper; 24 | import org.sonar.api.utils.log.Logger; 25 | import org.sonar.api.utils.log.Loggers; 26 | 27 | import java.io.IOException; 28 | import java.io.Serializable; 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | 32 | public class GitLabAPIMergeRequestDiscussionExt { 33 | 34 | private static final Logger LOG = Loggers.get(GitLabAPIMergeRequestDiscussionExt.class); 35 | 36 | private static final String BASE_URL = "/projects/%s/merge_requests/%d/discussions"; 37 | 38 | private final GitLabAPI gitLabAPI; 39 | static List discussions; 40 | 41 | public GitLabAPIMergeRequestDiscussionExt(GitLabAPI gitLabAPI) { 42 | this.gitLabAPI = gitLabAPI; 43 | } 44 | 45 | // 46 | // /** 47 | // * Gets a list of all discussions for a single merge request. 48 | // *

49 | // * GET /projects/:id/merge_requests/:merge_request_iid/discussions 50 | // * 51 | // * @param projectId (required) - The ID or URL-encoded path of the project 52 | // * @param iid (required) - The IID of a merge request 53 | // * @param pagination (optional) - The ID of a discussion 54 | // * @return Paged object of {@link GitlabDiscussionStatus} instances 55 | // * @throws IOException 56 | // */ 57 | public Paged getAllDiscussions(Serializable projectId, Integer iid, Pagination pagination) 58 | throws IOException { 59 | Query query = QueryHelper.getQuery(pagination); 60 | String tailUrl = String.format(BASE_URL + "%s", gitLabAPI.sanitize(projectId), iid, query.build()); 61 | return gitLabAPI.retrieve().toPaged(tailUrl, GitlabDiscussionStatus[].class); 62 | } 63 | 64 | public Boolean hasDiscussion(Integer projectId, Integer mergeRequestIid, String fullPath, Integer lineNumber, 65 | String body, String baseSha, String headSha) throws IOException { 66 | 67 | if (discussions == null) { 68 | 69 | LOG.debug("gettting existing Merge Request discussions"); 70 | 71 | Paged paged = getAllDiscussions(projectId, mergeRequestIid, null); 72 | 73 | do { 74 | if (paged.getResults() != null) { 75 | if (discussions == null) { 76 | discussions = new ArrayList<>(); 77 | } 78 | discussions.addAll(paged.getResults()); 79 | } 80 | } while ((paged = paged.nextPage()) != null); 81 | 82 | 83 | if (LOG.isDebugEnabled()) { 84 | LOG.debug("Existing count {}", discussions.size()); 85 | discussions.forEach(item -> { 86 | LOG.debug("discussion {} note count {}", item.getId(), item.getNotes().size()); 87 | item.getNotes().forEach(note -> { 88 | GitlabPosition position = note.getPosition(); 89 | if (position != null) { 90 | LOG.debug("-File: {} {}", position.getNewPath(), position.getNewLine()); 91 | LOG.debug("-bSha: {}, hSha {}", position.getBaseSha(), position.getHeadSha()); 92 | } 93 | LOG.debug("-Note {}: {}",note.getBody().length(), note.getBody()); 94 | }); 95 | }); 96 | 97 | LOG.debug("Current Issue Comment:"); 98 | LOG.debug("Path {} {}", fullPath, lineNumber); 99 | LOG.debug("bSha: {}, hSha {}", baseSha, headSha); 100 | LOG.debug("Note {}: {}",body.length(), body); 101 | } 102 | } 103 | 104 | boolean isExist = discussions.stream().anyMatch(x -> 105 | x.getNotes().stream().anyMatch(n -> 106 | body.equals(n.getBody()) 107 | && n.getPosition() != null 108 | && lineNumber.equals(n.getPosition().getNewLine()) 109 | && baseSha.equals(n.getPosition().getBaseSha()) 110 | && headSha.equals(n.getPosition().getHeadSha()) 111 | && fullPath.equals(n.getPosition().getNewPath()) 112 | ) 113 | ); 114 | 115 | if(isExist) 116 | LOG.debug("-------------------Issue Comment already exist on MR----------------------"); 117 | 118 | return isExist; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/api/GitlabDiscussionStatus.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.api; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | 21 | import java.util.List; 22 | 23 | public class GitlabDiscussionStatus { 24 | 25 | 26 | private String id; 27 | 28 | @JsonProperty("individual_note") 29 | private boolean individualNote; 30 | 31 | private List notes; 32 | 33 | public String getId() { 34 | return id; 35 | } 36 | 37 | public void setId(String id) { 38 | this.id = id; 39 | } 40 | 41 | public boolean isIndividualNote() { 42 | return individualNote; 43 | } 44 | 45 | public void setIndividualNote(boolean individualNote) { 46 | this.individualNote = individualNote; 47 | } 48 | 49 | public List getNotes() { 50 | return notes; 51 | } 52 | 53 | public void setNotes(List notes) { 54 | this.notes = notes; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/api/GitlabNote.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.api; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | import com.talanlabs.gitlab.api.v4.models.users.GitLabUser; 21 | 22 | import java.util.Date; 23 | 24 | public class GitlabNote { 25 | 26 | public static final String URL = "/notes"; 27 | 28 | private Integer id; 29 | private String body; 30 | private String attachment; 31 | private GitLabUser author; 32 | private boolean system; 33 | private boolean upvote; 34 | private boolean downvote; 35 | 36 | @JsonProperty("created_at") 37 | private Date createdAt; 38 | 39 | private GitlabPosition position; 40 | 41 | @JsonProperty("noteable_id") 42 | private Integer noteableId; 43 | 44 | @JsonProperty("noteableType") 45 | private String noteable_type; 46 | 47 | private boolean resolvable; 48 | private boolean resolved; 49 | 50 | @JsonProperty("resolved_by") 51 | private GitLabUser resolvedBy; 52 | 53 | @JsonProperty("noteable_iid") 54 | private Integer noteableIid; 55 | 56 | public Integer getId() { 57 | return id; 58 | } 59 | 60 | public void setId(Integer id) { 61 | this.id = id; 62 | } 63 | 64 | public String getBody() { 65 | return body; 66 | } 67 | 68 | public void setBody(String body) { 69 | this.body = body; 70 | } 71 | 72 | public GitLabUser getAuthor() { 73 | return author; 74 | } 75 | 76 | public void setAuthor(GitLabUser author) { 77 | this.author = author; 78 | } 79 | 80 | public Date getCreatedAt() { 81 | return createdAt; 82 | } 83 | 84 | public void setCreatedAt(Date createdAt) { 85 | this.createdAt = createdAt; 86 | } 87 | 88 | public String getAttachment() { 89 | return attachment; 90 | } 91 | 92 | public void setAttachment(String attachment) { 93 | this.attachment = attachment; 94 | } 95 | 96 | public boolean isSystem() { 97 | return system; 98 | } 99 | 100 | public void setSystem(boolean system) { 101 | this.system = system; 102 | } 103 | 104 | public boolean isUpvote() { 105 | return upvote; 106 | } 107 | 108 | public void setUpvote(boolean upvote) { 109 | this.upvote = upvote; 110 | } 111 | 112 | public boolean isDownvote() { 113 | return downvote; 114 | } 115 | 116 | public void setDownvote(boolean downvote) { 117 | this.downvote = downvote; 118 | } 119 | 120 | public GitlabPosition getPosition() { 121 | return position; 122 | } 123 | 124 | public void setPosition(GitlabPosition position) { 125 | this.position = position; 126 | } 127 | 128 | public Integer getNoteableId() { 129 | return noteableId; 130 | } 131 | 132 | public void setNoteableId(Integer noteableId) { 133 | this.noteableId = noteableId; 134 | } 135 | 136 | public String getNoteable_type() { 137 | return noteable_type; 138 | } 139 | 140 | public void setNoteable_type(String noteable_type) { 141 | this.noteable_type = noteable_type; 142 | } 143 | 144 | public boolean isResolvable() { 145 | return resolvable; 146 | } 147 | 148 | public void setResolvable(boolean resolvable) { 149 | this.resolvable = resolvable; 150 | } 151 | 152 | public boolean isResolved() { 153 | return resolved; 154 | } 155 | 156 | public void setResolved(boolean resolved) { 157 | this.resolved = resolved; 158 | } 159 | 160 | public GitLabUser getResolvedBy() { 161 | return resolvedBy; 162 | } 163 | 164 | public void setResolvedBy(GitLabUser resolvedBy) { 165 | this.resolvedBy = resolvedBy; 166 | } 167 | 168 | public Integer getNoteableIid() { 169 | return noteableIid; 170 | } 171 | 172 | public void setNoteableIid(Integer noteableIid) { 173 | this.noteableIid = noteableIid; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/api/GitlabPosition.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.api; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | 21 | public class GitlabPosition { 22 | 23 | 24 | @JsonProperty("base_sha") 25 | private String baseSha; 26 | 27 | @JsonProperty("start_sha") 28 | private String startSha; 29 | 30 | @JsonProperty("head_sha") 31 | private String headSha; 32 | 33 | @JsonProperty("position_type") 34 | private String positionType; 35 | 36 | @JsonProperty("new_path") 37 | private String newPath; 38 | 39 | @JsonProperty("new_line") 40 | private Integer newLine; 41 | 42 | @JsonProperty("old_path") 43 | private String oldPath; 44 | 45 | @JsonProperty("old_line") 46 | private Integer oldLine; 47 | 48 | private Integer width; 49 | private Integer height; 50 | private Integer x; 51 | private Integer y; 52 | 53 | public String getBaseSha() { 54 | return baseSha; 55 | } 56 | 57 | public void setBaseSha(String baseSha) { 58 | this.baseSha = baseSha; 59 | } 60 | 61 | public String getStartSha() { 62 | return startSha; 63 | } 64 | 65 | public void setStartSha(String startSha) { 66 | this.startSha = startSha; 67 | } 68 | 69 | public String getHeadSha() { 70 | return headSha; 71 | } 72 | 73 | public void setHeadSha(String headSha) { 74 | this.headSha = headSha; 75 | } 76 | 77 | public String getPositionType() { 78 | return positionType; 79 | } 80 | 81 | public void setPositionType(String positionType) { 82 | this.positionType = positionType; 83 | } 84 | 85 | public String getNewPath() { 86 | return newPath; 87 | } 88 | 89 | public void setNewPath(String newPath) { 90 | this.newPath = newPath; 91 | } 92 | 93 | public Integer getNewLine() { 94 | return newLine; 95 | } 96 | 97 | public void setNewLine(Integer newLine) { 98 | this.newLine = newLine; 99 | } 100 | 101 | public String getOldPath() { 102 | return oldPath; 103 | } 104 | 105 | public void setOldPath(String oldPath) { 106 | this.oldPath = oldPath; 107 | } 108 | 109 | public Integer getOldLine() { 110 | return oldLine; 111 | } 112 | 113 | public void setOldLine(Integer oldLine) { 114 | this.oldLine = oldLine; 115 | } 116 | 117 | public Integer getWidth() { 118 | return width; 119 | } 120 | 121 | public void setWidth(Integer width) { 122 | this.width = width; 123 | } 124 | 125 | public Integer getHeight() { 126 | return height; 127 | } 128 | 129 | public void setHeight(Integer height) { 130 | this.height = height; 131 | } 132 | 133 | public Integer getX() { 134 | return x; 135 | } 136 | 137 | public void setX(Integer x) { 138 | this.x = x; 139 | } 140 | 141 | public Integer getY() { 142 | return y; 143 | } 144 | 145 | public void setY(Integer y) { 146 | this.y = y; 147 | } 148 | } 149 | 150 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/freemarker/AbstractIssuesTemplateMethodModelEx.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.freemarker; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.models.ReportIssue; 20 | import freemarker.template.TemplateBooleanModel; 21 | import freemarker.template.TemplateMethodModelEx; 22 | import freemarker.template.TemplateModelException; 23 | import freemarker.template.TemplateScalarModel; 24 | import org.sonar.api.batch.rule.Severity; 25 | 26 | import java.util.ArrayList; 27 | import java.util.Collections; 28 | import java.util.List; 29 | import java.util.stream.Stream; 30 | 31 | public abstract class AbstractIssuesTemplateMethodModelEx implements TemplateMethodModelEx { 32 | 33 | private final List reportIssues; 34 | 35 | AbstractIssuesTemplateMethodModelEx(List reportIssues) { 36 | super(); 37 | 38 | this.reportIssues = Collections.unmodifiableList(new ArrayList<>(reportIssues)); 39 | } 40 | 41 | @Override 42 | public Object exec(List arguments) throws TemplateModelException { 43 | if (arguments.isEmpty()) { 44 | return execEmptyArg(); 45 | } else if (arguments.size() == 1) { 46 | return execOneArg(arguments.get(0)); 47 | } else if (arguments.size() == 2) { 48 | return execTwoArg(arguments.get(0), arguments.get(1)); 49 | } 50 | throw new TemplateModelException("Failed call accept 0, 1 or 2 args"); 51 | } 52 | 53 | protected abstract Object exec(Stream stream); 54 | 55 | private Object execEmptyArg() { 56 | return exec(reportIssues.stream()); 57 | } 58 | 59 | private Object execOneArg(Object arg) throws TemplateModelException { 60 | if (arg instanceof TemplateScalarModel) { 61 | String name = ((TemplateScalarModel) arg).getAsString(); 62 | try { 63 | Severity severity = Severity.valueOf(name); 64 | return exec(reportIssues.stream().filter(i -> isSeverityEquals(i, severity))); 65 | } catch (IllegalArgumentException e) { 66 | throw new TemplateModelException("Failed call 1 Severity arg (INFO,MINOR,MAJOR,CRITICAL,BLOCKER)", e); 67 | } 68 | } else if (arg instanceof TemplateBooleanModel) { 69 | boolean r = ((TemplateBooleanModel) arg).getAsBoolean(); 70 | return exec(reportIssues.stream().filter(i -> isSameReportedOnDiff(i, r))); 71 | } 72 | throw new TemplateModelException("Failed call accept boolean or Severity"); 73 | } 74 | 75 | private Object execTwoArg(Object arg1, Object arg2) throws TemplateModelException { 76 | if (arg1 instanceof TemplateScalarModel && arg2 instanceof TemplateBooleanModel) { 77 | String name = ((TemplateScalarModel) arg1).getAsString(); 78 | boolean r = ((TemplateBooleanModel) arg2).getAsBoolean(); 79 | try { 80 | Severity severity = Severity.valueOf(name); 81 | return exec(reportIssues.stream().filter(i -> isSeverityEquals(i, severity) && isSameReportedOnDiff(i, r))); 82 | } catch (IllegalArgumentException e) { 83 | throw new TemplateModelException("Failed call Severity arg (INFO,MINOR,MAJOR,CRITICAL,BLOCKER)", e); 84 | } 85 | } 86 | throw new TemplateModelException("Failed call accept 2 args boolean or Severity"); 87 | } 88 | 89 | private boolean isSeverityEquals(ReportIssue reportIssue, Severity severity) { 90 | return severity == reportIssue.getIssue().getSeverity(); 91 | } 92 | 93 | private boolean isSameReportedOnDiff(ReportIssue reportIssue, boolean r) { 94 | return (r && reportIssue.isReportedOnDiff()) || (!r && !reportIssue.isReportedOnDiff()); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/freemarker/AbstractQualityGateConditionsTemplateMethodModelEx.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.freemarker; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.models.QualityGate; 20 | import freemarker.template.TemplateMethodModelEx; 21 | import freemarker.template.TemplateModelException; 22 | import freemarker.template.TemplateScalarModel; 23 | 24 | import java.util.ArrayList; 25 | import java.util.Collections; 26 | import java.util.List; 27 | import java.util.stream.Stream; 28 | 29 | public abstract class AbstractQualityGateConditionsTemplateMethodModelEx implements TemplateMethodModelEx { 30 | 31 | private final List conditions; 32 | 33 | AbstractQualityGateConditionsTemplateMethodModelEx(List conditions) { 34 | super(); 35 | 36 | this.conditions = Collections.unmodifiableList(new ArrayList<>(conditions)); 37 | } 38 | 39 | @Override 40 | public Object exec(List arguments) throws TemplateModelException { 41 | if (arguments.isEmpty()) { 42 | return execEmptyArg(); 43 | } else if (arguments.size() == 1) { 44 | return execOneArg(arguments.get(0)); 45 | } 46 | throw new TemplateModelException("Failed call accept 0 or 1 args"); 47 | } 48 | 49 | protected abstract Object exec(Stream stream); 50 | 51 | private Object execEmptyArg() { 52 | return exec(conditions.stream()); 53 | } 54 | 55 | private Object execOneArg(Object arg) throws TemplateModelException { 56 | if (arg instanceof TemplateScalarModel) { 57 | String name = ((TemplateScalarModel) arg).getAsString(); 58 | try { 59 | QualityGate.Status status = QualityGate.Status.valueOf(name); 60 | return exec(conditions.stream().filter(c -> isStatusEquals(c, status))); 61 | } catch (IllegalArgumentException e) { 62 | throw new TemplateModelException("Failed call 1 Status arg (OK,WARN,ERROR)", e); 63 | } 64 | } 65 | throw new TemplateModelException("Failed call accept Status"); 66 | } 67 | 68 | private boolean isStatusEquals(QualityGate.Condition condition, QualityGate.Status status) { 69 | return status == condition.getStatus(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/freemarker/AbstractSeverityTemplateMethodModelEx.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.freemarker; 18 | 19 | import freemarker.template.TemplateMethodModelEx; 20 | import freemarker.template.TemplateModelException; 21 | import freemarker.template.TemplateScalarModel; 22 | import org.sonar.api.batch.rule.Severity; 23 | 24 | import java.util.List; 25 | 26 | public abstract class AbstractSeverityTemplateMethodModelEx implements TemplateMethodModelEx { 27 | 28 | AbstractSeverityTemplateMethodModelEx() { 29 | super(); 30 | } 31 | 32 | @Override 33 | public Object exec(List arguments) throws TemplateModelException { 34 | if (arguments.size() == 1) { 35 | return execOneArg(arguments.get(0)); 36 | } 37 | throw new TemplateModelException("Failed call accept 1 Severity arg (INFO,MINOR,MAJOR,CRITICAL,BLOCKER)"); 38 | } 39 | 40 | protected abstract Object exec(Severity severity); 41 | 42 | private Object execOneArg(Object arg) throws TemplateModelException { 43 | if (arg instanceof TemplateScalarModel) { 44 | String name = ((TemplateScalarModel) arg).getAsString(); 45 | try { 46 | Severity severity = Severity.valueOf(name); 47 | return exec(severity); 48 | } catch (IllegalArgumentException e) { 49 | throw new TemplateModelException("Failed call 1 Severity arg (INFO,MINOR,MAJOR,CRITICAL,BLOCKER)", e); 50 | } 51 | } 52 | throw new TemplateModelException("Failed call accept 1 Severity arg (INFO,MINOR,MAJOR,CRITICAL,BLOCKER)"); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/freemarker/EmojiSeverityTemplateMethodModelEx.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.freemarker; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.MarkDownUtils; 20 | import org.sonar.api.batch.rule.Severity; 21 | 22 | public class EmojiSeverityTemplateMethodModelEx extends AbstractSeverityTemplateMethodModelEx { 23 | 24 | private final MarkDownUtils markDownUtils; 25 | 26 | public EmojiSeverityTemplateMethodModelEx(MarkDownUtils markDownUtils) { 27 | super(); 28 | 29 | this.markDownUtils = markDownUtils; 30 | } 31 | 32 | @Override 33 | protected Object exec(Severity severity) { 34 | return markDownUtils.getEmojiForSeverity(severity); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/freemarker/ImageSeverityTemplateMethodModelEx.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.freemarker; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.MarkDownUtils; 20 | import org.sonar.api.batch.rule.Severity; 21 | 22 | public class ImageSeverityTemplateMethodModelEx extends AbstractSeverityTemplateMethodModelEx { 23 | 24 | private final MarkDownUtils markDownUtils; 25 | 26 | public ImageSeverityTemplateMethodModelEx(MarkDownUtils markDownUtils) { 27 | super(); 28 | 29 | this.markDownUtils = markDownUtils; 30 | } 31 | 32 | @Override 33 | protected Object exec(Severity severity) { 34 | return markDownUtils.getImageForSeverity(severity); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/freemarker/IssueCountTemplateMethodModelEx.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.freemarker; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.models.ReportIssue; 20 | 21 | import java.util.List; 22 | import java.util.stream.Stream; 23 | 24 | public class IssueCountTemplateMethodModelEx extends AbstractIssuesTemplateMethodModelEx { 25 | 26 | public IssueCountTemplateMethodModelEx(List reportIssues) { 27 | super(reportIssues); 28 | } 29 | 30 | @Override 31 | protected Object exec(Stream stream) { 32 | return (int) stream.count(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/freemarker/IssuesTemplateMethodModelEx.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.freemarker; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.models.ReportIssue; 20 | import com.talanlabs.sonar.plugins.gitlab.models.Rule; 21 | 22 | import java.util.HashMap; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.stream.Collectors; 26 | import java.util.stream.Stream; 27 | 28 | public class IssuesTemplateMethodModelEx extends AbstractIssuesTemplateMethodModelEx { 29 | 30 | public IssuesTemplateMethodModelEx(List reportIssues) { 31 | super(reportIssues); 32 | } 33 | 34 | @Override 35 | protected Object exec(Stream stream) { 36 | return stream.map(this::convertReportIssue).collect(Collectors.toList()); 37 | } 38 | 39 | private Map convertReportIssue(ReportIssue reportIssue) { 40 | Map root = new HashMap<>(); 41 | root.put("reportedOnDiff", reportIssue.isReportedOnDiff()); 42 | root.put("url", reportIssue.getUrl()); 43 | root.put("componentKey", reportIssue.getIssue().getComponentKey()); 44 | root.put("severity", reportIssue.getIssue().getSeverity()); 45 | root.put("line", reportIssue.getIssue().getLine()); 46 | root.put("key", reportIssue.getIssue().getKey()); 47 | root.put("message", reportIssue.getIssue().getMessage()); 48 | root.put("ruleKey", reportIssue.getIssue().getRuleKey()); 49 | root.put("new", reportIssue.getIssue().isNewIssue()); 50 | root.put("ruleLink", reportIssue.getRuleLink()); 51 | root.put("src", reportIssue.getFile()); 52 | root.put("rule", reportIssue.getRule() != null ? convertRule(reportIssue.getRule()) : null); 53 | return root; 54 | } 55 | 56 | private Map convertRule(Rule rule) { 57 | Map root = new HashMap<>(); 58 | root.put("key", rule.getKey()); 59 | root.put("repo", rule.getRepo()); 60 | root.put("name", rule.getName()); 61 | root.put("description", rule.getDescription()); 62 | root.put("type", rule.getType()); 63 | root.put("debtRemFnType", rule.getDebtRemFnType()); 64 | root.put("debtRemFnBaseEffort", rule.getDebtRemFnBaseEffort()); 65 | return root; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/freemarker/PrintTemplateMethodModelEx.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.freemarker; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.MarkDownUtils; 20 | import freemarker.ext.util.WrapperTemplateModel; 21 | import freemarker.template.TemplateMethodModelEx; 22 | import freemarker.template.TemplateModelException; 23 | import org.sonar.api.batch.rule.Severity; 24 | 25 | import java.util.List; 26 | import java.util.Map; 27 | 28 | public class PrintTemplateMethodModelEx implements TemplateMethodModelEx { 29 | 30 | private final MarkDownUtils markDownUtils; 31 | 32 | public PrintTemplateMethodModelEx(MarkDownUtils markDownUtils) { 33 | super(); 34 | 35 | this.markDownUtils = markDownUtils; 36 | } 37 | 38 | @Override 39 | public Object exec(List arguments) throws TemplateModelException { 40 | if (arguments.size() == 1) { 41 | return execOneArg(arguments.get(0)); 42 | } 43 | throw new TemplateModelException("Failed call accept 1 issue arg"); 44 | } 45 | 46 | private String execOneArg(Object arg) throws TemplateModelException { 47 | if (arg instanceof WrapperTemplateModel && ((WrapperTemplateModel) arg).getWrappedObject() instanceof Map) { 48 | Map map = (Map) ((WrapperTemplateModel) arg).getWrappedObject(); 49 | return markDownUtils.printIssue((Severity) (map.get("severity")), (String) map.get("message"), (String) map.get("ruleLink"), (String) map.get("url"), (String) map.get("componentKey")); 50 | } 51 | throw new TemplateModelException("Failed call accept 1 issue arg"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/freemarker/QualityGateConditionCountTemplateMethodModelEx.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.freemarker; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.models.QualityGate; 20 | 21 | import java.util.List; 22 | import java.util.stream.Stream; 23 | 24 | public class QualityGateConditionCountTemplateMethodModelEx extends AbstractQualityGateConditionsTemplateMethodModelEx { 25 | 26 | public QualityGateConditionCountTemplateMethodModelEx(List conditions) { 27 | super(conditions); 28 | } 29 | 30 | @Override 31 | protected Object exec(Stream stream) { 32 | return (int)stream.count(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/freemarker/QualityGateConditionsTemplateMethodModelEx.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.freemarker; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.models.QualityGate; 20 | 21 | import java.util.HashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.stream.Collectors; 25 | import java.util.stream.Stream; 26 | 27 | public class QualityGateConditionsTemplateMethodModelEx extends AbstractQualityGateConditionsTemplateMethodModelEx { 28 | 29 | public QualityGateConditionsTemplateMethodModelEx(List conditions) { 30 | super(conditions); 31 | } 32 | 33 | @Override 34 | protected Object exec(Stream stream) { 35 | return stream.map(this::convertCondition).collect(Collectors.toList()); 36 | } 37 | 38 | private Map convertCondition(QualityGate.Condition condition) { 39 | Map root = new HashMap<>(); 40 | root.put("status", condition.getStatus()); 41 | root.put("actual", condition.getActual()); 42 | root.put("warning", condition.getWarning()); 43 | root.put("error", condition.getError()); 44 | root.put("metricKey", condition.getMetricKey()); 45 | root.put("metricName", condition.getMetricName()); 46 | root.put("symbol", condition.getSymbol()); 47 | return root; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/freemarker/RuleLinkTemplateMethodModelEx.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.freemarker; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.CommitFacade; 20 | import com.talanlabs.sonar.plugins.gitlab.GitLabPluginConfiguration; 21 | import freemarker.template.TemplateMethodModelEx; 22 | import freemarker.template.TemplateModelException; 23 | import freemarker.template.TemplateScalarModel; 24 | 25 | import java.util.List; 26 | 27 | public class RuleLinkTemplateMethodModelEx implements TemplateMethodModelEx { 28 | 29 | private final String ruleUrlPrefix; 30 | 31 | public RuleLinkTemplateMethodModelEx(GitLabPluginConfiguration gitLabPluginConfiguration) { 32 | super(); 33 | 34 | this.ruleUrlPrefix = gitLabPluginConfiguration.baseUrl(); 35 | } 36 | 37 | @Override 38 | public Object exec(List arguments) throws TemplateModelException { 39 | if (arguments.size() == 1) { 40 | return execOneArg(arguments.get(0)); 41 | } 42 | throw new TemplateModelException("Failed call accept 1 url arg"); 43 | } 44 | 45 | private Object execOneArg(Object arg) throws TemplateModelException { 46 | if (arg instanceof TemplateScalarModel) { 47 | String name = ((TemplateScalarModel) arg).getAsString(); 48 | return ruleUrlPrefix + "coding_rules#rule_key=" + CommitFacade.encodeForUrl(name); 49 | } 50 | throw new TemplateModelException("Failed call accept 1 url arg"); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/models/Issue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.models; 18 | 19 | import org.sonar.api.batch.rule.Severity; 20 | 21 | import java.io.File; 22 | 23 | public class Issue { 24 | 25 | private String key; 26 | private String ruleKey; 27 | private String componentKey; 28 | private File file; 29 | private Integer line; 30 | private String message; 31 | private Severity severity; 32 | private boolean newIssue; 33 | 34 | private Issue() { 35 | // Nothing 36 | } 37 | 38 | public static Builder newBuilder() { 39 | return new Builder(); 40 | } 41 | 42 | public String getKey() { 43 | return key; 44 | } 45 | 46 | public String getRuleKey() { 47 | return ruleKey; 48 | } 49 | 50 | public String getComponentKey() { 51 | return componentKey; 52 | } 53 | 54 | public File getFile() { 55 | return file; 56 | } 57 | 58 | public Integer getLine() { 59 | return line; 60 | } 61 | 62 | public String getMessage() { 63 | return message; 64 | } 65 | 66 | public Severity getSeverity() { 67 | return severity; 68 | } 69 | 70 | public boolean isNewIssue() { 71 | return newIssue; 72 | } 73 | 74 | public static class Builder { 75 | 76 | private final Issue issue; 77 | 78 | private Builder() { 79 | this.issue = new Issue(); 80 | } 81 | 82 | public Builder key(String key) { 83 | issue.key = key; 84 | return this; 85 | } 86 | 87 | public Builder ruleKey(String ruleKey) { 88 | issue.ruleKey = ruleKey; 89 | return this; 90 | } 91 | 92 | public Builder componentKey(String componentKey) { 93 | issue.componentKey = componentKey; 94 | return this; 95 | } 96 | 97 | public Builder file(File file) { 98 | issue.file = file; 99 | return this; 100 | } 101 | 102 | public Builder line(Integer line) { 103 | issue.line = line; 104 | return this; 105 | } 106 | 107 | public Builder message(String message) { 108 | issue.message = message; 109 | return this; 110 | } 111 | 112 | public Builder severity(Severity severity) { 113 | issue.severity = severity; 114 | return this; 115 | } 116 | 117 | public Builder newIssue(boolean newIssue) { 118 | issue.newIssue = newIssue; 119 | return this; 120 | } 121 | 122 | public Issue build() { 123 | return issue; 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/models/JsonMode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.models; 18 | 19 | public enum JsonMode { 20 | 21 | NONE, CODECLIMATE, SAST; 22 | 23 | public static JsonMode of(String name) { 24 | for (JsonMode m : values()) { 25 | if (m.name().equals(name)) { 26 | return m; 27 | } 28 | } 29 | return null; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/models/QualityGate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.models; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Collections; 21 | import java.util.List; 22 | 23 | public class QualityGate { 24 | 25 | private Status status; 26 | private List conditions; 27 | 28 | private QualityGate() { 29 | // Nothing 30 | } 31 | 32 | public static Builder newBuilder() { 33 | return new Builder(); 34 | } 35 | 36 | public Status getStatus() { 37 | return status; 38 | } 39 | 40 | public List getConditions() { 41 | return Collections.unmodifiableList(conditions); 42 | } 43 | 44 | public enum Status { 45 | 46 | OK, WARN, ERROR, NONE; 47 | 48 | public static Status of(String name) { 49 | for (Status m : values()) { 50 | if (m.name().equals(name)) { 51 | return m; 52 | } 53 | } 54 | return null; 55 | } 56 | } 57 | 58 | public static class Builder { 59 | 60 | private final QualityGate qualityGate; 61 | 62 | private Builder() { 63 | this.qualityGate = new QualityGate(); 64 | } 65 | 66 | public Builder status(Status status) { 67 | this.qualityGate.status = status; 68 | return this; 69 | } 70 | 71 | public Builder conditions(List conditions) { 72 | this.qualityGate.conditions = new ArrayList<>(conditions); 73 | return this; 74 | } 75 | 76 | public QualityGate build() { 77 | return qualityGate; 78 | } 79 | } 80 | 81 | public static class Condition { 82 | 83 | private Status status; 84 | private String metricKey; 85 | private String metricName; 86 | private String actual; 87 | private String symbol; 88 | private String warning; 89 | private String error; 90 | 91 | private Condition() { 92 | // Nothing 93 | } 94 | 95 | public static Builder newBuilder() { 96 | return new Builder(); 97 | } 98 | 99 | public Status getStatus() { 100 | return status; 101 | } 102 | 103 | public String getMetricKey() { 104 | return metricKey; 105 | } 106 | 107 | public String getMetricName() { 108 | return metricName; 109 | } 110 | 111 | public String getActual() { 112 | return actual; 113 | } 114 | 115 | public String getSymbol() { 116 | return symbol; 117 | } 118 | 119 | public String getWarning() { 120 | return warning; 121 | } 122 | 123 | public String getError() { 124 | return error; 125 | } 126 | 127 | public static class Builder { 128 | 129 | private final Condition condition; 130 | 131 | private Builder() { 132 | this.condition = new Condition(); 133 | } 134 | 135 | public Builder status(Status status) { 136 | this.condition.status = status; 137 | return this; 138 | } 139 | 140 | public Builder metricKey(String metricKey) { 141 | this.condition.metricKey = metricKey; 142 | return this; 143 | } 144 | 145 | public Builder metricName(String metricName) { 146 | this.condition.metricName = metricName; 147 | return this; 148 | } 149 | 150 | public Builder actual(String actual) { 151 | this.condition.actual = actual; 152 | return this; 153 | } 154 | 155 | public Builder symbol(String symbol) { 156 | this.condition.symbol = symbol; 157 | return this; 158 | } 159 | 160 | public Builder warning(String warning) { 161 | this.condition.warning = warning; 162 | return this; 163 | } 164 | 165 | public Builder error(String error) { 166 | this.condition.error = error; 167 | return this; 168 | } 169 | 170 | public Condition build() { 171 | return condition; 172 | } 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/models/QualityGateFailMode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.models; 18 | 19 | public enum QualityGateFailMode { 20 | 21 | ERROR("ERROR"), WARN("WARN"), NONE("NONE"); 22 | 23 | private final String meaning; 24 | 25 | QualityGateFailMode(String meaning) { 26 | this.meaning = meaning; 27 | } 28 | 29 | public static QualityGateFailMode of(String meaning) { 30 | for (QualityGateFailMode m : values()) { 31 | if (m.meaning.equalsIgnoreCase(meaning)) { 32 | return m; 33 | } 34 | } 35 | return null; 36 | } 37 | 38 | public String getMeaning() { 39 | return meaning; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/models/ReportIssue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.models; 18 | 19 | public class ReportIssue { 20 | 21 | private Issue issue; 22 | private Rule rule; 23 | private String revision; 24 | private String url; 25 | private String file; 26 | private String ruleLink; 27 | private boolean reportedOnDiff; 28 | 29 | private ReportIssue() { 30 | // Nothing 31 | } 32 | 33 | public static Builder newBuilder() { 34 | return new Builder(); 35 | } 36 | 37 | public Issue getIssue() { 38 | return issue; 39 | } 40 | 41 | public String getRevision() { 42 | return revision; 43 | } 44 | 45 | public String getUrl() { 46 | return url; 47 | } 48 | 49 | public String getFile() { 50 | return file; 51 | } 52 | 53 | public String getRuleLink() { 54 | return ruleLink; 55 | } 56 | 57 | public boolean isReportedOnDiff() { 58 | return reportedOnDiff; 59 | } 60 | 61 | public Rule getRule() { 62 | return rule; 63 | } 64 | 65 | public static class Builder { 66 | 67 | private final ReportIssue reportIssue; 68 | 69 | private Builder() { 70 | this.reportIssue = new ReportIssue(); 71 | } 72 | 73 | public Builder issue(Issue issue) { 74 | this.reportIssue.issue = issue; 75 | return this; 76 | } 77 | 78 | public Builder rule(Rule rule) { 79 | this.reportIssue.rule = rule; 80 | return this; 81 | } 82 | 83 | public Builder revision(String revision) { 84 | this.reportIssue.revision = revision; 85 | return this; 86 | } 87 | 88 | public Builder url(String url) { 89 | this.reportIssue.url = url; 90 | return this; 91 | } 92 | 93 | public Builder file(String file) { 94 | this.reportIssue.file = file; 95 | return this; 96 | } 97 | 98 | public Builder ruleLink(String ruleLink) { 99 | this.reportIssue.ruleLink = ruleLink; 100 | return this; 101 | } 102 | 103 | public Builder reportedOnDiff(boolean reportedOnDiff) { 104 | this.reportIssue.reportedOnDiff = reportedOnDiff; 105 | return this; 106 | } 107 | 108 | public ReportIssue build() { 109 | return reportIssue; 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/models/Rule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.models; 18 | 19 | public class Rule { 20 | 21 | private String key; 22 | private String repo; 23 | private String name; 24 | private String description; 25 | private String type; 26 | private String debtRemFnType; 27 | private String debtRemFnBaseEffort; 28 | 29 | private Rule() { 30 | // Nothing 31 | } 32 | 33 | public static Builder newBuilder() { 34 | return new Builder(); 35 | } 36 | 37 | public String getKey() { 38 | return key; 39 | } 40 | 41 | public String getRepo() { 42 | return repo; 43 | } 44 | 45 | public String getName() { 46 | return name; 47 | } 48 | 49 | public String getDescription() { 50 | return description; 51 | } 52 | 53 | public String getType() { 54 | return type; 55 | } 56 | 57 | public String getDebtRemFnType() { 58 | return debtRemFnType; 59 | } 60 | 61 | public String getDebtRemFnBaseEffort() { 62 | return debtRemFnBaseEffort; 63 | } 64 | 65 | public static class Builder { 66 | 67 | private final Rule rule; 68 | 69 | private Builder() { 70 | this.rule = new Rule(); 71 | } 72 | 73 | public Builder key(String key) { 74 | this.rule.key = key; 75 | return this; 76 | } 77 | 78 | public Builder repo(String repo) { 79 | this.rule.repo = repo; 80 | return this; 81 | } 82 | 83 | public Builder name(String name) { 84 | this.rule.name = name; 85 | return this; 86 | } 87 | 88 | public Builder description(String description) { 89 | this.rule.description = description; 90 | return this; 91 | } 92 | 93 | public Builder type(String type) { 94 | this.rule.type = type; 95 | return this; 96 | } 97 | 98 | public Builder debtRemFnType(String debtRemFnType) { 99 | this.rule.debtRemFnType = debtRemFnType; 100 | return this; 101 | } 102 | 103 | public Builder debtRemFnBaseEffort(String debtRemFnBaseEffort) { 104 | this.rule.debtRemFnBaseEffort = debtRemFnBaseEffort; 105 | return this; 106 | } 107 | 108 | public Rule build() { 109 | return rule; 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/com/talanlabs/sonar/plugins/gitlab/models/StatusNotificationsMode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.models; 18 | 19 | public enum StatusNotificationsMode { 20 | 21 | COMMIT_STATUS("commit-status"), EXIT_CODE("exit-code"), NOTHING("nothing"); 22 | 23 | private final String meaning; 24 | 25 | StatusNotificationsMode(String meaning) { 26 | this.meaning = meaning; 27 | } 28 | 29 | public static StatusNotificationsMode of(String meaning) { 30 | for (StatusNotificationsMode m : values()) { 31 | if (m.meaning.equals(meaning)) { 32 | return m; 33 | } 34 | } 35 | return null; 36 | } 37 | 38 | public String getMeaning() { 39 | return meaning; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/resources/org/sonar/l10n/gitlab.properties: -------------------------------------------------------------------------------- 1 | property.category.gitlab=GitLab 2 | property.category.gitlab.reporting=Reporting 3 | property.category.gitlab.reporting.description=Set GitLab URL and Token access -------------------------------------------------------------------------------- /src/test/java/com/talanlabs/sonar/plugins/gitlab/CommitFacadeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.models.JsonMode; 20 | import org.assertj.core.api.Assertions; 21 | import org.junit.Rule; 22 | import org.junit.Test; 23 | import org.junit.rules.TemporaryFolder; 24 | import org.mockito.Mockito; 25 | 26 | import java.io.File; 27 | import java.io.IOException; 28 | import java.nio.file.Files; 29 | import java.util.Collections; 30 | 31 | import static org.assertj.core.api.Assertions.assertThat; 32 | import static org.mockito.Mockito.mock; 33 | import static org.mockito.Mockito.when; 34 | 35 | public class CommitFacadeTest { 36 | 37 | @Rule 38 | public TemporaryFolder temp = new TemporaryFolder(); 39 | 40 | @Test 41 | public void testInitGitBaseDirNotFound() throws Exception { 42 | CommitFacade facade = new CommitFacade(mock(GitLabPluginConfiguration.class)); 43 | File projectBaseDir = temp.newFolder(); 44 | facade.initGitBaseDir(projectBaseDir); 45 | assertThat(facade.getPath(new File(projectBaseDir, "src/main/java/Foo.java"))).isEqualTo("src/main/java/Foo.java"); 46 | } 47 | 48 | @Test 49 | public void testInitGitBaseDir() throws Exception { 50 | CommitFacade facade = new CommitFacade(mock(GitLabPluginConfiguration.class)); 51 | File gitBaseDir = temp.newFolder(); 52 | Files.createDirectory(gitBaseDir.toPath().resolve(".git")); 53 | File projectBaseDir = new File(gitBaseDir, "myProject"); 54 | facade.initGitBaseDir(projectBaseDir); 55 | assertThat(facade.getPath(new File(projectBaseDir, "src/main/java/Foo.java"))).isEqualTo("myProject/src/main/java/Foo.java"); 56 | } 57 | 58 | @Test 59 | public void testGetPath() throws IOException { 60 | GitLabPluginConfiguration gitLabPluginConfiguration = mock(GitLabPluginConfiguration.class); 61 | when(gitLabPluginConfiguration.commitSHA()).thenReturn(Collections.singletonList("1")); 62 | when(gitLabPluginConfiguration.refName()).thenReturn("master"); 63 | 64 | CommitFacade facade = new CommitFacade(gitLabPluginConfiguration); 65 | 66 | File gitBasedir = temp.newFolder(); 67 | facade.setGitBaseDir(gitBasedir); 68 | 69 | Assertions.assertThat(facade.getPath(new File(gitBasedir, "src/main/Foo.java"))).isEqualTo("src/main/Foo.java"); 70 | 71 | when(gitLabPluginConfiguration.prefixDirectory()).thenReturn("toto/"); 72 | 73 | Assertions.assertThat(facade.getPath(new File(gitBasedir, "src/main/Foo.java"))).isEqualTo("toto/src/main/Foo.java"); 74 | } 75 | 76 | @Test 77 | public void testWriteCodeClimateJson() throws IOException { 78 | GitLabPluginConfiguration gitLabPluginConfiguration = mock(GitLabPluginConfiguration.class); 79 | when(gitLabPluginConfiguration.jsonMode()).thenReturn(JsonMode.CODECLIMATE); 80 | CommitFacade facade = new CommitFacade(gitLabPluginConfiguration); 81 | File projectBaseDir = temp.newFolder(); 82 | facade.initGitBaseDir(projectBaseDir); 83 | 84 | facade.writeJsonFile("[{\"tool\":\"sonarqube\",\"fingerprint\":\"null\",\"message\":\"Issue\",\"file\":\"file\",\"line\":\"0\",\"priority\":\"INFO\",\"solution\":\"http://myserver\"}]"); 85 | 86 | File file = new File(projectBaseDir, "gl-code-quality-report.json"); 87 | Assertions.assertThat(file).exists().hasContent("[{\"tool\":\"sonarqube\",\"fingerprint\":\"null\",\"message\":\"Issue\",\"file\":\"file\",\"line\":\"0\",\"priority\":\"INFO\",\"solution\":\"http://myserver\"}]"); 88 | } 89 | 90 | @Test 91 | public void testWriteSastJson() throws IOException { 92 | GitLabPluginConfiguration gitLabPluginConfiguration = mock(GitLabPluginConfiguration.class); 93 | when(gitLabPluginConfiguration.jsonMode()).thenReturn(JsonMode.SAST); 94 | CommitFacade facade = new CommitFacade(gitLabPluginConfiguration); 95 | File projectBaseDir = temp.newFolder(); 96 | facade.initGitBaseDir(projectBaseDir); 97 | 98 | facade.writeJsonFile("[{\"tool\":\"sonarqube\",\"fingerprint\":\"null\",\"message\":\"Issue\",\"file\":\"file\",\"line\":\"0\",\"priority\":\"INFO\",\"solution\":\"http://myserver\"}]"); 99 | 100 | File file = new File(projectBaseDir, "gl-sast-report.json"); 101 | Assertions.assertThat(file).exists().hasContent("[{\"tool\":\"sonarqube\",\"fingerprint\":\"null\",\"message\":\"Issue\",\"file\":\"file\",\"line\":\"0\",\"priority\":\"INFO\",\"solution\":\"http://myserver\"}]"); 102 | } 103 | 104 | @Test 105 | public void testWriteNoneJson() throws IOException { 106 | GitLabPluginConfiguration gitLabPluginConfiguration = mock(GitLabPluginConfiguration.class); 107 | when(gitLabPluginConfiguration.jsonMode()).thenReturn(JsonMode.NONE); 108 | CommitFacade facade = new CommitFacade(gitLabPluginConfiguration); 109 | File projectBaseDir = temp.newFolder(); 110 | facade.initGitBaseDir(projectBaseDir); 111 | 112 | facade.writeJsonFile("[{\"tool\":\"sonarqube\",\"fingerprint\":\"null\",\"message\":\"Issue\",\"file\":\"file\",\"line\":\"0\",\"priority\":\"INFO\",\"solution\":\"http://myserver\"}]"); 113 | 114 | File file = new File(projectBaseDir, "gl-code-quality-report.json"); 115 | Assertions.assertThat(projectBaseDir.listFiles((p) -> p.getPath().endsWith(".json"))).isEmpty(); 116 | } 117 | 118 | @Test 119 | public void testUsernameForRevision() { 120 | GitLabPluginConfiguration gitLabPluginConfiguration = mock(GitLabPluginConfiguration.class); 121 | CommitFacade facade = new CommitFacade(gitLabPluginConfiguration); 122 | IGitLabApiWrapper gitLabApiWrapper = mock(IGitLabApiWrapper.class); 123 | facade.setGitLabWrapper(gitLabApiWrapper); 124 | facade.getUsernameForRevision("123"); 125 | 126 | Mockito.verify(gitLabApiWrapper).getUsernameForRevision("123"); 127 | } 128 | 129 | @Test 130 | public void testCreateOrUpdateSonarQubeStatus() { 131 | GitLabPluginConfiguration gitLabPluginConfiguration = mock(GitLabPluginConfiguration.class); 132 | CommitFacade facade = new CommitFacade(gitLabPluginConfiguration); 133 | IGitLabApiWrapper gitLabApiWrapper = mock(IGitLabApiWrapper.class); 134 | facade.setGitLabWrapper(gitLabApiWrapper); 135 | facade.createOrUpdateSonarQubeStatus("ok", "hello"); 136 | 137 | Mockito.verify(gitLabApiWrapper).createOrUpdateSonarQubeStatus("ok", "hello"); 138 | } 139 | 140 | @Test 141 | public void testGetGitLabUrl() throws IOException { 142 | GitLabPluginConfiguration gitLabPluginConfiguration = mock(GitLabPluginConfiguration.class); 143 | when(gitLabPluginConfiguration.commitSHA()).thenReturn(Collections.singletonList("1")); 144 | when(gitLabPluginConfiguration.refName()).thenReturn("master"); 145 | 146 | CommitFacade facade = new CommitFacade(gitLabPluginConfiguration); 147 | IGitLabApiWrapper gitLabApiWrapper = mock(IGitLabApiWrapper.class); 148 | 149 | File gitBasedir = temp.newFolder(); 150 | facade.setGitBaseDir(gitBasedir); 151 | facade.setGitLabWrapper(gitLabApiWrapper); 152 | 153 | Assertions.assertThat(facade.getGitLabUrl("123", null, null)).isNull(); 154 | 155 | when(gitLabApiWrapper.getGitLabUrl("123", "src/main/Foo.java", 1)).thenReturn("response"); 156 | Assertions.assertThat(facade.getGitLabUrl("123", new File(gitBasedir, "src/main/Foo.java"), 1)).isEqualTo("response"); 157 | } 158 | 159 | @Test 160 | public void testGetSrc() throws IOException { 161 | GitLabPluginConfiguration gitLabPluginConfiguration = mock(GitLabPluginConfiguration.class); 162 | when(gitLabPluginConfiguration.commitSHA()).thenReturn(Collections.singletonList("1")); 163 | when(gitLabPluginConfiguration.refName()).thenReturn("master"); 164 | 165 | CommitFacade facade = new CommitFacade(gitLabPluginConfiguration); 166 | 167 | File gitBasedir = temp.newFolder(); 168 | facade.setGitBaseDir(gitBasedir); 169 | 170 | Assertions.assertThat(facade.getSrc(null)).isNull(); 171 | 172 | Assertions.assertThat(facade.getSrc(new File(gitBasedir, "src/main/Foo.java"))).isEqualTo("src/main/Foo.java"); 173 | 174 | when(gitLabPluginConfiguration.prefixDirectory()).thenReturn("toto/"); 175 | 176 | Assertions.assertThat(facade.getSrc(new File(gitBasedir, "src/main/Foo.java"))).isEqualTo("toto/src/main/Foo.java"); 177 | } 178 | 179 | @Test 180 | public void testCreateOrUpdateReviewComment() throws IOException { 181 | GitLabPluginConfiguration gitLabPluginConfiguration = mock(GitLabPluginConfiguration.class); 182 | when(gitLabPluginConfiguration.commitSHA()).thenReturn(Collections.singletonList("1")); 183 | when(gitLabPluginConfiguration.refName()).thenReturn("master"); 184 | 185 | CommitFacade facade = new CommitFacade(gitLabPluginConfiguration); 186 | IGitLabApiWrapper gitLabApiWrapper = mock(IGitLabApiWrapper.class); 187 | 188 | File gitBasedir = temp.newFolder(); 189 | facade.setGitBaseDir(gitBasedir); 190 | facade.setGitLabWrapper(gitLabApiWrapper); 191 | 192 | facade.createOrUpdateReviewComment("123", new File(gitBasedir, "src/main/Foo.java"), 5, "toto"); 193 | 194 | Mockito.verify(gitLabApiWrapper).createOrUpdateReviewComment("123", "src/main/Foo.java", 5, "toto"); 195 | } 196 | 197 | @Test 198 | public void testAddGlobalComment() { 199 | GitLabPluginConfiguration gitLabPluginConfiguration = mock(GitLabPluginConfiguration.class); 200 | CommitFacade facade = new CommitFacade(gitLabPluginConfiguration); 201 | IGitLabApiWrapper gitLabApiWrapper = mock(IGitLabApiWrapper.class); 202 | facade.setGitLabWrapper(gitLabApiWrapper); 203 | facade.addGlobalComment("hello"); 204 | 205 | Mockito.verify(gitLabApiWrapper).addGlobalComment("hello"); 206 | } 207 | 208 | @Test 209 | public void testGetRuleLink() { 210 | GitLabPluginConfiguration gitLabPluginConfiguration = mock(GitLabPluginConfiguration.class); 211 | when(gitLabPluginConfiguration.baseUrl()).thenReturn("http://test/"); 212 | 213 | CommitFacade facade = new CommitFacade(gitLabPluginConfiguration); 214 | 215 | Assertions.assertThat(facade.getRuleLink("hello")).isEqualTo("http://test/coding_rules#rule_key=hello"); 216 | 217 | } 218 | } -------------------------------------------------------------------------------- /src/test/java/com/talanlabs/sonar/plugins/gitlab/GitLabPluginTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab; 18 | 19 | import org.junit.Test; 20 | import org.sonar.api.Plugin; 21 | import org.sonar.api.SonarEdition; 22 | import org.sonar.api.SonarQubeSide; 23 | import org.sonar.api.internal.SonarRuntimeImpl; 24 | import org.sonar.api.utils.Version; 25 | 26 | import static org.assertj.core.api.Assertions.assertThat; 27 | 28 | public class GitLabPluginTest { 29 | 30 | @Test 31 | public void uselessTest() { 32 | final Version version = Version.parse("7.9.1"); 33 | Plugin.Context context = new Plugin.Context(SonarRuntimeImpl.forSonarQube(version, SonarQubeSide.SCANNER, SonarEdition.COMMUNITY)); 34 | new GitLabPlugin().define(context); 35 | assertThat(context.getExtensions().size()).isGreaterThan(7); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/talanlabs/sonar/plugins/gitlab/InlineTemplateTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.models.ReportIssue; 20 | import org.assertj.core.api.Assertions; 21 | import org.junit.Before; 22 | import org.junit.Test; 23 | import org.sonar.api.CoreProperties; 24 | import org.sonar.api.batch.rule.Severity; 25 | import org.sonar.api.config.PropertyDefinition; 26 | import org.sonar.api.config.PropertyDefinitions; 27 | import org.sonar.api.config.internal.MapSettings; 28 | import org.sonar.api.utils.System2; 29 | 30 | import java.util.Collections; 31 | import java.util.List; 32 | import java.util.stream.Collectors; 33 | import java.util.stream.Stream; 34 | 35 | public class InlineTemplateTest { 36 | 37 | private static final String TEMPLATE = "<#list issues() as issue>\n" + "<@p issue=issue/>\n" + "\n" + "<#macro p issue>\n" 38 | + "${emojiSeverity(issue.severity)} ${issue.message} [:blue_book:](${ruleLink(issue.ruleKey)})\n" + ""; 39 | 40 | private MapSettings settings; 41 | private GitLabPluginConfiguration config; 42 | 43 | @Before 44 | public void setUp() { 45 | settings = new MapSettings(new PropertyDefinitions(System2.INSTANCE, PropertyDefinition.builder(CoreProperties.SERVER_BASE_URL).name("Server base URL") 46 | .description("HTTP URL of this SonarQube server, such as http://yourhost.yourdomain/sonar. This value is used i.e. to create links in emails.") 47 | .category(CoreProperties.CATEGORY_GENERAL).defaultValue("http://localhost:9000").build()).addComponents(GitLabPlugin.definitions())); 48 | 49 | settings.setProperty(CoreProperties.SERVER_BASE_URL, "http://myserver"); 50 | settings.setProperty(GitLabPlugin.GITLAB_COMMIT_SHA, "abc123"); 51 | 52 | config = new GitLabPluginConfiguration(settings.asConfig(), new System2()); 53 | 54 | settings.setProperty(GitLabPlugin.GITLAB_INLINE_TEMPLATE, TEMPLATE); 55 | } 56 | 57 | @Test 58 | public void testOneIssue() { 59 | ReportIssue r1 =ReportIssue.newBuilder().issue(Utils.newIssue("component", null, 1, Severity.INFO, true, "Issue", "rule")).revision(null).url("lalal").file("file").ruleLink( 60 | "http://myserver/coding_rules#rule_key=repo%3Arule").reportedOnDiff(true).build(); 61 | 62 | Assertions.assertThat(new InlineCommentBuilder(config, "123", null, 1, Collections.singletonList(r1), new MarkDownUtils()).buildForMarkdown()) 63 | .isEqualTo(":information_source: Issue [:blue_book:](http://myserver/coding_rules#rule_key=repo%3Arule)\n"); 64 | } 65 | 66 | @Test 67 | public void testTwoIssue() { 68 | List ris = Stream.iterate(0, i -> i++).limit(2) 69 | .map(i ->ReportIssue.newBuilder().issue(Utils.newIssue("component", null, 1, Severity.INFO, true, "Issue", "rule")).revision(null).url("lalal").file("file").ruleLink( 70 | "http://myserver/coding_rules#rule_key=repo%3Arule").reportedOnDiff(true).build()).collect(Collectors.toList()); 71 | 72 | Assertions.assertThat(new InlineCommentBuilder(config, "123", null, 1, ris, new MarkDownUtils()).buildForMarkdown()).isEqualTo( 73 | ":information_source: Issue [:blue_book:](http://myserver/coding_rules#rule_key=repo%3Arule)\n" 74 | + ":information_source: Issue [:blue_book:](http://myserver/coding_rules#rule_key=repo%3Arule)\n"); 75 | } 76 | 77 | @Test 78 | public void testUnescapeHTML() { 79 | settings.setProperty(GitLabPlugin.GITLAB_INLINE_TEMPLATE, TEMPLATE + "àâéç"); 80 | 81 | ReportIssue r1 =ReportIssue.newBuilder().issue(Utils.newIssue("component", null, 1, Severity.INFO, true, "Issue", "rule")).revision(null).url("lalal").file("file").ruleLink( 82 | "http://myserver/coding_rules#rule_key=repo%3Arule").reportedOnDiff(true).build(); 83 | 84 | Assertions.assertThat(new InlineCommentBuilder(config, "123", null, 1, Collections.singletonList(r1), new MarkDownUtils()).buildForMarkdown()) 85 | .isEqualTo(":information_source: Issue [:blue_book:](http://myserver/coding_rules#rule_key=repo%3Arule)\nàâéç"); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/test/java/com/talanlabs/sonar/plugins/gitlab/IssueComparatorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.models.Issue; 20 | import org.assertj.core.api.Assertions; 21 | import org.junit.Before; 22 | import org.junit.Test; 23 | import org.sonar.api.batch.rule.Severity; 24 | 25 | public class IssueComparatorTest { 26 | 27 | private IssueComparator issueComparator; 28 | 29 | @Before 30 | public void before() { 31 | issueComparator = new IssueComparator(); 32 | } 33 | 34 | @Test 35 | public void testNull() { 36 | Assertions.assertThat(issueComparator.compare(null, null)).isEqualTo(0); 37 | Assertions.assertThat(issueComparator.compare(Issue.newBuilder().build(), null)).isEqualTo(-1); 38 | Assertions.assertThat(issueComparator.compare(null, Issue.newBuilder().build())).isEqualTo(1); 39 | } 40 | 41 | @Test 42 | public void testSeverity() { 43 | Assertions.assertThat(issueComparator.compare(Issue.newBuilder().severity(Severity.BLOCKER).componentKey("toto").line(1).build(), Issue.newBuilder().severity(Severity.BLOCKER).componentKey( "toto").line(1).build())).isEqualTo(0); 44 | Assertions.assertThat(issueComparator.compare(Issue.newBuilder().severity(Severity.MAJOR).componentKey( "toto").line(1).build(), Issue.newBuilder().severity(Severity.BLOCKER).componentKey( "toto").line(1).build())).isEqualTo(1); 45 | Assertions.assertThat(issueComparator.compare(Issue.newBuilder().severity(Severity.MAJOR).componentKey( "toto").line(1).build(), Issue.newBuilder().severity(Severity.MINOR).componentKey( "toto").line(1).build())).isEqualTo(-1); 46 | } 47 | 48 | @Test 49 | public void testComponentKey() { 50 | Assertions.assertThat(issueComparator.compare(Issue.newBuilder().severity(Severity.BLOCKER).componentKey( "a").line(1).build(), Issue.newBuilder().severity(Severity.BLOCKER).componentKey( "b").line( 1).build())).isEqualTo(-1); 51 | Assertions.assertThat(issueComparator.compare(Issue.newBuilder().severity(Severity.BLOCKER).componentKey( "b").line(1).build(), Issue.newBuilder().severity(Severity.BLOCKER).componentKey( "a").line( 1).build())).isEqualTo(1); 52 | } 53 | 54 | @Test 55 | public void testSimple() { 56 | Assertions.assertThat(issueComparator.compare(Issue.newBuilder().severity(Severity.BLOCKER).componentKey( "a").line(null).build(), Issue.newBuilder().severity(Severity.BLOCKER).componentKey( "a").line( null).build())).isEqualTo(0); 57 | Assertions.assertThat(issueComparator.compare(Issue.newBuilder().severity(Severity.BLOCKER).componentKey( "a").line(1).build(), Issue.newBuilder().severity(Severity.BLOCKER).componentKey( "a").line( 2).build())).isEqualTo(-1); 58 | Assertions.assertThat(issueComparator.compare(Issue.newBuilder().severity(Severity.BLOCKER).componentKey( "a").line(1).build(), Issue.newBuilder().severity(Severity.BLOCKER).componentKey( "a").line( null).build())).isEqualTo(1); 59 | Assertions.assertThat(issueComparator.compare(Issue.newBuilder().severity(Severity.BLOCKER).componentKey( "a").line(10).build(), Issue.newBuilder().severity(Severity.BLOCKER).componentKey( "a").line( 1).build())).isEqualTo(1); 60 | Assertions.assertThat(issueComparator.compare(Issue.newBuilder().severity(Severity.BLOCKER).componentKey( "a").line(null).build(), Issue.newBuilder().severity(Severity.BLOCKER).componentKey( "a").line( 1).build())).isEqualTo(-1); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/com/talanlabs/sonar/plugins/gitlab/PatchUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab; 18 | 19 | import org.assertj.core.api.Assertions; 20 | import org.junit.Test; 21 | 22 | public class PatchUtilsTest { 23 | 24 | @Test 25 | public void testEmpty() { 26 | Assertions.assertThat(PatchUtils.getPositionsFromPatch("12313")).isEmpty(); 27 | } 28 | 29 | @Test 30 | public void testWrong() { 31 | Assertions.assertThatThrownBy(() -> PatchUtils.getPositionsFromPatch("@ wrong")).isInstanceOf(IllegalStateException.class).hasMessage("Unable to parse line:\n" + 32 | "\t@ wrong\n" + 33 | "Full patch: \n" + 34 | "\t@ wrong"); 35 | } 36 | 37 | @Test 38 | public void testCorrectUnixEOL() { 39 | Assertions.assertThat(PatchUtils.getPositionsFromPatch("@@ -78,6 +78,27 @@\n" + 40 | "\t\t\t\t\"src/styles.scss\",\n" + 41 | " \"src/cordova-styles.scss\"\n" + 42 | " ]\n" + 43 | " },\n" + 44 | "+ \"prod-cordova\": {\n" + 45 | "+ \"optimization\": true,\n" + 46 | "+ \"outputHashing\": \"all\",\n" + 47 | " \"sourceMap\": false,\n" + 48 | " \"extractCss\": true,\n" + 49 | " \"namedChunks\": false,\n" + 50 | " \"aot\": true,\n" + 51 | " \"extractLicenses\": true,\n" + 52 | " \"vendorChunk\": false,\n" + 53 | " \"buildOptimizer\": true,\n" + 54 | " \"fileReplacements\": [\n" + 55 | " {\n" + 56 | " \"replace\": \"src/environments/environment.ts\",\n" + 57 | " \"with\": \"src/environments/environment.prod-cordova.ts\"\n" + 58 | " }\n" + 59 | " ],\n" + 60 | " \"styles\": [\n" + 61 | " \"src/styles.scss\",\n" + 62 | " \"src/cordova-styles.scss\"\n" + 63 | " ]\n" + 64 | " }\n" + 65 | " }\n" + 66 | " },")).isNotEmpty().hasSize(3).containsExactlyInAnyOrder( 67 | new IGitLabApiWrapper.Line(83, " \"outputHashing\": \"all\","), 68 | new IGitLabApiWrapper.Line(82, " \"optimization\": true,"), 69 | new IGitLabApiWrapper.Line(81, " \"prod-cordova\": {") 70 | ); 71 | } 72 | 73 | 74 | @Test 75 | public void testCorrectMacEOL() { 76 | Assertions.assertThat(PatchUtils.getPositionsFromPatch("@@ -78,6 +78,27 @@\r" + 77 | "\t\t\t\t\"src/styles.scss\",\r" + 78 | " \"src/cordova-styles.scss\"\r" + 79 | " ]\r" + 80 | " },\r" + 81 | "+ \"prod-cordova\": {\r" + 82 | "+ \"optimization\": true,\r" + 83 | "+ \"outputHashing\": \"all\",\r" + 84 | " \"sourceMap\": false,\r" + 85 | " \"extractCss\": true,\r" + 86 | " \"namedChunks\": false,\r" + 87 | " \"aot\": true,\r" + 88 | " \"extractLicenses\": true,\r" + 89 | " \"vendorChunk\": false,\r" + 90 | " \"buildOptimizer\": true,\r" + 91 | " \"fileReplacements\": [\r" + 92 | " {\r" + 93 | " \"replace\": \"src/environments/environment.ts\",\r" + 94 | " \"with\": \"src/environments/environment.prod-cordova.ts\"\r" + 95 | " }\r" + 96 | " ],\r" + 97 | " \"styles\": [\r" + 98 | " \"src/styles.scss\",\r" + 99 | " \"src/cordova-styles.scss\"\r" + 100 | " ]\r" + 101 | " }\r" + 102 | " }\r" + 103 | " },")).isNotEmpty().hasSize(3).containsExactlyInAnyOrder( 104 | new IGitLabApiWrapper.Line(83, " \"outputHashing\": \"all\","), 105 | new IGitLabApiWrapper.Line(82, " \"optimization\": true,"), 106 | new IGitLabApiWrapper.Line(81, " \"prod-cordova\": {") 107 | ); 108 | } 109 | 110 | @Test 111 | public void testCorrectWindowsEOL() { 112 | Assertions.assertThat(PatchUtils.getPositionsFromPatch("@@ -78,6 +78,27 @@\n" + 113 | "\t\t\t\t\"src/styles.scss\",\r\n" + 114 | " \"src/cordova-styles.scss\"\r\n" + 115 | " ]\r\n" + 116 | " },\r\n" + 117 | "+ \"prod-cordova\": {\r\n" + 118 | "+ \"optimization\": true,\r\n" + 119 | "+ \"outputHashing\": \"all\",\r\n" + 120 | " \"sourceMap\": false,\r\n" + 121 | " \"extractCss\": true,\r\n" + 122 | " \"namedChunks\": false,\r\n" + 123 | " \"aot\": true,\r\n" + 124 | " \"extractLicenses\": true,\r\n" + 125 | " \"vendorChunk\": false,\r\n" + 126 | " \"buildOptimizer\": true,\r\n" + 127 | " \"fileReplacements\": [\r\n" + 128 | " {\r\n" + 129 | " \"replace\": \"src/environments/environment.ts\",\r\n" + 130 | " \"with\": \"src/environments/environment.prod-cordova.ts\"\r\n" + 131 | " }\r\n" + 132 | " ],\r\n" + 133 | " \"styles\": [\rn" + 134 | " \"src/styles.scss\",\r\n" + 135 | " \"src/cordova-styles.scss\"\r\n" + 136 | " ]\r\n" + 137 | " }\r\n" + 138 | " }\r\n" + 139 | " },")).isNotEmpty().hasSize(3).containsExactlyInAnyOrder( 140 | new IGitLabApiWrapper.Line(83, " \"outputHashing\": \"all\","), 141 | new IGitLabApiWrapper.Line(82, " \"optimization\": true,"), 142 | new IGitLabApiWrapper.Line(81, " \"prod-cordova\": {") 143 | ); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/test/java/com/talanlabs/sonar/plugins/gitlab/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.models.Issue; 20 | import org.mockito.Mockito; 21 | import org.sonar.api.batch.fs.InputComponent; 22 | import org.sonar.api.batch.fs.InputFile; 23 | import org.sonar.api.batch.rule.Severity; 24 | 25 | import java.io.BufferedWriter; 26 | import java.io.File; 27 | import java.io.IOException; 28 | import java.nio.charset.Charset; 29 | import java.nio.file.Files; 30 | 31 | public class Utils { 32 | 33 | private Utils() { 34 | super(); 35 | } 36 | 37 | public static InputComponent newMockedInputComponent(String key) { 38 | InputComponent inputComponent = Mockito.mock(InputComponent.class); 39 | Mockito.when(inputComponent.key()).thenReturn(key); 40 | Mockito.when(inputComponent.isFile()).thenReturn(false); 41 | return inputComponent; 42 | } 43 | 44 | public static InputFile newMockedInputFile(File file) { 45 | InputFile inputFile = Mockito.mock(InputFile.class); 46 | Mockito.when(inputFile.key()).thenReturn(file.getPath()); 47 | Mockito.when(inputFile.isFile()).thenReturn(true); 48 | Mockito.when(inputFile.uri()).thenReturn(file.toURI()); 49 | return inputFile; 50 | } 51 | 52 | public static Issue newIssue(String componentKey, Severity severity, boolean isNew, String message) { 53 | return newIssue(componentKey, null, null, severity, isNew, message, "rule"); 54 | } 55 | 56 | public static Issue newIssue(String componentKey, File file, Integer line, Severity severity, boolean isNew, String message) { 57 | return newIssue(componentKey, file, line, severity, isNew, message, "rule"); 58 | } 59 | 60 | public static Issue newIssue(String componentKey, File file, Integer line, Severity severity, boolean isNew, String message, String rule) { 61 | return newIssue(null, componentKey, file, line, severity, isNew, message, rule); 62 | } 63 | 64 | public static Issue newIssue(String key, String componentKey, File file, Integer line, Severity severity, boolean isNew, String message, String rule) { 65 | return Issue.newBuilder().key(key).componentKey(componentKey).file(file).line(line).severity(severity).newIssue(isNew).message(message).ruleKey("repo:" + rule).build(); 66 | } 67 | 68 | public static void createFile(File root, String path, String filename, String value) throws IOException { 69 | File dir = new File(root, path); 70 | if (!dir.exists()) { 71 | if (!dir.mkdirs()) { 72 | throw new IOException("Failed to create directories " + dir.toString()); 73 | } 74 | } 75 | File file = new File(dir, filename); 76 | if (!file.createNewFile()) { 77 | throw new IOException("Failed to create file " + file.toString()); 78 | } 79 | try (BufferedWriter bw = Files.newBufferedWriter(file.toPath(), Charset.defaultCharset())) { 80 | bw.write(value); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/com/talanlabs/sonar/plugins/gitlab/freemarker/EmojiSeverityTemplateMethodModelExTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.freemarker; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.GitLabPlugin; 20 | import com.talanlabs.sonar.plugins.gitlab.MarkDownUtils; 21 | import freemarker.template.SimpleScalar; 22 | import freemarker.template.TemplateModelException; 23 | import org.assertj.core.api.Assertions; 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | import org.sonar.api.CoreProperties; 27 | import org.sonar.api.batch.rule.Severity; 28 | import org.sonar.api.config.PropertyDefinition; 29 | import org.sonar.api.config.PropertyDefinitions; 30 | import org.sonar.api.config.internal.MapSettings; 31 | import org.sonar.api.utils.System2; 32 | 33 | import java.util.Collections; 34 | import java.util.List; 35 | 36 | public class EmojiSeverityTemplateMethodModelExTest { 37 | 38 | private EmojiSeverityTemplateMethodModelEx emojiSeverityTemplateMethodModelEx; 39 | 40 | @Before 41 | public void setUp() throws Exception { 42 | MapSettings settings = new MapSettings(new PropertyDefinitions(System2.INSTANCE, PropertyDefinition.builder(CoreProperties.SERVER_BASE_URL).name("Server base URL") 43 | .description("HTTP URL of this SonarQube server, such as http://yourhost.yourdomain/sonar. This value is used i.e. to create links in emails.") 44 | .category(CoreProperties.CATEGORY_GENERAL).defaultValue("http://localhost:9000").build()).addComponents(GitLabPlugin.definitions())); 45 | 46 | settings.setProperty(CoreProperties.SERVER_BASE_URL, "http://myserver"); 47 | 48 | MarkDownUtils markDownUtils = new MarkDownUtils(); 49 | 50 | emojiSeverityTemplateMethodModelEx = new EmojiSeverityTemplateMethodModelEx(markDownUtils); 51 | } 52 | 53 | private String emoji(List arguments) { 54 | try { 55 | return (String) emojiSeverityTemplateMethodModelEx.exec(arguments); 56 | } catch (TemplateModelException e) { 57 | throw new RuntimeException(e); 58 | } 59 | } 60 | 61 | @Test 62 | public void testSuccess() { 63 | Assertions.assertThat(emoji(Collections.singletonList(new SimpleScalar(Severity.BLOCKER.name())))).isEqualTo(":no_entry:"); 64 | } 65 | 66 | @Test 67 | public void testFailed() { 68 | Assertions.assertThatThrownBy(() -> emoji(Collections.emptyList())).hasCauseInstanceOf(TemplateModelException.class); 69 | Assertions.assertThatThrownBy(() -> emoji(Collections.singletonList(null))).hasCauseInstanceOf(TemplateModelException.class); 70 | Assertions.assertThatThrownBy(() -> emoji(Collections.singletonList(new SimpleScalar("TOTO")))).hasCauseInstanceOf(TemplateModelException.class); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/com/talanlabs/sonar/plugins/gitlab/freemarker/ImageSeverityTemplateMethodModelExTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.freemarker; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.GitLabPlugin; 20 | import com.talanlabs.sonar.plugins.gitlab.MarkDownUtils; 21 | import freemarker.template.SimpleScalar; 22 | import freemarker.template.TemplateModelException; 23 | import org.assertj.core.api.Assertions; 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | import org.sonar.api.CoreProperties; 27 | import org.sonar.api.batch.rule.Severity; 28 | import org.sonar.api.config.PropertyDefinition; 29 | import org.sonar.api.config.PropertyDefinitions; 30 | import org.sonar.api.config.internal.MapSettings; 31 | import org.sonar.api.utils.System2; 32 | 33 | import java.util.Collections; 34 | import java.util.List; 35 | 36 | public class ImageSeverityTemplateMethodModelExTest { 37 | 38 | private ImageSeverityTemplateMethodModelEx imageSeverityTemplateMethodModelEx; 39 | 40 | @Before 41 | public void setUp() throws Exception { 42 | MapSettings settings = new MapSettings(new PropertyDefinitions(System2.INSTANCE, PropertyDefinition.builder(CoreProperties.SERVER_BASE_URL).name("Server base URL") 43 | .description("HTTP URL of this SonarQube server, such as http://yourhost.yourdomain/sonar. This value is used i.e. to create links in emails.") 44 | .category(CoreProperties.CATEGORY_GENERAL).defaultValue("http://localhost:9000").build()).addComponents(GitLabPlugin.definitions())); 45 | 46 | settings.setProperty(CoreProperties.SERVER_BASE_URL, "http://myserver"); 47 | 48 | MarkDownUtils markDownUtils = new MarkDownUtils(); 49 | 50 | imageSeverityTemplateMethodModelEx = new ImageSeverityTemplateMethodModelEx(markDownUtils); 51 | } 52 | 53 | private String image(List arguments) { 54 | try { 55 | return (String) imageSeverityTemplateMethodModelEx.exec(arguments); 56 | } catch (TemplateModelException e) { 57 | throw new RuntimeException(e); 58 | } 59 | } 60 | 61 | @Test 62 | public void testSuccess() { 63 | Assertions.assertThat(image(Collections.singletonList(new SimpleScalar(Severity.BLOCKER.name())))).isEqualTo("![BLOCKER](https://github.com/gabrie-allaigre/sonar-gitlab-plugin/raw/master/images/severity-blocker.png)"); 64 | } 65 | 66 | @Test 67 | public void testFailed() { 68 | Assertions.assertThatThrownBy(() -> image(Collections.emptyList())).hasCauseInstanceOf(TemplateModelException.class); 69 | Assertions.assertThatThrownBy(() -> image(Collections.singletonList(null))).hasCauseInstanceOf(TemplateModelException.class); 70 | Assertions.assertThatThrownBy(() -> image(Collections.singletonList(new SimpleScalar("TOTO")))).hasCauseInstanceOf(TemplateModelException.class); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/com/talanlabs/sonar/plugins/gitlab/freemarker/IssueCountTemplateMethodModelExTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.freemarker; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.Utils; 20 | import com.talanlabs.sonar.plugins.gitlab.models.ReportIssue; 21 | import freemarker.template.SimpleScalar; 22 | import freemarker.template.TemplateBooleanModel; 23 | import freemarker.template.TemplateModelException; 24 | import org.assertj.core.api.Assertions; 25 | import org.junit.Test; 26 | import org.sonar.api.batch.rule.Severity; 27 | 28 | import java.util.ArrayList; 29 | import java.util.Arrays; 30 | import java.util.Collections; 31 | import java.util.List; 32 | 33 | public class IssueCountTemplateMethodModelExTest { 34 | 35 | private IssueCountTemplateMethodModelEx issueCountTemplateMethodModelEx; 36 | 37 | private Object count(List arguments) { 38 | try { 39 | return issueCountTemplateMethodModelEx.exec(arguments); 40 | } catch (TemplateModelException e) { 41 | throw new RuntimeException(e); 42 | } 43 | } 44 | 45 | @Test 46 | public void testSuccessEmpty() { 47 | issueCountTemplateMethodModelEx = new IssueCountTemplateMethodModelEx(Collections.emptyList()); 48 | 49 | Assertions.assertThat(count(Collections.emptyList())).isEqualTo(0); 50 | Assertions.assertThat(count(Collections.singletonList(new SimpleScalar("MAJOR")))).isEqualTo(0); 51 | Assertions.assertThat(count(Collections.singletonList(TemplateBooleanModel.FALSE))).isEqualTo(0); 52 | Assertions.assertThat(count(Arrays.asList(new SimpleScalar("MAJOR"), TemplateBooleanModel.FALSE))).isEqualTo(0); 53 | } 54 | 55 | @Test 56 | public void testSuccess() { 57 | List reportIssues = new ArrayList<>(); 58 | reportIssues.add(ReportIssue.newBuilder().issue(Utils.newIssue("foo:src/Foo.php", null, 1, Severity.BLOCKER, true, "msg1")).revision("123").url("url").file("file").ruleLink("ruleLink").reportedOnDiff(true).build()); 59 | reportIssues.add(ReportIssue.newBuilder().issue(Utils.newIssue("foo:src/Foo.php", null, 1, Severity.BLOCKER, true, "msg1")).revision("123").url("url").file("file").ruleLink("ruleLink").reportedOnDiff( false).build()); 60 | reportIssues.add(ReportIssue.newBuilder().issue(Utils.newIssue("foo:src/Foo.php", null, 1, Severity.BLOCKER, true, "msg1")).revision("123").url("url").file("file").ruleLink("ruleLink").reportedOnDiff(false).build()); 61 | 62 | reportIssues.add(ReportIssue.newBuilder().issue(Utils.newIssue("foo:src/Foo.php", null, 1, Severity.MAJOR, true, "msg1")).revision("123").url("url").file("file").ruleLink("ruleLink").reportedOnDiff(false).build()); 63 | reportIssues.add(ReportIssue.newBuilder().issue(Utils.newIssue("foo:src/Foo.php", null, 1, Severity.MAJOR, true, "msg1")).revision("123").url("url").file("file").ruleLink("ruleLink").reportedOnDiff(true).build()); 64 | 65 | issueCountTemplateMethodModelEx = new IssueCountTemplateMethodModelEx(reportIssues); 66 | 67 | Assertions.assertThat(count(Collections.emptyList())).isEqualTo(5); 68 | Assertions.assertThat(count(Collections.singletonList(new SimpleScalar("MAJOR")))).isEqualTo(2); 69 | Assertions.assertThat(count(Collections.singletonList(TemplateBooleanModel.FALSE))).isEqualTo(3); 70 | Assertions.assertThat(count(Arrays.asList(new SimpleScalar("MAJOR"), TemplateBooleanModel.FALSE))).isEqualTo(1); 71 | } 72 | 73 | @Test 74 | public void testFailed() { 75 | List reportIssues = new ArrayList<>(); 76 | reportIssues.add(ReportIssue.newBuilder().issue(Utils.newIssue("foo:src/Foo.php", null, 1, Severity.BLOCKER, true, "msg1")).revision("123").url("url").file("file").ruleLink("ruleLink").reportedOnDiff(true).build()); 77 | reportIssues.add(ReportIssue.newBuilder().issue(Utils.newIssue("foo:src/Foo.php", null, 1, Severity.BLOCKER, true, "msg1")).revision("123").url("url").file("file").ruleLink("ruleLink").reportedOnDiff(false).build()); 78 | reportIssues.add(ReportIssue.newBuilder().issue(Utils.newIssue("foo:src/Foo.php", null, 1, Severity.BLOCKER, true, "msg1")).revision("123").url("url").file("file").ruleLink("ruleLink").reportedOnDiff(false).build()); 79 | 80 | reportIssues.add(ReportIssue.newBuilder().issue(Utils.newIssue("foo:src/Foo.php", null, 1, Severity.MAJOR, true, "msg1")).revision("123").url("url").file("file").ruleLink("ruleLink").reportedOnDiff(false).build()); 81 | reportIssues.add(ReportIssue.newBuilder().issue(Utils.newIssue("foo:src/Foo.php", null, 1, Severity.MAJOR, true, "msg1")).revision("123").url("url").file("file").ruleLink("ruleLink").reportedOnDiff(true).build()); 82 | 83 | issueCountTemplateMethodModelEx = new IssueCountTemplateMethodModelEx(reportIssues); 84 | 85 | Assertions.assertThatThrownBy(() -> count(Collections.singletonList(null))).hasCauseInstanceOf(TemplateModelException.class); 86 | Assertions.assertThatThrownBy(() -> count(Collections.singletonList(new SimpleScalar("TOTO")))).hasCauseInstanceOf(TemplateModelException.class); 87 | Assertions.assertThatThrownBy(() -> count(Arrays.asList(TemplateBooleanModel.FALSE, new SimpleScalar("MAJOR")))).hasCauseInstanceOf(TemplateModelException.class); 88 | Assertions.assertThatThrownBy(() -> count(Arrays.asList(new SimpleScalar("TOTO"), TemplateBooleanModel.FALSE))).hasCauseInstanceOf(TemplateModelException.class); 89 | Assertions.assertThatThrownBy(() -> count(Arrays.asList(new SimpleScalar("TOTO"), TemplateBooleanModel.FALSE, TemplateBooleanModel.FALSE))).hasCauseInstanceOf(TemplateModelException.class); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/com/talanlabs/sonar/plugins/gitlab/freemarker/IssuesTemplateMethodModelExTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.freemarker; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.Utils; 20 | import com.talanlabs.sonar.plugins.gitlab.models.ReportIssue; 21 | import com.talanlabs.sonar.plugins.gitlab.models.Rule; 22 | import freemarker.template.SimpleScalar; 23 | import freemarker.template.TemplateBooleanModel; 24 | import freemarker.template.TemplateModelException; 25 | import org.assertj.core.api.Assertions; 26 | import org.junit.Test; 27 | import org.sonar.api.batch.rule.Severity; 28 | 29 | import java.util.*; 30 | 31 | public class IssuesTemplateMethodModelExTest { 32 | 33 | private IssuesTemplateMethodModelEx issuesTemplateMethodModelEx; 34 | 35 | private List> list(List arguments) { 36 | try { 37 | return (List>) issuesTemplateMethodModelEx.exec(arguments); 38 | } catch (TemplateModelException e) { 39 | throw new RuntimeException(e); 40 | } 41 | } 42 | 43 | @Test 44 | public void testSuccessEmpty() { 45 | issuesTemplateMethodModelEx = new IssuesTemplateMethodModelEx(Collections.emptyList()); 46 | 47 | Assertions.assertThat(list(Collections.emptyList())).isEmpty(); 48 | Assertions.assertThat(list(Collections.singletonList(new SimpleScalar("MAJOR")))).isEmpty(); 49 | Assertions.assertThat(list(Collections.singletonList(TemplateBooleanModel.FALSE))).isEmpty(); 50 | Assertions.assertThat(list(Arrays.asList(new SimpleScalar("MAJOR"), TemplateBooleanModel.FALSE))).isEmpty(); 51 | } 52 | 53 | @Test 54 | public void testSuccess() { 55 | List reportIssues = new ArrayList<>(); 56 | reportIssues.add(ReportIssue.newBuilder().issue(Utils.newIssue("foo:src/Foo.php", null, 1, Severity.BLOCKER, true, "msg1")).revision("123").url("url").file("file").ruleLink("ruleLink").reportedOnDiff(true).rule(Rule.newBuilder().key("toto").name("Toto").build()).build()); 57 | reportIssues.add(ReportIssue.newBuilder().issue(Utils.newIssue("foo:src/Foo.php", null, 1, Severity.BLOCKER, true, "msg1")).revision("123").url("url").file("file").ruleLink("ruleLink").reportedOnDiff(false).build()); 58 | reportIssues.add(ReportIssue.newBuilder().issue(Utils.newIssue("foo:src/Foo.php", null, 1, Severity.BLOCKER, true, "msg1")).revision("123").url("url").file("file").ruleLink("ruleLink").reportedOnDiff(false).build()); 59 | 60 | reportIssues.add(ReportIssue.newBuilder().issue(Utils.newIssue("foo:src/Foo.php", null, 1, Severity.MAJOR, true, "msg1")).revision("123").url("url").file("file").ruleLink("ruleLink").reportedOnDiff(false).build()); 61 | reportIssues.add(ReportIssue.newBuilder().issue(Utils.newIssue("foo:src/Foo.php", null, 1, Severity.MAJOR, true, "msg1")).revision("123").url("url").file("file").ruleLink("ruleLink").reportedOnDiff(true).build()); 62 | 63 | issuesTemplateMethodModelEx = new IssuesTemplateMethodModelEx(reportIssues); 64 | 65 | Assertions.assertThat(list(Collections.emptyList())).hasSize(5); 66 | Assertions.assertThat(list(Collections.singletonList(new SimpleScalar("MAJOR")))).hasSize(2); 67 | Assertions.assertThat(list(Collections.singletonList(TemplateBooleanModel.FALSE))).hasSize(3); 68 | Assertions.assertThat(list(Arrays.asList(new SimpleScalar("MAJOR"), TemplateBooleanModel.FALSE))).hasSize(1); 69 | } 70 | 71 | @Test 72 | public void testFailed() { 73 | List reportIssues = new ArrayList<>(); 74 | reportIssues.add(ReportIssue.newBuilder().issue(Utils.newIssue("foo:src/Foo.php", null, 1, Severity.BLOCKER, true, "msg1")).revision("123").url("url").file("file").ruleLink("ruleLink").reportedOnDiff(true).build()); 75 | reportIssues.add(ReportIssue.newBuilder().issue(Utils.newIssue("foo:src/Foo.php", null, 1, Severity.BLOCKER, true, "msg1")).revision("123").url("url").file("file").ruleLink("ruleLink").reportedOnDiff(false).build()); 76 | reportIssues.add(ReportIssue.newBuilder().issue(Utils.newIssue("foo:src/Foo.php", null, 1, Severity.BLOCKER, true, "msg1")).revision("123").url("url").file("file").ruleLink("ruleLink").reportedOnDiff(false).build()); 77 | 78 | reportIssues.add(ReportIssue.newBuilder().issue(Utils.newIssue("foo:src/Foo.php", null, 1, Severity.MAJOR, true, "msg1")).revision("123").url("url").file("file").ruleLink("ruleLink").reportedOnDiff(false).build()); 79 | reportIssues.add(ReportIssue.newBuilder().issue(Utils.newIssue("foo:src/Foo.php", null, 1, Severity.MAJOR, true, "msg1")).revision("123").url("url").file("file").ruleLink("ruleLink").reportedOnDiff(true).build()); 80 | 81 | issuesTemplateMethodModelEx = new IssuesTemplateMethodModelEx(reportIssues); 82 | 83 | Assertions.assertThatThrownBy(() -> list(Collections.singletonList(null))).hasCauseInstanceOf(TemplateModelException.class); 84 | Assertions.assertThatThrownBy(() -> list(Collections.singletonList(new SimpleScalar("TOTO")))).hasCauseInstanceOf(TemplateModelException.class); 85 | Assertions.assertThatThrownBy(() -> list(Arrays.asList(TemplateBooleanModel.FALSE, new SimpleScalar("MAJOR")))).hasCauseInstanceOf(TemplateModelException.class); 86 | Assertions.assertThatThrownBy(() -> list(Arrays.asList(new SimpleScalar("TOTO"), TemplateBooleanModel.FALSE))).hasCauseInstanceOf(TemplateModelException.class); 87 | Assertions.assertThatThrownBy(() -> list(Arrays.asList(new SimpleScalar("TOTO"), TemplateBooleanModel.FALSE, TemplateBooleanModel.FALSE))).hasCauseInstanceOf(TemplateModelException.class); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/com/talanlabs/sonar/plugins/gitlab/freemarker/PrintTemplateMethodModelExTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.freemarker; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.GitLabPlugin; 20 | import com.talanlabs.sonar.plugins.gitlab.MarkDownUtils; 21 | import freemarker.template.DefaultMapAdapter; 22 | import freemarker.template.TemplateModelException; 23 | import org.assertj.core.api.Assertions; 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | import org.sonar.api.CoreProperties; 27 | import org.sonar.api.batch.rule.Severity; 28 | import org.sonar.api.config.PropertyDefinition; 29 | import org.sonar.api.config.PropertyDefinitions; 30 | import org.sonar.api.config.internal.MapSettings; 31 | import org.sonar.api.utils.System2; 32 | 33 | import java.util.Collections; 34 | import java.util.HashMap; 35 | import java.util.List; 36 | import java.util.Map; 37 | 38 | public class PrintTemplateMethodModelExTest { 39 | 40 | private PrintTemplateMethodModelEx printTemplateMethodModelEx; 41 | 42 | @Before 43 | public void setUp() { 44 | MapSettings settings = new MapSettings(new PropertyDefinitions(System2.INSTANCE, PropertyDefinition.builder(CoreProperties.SERVER_BASE_URL).name("Server base URL") 45 | .description("HTTP URL of this SonarQube server, such as http://yourhost.yourdomain/sonar. This value is used i.e. to create links in emails.") 46 | .category(CoreProperties.CATEGORY_GENERAL).defaultValue("http://localhost:9000").build()).addComponents(GitLabPlugin.definitions())); 47 | 48 | settings.setProperty(CoreProperties.SERVER_BASE_URL, "http://myserver"); 49 | 50 | MarkDownUtils markDownUtils = new MarkDownUtils(); 51 | 52 | printTemplateMethodModelEx = new PrintTemplateMethodModelEx(markDownUtils); 53 | } 54 | 55 | private String print(List arguments) { 56 | try { 57 | return (String) printTemplateMethodModelEx.exec(arguments); 58 | } catch (TemplateModelException e) { 59 | throw new RuntimeException(e); 60 | } 61 | } 62 | 63 | @Test 64 | public void testSuccess() { 65 | Map root = new HashMap<>(); 66 | root.put("url", "toto"); 67 | root.put("componentKey", "tata"); 68 | root.put("severity", Severity.BLOCKER); 69 | root.put("message", "titi"); 70 | root.put("ruleKey", "ici"); 71 | root.put("ruleLink", "http://myserver/coding_rules#rule_key=ici"); 72 | Assertions.assertThat(print(Collections.singletonList(DefaultMapAdapter.adapt(root, null)))).isEqualTo(":no_entry: [titi](toto) [:blue_book:](http://myserver/coding_rules#rule_key=ici)"); 73 | } 74 | 75 | @Test 76 | public void testFailed() { 77 | Assertions.assertThatThrownBy(() -> print(Collections.emptyList())).hasCauseInstanceOf(TemplateModelException.class); 78 | Assertions.assertThatThrownBy(() -> print(Collections.singletonList(null))).hasCauseInstanceOf(TemplateModelException.class); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/com/talanlabs/sonar/plugins/gitlab/freemarker/QualityGateConditionCountTemplateMethodModelExTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.freemarker; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.models.QualityGate; 20 | import freemarker.template.SimpleScalar; 21 | import freemarker.template.TemplateBooleanModel; 22 | import freemarker.template.TemplateModelException; 23 | import org.assertj.core.api.Assertions; 24 | import org.junit.Test; 25 | 26 | import java.util.ArrayList; 27 | import java.util.Arrays; 28 | import java.util.Collections; 29 | import java.util.List; 30 | 31 | public class QualityGateConditionCountTemplateMethodModelExTest { 32 | 33 | private QualityGateConditionCountTemplateMethodModelEx qualityGateConditionCountTemplateMethodModelEx; 34 | 35 | private Object count(List arguments) { 36 | try { 37 | return qualityGateConditionCountTemplateMethodModelEx.exec(arguments); 38 | } catch (TemplateModelException e) { 39 | throw new RuntimeException(e); 40 | } 41 | } 42 | 43 | @Test 44 | public void testSuccessEmpty() { 45 | qualityGateConditionCountTemplateMethodModelEx = new QualityGateConditionCountTemplateMethodModelEx(Collections.emptyList()); 46 | 47 | Assertions.assertThat(count(Collections.emptyList())).isEqualTo(0); 48 | Assertions.assertThat(count(Collections.singletonList(new SimpleScalar("OK")))).isEqualTo(0); 49 | } 50 | 51 | @Test 52 | public void testSuccess() { 53 | List conditions = new ArrayList<>(); 54 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.OK).metricKey("toto").metricName("Toto1").actual("10").symbol("<").warning("").error("0").build()); 55 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.OK).metricKey("toto").metricName("Toto2").actual("11").symbol(">=").warning("").error("").build()); 56 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.OK).metricKey("toto").metricName("Toto3").actual("13").symbol("<=").warning("").error("").build()); 57 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.WARN).metricKey("toto").metricName("Toto4").actual("14").symbol(">").warning("20").error("30").build()); 58 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.WARN).metricKey("toto").metricName("Toto5").actual("15").symbol("=").warning("10").error("").build()); 59 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.ERROR).metricKey("toto").metricName("Toto").actual("10").symbol("<").warning("20").error("40").build()); 60 | 61 | qualityGateConditionCountTemplateMethodModelEx = new QualityGateConditionCountTemplateMethodModelEx(conditions); 62 | 63 | Assertions.assertThat(count(Collections.emptyList())).isEqualTo(6); 64 | Assertions.assertThat(count(Collections.singletonList(new SimpleScalar("OK")))).isEqualTo(3); 65 | Assertions.assertThat(count(Collections.singletonList(new SimpleScalar("OK")))).isEqualTo(3); 66 | Assertions.assertThat(count(Collections.singletonList(new SimpleScalar("OK")))).isEqualTo(3); 67 | } 68 | 69 | @Test 70 | public void testFailed() { 71 | List conditions = new ArrayList<>(); 72 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.OK).metricKey("toto").metricName("Toto1").actual("10").symbol("<").warning("").error("0").build()); 73 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.OK).metricKey("toto").metricName("Toto2").actual("11").symbol(">=").warning("").error("").build()); 74 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.OK).metricKey("toto").metricName("Toto3").actual("13").symbol("<=").warning("").error("").build()); 75 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.WARN).metricKey("toto").metricName("Toto4").actual("14").symbol(">").warning("20").error("30").build()); 76 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.WARN).metricKey("toto").metricName("Toto5").actual("15").symbol("=").warning("10").error("").build()); 77 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.ERROR).metricKey("toto").metricName("Toto").actual("10").symbol("<").warning("20").error("40").build()); 78 | 79 | qualityGateConditionCountTemplateMethodModelEx = new QualityGateConditionCountTemplateMethodModelEx(conditions); 80 | 81 | Assertions.assertThatThrownBy(() -> count(Collections.singletonList(null))).hasCauseInstanceOf(TemplateModelException.class); 82 | Assertions.assertThatThrownBy(() -> count(Collections.singletonList(new SimpleScalar("TOTO")))).hasCauseInstanceOf(TemplateModelException.class); 83 | Assertions.assertThatThrownBy(() -> count(Arrays.asList(TemplateBooleanModel.FALSE, new SimpleScalar("MAJOR")))).hasCauseInstanceOf(TemplateModelException.class); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/test/java/com/talanlabs/sonar/plugins/gitlab/freemarker/QualityGateConditionsTemplateMethodModelExTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.freemarker; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.models.QualityGate; 20 | import freemarker.template.SimpleScalar; 21 | import freemarker.template.TemplateBooleanModel; 22 | import freemarker.template.TemplateModelException; 23 | import org.assertj.core.api.Assertions; 24 | import org.junit.Test; 25 | 26 | import java.util.*; 27 | 28 | public class QualityGateConditionsTemplateMethodModelExTest { 29 | 30 | private QualityGateConditionsTemplateMethodModelEx qualityGateConditionsTemplateMethodModelEx; 31 | 32 | private List> list(List arguments) { 33 | try { 34 | return (List>) qualityGateConditionsTemplateMethodModelEx.exec(arguments); 35 | } catch (TemplateModelException e) { 36 | throw new RuntimeException(e); 37 | } 38 | } 39 | 40 | @Test 41 | public void testSuccessEmpty() { 42 | qualityGateConditionsTemplateMethodModelEx = new QualityGateConditionsTemplateMethodModelEx(Collections.emptyList()); 43 | 44 | Assertions.assertThat(list(Collections.emptyList())).isEmpty(); 45 | Assertions.assertThat(list(Collections.singletonList(new SimpleScalar("OK")))).isEmpty(); 46 | } 47 | 48 | @Test 49 | public void testSuccess() { 50 | List conditions = new ArrayList<>(); 51 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.OK).metricKey("toto").metricName("Toto1").actual("10").symbol("<").warning("").error("0").build()); 52 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.OK).metricKey("toto").metricName("Toto2").actual("11").symbol(">=").warning("").error("").build()); 53 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.OK).metricKey("toto").metricName("Toto3").actual("13").symbol("<=").warning("").error("").build()); 54 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.WARN).metricKey("toto").metricName("Toto4").actual("14").symbol(">").warning("20").error("30").build()); 55 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.WARN).metricKey("toto").metricName("Toto5").actual("15").symbol("=").warning("10").error("").build()); 56 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.ERROR).metricKey("toto").metricName("Toto").actual("10").symbol("<").warning("20").error("40").build()); 57 | qualityGateConditionsTemplateMethodModelEx = new QualityGateConditionsTemplateMethodModelEx(conditions); 58 | 59 | Assertions.assertThat(list(Collections.emptyList())).hasSize(6); 60 | Assertions.assertThat(list(Collections.singletonList(new SimpleScalar("OK")))).hasSize(3); 61 | } 62 | 63 | @Test 64 | public void testFailedEmpty() { 65 | List conditions = new ArrayList<>(); 66 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.OK).metricKey("toto").metricName("Toto1").actual("10").symbol("<").warning("").error("0").build()); 67 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.OK).metricKey("toto").metricName("Toto2").actual("11").symbol(">=").warning("").error("").build()); 68 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.OK).metricKey("toto").metricName("Toto3").actual("13").symbol("<=").warning("").error("").build()); 69 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.WARN).metricKey("toto").metricName("Toto4").actual("14").symbol(">").warning("20").error("30").build()); 70 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.WARN).metricKey("toto").metricName("Toto5").actual("15").symbol("=").warning("10").error("").build()); 71 | conditions.add(QualityGate.Condition.newBuilder().status(QualityGate.Status.ERROR).metricKey("toto").metricName("Toto").actual("10").symbol("<").warning("20").error("40").build()); 72 | qualityGateConditionsTemplateMethodModelEx = new QualityGateConditionsTemplateMethodModelEx(conditions); 73 | 74 | Assertions.assertThatThrownBy(() -> list(Collections.singletonList(null))).hasCauseInstanceOf(TemplateModelException.class); 75 | Assertions.assertThatThrownBy(() -> list(Collections.singletonList(new SimpleScalar("TOTO")))).hasCauseInstanceOf(TemplateModelException.class); 76 | Assertions.assertThatThrownBy(() -> list(Arrays.asList(TemplateBooleanModel.FALSE, new SimpleScalar("MAJOR")))).hasCauseInstanceOf(TemplateModelException.class); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/com/talanlabs/sonar/plugins/gitlab/freemarker/RuleLinkTemplateMethodModelExTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube :: GitLab Plugin 3 | * Copyright (C) 2016-2025 Talanlabs 4 | * gabriel.allaigre@gmail.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the Sonar Source-Available License for more details. 13 | * 14 | * You should have received a copy of the Sonar Source-Available License 15 | * along with this program; if not, see https://sonarsource.com/license/ssal/ 16 | */ 17 | package com.talanlabs.sonar.plugins.gitlab.freemarker; 18 | 19 | import com.talanlabs.sonar.plugins.gitlab.GitLabPlugin; 20 | import com.talanlabs.sonar.plugins.gitlab.GitLabPluginConfiguration; 21 | import freemarker.template.SimpleScalar; 22 | import freemarker.template.TemplateModelException; 23 | import org.assertj.core.api.Assertions; 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | import org.sonar.api.CoreProperties; 27 | import org.sonar.api.config.PropertyDefinition; 28 | import org.sonar.api.config.PropertyDefinitions; 29 | import org.sonar.api.config.internal.MapSettings; 30 | import org.sonar.api.utils.System2; 31 | 32 | import java.util.Collections; 33 | import java.util.List; 34 | 35 | public class RuleLinkTemplateMethodModelExTest { 36 | 37 | private RuleLinkTemplateMethodModelEx ruleLinkTemplateMethodModelEx; 38 | 39 | @Before 40 | public void setUp() { 41 | MapSettings settings = new MapSettings(new PropertyDefinitions(System2.INSTANCE, PropertyDefinition.builder(CoreProperties.SERVER_BASE_URL).name("Server base URL") 42 | .description("HTTP URL of this SonarQube server, such as http://yourhost.yourdomain/sonar. This value is used i.e. to create links in emails.") 43 | .category(CoreProperties.CATEGORY_GENERAL).defaultValue("http://localhost:9000").build()).addComponents(GitLabPlugin.definitions())); 44 | 45 | settings.setProperty(CoreProperties.SERVER_BASE_URL, "http://myserver"); 46 | 47 | GitLabPluginConfiguration gitLabPluginConfiguration = new GitLabPluginConfiguration(settings.asConfig(),new System2()); 48 | 49 | ruleLinkTemplateMethodModelEx = new RuleLinkTemplateMethodModelEx(gitLabPluginConfiguration); 50 | } 51 | 52 | private String ruleLink(List arguments) { 53 | try { 54 | return (String) ruleLinkTemplateMethodModelEx.exec(arguments); 55 | } catch (TemplateModelException e) { 56 | throw new RuntimeException(e); 57 | } 58 | } 59 | 60 | @Test 61 | public void testSuccess() { 62 | Assertions.assertThat(ruleLink(Collections.singletonList(new SimpleScalar("http://rien")))).isEqualTo("http://myserver/coding_rules#rule_key=http%3A%2F%2Frien"); 63 | } 64 | 65 | @Test 66 | public void testFailed() { 67 | Assertions.assertThatThrownBy(() -> ruleLink(Collections.emptyList())).hasCauseInstanceOf(TemplateModelException.class); 68 | Assertions.assertThatThrownBy(() -> ruleLink(Collections.singletonList(null))).hasCauseInstanceOf(TemplateModelException.class); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/resources/report-task.txt: -------------------------------------------------------------------------------- 1 | projectKey=com.talanlabs:avatar-generator-parent 2 | serverUrl={0} 3 | dashboardUrl={0}/dashboard/index/com.talanlabs:avatar-generator-parent 4 | ceTaskId=AVz4Pj0lCGu3nUwPQk4H 5 | ceTaskUrl={0}/api/ce/task?id=AVz4Pj0lCGu3nUwPQk4H 6 | -------------------------------------------------------------------------------- /templates/global/all-issues.md: -------------------------------------------------------------------------------- 1 | # All issues global template 2 | 3 | ```injectedfreemarker 4 | <#assign newIssueCount = issueCount() notReportedIssueCount = issueCount(false)> 5 | <#assign hasInlineIssues = newIssueCount gt notReportedIssueCount extraIssuesTruncated = notReportedIssueCount gt maxGlobalIssues> 6 | <#if newIssueCount == 0> 7 | SonarQube analysis reported no issues. 8 | <#else> 9 | SonarQube analysis reported ${newIssueCount} issue<#if newIssueCount gt 1>s 10 | <#assign newIssuesBlocker = issueCount(BLOCKER) newIssuesCritical = issueCount(CRITICAL) newIssuesMajor = issueCount(MAJOR) newIssuesMinor = issueCount(MINOR) newIssuesInfo = issueCount(INFO)> 11 | <#if newIssuesBlocker gt 0> 12 | * ${emojiSeverity(BLOCKER)} ${newIssuesBlocker} blocker 13 | 14 | <#if newIssuesCritical gt 0> 15 | * ${emojiSeverity(CRITICAL)} ${newIssuesCritical} critical 16 | 17 | <#if newIssuesMajor gt 0> 18 | * ${emojiSeverity(MAJOR)} ${newIssuesMajor} major 19 | 20 | <#if newIssuesMinor gt 0> 21 | * ${emojiSeverity(MINOR)} ${newIssuesMinor} minor 22 | 23 | <#if newIssuesInfo gt 0> 24 | * ${emojiSeverity(INFO)} ${newIssuesInfo} info 25 | 26 | 27 | <#list issues() as issue> 28 | 1. ${print(issue)} 29 | 30 | 31 | ``` -------------------------------------------------------------------------------- /templates/global/default-image.md: -------------------------------------------------------------------------------- 1 | # Default global template with image 2 | 3 | ```injectedfreemarker 4 | <#if qualityGate??> 5 | SonarQube analysis indicates that quality gate is <@s status=qualityGate.status/>. 6 | <#list qualityGate.conditions() as condition> 7 | <@c condition=condition/> 8 | 9 | 10 | 11 | <#macro c condition>* ${condition.metricName} is <@s status=condition.status/>: Actual value ${condition.actual}<#if condition.status == WARN> is ${condition.symbol} ${condition.warning}<#if condition.status == ERROR> is ${condition.symbol} ${condition.error} 12 | <#macro s status><#if status == OK>passed<#elseif status == WARN>warning<#elseif status == ERROR>failed<#else>unknown 13 | <#assign newIssueCount = issueCount() notReportedIssueCount = issueCount(false)> 14 | <#assign hasInlineIssues = newIssueCount gt notReportedIssueCount extraIssuesTruncated = notReportedIssueCount gt maxGlobalIssues> 15 | <#if newIssueCount == 0> 16 | SonarQube analysis reported no issues. 17 | <#else> 18 | SonarQube analysis reported ${newIssueCount} issue<#if newIssueCount gt 1>s 19 | <#assign newIssuesBlocker = issueCount(BLOCKER) newIssuesCritical = issueCount(CRITICAL) newIssuesMajor = issueCount(MAJOR) newIssuesMinor = issueCount(MINOR) newIssuesInfo = issueCount(INFO)> 20 | <#if newIssuesBlocker gt 0> 21 | * ${imageSeverity(BLOCKER)} ${newIssuesBlocker} blocker 22 | 23 | <#if newIssuesCritical gt 0> 24 | * ${imageSeverity(CRITICAL)} ${newIssuesCritical} critical 25 | 26 | <#if newIssuesMajor gt 0> 27 | * ${imageSeverity(MAJOR)} ${newIssuesMajor} major 28 | 29 | <#if newIssuesMinor gt 0> 30 | * ${imageSeverity(MINOR)} ${newIssuesMinor} minor 31 | 32 | <#if newIssuesInfo gt 0> 33 | * ${imageSeverity(INFO)} ${newIssuesInfo} info 34 | 35 | <#if !disableIssuesInline && hasInlineIssues> 36 | 37 | Watch the comments in this conversation to review them. 38 | 39 | <#if notReportedIssueCount gt 0> 40 | <#if !disableIssuesInline> 41 | <#if hasInlineIssues || extraIssuesTruncated> 42 | <#if notReportedIssueCount <= maxGlobalIssues> 43 | 44 | #### ${notReportedIssueCount} extra issue<#if notReportedIssueCount gt 1>s 45 | <#else> 46 | 47 | #### Top ${maxGlobalIssues} extra issue<#if maxGlobalIssues gt 1>s 48 | 49 | 50 | 51 | Note: The following issues were found on lines that were not modified in the commit. Because these issues can't be reported as line comments, they are summarized here: 52 | <#elseif extraIssuesTruncated> 53 | 54 | #### Top ${maxGlobalIssues} issue<#if maxGlobalIssues gt 1>s 55 | 56 | 57 | <#assign reportedIssueCount = 0> 58 | <#list issues(false) as issue> 59 | <#if reportedIssueCount < maxGlobalIssues> 60 | 1. <@p issue=issue/> 61 | 62 | <#assign reportedIssueCount++> 63 | 64 | <#if notReportedIssueCount gt maxGlobalIssues> 65 | * ... ${notReportedIssueCount-maxGlobalIssues} more 66 | 67 | 68 | 69 | <#macro p issue> 70 | ${imageSeverity(issue.severity)} <#if issue.url??>[${issue.message}](${issue.url})<#else>${issue.message}<#if issue.componentKey??> (${issue.componentKey}) [![RULE](https://github.com/gabrie-allaigre/sonar-gitlab-plugin/raw/master/images/rule.png)](${ruleLink(issue.ruleKey)}) 71 | 72 | ``` -------------------------------------------------------------------------------- /templates/global/default.md: -------------------------------------------------------------------------------- 1 | # Default global template 2 | 3 | ```injectedfreemarker 4 | <#if qualityGate??> 5 | SonarQube analysis indicates that quality gate is <@s status=qualityGate.status/>. 6 | <#list qualityGate.conditions() as condition> 7 | <@c condition=condition/> 8 | 9 | 10 | 11 | <#macro c condition>* ${condition.metricName} is <@s status=condition.status/>: Actual value ${condition.actual}<#if condition.status == WARN> is ${condition.symbol} ${condition.warning}<#if condition.status == ERROR> is ${condition.symbol} ${condition.error} 12 | <#macro s status><#if status == OK>passed<#elseif status == WARN>warning<#elseif status == ERROR>failed<#else>unknown 13 | <#assign newIssueCount = issueCount() notReportedIssueCount = issueCount(false)> 14 | <#assign hasInlineIssues = newIssueCount gt notReportedIssueCount extraIssuesTruncated = notReportedIssueCount gt maxGlobalIssues> 15 | <#if newIssueCount == 0> 16 | SonarQube analysis reported no issues. 17 | <#else> 18 | SonarQube analysis reported ${newIssueCount} issue<#if newIssueCount gt 1>s 19 | <#assign newIssuesBlocker = issueCount(BLOCKER) newIssuesCritical = issueCount(CRITICAL) newIssuesMajor = issueCount(MAJOR) newIssuesMinor = issueCount(MINOR) newIssuesInfo = issueCount(INFO)> 20 | <#if newIssuesBlocker gt 0> 21 | * ${emojiSeverity(BLOCKER)} ${newIssuesBlocker} blocker 22 | 23 | <#if newIssuesCritical gt 0> 24 | * ${emojiSeverity(CRITICAL)} ${newIssuesCritical} critical 25 | 26 | <#if newIssuesMajor gt 0> 27 | * ${emojiSeverity(MAJOR)} ${newIssuesMajor} major 28 | 29 | <#if newIssuesMinor gt 0> 30 | * ${emojiSeverity(MINOR)} ${newIssuesMinor} minor 31 | 32 | <#if newIssuesInfo gt 0> 33 | * ${emojiSeverity(INFO)} ${newIssuesInfo} info 34 | 35 | <#if !disableIssuesInline && hasInlineIssues> 36 | 37 | Watch the comments in this conversation to review them. 38 | 39 | <#if notReportedIssueCount gt 0> 40 | <#if !disableIssuesInline> 41 | <#if hasInlineIssues || extraIssuesTruncated> 42 | <#if notReportedIssueCount <= maxGlobalIssues> 43 | 44 | #### ${notReportedIssueCount} extra issue<#if notReportedIssueCount gt 1>s 45 | <#else> 46 | 47 | #### Top ${maxGlobalIssues} extra issue<#if maxGlobalIssues gt 1>s 48 | 49 | 50 | 51 | Note: The following issues were found on lines that were not modified in the commit. Because these issues can't be reported as line comments, they are summarized here: 52 | <#elseif extraIssuesTruncated> 53 | 54 | #### Top ${maxGlobalIssues} issue<#if maxGlobalIssues gt 1>s 55 | 56 | 57 | <#assign reportedIssueCount = 0> 58 | <#list issues(false) as issue> 59 | <#if reportedIssueCount < maxGlobalIssues> 60 | 1. ${print(issue)} 61 | 62 | <#assign reportedIssueCount++> 63 | 64 | <#if notReportedIssueCount gt maxGlobalIssues> 65 | * ... ${notReportedIssueCount-maxGlobalIssues} more 66 | 67 | 68 | 69 | ``` -------------------------------------------------------------------------------- /templates/global/french.md: -------------------------------------------------------------------------------- 1 | # Default global template 2 | 3 | ```injectedfreemarker 4 | <#if qualityGate??> 5 | L'analyse de SonarQube indique que la qualitée est <@s status=qualityGate.status/>. 6 | <#list qualityGate.conditions() as condition> 7 | <@c condition=condition/> 8 | 9 | 10 | 11 | <#macro c condition>* ${condition.metricName} est <@s status=condition.status/>: Value actuelle ${condition.actual}<#if condition.status == WARN> est ${condition.symbol} ${condition.warning}<#if condition.status == ERROR> est ${condition.symbol} ${condition.error} 12 | <#macro s status><#if status == OK>passée<#elseif status == WARN>attention<#elseif status == ERROR>échec<#else>inconnue 13 | <#assign newIssueCount = issueCount() notReportedIssueCount = issueCount(false)> 14 | <#assign hasInlineIssues = newIssueCount gt notReportedIssueCount extraIssuesTruncated = notReportedIssueCount gt maxGlobalIssues> 15 | <#if newIssueCount == 0> 16 | L'analyse de SonarQube indique aucun problème. 17 | <#else> 18 | L'analyse de SonarQube indique ${newIssueCount} problème<#if newIssueCount gt 1>s 19 | <#assign newIssuesBlocker = issueCount(BLOCKER) newIssuesCritical = issueCount(CRITICAL) newIssuesMajor = issueCount(MAJOR) newIssuesMinor = issueCount(MINOR) newIssuesInfo = issueCount(INFO)> 20 | <#if newIssuesBlocker gt 0> 21 | * ${emojiSeverity(BLOCKER)} ${newIssuesBlocker} bloquante<#if newIssuesBlocker gt 1>s 22 | 23 | <#if newIssuesCritical gt 0> 24 | * ${emojiSeverity(CRITICAL)} ${newIssuesCritical} critique<#if newIssuesCritical gt 1>s 25 | 26 | <#if newIssuesMajor gt 0> 27 | * ${emojiSeverity(MAJOR)} ${newIssuesMajor} majeur<#if newIssuesMajor gt 1>s 28 | 29 | <#if newIssuesMinor gt 0> 30 | * ${emojiSeverity(MINOR)} ${newIssuesMinor} mineur<#if newIssuesMinor gt 1>s 31 | 32 | <#if newIssuesInfo gt 0> 33 | * ${emojiSeverity(INFO)} ${newIssuesInfo} info<#if newIssuesInfo gt 1>s 34 | 35 | <#if !disableIssuesInline && hasInlineIssues> 36 | 37 | Regarder les commentaires dans cette conversation pour les consulter. 38 | 39 | <#if notReportedIssueCount gt 0> 40 | <#if !disableIssuesInline> 41 | <#if hasInlineIssues || extraIssuesTruncated> 42 | <#if notReportedIssueCount <= maxGlobalIssues> 43 | 44 | #### ${notReportedIssueCount} problème<#if notReportedIssueCount gt 1>s en plus 45 | <#else> 46 | 47 | #### Top ${maxGlobalIssues} problème<#if maxGlobalIssues gt 1>s en plus 48 | 49 | 50 | 51 | Remarque: Les problèmes suivants ont été trouvés sur des lignes qui n'ont pas été modifiées dans le commit. Puisque ces problèmes ne peuvent pas être signalés dans les commentaires de ligne, ils sont résumés ici : 52 | <#elseif extraIssuesTruncated> 53 | 54 | #### Top ${maxGlobalIssues} des problème<#if maxGlobalIssues gt 1>s 55 | 56 | 57 | <#assign reportedIssueCount = 0> 58 | <#list issues(false) as issue> 59 | <#if reportedIssueCount < maxGlobalIssues> 60 | 1. ${print(issue)} 61 | 62 | <#assign reportedIssueCount++> 63 | 64 | <#if notReportedIssueCount gt maxGlobalIssues> 65 | * ... ${notReportedIssueCount-maxGlobalIssues} en plus 66 | 67 | 68 | 69 | ``` 70 | -------------------------------------------------------------------------------- /templates/inline/default-image.md: -------------------------------------------------------------------------------- 1 | # Default inline template 2 | 3 | ```injectedfreemarker 4 | <#list issues() as issue> 5 | <@p issue=issue/> 6 | 7 | <#macro p issue> 8 | ${imageSeverity(issue.severity)} ${issue.message} [![RULE](https://github.com/gabrie-allaigre/sonar-gitlab-plugin/raw/master/images/rule.png)](${issue.ruleLink}) 9 | 10 | ``` -------------------------------------------------------------------------------- /templates/inline/default.md: -------------------------------------------------------------------------------- 1 | # Default inline template 2 | 3 | ```injectedfreemarker 4 | <#list issues() as issue> 5 | <@p issue=issue/> 6 | 7 | <#macro p issue> 8 | ${emojiSeverity(issue.severity)} ${issue.message} [:blue_book:](${issue.ruleLink}) 9 | 10 | ``` --------------------------------------------------------------------------------