├── .cirrus.yml ├── .gitattributes ├── .github └── CODEOWNERS ├── .gitignore ├── .yarn └── releases │ └── yarn-4.5.0.cjs ├── .yarnrc.yml ├── LICENSE ├── README.md ├── SECURITY.md ├── conf ├── env.js ├── jest │ ├── CSSStub.js │ ├── FileStub.js │ └── SetupTestEnvironment.js └── webpack │ ├── webpack.config.dev.js │ ├── webpack.config.js │ └── webpack.config.prod.js ├── package-lock.json ├── package.json ├── pom.xml ├── scripts ├── build.js ├── start.js └── test.js ├── src └── main │ ├── java │ └── org │ │ └── sonarsource │ │ └── plugins │ │ └── example │ │ ├── ExamplePlugin.java │ │ ├── hooks │ │ ├── DisplayQualityGateStatus.java │ │ └── PostJobInScanner.java │ │ ├── languages │ │ ├── FooLanguage.java │ │ └── FooQualityProfile.java │ │ ├── measures │ │ ├── ComputeSizeAverage.java │ │ ├── ComputeSizeRating.java │ │ ├── ExampleMetrics.java │ │ └── SetSizeOnFilesSensor.java │ │ ├── rules │ │ ├── CreateIssuesOnJavaFilesSensor.java │ │ ├── FlagLine1Rule.java │ │ ├── FlagLine2Rule.java │ │ ├── FlagLine3Rule.java │ │ ├── FlagLineRule.java │ │ ├── FlagLineSensor.java │ │ ├── FlagRuleDefinition.java │ │ ├── FooLintIssuesLoaderSensor.java │ │ └── JavaRulesDefinition.java │ │ ├── settings │ │ ├── FooLanguageProperties.java │ │ ├── HelloWorldProperties.java │ │ └── SayHelloFromScanner.java │ │ └── web │ │ └── MyPluginPageDefinition.java │ ├── js │ ├── admin_page │ │ ├── components │ │ │ └── InstanceStatisticsApp.js │ │ └── index.js │ ├── common │ │ └── api.js │ ├── global_page │ │ ├── app.css │ │ ├── app.js │ │ └── index.js │ ├── portfolio_page │ │ ├── components │ │ │ ├── MeasuresHistory.js │ │ │ └── VersionsMeasuresHistoryApp.js │ │ └── index.js │ ├── project_page │ │ ├── index.js │ │ └── view │ │ │ └── AppView.js │ └── style.css │ └── resources │ └── org │ └── sonar │ └── l10n │ ├── example.properties │ └── example_fr.properties └── yarn.lock /.cirrus.yml: -------------------------------------------------------------------------------- 1 | container: 2 | image: maven:3.9.4-eclipse-temurin-11 3 | 4 | build_task: 5 | maven_cache: 6 | folder: ~/.m2/repository 7 | build_script: mvn clean package 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /.yarn/releases/** binary 2 | /.yarn/plugins/** binary 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | .github/CODEOWNERS @sonarsource/orchestration-processing-squad 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | target/ 4 | node/ 5 | .idea/ 6 | sonar-example-plugin.iml 7 | 8 | # Eclipse 9 | .classpath 10 | .project 11 | .settings/ 12 | 13 | # yarn 14 | .yarn/* 15 | !.yarn/cache 16 | !.yarn/patches 17 | !.yarn/plugins 18 | !.yarn/releases 19 | !.yarn/sdks 20 | !.yarn/versions -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-4.5.0.cjs 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SonarQube Server Custom Plugin Example [![Build Status](https://travis-ci.org/SonarSource/sonar-custom-plugin-example.svg?branch=7.x)](https://travis-ci.org/SonarSource/sonar-custom-plugin-example) 2 | ========== 3 | 4 | An example SonarQube plugin compatible with SonarQube Server 10.x. 5 | 6 | Sonar's [Clean Code solutions](https://www.sonarsource.com/solutions/clean-code/?utm_medium=referral&utm_source=github&utm_campaign=clean-code&utm_content=sonar-custom-plugin-example) help developers deliver high-quality, efficient code standards that benefit the entire team or organization. 7 | 8 | Back-end 9 | -------- 10 | 11 | Todo... 12 | 13 | ### Building 14 | 15 | To build the plugin JAR file, call: 16 | 17 | ``` 18 | mvn clean package 19 | ``` 20 | 21 | The JAR will be deployed to `target/sonar-example-plugin-VERSION.jar`. Copy this to your SonarQube Server's `extensions/plugins/` directory, and restart the server. 22 | 23 | Front-end 24 | --------- 25 | 26 | This plugin registers 4 extension pages in the SonarQube Server web app. These pages demonstrate how to extend SonarQube Server's UI with new pages and interfaces. 27 | 28 | ### Prerequisites 29 | 30 | * [NodeJS](https://nodejs.org/en/) 31 | 32 | ### Scripts 33 | 34 | * `npm install` to install your dependencies. 35 | * `npm start` to start a proxy server on port 3000 to debug your JS code. 36 | *Note: This plugin must first be deployed and installed on your SonarQube Server instance, otherwise the extension paths will not be registered. See above under Back-end > Building* 37 | This will proxy to a running SonarQube Server instance, but allow you to use your own local JavaScript instead of what was bundled with your plugin. Once started, you can access `http://localhost:3000` in your browser, and use SonarQube Server as you normally would. 38 | You can use a different port by using the `PORT` environment variable. Example: 39 | ``` 40 | PORT=6060 npm start 41 | ``` 42 | You can control to which SonarQube instance you proxy by setting the `PROXY_URL` environment variable to any valid URL (defaults to `http://localhost:9000`). Example: 43 | ``` 44 | PROXY_URL=https://sonarqube.example.com npm start 45 | ``` 46 | * `npm test` to start watching your files for changes, and run tests accordingly. 47 | * `npm run build` to build your front-end code. 48 | You should not usually need to call this; instead, it should be part of your package-building process. 49 | See Back-end > Building above. 50 | 51 | ### Building 52 | 53 | This example plugin uses [Webpack](https://webpack.js.org/) for building the final JavaScript. Whatever build system you choose to use, the final result *MUST* adhere to the following rules: 54 | 55 | * 1 entry file *per extension page*. 56 | * The name of each entry file must correspond to the `page_id` of the registered page (see `src/main/java/org/sonarsource/plugins/example/web/MyPluginPageDefinition.java` and compare with the entry points in `conf/webpack/webpack.config.js`). 57 | * Each entry file must be located in the resulting JAR's `static/` folder. 58 | 59 | The building process should be included in your full packaging process. In this example plugin, `mvn package` will call `npm run build` prior to finalizing the JAR package. 60 | 61 | ### Testing 62 | 63 | This project uses [Jest](https://jestjs.io/) for testing. Running `npm test` will run Jest in `--watch` mode. You can find the configuration for Jest in `package.json`. 64 | 65 | ### How to use these files 66 | 67 | It is recommended you check out the sources in `src/main/js/` directly. The code is well-commented and provides real-world examples of how to interact with SonarQube Server. 68 | 69 | The pages are registered in `src/main/java/org/sonarsource/plugins/example/web/MyPluginPageDefinition.java`, and their respective front-end source code is located in `src/main/js/`. These examples use different stacks to demonstrate different possibilities: 70 | 71 | * React JS examples (recommended, SonarQube Server uses React 16): 72 | * `src/main/js/portfolio_page/` 73 | * `src/main/js/admin_page/` 74 | * Backbone JS example: `src/main/js/project_page/` 75 | * Vanilla JS example: `src/main/js/global_page/` 76 | 77 | #### Helper APIs exposed by SonarQube Server 78 | 79 | There are several helper APIs exposed by SonarQube Server, like functions to make authenticated API requests. 80 | 81 | You can find the full list of exposed helpers [here](https://github.com/SonarSource/sonarqube/blob/master/server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts). 82 | 83 | The included pages contain several examples: 84 | 85 | * **API calls (`window.SonarRequest`)** 86 | Check `src/main/js/common/api.js` for some examples. 87 | 88 | * **Localization (`window.t()` and `window.tp()`)** 89 | Localizable UI strings are defined in `src/main/resources/org/sonar/l10n/example/`. They are loaded at startup time and can used by the global `t()` and `tp()` functions. See `src/main/js/admin_page/components/InstanceStatisticsApp.js` and `src/main/js/portfolio_page/components/VersionsMeasuresHistoryApp.js` for some examples. 90 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Issues 2 | 3 | A mature software vulnerability treatment process is a cornerstone of a robust information security management system. Contributions from the community play an important role in the evolution and security of our products, and in safeguarding the security and privacy of our users. 4 | 5 | If you believe you have discovered a security vulnerability in Sonar's products, we encourage you to report it immediately. 6 | 7 | To responsibly report a security issue, please email us at [security@sonarsource.com](mailto:security@sonarsource.com). Sonar’s security team will acknowledge your report, guide you through the next steps, or request additional information if necessary. Customers with a support contract can also report the vulnerability directly through the support channel. 8 | 9 | For security vulnerabilities found in third-party libraries, please also contact the library's owner or maintainer directly. 10 | 11 | ## Responsible Disclosure Policy 12 | 13 | For more information about disclosing a security vulnerability to Sonar, please refer to our community post: [Responsible Vulnerability Disclosure](https://community.sonarsource.com/t/responsible-vulnerability-disclosure/9317). -------------------------------------------------------------------------------- /conf/env.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 20 | // injected into the application via DefinePlugin in Webpack configuration. 21 | 22 | const REACT_APP = /^REACT_APP_/i; 23 | 24 | function getClientEnvironment() { 25 | return Object.keys(process.env).filter(key => REACT_APP.test(key)).reduce((env, key) => { 26 | env['process.env.' + key] = JSON.stringify(process.env[key]); 27 | return env; 28 | }, { 29 | // Useful for determining whether we’re running in production mode. 30 | // Most importantly, it switches React into the correct mode. 31 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development') 32 | }); 33 | } 34 | 35 | module.exports = getClientEnvironment; 36 | -------------------------------------------------------------------------------- /conf/jest/CSSStub.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | module.exports = {}; 20 | -------------------------------------------------------------------------------- /conf/jest/FileStub.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | module.exports = 'test-file-stub'; 20 | -------------------------------------------------------------------------------- /conf/jest/SetupTestEnvironment.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | window.baseUrl = ''; 20 | 21 | window.t = (window.tp = function() { 22 | const args = Array.prototype.slice.call(arguments, 0); 23 | return args.join('.'); 24 | }); 25 | -------------------------------------------------------------------------------- /conf/webpack/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | const webpack = require('webpack'); 20 | const config = require('./webpack.config'); 21 | 22 | config.devtool = 'inline-source-map'; 23 | 24 | config.output.publicPath = '/static/example/'; 25 | 26 | config.output.pathinfo = true; 27 | 28 | Object.keys(config.entry).forEach(key => { 29 | config.entry[key].unshift(require.resolve('react-dev-utils/webpackHotDevClient')); 30 | }); 31 | 32 | config.plugins = [new webpack.HotModuleReplacementPlugin()]; 33 | 34 | module.exports = config; 35 | -------------------------------------------------------------------------------- /conf/webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | const path = require("path"); 20 | const autoprefixer = require("autoprefixer"); 21 | 22 | module.exports = { 23 | // Define the entry points here. They MUST have the same name as the page_id 24 | // defined in src/main/java/org/sonarsource/plugins/example/web/MyPluginPageDefinition.java 25 | entry: { 26 | // Using Vanilla JS: 27 | global_page: ["./src/main/js/global_page/index.js"], 28 | 29 | // Using Backbone JS: 30 | project_page: ["./src/main/js/project_page/index.js"], 31 | 32 | // Using React: 33 | portfolio_page: ["./src/main/js/portfolio_page/index.js"], 34 | admin_page: ["./src/main/js/admin_page/index.js"] 35 | }, 36 | output: { 37 | // The entry point files MUST be shipped inside the final JAR's static/ 38 | // directory. 39 | path: path.join(__dirname, "../../target/classes/static"), 40 | filename: "[name].js" 41 | }, 42 | resolve: { 43 | root: path.join(__dirname, "src/main/js") 44 | }, 45 | externals: { 46 | // React 16.8 ships with SonarQube, and should be re-used to avoid 47 | // collisions at runtime. 48 | react: "React", 49 | "react-dom": "ReactDOM", 50 | // Register the Sonar* globals as packages, to simplify importing. 51 | // See src/main/js/common/api.js for more information on what is exposed 52 | // in SonarRequest. 53 | "sonar-request": "SonarRequest", 54 | }, 55 | module: { 56 | // Our example uses Babel to transpile our code. 57 | loaders: [ 58 | { 59 | test: /\.js$/, 60 | loader: "babel", 61 | exclude: /(node_modules)/ 62 | }, 63 | { 64 | test: /\.css/, 65 | loader: "style-loader!css-loader!postcss-loader" 66 | }, 67 | { test: /\.json$/, loader: "json" } 68 | ] 69 | }, 70 | postcss() { 71 | return [ 72 | autoprefixer({ 73 | browsers: [ 74 | "last 3 Chrome versions", 75 | "last 3 Firefox versions", 76 | "last 3 Safari versions", 77 | "last 3 Edge versions", 78 | "IE 11" 79 | ] 80 | }) 81 | ]; 82 | } 83 | }; 84 | -------------------------------------------------------------------------------- /conf/webpack/webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | const webpack = require('webpack'); 20 | const config = require('./webpack.config'); 21 | const getClientEnvironment = require('../env'); 22 | 23 | // Get environment variables to inject into our app. 24 | const env = getClientEnvironment(); 25 | 26 | // Assert this just to be safe. 27 | // Development builds of React are slow and not intended for production. 28 | if (env['process.env.NODE_ENV'] !== '"production"') { 29 | throw new Error('Production builds must have NODE_ENV=production.'); 30 | } 31 | 32 | const noUglify = process.argv.some(arg => arg.indexOf('--no-uglify') > -1); 33 | 34 | // Don't attempt to continue if there are any errors. 35 | config.bail = true; 36 | 37 | config.plugins = [ 38 | // Makes some environment variables available to the JS code, for example: 39 | // if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`. 40 | // It is absolutely essential that NODE_ENV was set to production here. 41 | // Otherwise React will be compiled in the very slow development mode. 42 | new webpack.DefinePlugin(env), 43 | 44 | // This helps ensure the builds are consistent if source hasn't changed: 45 | new webpack.optimize.OccurrenceOrderPlugin(), 46 | 47 | // Try to dedupe duplicated modules, if any: 48 | new webpack.optimize.DedupePlugin() 49 | ]; 50 | 51 | if (!noUglify) { 52 | config.plugins.push( 53 | new webpack.optimize.UglifyJsPlugin({ 54 | compress: { 55 | screw_ie8: true, // React doesn't support IE8 56 | warnings: false 57 | }, 58 | mangle: { 59 | screw_ie8: true 60 | }, 61 | output: { 62 | comments: false, 63 | screw_ie8: true 64 | } 65 | }) 66 | ); 67 | } 68 | 69 | module.exports = config; 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sonar-custom-plugin-example", 3 | "license": "LGPL-3.0", 4 | "version": "7.0.0", 5 | "devDependencies": { 6 | "autoprefixer": "6.2.2", 7 | "babel-core": "6.14.0", 8 | "babel-jest": "18.0.0", 9 | "babel-loader": "6.2.5", 10 | "babel-preset-react-app": "0.2.1", 11 | "cross-env": "2.0.0", 12 | "cross-spawn": "4.0.0", 13 | "css-loader": "0.23.1", 14 | "detect-port": "1.0.0", 15 | "dotenv": "2.0.0", 16 | "enzyme": "2.6.0", 17 | "enzyme-to-json": "1.4.5", 18 | "expose-loader": "0.7.1", 19 | "express": "4.13.4", 20 | "express-http-proxy": "0.6.0", 21 | "filesize": "3.3.0", 22 | "find-cache-dir": "0.1.1", 23 | "gzip-size": "3.0.0", 24 | "imports-loader": "0.6.5", 25 | "jest": "18.0.0", 26 | "json-loader": "0.5.4", 27 | "path-exists": "2.1.0", 28 | "postcss-loader": "0.8.0", 29 | "prettier": "0.22.0", 30 | "react": "15.6.2", 31 | "react-addons-shallow-compare": "15.6.2", 32 | "react-addons-test-utils": "15.6.2", 33 | "react-dev-utils": "0.2.1", 34 | "react-dom": "15.6.2", 35 | "react-router": "3.0.2", 36 | "react-transform-hmr": "1.0.4", 37 | "recursive-readdir": "2.1.0", 38 | "rimraf": "2.5.4", 39 | "script-loader": "0.6.1", 40 | "strip-ansi": "3.0.1", 41 | "style-loader": "0.13.0", 42 | "webpack": "1.13.2", 43 | "webpack-dev-server": "1.16.1" 44 | }, 45 | "scripts": { 46 | "build": "node scripts/build.js", 47 | "start": "node scripts/start.js", 48 | "test": "node scripts/test.js" 49 | }, 50 | "babel": { 51 | "presets": [ 52 | "react-app" 53 | ] 54 | }, 55 | "jest": { 56 | "coverageDirectory": "/target/coverage", 57 | "coveragePathIgnorePatterns": [ 58 | "/node_modules", 59 | "/tests" 60 | ], 61 | "moduleFileExtensions": [ 62 | "jsx", 63 | "js", 64 | "json" 65 | ], 66 | "moduleNameMapper": { 67 | "^.+\\.(hbs|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/conf/jest/FileStub.js", 68 | "^.+\\.css$": "/conf/jest/CSSStub.js" 69 | }, 70 | "setupFiles": [ 71 | "/conf/jest/SetupTestEnvironment.js" 72 | ], 73 | "snapshotSerializers": [ 74 | "enzyme-to-json/serializer" 75 | ], 76 | "testPathIgnorePatterns": [ 77 | "/node_modules", 78 | "/scripts", 79 | "/conf" 80 | ] 81 | }, 82 | "dependencies": { 83 | "backbone": "^1.4.0", 84 | "jquery": "^1.11.0", 85 | "underscore": "^1.9.1" 86 | }, 87 | "packageManager": "yarn@4.5.0" 88 | } 89 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.sonarsource.plugins.example 6 | sonar-example-plugin 7 | sonar-plugin 8 | 10.0.0 9 | 10 | Example Plugin for SonarQube Server 10.x 11 | Example of Plugin for SonarQube Server: Foo Language, FooLint, Custom Metrics and MeasureComputers 12 | 13 | 14 | UTF-8 15 | 10.11.0.2468 16 | 10.7.0.96327 17 | 17 18 | src/main/java,src/main/js 19 | 20 | 21 | 22 | 23 | org.sonarsource.api.plugin 24 | sonar-plugin-api 25 | ${sonar.apiVersion} 26 | provided 27 | 28 | 29 | 30 | org.apache.commons 31 | commons-lang3 32 | 3.17.0 33 | 34 | 35 | 36 | 37 | 38 | org.sonarsource.sonarqube 39 | sonar-testing-harness 40 | ${sonar.testingHarnessVersion} 41 | test 42 | 43 | 44 | junit 45 | junit 46 | 4.13.2 47 | test 48 | 49 | 50 | 51 | 52 | 53 | 54 | org.sonarsource.sonar-packaging-maven-plugin 55 | sonar-packaging-maven-plugin 56 | 1.23.0.740 57 | true 58 | 59 | example 60 | org.sonarsource.plugins.example.ExamplePlugin 61 | 62 | 9.9 63 | 64 | 65 | 66 | org.apache.maven.plugins 67 | maven-compiler-plugin 68 | 3.13.0 69 | 70 | 11 71 | 72 | 73 | 74 | com.github.eirslett 75 | frontend-maven-plugin 76 | 1.12.1 77 | 78 | 79 | generate-resources 80 | install node and yarn 81 | 82 | install-node-and-yarn 83 | 84 | 85 | v20.18.0 86 | 87 | v1.22.5 88 | 89 | 90 | 91 | yarn install 92 | 93 | yarn 94 | 95 | 96 | install 97 | 98 | 99 | 100 | generate-resources 101 | yarn run script 102 | 103 | yarn 104 | 105 | 106 | run build 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | /* eslint-disable no-console */ 20 | process.env.NODE_ENV = 'production'; 21 | 22 | const chalk = require('chalk'); 23 | const webpack = require('webpack'); 24 | const config = require('../conf/webpack/webpack.config.prod.js'); 25 | 26 | function formatSize(bytes) { 27 | if (bytes === 0) { 28 | return '0'; 29 | } 30 | const k = 1000; // or 1024 for binary 31 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; 32 | const i = Math.floor(Math.log(bytes) / Math.log(k)); 33 | return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; 34 | } 35 | 36 | function build() { 37 | console.log(chalk.cyan.bold('Creating optimized production build...')); 38 | console.log(); 39 | 40 | webpack(config, (err, stats) => { 41 | if (err) { 42 | console.log(chalk.red.bold('Failed to create a production build!')); 43 | console.log(chalk.red(err.message || err)); 44 | process.exit(1); 45 | } 46 | 47 | if (stats.compilation.errors && stats.compilation.errors.length) { 48 | console.log(chalk.red.bold('Failed to create a production build!')); 49 | stats.compilation.errors.forEach(err => console.log(chalk.red(err.message || err))); 50 | process.exit(1); 51 | } 52 | 53 | const jsonStats = stats.toJson(); 54 | 55 | console.log('Assets:'); 56 | const assets = jsonStats.assets.slice(); 57 | assets.sort((a, b) => b.size - a.size); 58 | assets.forEach(asset => { 59 | let sizeLabel = formatSize(asset.size); 60 | const leftPadding = ' '.repeat(Math.max(0, 8 - sizeLabel.length)); 61 | sizeLabel = leftPadding + sizeLabel; 62 | console.log('', chalk.yellow(sizeLabel), asset.name); 63 | }); 64 | console.log(); 65 | 66 | const seconds = jsonStats.time / 1000; 67 | console.log('Duration: ' + seconds.toFixed(2) + 's'); 68 | console.log(); 69 | 70 | console.log(chalk.green.bold('Compiled successfully!')); 71 | }); 72 | } 73 | 74 | build(); 75 | -------------------------------------------------------------------------------- /scripts/start.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | process.env.NODE_ENV = 'development'; 20 | 21 | // Load environment variables from .env file. Surpress warnings using silent 22 | // if this file is missing. dotenv will never modify any environment variables 23 | // that have already been set. 24 | // https://github.com/motdotla/dotenv 25 | require('dotenv').config({ silent: true }); 26 | 27 | const chalk = require('chalk'); 28 | const webpack = require('webpack'); 29 | const WebpackDevServer = require('webpack-dev-server'); 30 | const httpProxyMiddleware = require('http-proxy-middleware'); 31 | const detect = require('detect-port'); 32 | const clearConsole = require('react-dev-utils/clearConsole'); 33 | const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); 34 | const prompt = require('react-dev-utils/prompt'); 35 | const config = require('../conf/webpack/webpack.config.dev.js'); 36 | 37 | // Tools like Cloud9 rely on this. 38 | const DEFAULT_PORT = process.env.PORT || 3000; 39 | let compiler; 40 | let handleCompile; 41 | 42 | const PROXY_URL = process.env.PROXY_URL || 'http://localhost:9000'; 43 | 44 | function setupCompiler(host, port, protocol) { 45 | // "Compiler" is a low-level interface to Webpack. 46 | // It lets us listen to some events and provide our own custom messages. 47 | compiler = webpack(config, handleCompile); 48 | 49 | // "invalid" event fires when you have changed a file, and Webpack is 50 | // recompiling a bundle. WebpackDevServer takes care to pause serving the 51 | // bundle, so if you refresh, it'll wait instead of serving the old one. 52 | // "invalid" is short for "bundle invalidated", it doesn't imply any errors. 53 | compiler.plugin('invalid', () => { 54 | clearConsole(); 55 | console.log('Compiling...'); 56 | }); 57 | 58 | // "done" event fires when Webpack has finished recompiling the bundle. 59 | // Whether or not you have warnings or errors, you will get this event. 60 | compiler.plugin('done', stats => { 61 | clearConsole(); 62 | 63 | // We have switched off the default Webpack output in WebpackDevServer 64 | // options so we are going to "massage" the warnings and errors and present 65 | // them in a readable focused way. 66 | const jsonStats = stats.toJson({}, true); 67 | const messages = formatWebpackMessages(jsonStats); 68 | const seconds = jsonStats.time / 1000; 69 | if (!messages.errors.length && !messages.warnings.length) { 70 | console.log(chalk.green('Compiled successfully!')); 71 | console.log('Duration: ' + seconds.toFixed(2) + 's'); 72 | console.log(); 73 | console.log('The app is running at:'); 74 | console.log(); 75 | console.log(' ' + chalk.cyan(protocol + '://' + host + ':' + port + '/')); 76 | console.log(); 77 | console.log('Note that the development build is not optimized.'); 78 | console.log('To create a production build, use ' + chalk.cyan('npm run build') + '.'); 79 | console.log(); 80 | } 81 | 82 | // If errors exist, only show errors. 83 | if (messages.errors.length) { 84 | console.log(chalk.red('Failed to compile.')); 85 | console.log(); 86 | messages.errors.forEach(message => { 87 | console.log(message); 88 | console.log(); 89 | }); 90 | return; 91 | } 92 | 93 | // Show warnings if no errors were found. 94 | if (messages.warnings.length) { 95 | console.log(chalk.yellow('Compiled with warnings.')); 96 | console.log(); 97 | messages.warnings.forEach(message => { 98 | console.log(message); 99 | console.log(); 100 | }); 101 | } 102 | }); 103 | } 104 | 105 | // We need to provide a custom onError function for httpProxyMiddleware. 106 | // It allows us to log custom error messages on the console. 107 | function onProxyError(proxy) { 108 | return function(err, req, res) { 109 | const host = req.headers && req.headers.host; 110 | console.log( 111 | chalk.red('Proxy error:') + 112 | ' Could not proxy request ' + 113 | chalk.cyan(req.url) + 114 | ' from ' + 115 | chalk.cyan(host) + 116 | ' to ' + 117 | chalk.cyan(proxy) + 118 | '.' 119 | ); 120 | console.log( 121 | 'See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (' + 122 | chalk.cyan(err.code) + 123 | ').' 124 | ); 125 | console.log(); 126 | 127 | // And immediately send the proper error response to the client. 128 | // Otherwise, the request will eventually timeout with ERR_EMPTY_RESPONSE on the client side. 129 | if (res.writeHead && !res.headersSent) { 130 | res.writeHead(500); 131 | } 132 | res.end( 133 | 'Proxy error: Could not proxy request ' + 134 | req.url + 135 | ' from ' + 136 | host + 137 | ' to ' + 138 | proxy + 139 | ' (' + 140 | err.code + 141 | ').' 142 | ); 143 | }; 144 | } 145 | 146 | function addMiddleware(devServer) { 147 | // `proxy` lets you to specify a fallback server during development. 148 | // Every unrecognized request will be forwarded to it. 149 | const proxy = PROXY_URL; 150 | if (proxy) { 151 | if (typeof proxy !== 'string') { 152 | console.log(chalk.red('When specified, "proxy" in package.json must be a string.')); 153 | console.log(chalk.red('Instead, the type of "proxy" was "' + typeof proxy + '".')); 154 | console.log(chalk.red('Either remove "proxy" from package.json, or make it a string.')); 155 | process.exit(1); 156 | } 157 | 158 | // Otherwise, if proxy is specified, we will let it handle any request. 159 | // There are a few exceptions which we won't send to the proxy: 160 | // - /*.hot-update.json (WebpackDevServer uses this too for hot reloading) 161 | // - /sockjs-node/* (WebpackDevServer uses this for hot reloading) 162 | // Tip: use https://jex.im/regulex/ to visualize the regex 163 | const mayProxy = /^(?!\/(index\.html$|.*\.hot-update\.json$|sockjs-node\/)).*$/; 164 | devServer.use( 165 | mayProxy, 166 | // Pass the scope regex both to Express and to the middleware for proxying 167 | // of both HTTP and WebSockets to work without false positives. 168 | httpProxyMiddleware(pathname => mayProxy.test(pathname), { 169 | target: proxy, 170 | logLevel: 'silent', 171 | onError: onProxyError(proxy), 172 | secure: false, 173 | changeOrigin: true 174 | }) 175 | ); 176 | } 177 | // Finally, by now we have certainly resolved the URL. 178 | // It may be /index.html, so let the dev server try serving it again. 179 | devServer.use(devServer.middleware); 180 | } 181 | 182 | function runDevServer(host, port, protocol) { 183 | const devServer = new WebpackDevServer(compiler, { 184 | // Enable gzip compression of generated files. 185 | compress: true, 186 | // Silence WebpackDevServer's own logs since they're generally not useful. 187 | // It will still show compile warnings and errors with this setting. 188 | clientLogLevel: 'none', 189 | // Enable hot reloading server. It will provide /sockjs-node/ endpoint 190 | // for the WebpackDevServer client so it can learn when the files were 191 | // updated. The WebpackDevServer client is included as an entry point 192 | // in the Webpack development configuration. Note that only changes 193 | // to CSS are currently hot reloaded. JS changes will refresh the browser. 194 | hot: true, 195 | // It is important to tell WebpackDevServer to use the same "root" path 196 | // as we specified in the config. In development, we always serve from /. 197 | publicPath: config.output.publicPath, 198 | // WebpackDevServer is noisy by default so we emit custom message instead 199 | // by listening to the compiler events with `compiler.plugin` calls above. 200 | quiet: true, 201 | // Reportedly, this avoids CPU overload on some systems. 202 | // https://github.com/facebookincubator/create-react-app/issues/293 203 | watchOptions: { 204 | ignored: /node_modules/ 205 | }, 206 | // Enable HTTPS if the HTTPS environment variable is set to 'true' 207 | https: protocol === 'https', 208 | host 209 | }); 210 | 211 | // Our custom middleware proxies requests to /index.html or a remote API. 212 | addMiddleware(devServer); 213 | 214 | // Launch WebpackDevServer. 215 | devServer.listen(port, err => { 216 | if (err) { 217 | return console.log(err); 218 | } 219 | 220 | clearConsole(); 221 | console.log(chalk.cyan('Starting the development server...')); 222 | console.log(); 223 | }); 224 | } 225 | 226 | function run(port) { 227 | const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; 228 | const host = process.env.HOST || 'localhost'; 229 | setupCompiler(host, port, protocol); 230 | runDevServer(host, port, protocol); 231 | } 232 | 233 | // We attempt to use the default port but if it is busy, we offer the user to 234 | // run on a different port. `detect()` Promise resolves to the next free port. 235 | detect(DEFAULT_PORT).then(port => { 236 | if (port === DEFAULT_PORT) { 237 | run(port); 238 | return; 239 | } 240 | 241 | clearConsole(); 242 | const question = chalk.yellow('Something is already running on port ' + DEFAULT_PORT + '.') + 243 | '\n\nWould you like to run the app on another port instead?'; 244 | 245 | prompt(question, true).then(shouldChangePort => { 246 | if (shouldChangePort) { 247 | run(port); 248 | } 249 | }); 250 | }); 251 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | process.env.NODE_ENV = 'test'; 20 | process.env.PUBLIC_URL = ''; 21 | 22 | // Load environment variables from .env file. Surpress warnings using silent 23 | // if this file is missing. dotenv will never modify any environment variables 24 | // that have already been set. 25 | // https://github.com/motdotla/dotenv 26 | require('dotenv').config({ silent: true }); 27 | 28 | const jest = require('jest'); 29 | 30 | const argv = process.argv.slice(2); 31 | 32 | // Watch unless on CI 33 | if (!process.env.CI) { 34 | argv.push('--watch'); 35 | } 36 | 37 | argv.push('--coverage'); 38 | 39 | jest.run(argv); 40 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/ExamplePlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Example Plugin for SonarQube 3 | * Copyright (C) 2009-2025 SonarSource SA 4 | * mailto:contact AT sonarsource DOT com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | package org.sonarsource.plugins.example; 21 | 22 | import org.sonar.api.Plugin; 23 | import org.sonarsource.plugins.example.hooks.PostJobInScanner; 24 | import org.sonarsource.plugins.example.hooks.DisplayQualityGateStatus; 25 | import org.sonarsource.plugins.example.languages.FooLanguage; 26 | import org.sonarsource.plugins.example.languages.FooQualityProfile; 27 | import org.sonarsource.plugins.example.measures.ComputeSizeAverage; 28 | import org.sonarsource.plugins.example.measures.ComputeSizeRating; 29 | import org.sonarsource.plugins.example.measures.ExampleMetrics; 30 | import org.sonarsource.plugins.example.measures.SetSizeOnFilesSensor; 31 | import org.sonarsource.plugins.example.rules.CreateIssuesOnJavaFilesSensor; 32 | import org.sonarsource.plugins.example.rules.FlagLineSensor; 33 | import org.sonarsource.plugins.example.rules.FooLintIssuesLoaderSensor; 34 | import org.sonarsource.plugins.example.rules.FlagRuleDefinition; 35 | import org.sonarsource.plugins.example.rules.JavaRulesDefinition; 36 | import org.sonarsource.plugins.example.settings.FooLanguageProperties; 37 | import org.sonarsource.plugins.example.settings.HelloWorldProperties; 38 | import org.sonarsource.plugins.example.settings.SayHelloFromScanner; 39 | import org.sonarsource.plugins.example.web.MyPluginPageDefinition; 40 | 41 | /** 42 | * This class is the entry point for all extensions. It is referenced in pom.xml. 43 | */ 44 | public class ExamplePlugin implements Plugin { 45 | 46 | @Override 47 | public void define(Context context) { 48 | // tutorial on hooks 49 | context.addExtensions(PostJobInScanner.class, DisplayQualityGateStatus.class); 50 | 51 | // tutorial on languages 52 | // https://docs.sonarqube.org/9.4/extend/new-languages/ 53 | context.addExtensions(FooLanguage.class, FooQualityProfile.class); 54 | context.addExtensions(FooLanguageProperties.getProperties()); 55 | 56 | // tutorial on measures 57 | context 58 | .addExtensions(ExampleMetrics.class, SetSizeOnFilesSensor.class, ComputeSizeAverage.class, ComputeSizeRating.class); 59 | 60 | // tutorial on rules 61 | context.addExtensions(JavaRulesDefinition.class, CreateIssuesOnJavaFilesSensor.class); 62 | context.addExtensions(FlagRuleDefinition.class, FlagLineSensor.class, FooLintIssuesLoaderSensor.class); 63 | 64 | // tutorial on settings 65 | context 66 | .addExtensions(HelloWorldProperties.getProperties()) 67 | .addExtension(SayHelloFromScanner.class); 68 | 69 | // tutorial on web extensions 70 | context.addExtension(MyPluginPageDefinition.class); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/hooks/DisplayQualityGateStatus.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Example Plugin for SonarQube 3 | * Copyright (C) 2009-2025 SonarSource SA 4 | * mailto:contact AT sonarsource DOT com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | package org.sonarsource.plugins.example.hooks; 21 | 22 | import org.sonar.api.ce.posttask.PostProjectAnalysisTask; 23 | import org.sonar.api.ce.posttask.QualityGate; 24 | import org.sonar.api.utils.log.Loggers; 25 | 26 | /** 27 | * Logs the Quality gate status in Compute Engine when analysis is finished (browse 28 | * Administration > Projects > Background Tasks). 29 | * A real use-case would be to send an email or to notify an IRC channel. 30 | */ 31 | public class DisplayQualityGateStatus implements PostProjectAnalysisTask { 32 | @Override 33 | public void finished(ProjectAnalysis analysis) { 34 | QualityGate gate = analysis.getQualityGate(); 35 | if (gate != null) { 36 | Loggers.get(getClass()).info("Quality gate is " + gate.getStatus()); 37 | } 38 | } 39 | 40 | @Override 41 | public String getDescription() { 42 | return "Display Quality Gate status"; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/hooks/PostJobInScanner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Example Plugin for SonarQube 3 | * Copyright (C) 2009-2025 SonarSource SA 4 | * mailto:contact AT sonarsource DOT com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | package org.sonarsource.plugins.example.hooks; 21 | 22 | import org.sonar.api.batch.postjob.PostJob; 23 | import org.sonar.api.batch.postjob.PostJobContext; 24 | import org.sonar.api.batch.postjob.PostJobDescriptor; 25 | import org.sonar.api.utils.log.Logger; 26 | import org.sonar.api.utils.log.Loggers; 27 | 28 | public class PostJobInScanner implements PostJob { 29 | 30 | private static final Logger LOGGER = Loggers.get(PostJobInScanner.class); 31 | 32 | @Override 33 | public void describe(PostJobDescriptor descriptor) { 34 | descriptor.name("After scan"); 35 | } 36 | 37 | @Override 38 | public void execute(PostJobContext context) { 39 | LOGGER.info("Something to do after the analysis report has been submitted"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/languages/FooLanguage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Example Plugin for SonarQube 3 | * Copyright (C) 2009-2025 SonarSource SA 4 | * mailto:contact AT sonarsource DOT com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | package org.sonarsource.plugins.example.languages; 21 | 22 | import org.sonar.api.config.Configuration; 23 | import org.sonar.api.resources.AbstractLanguage; 24 | import org.sonarsource.plugins.example.settings.FooLanguageProperties; 25 | 26 | /** 27 | * This class defines the fictive Foo language. 28 | */ 29 | public final class FooLanguage extends AbstractLanguage { 30 | 31 | public static final String NAME = "Foo"; 32 | public static final String KEY = "foo"; 33 | 34 | private final Configuration config; 35 | 36 | public FooLanguage(Configuration config) { 37 | super(KEY, NAME); 38 | this.config = config; 39 | } 40 | 41 | @Override 42 | public String[] getFileSuffixes() { 43 | return config.getStringArray(FooLanguageProperties.FILE_SUFFIXES_KEY); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/languages/FooQualityProfile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Example Plugin for SonarQube 3 | * Copyright (C) 2009-2025 SonarSource SA 4 | * mailto:contact AT sonarsource DOT com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | package org.sonarsource.plugins.example.languages; 21 | 22 | import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; 23 | import org.sonarsource.plugins.example.rules.FlagLine1Rule; 24 | import org.sonarsource.plugins.example.rules.FlagLine2Rule; 25 | import org.sonarsource.plugins.example.rules.FlagLine3Rule; 26 | 27 | import static org.sonarsource.plugins.example.rules.FlagRuleDefinition.REPO_KEY; 28 | 29 | /** 30 | * Default, BuiltIn Quality Profile for the projects having files of the language "foo" 31 | */ 32 | public final class FooQualityProfile implements BuiltInQualityProfilesDefinition { 33 | 34 | @Override 35 | public void define(Context context) { 36 | NewBuiltInQualityProfile profile = context.createBuiltInQualityProfile("FooLint Rules", FooLanguage.KEY); 37 | profile.setDefault(true); 38 | 39 | NewBuiltInActiveRule rule1 = profile.activateRule(REPO_KEY, FlagLine1Rule.RULE_KEY); 40 | rule1.overrideSeverity("BLOCKER"); 41 | 42 | 43 | NewBuiltInActiveRule rule2 = profile.activateRule(REPO_KEY, FlagLine2Rule.RULE_KEY); 44 | rule2.overrideSeverity("MAJOR"); 45 | NewBuiltInActiveRule rule3 = profile.activateRule(REPO_KEY, FlagLine3Rule.RULE_KEY); 46 | rule3.overrideSeverity("CRITICAL"); 47 | 48 | profile.done(); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/measures/ComputeSizeAverage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Example Plugin for SonarQube 3 | * Copyright (C) 2009-2025 SonarSource SA 4 | * mailto:contact AT sonarsource DOT com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | package org.sonarsource.plugins.example.measures; 21 | 22 | import org.sonar.api.ce.measure.Component; 23 | import org.sonar.api.ce.measure.Measure; 24 | import org.sonar.api.ce.measure.MeasureComputer; 25 | 26 | import static org.sonarsource.plugins.example.measures.ExampleMetrics.FILENAME_SIZE; 27 | 28 | public class ComputeSizeAverage implements MeasureComputer { 29 | 30 | @Override 31 | public MeasureComputerDefinition define(MeasureComputerDefinitionContext def) { 32 | return def.newDefinitionBuilder() 33 | .setOutputMetrics(FILENAME_SIZE.key()) 34 | .build(); 35 | } 36 | 37 | @Override 38 | public void compute(MeasureComputerContext context) { 39 | // measure is already defined on files by {@link SetSizeOnFilesSensor} 40 | // in scanner stack 41 | if (context.getComponent().getType() != Component.Type.FILE) { 42 | int sum = 0; 43 | int count = 0; 44 | for (Measure child : context.getChildrenMeasures(FILENAME_SIZE.key())) { 45 | sum += child.getIntValue(); 46 | count++; 47 | } 48 | int average = count == 0 ? 0 : sum / count; 49 | context.addMeasure(FILENAME_SIZE.key(), average); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/measures/ComputeSizeRating.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Example Plugin for SonarQube 3 | * Copyright (C) 2009-2025 SonarSource SA 4 | * mailto:contact AT sonarsource DOT com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | package org.sonarsource.plugins.example.measures; 21 | 22 | import org.sonar.api.ce.measure.Measure; 23 | import org.sonar.api.ce.measure.MeasureComputer; 24 | 25 | import static org.sonarsource.plugins.example.measures.ExampleMetrics.FILENAME_SIZE; 26 | import static org.sonarsource.plugins.example.measures.ExampleMetrics.FILENAME_SIZE_RATING; 27 | 28 | /** 29 | * Rating is computed from value of metric {@link ExampleMetrics#FILENAME_SIZE}. 30 | */ 31 | public class ComputeSizeRating implements MeasureComputer { 32 | 33 | private static final int THRESHOLD = 20; 34 | private static final int RATING_A = 1; 35 | private static final int RATING_B = 2; 36 | 37 | @Override 38 | public MeasureComputerDefinition define(MeasureComputerDefinitionContext def) { 39 | return def.newDefinitionBuilder() 40 | .setInputMetrics(FILENAME_SIZE.key()) 41 | .setOutputMetrics(FILENAME_SIZE_RATING.key()) 42 | .build(); 43 | } 44 | 45 | @Override 46 | public void compute(MeasureComputerContext context) { 47 | Measure size = context.getMeasure(FILENAME_SIZE.key()); 48 | if (size != null) { 49 | // rating values are currently implemented as integers in API 50 | int rating = RATING_A; 51 | if (size.getIntValue() > THRESHOLD) { 52 | rating = RATING_B; 53 | } 54 | context.addMeasure(FILENAME_SIZE_RATING.key(), rating); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/measures/ExampleMetrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Example Plugin for SonarQube 3 | * Copyright (C) 2009-2025 SonarSource SA 4 | * mailto:contact AT sonarsource DOT com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | package org.sonarsource.plugins.example.measures; 21 | 22 | import java.util.List; 23 | import org.sonar.api.measures.CoreMetrics; 24 | import org.sonar.api.measures.Metric; 25 | import org.sonar.api.measures.Metrics; 26 | 27 | import static java.util.Arrays.asList; 28 | 29 | public class ExampleMetrics implements Metrics { 30 | 31 | public static final Metric FILENAME_SIZE = new Metric.Builder("filename_size", "Filename Size", Metric.ValueType.INT) 32 | .setDescription("Number of characters of file names") 33 | .setDirection(Metric.DIRECTION_BETTER) 34 | .setQualitative(false) 35 | .setDomain(CoreMetrics.DOMAIN_GENERAL) 36 | .create(); 37 | 38 | public static final Metric FILENAME_SIZE_RATING = new Metric.Builder("filename_size_rating", "Filename Size Rating", Metric.ValueType.RATING) 39 | .setDescription("Rating based on size of file names") 40 | .setDirection(Metric.DIRECTION_BETTER) 41 | .setQualitative(true) 42 | .setDomain(CoreMetrics.DOMAIN_GENERAL) 43 | .create(); 44 | 45 | @Override 46 | public List getMetrics() { 47 | return asList(FILENAME_SIZE, FILENAME_SIZE_RATING); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/measures/SetSizeOnFilesSensor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Example Plugin for SonarQube 3 | * Copyright (C) 2009-2025 SonarSource SA 4 | * mailto:contact AT sonarsource DOT com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | package org.sonarsource.plugins.example.measures; 21 | 22 | import org.sonar.api.batch.fs.FileSystem; 23 | import org.sonar.api.batch.fs.InputFile; 24 | import org.sonar.api.batch.sensor.Sensor; 25 | import org.sonar.api.batch.sensor.SensorContext; 26 | import org.sonar.api.batch.sensor.SensorDescriptor; 27 | 28 | import static org.sonarsource.plugins.example.measures.ExampleMetrics.FILENAME_SIZE; 29 | 30 | /** 31 | * Scanner feeds raw measures on files but must not aggregate values to directories and project. 32 | * This class emulates loading of file measures from a 3rd-party analyser. 33 | */ 34 | public class SetSizeOnFilesSensor implements Sensor { 35 | @Override 36 | public void describe(SensorDescriptor descriptor) { 37 | descriptor.name("Compute size of file names"); 38 | } 39 | 40 | @Override 41 | public void execute(SensorContext context) { 42 | FileSystem fs = context.fileSystem(); 43 | // only "main" files, but not "tests" 44 | Iterable files = fs.inputFiles(fs.predicates().hasType(InputFile.Type.MAIN)); 45 | for (InputFile file : files) { 46 | context.newMeasure() 47 | .forMetric(FILENAME_SIZE) 48 | .on(file) 49 | .withValue(file.filename().length()) 50 | .save(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/rules/CreateIssuesOnJavaFilesSensor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Example Plugin for SonarQube 3 | * Copyright (C) 2009-2025 SonarSource SA 4 | * mailto:contact AT sonarsource DOT com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | package org.sonarsource.plugins.example.rules; 21 | 22 | import org.sonar.api.batch.fs.FileSystem; 23 | import org.sonar.api.batch.fs.InputFile; 24 | import org.sonar.api.batch.sensor.Sensor; 25 | import org.sonar.api.batch.sensor.SensorContext; 26 | import org.sonar.api.batch.sensor.SensorDescriptor; 27 | import org.sonar.api.batch.sensor.issue.NewIssue; 28 | import org.sonar.api.batch.sensor.issue.NewIssueLocation; 29 | 30 | /** 31 | * Generates issues on all java files at line 1. This rule 32 | * must be activated in the Quality profile. 33 | */ 34 | public class CreateIssuesOnJavaFilesSensor implements Sensor { 35 | 36 | private static final double ARBITRARY_GAP = 2.0; 37 | private static final int LINE_1 = 1; 38 | 39 | @Override 40 | public void describe(SensorDescriptor descriptor) { 41 | descriptor.name("Add issues on line 1 of all Java files"); 42 | 43 | // optimisation to disable execution of sensor if project does 44 | // not contain Java files or if the example rule is not activated 45 | // in the Quality profile 46 | descriptor.onlyOnLanguage("java"); 47 | descriptor.createIssuesForRuleRepositories(JavaRulesDefinition.REPOSITORY); 48 | } 49 | 50 | @Override 51 | public void execute(SensorContext context) { 52 | FileSystem fs = context.fileSystem(); 53 | Iterable javaFiles = fs.inputFiles(fs.predicates().hasLanguage("java")); 54 | for (InputFile javaFile : javaFiles) { 55 | // no need to define the severity as it is automatically set according 56 | // to the configured Quality profile 57 | NewIssue newIssue = context.newIssue() 58 | .forRule(JavaRulesDefinition.RULE_ON_LINE_1) 59 | 60 | // gap is used to estimate the remediation cost to fix the debt 61 | .gap(ARBITRARY_GAP); 62 | 63 | NewIssueLocation primaryLocation = newIssue.newLocation() 64 | .on(javaFile) 65 | .at(javaFile.selectLine(LINE_1)) 66 | .message("You can't do anything. This is first line!"); 67 | newIssue.at(primaryLocation); 68 | newIssue.save(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/rules/FlagLine1Rule.java: -------------------------------------------------------------------------------- 1 | package org.sonarsource.plugins.example.rules; 2 | 3 | import org.sonar.api.batch.fs.InputFile; 4 | import org.sonar.api.batch.sensor.SensorContext; 5 | import org.sonar.api.batch.sensor.issue.NewIssue; 6 | import org.sonar.api.rule.RuleKey; 7 | import org.sonar.check.Rule; 8 | 9 | @Rule(key = FlagLine1Rule.RULE_KEY, name = "Example rule 1", description = "Example rule 1 description") 10 | public class FlagLine1Rule implements FlagLineRule { 11 | public static final String RULE_KEY = "ExampleRule1"; 12 | 13 | @Override 14 | public void execute(SensorContext sensorContext, InputFile file, RuleKey ruleKey) { 15 | NewIssue newIssue = sensorContext.newIssue(); 16 | newIssue 17 | .forRule(ruleKey) 18 | .at(newIssue.newLocation() 19 | .on(file) 20 | .at(file.selectLine(1))) 21 | .save(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/rules/FlagLine2Rule.java: -------------------------------------------------------------------------------- 1 | package org.sonarsource.plugins.example.rules; 2 | 3 | import org.sonar.api.batch.fs.InputFile; 4 | import org.sonar.api.batch.sensor.SensorContext; 5 | import org.sonar.api.batch.sensor.issue.NewIssue; 6 | import org.sonar.api.rule.RuleKey; 7 | import org.sonar.check.Rule; 8 | 9 | @Rule(key = FlagLine2Rule.RULE_KEY, name = "Example rule 2", description = "Example rule 2 description") 10 | public class FlagLine2Rule implements FlagLineRule { 11 | public static final String RULE_KEY = "ExampleRule2"; 12 | 13 | @Override 14 | public void execute(SensorContext sensorContext, InputFile file, RuleKey ruleKey) { 15 | NewIssue newIssue = sensorContext.newIssue(); 16 | newIssue 17 | .forRule(ruleKey) 18 | .at(newIssue.newLocation() 19 | .on(file) 20 | .at(file.selectLine(2))) 21 | .save(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/rules/FlagLine3Rule.java: -------------------------------------------------------------------------------- 1 | package org.sonarsource.plugins.example.rules; 2 | 3 | import org.sonar.api.batch.fs.InputFile; 4 | import org.sonar.api.batch.sensor.SensorContext; 5 | import org.sonar.api.batch.sensor.issue.NewIssue; 6 | import org.sonar.api.rule.RuleKey; 7 | import org.sonar.check.Rule; 8 | 9 | @Rule(key = FlagLine3Rule.RULE_KEY, name = "Example rule 3", description = "Example rule 3 description") 10 | public class FlagLine3Rule implements FlagLineRule { 11 | public static final String RULE_KEY = "ExampleRule3"; 12 | 13 | @Override 14 | public void execute(SensorContext sensorContext, InputFile file, RuleKey ruleKey) { 15 | NewIssue newIssue = sensorContext.newIssue(); 16 | newIssue 17 | .forRule(ruleKey) 18 | .at(newIssue.newLocation() 19 | .on(file) 20 | .at(file.selectLine(3))) 21 | .save(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/rules/FlagLineRule.java: -------------------------------------------------------------------------------- 1 | package org.sonarsource.plugins.example.rules; 2 | 3 | import org.sonar.api.batch.fs.InputFile; 4 | import org.sonar.api.batch.sensor.SensorContext; 5 | import org.sonar.api.rule.RuleKey; 6 | 7 | public interface FlagLineRule { 8 | 9 | void execute(SensorContext sensorContext, InputFile file, RuleKey ruleKey); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/rules/FlagLineSensor.java: -------------------------------------------------------------------------------- 1 | package org.sonarsource.plugins.example.rules; 2 | 3 | import org.sonar.api.batch.fs.FilePredicates; 4 | import org.sonar.api.batch.fs.InputFile; 5 | import org.sonar.api.batch.rule.CheckFactory; 6 | import org.sonar.api.batch.rule.Checks; 7 | import org.sonar.api.batch.sensor.Sensor; 8 | import org.sonar.api.batch.sensor.SensorContext; 9 | import org.sonar.api.batch.sensor.SensorDescriptor; 10 | import org.sonarsource.plugins.example.languages.FooLanguage; 11 | 12 | public class FlagLineSensor implements Sensor { 13 | 14 | private final Checks checks; 15 | 16 | public FlagLineSensor(CheckFactory checkFactory) { 17 | checks = checkFactory.create(FlagRuleDefinition.REPO_KEY); 18 | checks.addAnnotatedChecks(FlagLine1Rule.class, FlagLine2Rule.class, FlagLine3Rule.class); 19 | } 20 | 21 | @Override 22 | public void describe(SensorDescriptor descriptor) { 23 | descriptor.name(FlagLine1Rule.RULE_KEY + "sensor"); 24 | descriptor.onlyOnLanguages(FooLanguage.KEY); 25 | descriptor.createIssuesForRuleRepository(FlagRuleDefinition.REPO_KEY); 26 | } 27 | 28 | @Override 29 | public void execute(SensorContext context) { 30 | FilePredicates p = context.fileSystem().predicates(); 31 | for (InputFile inputFile : context.fileSystem().inputFiles(p.hasLanguages(FooLanguage.KEY))) { 32 | checks.all().forEach(check -> check.execute(context, inputFile, checks.ruleKey(check))); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/rules/FlagRuleDefinition.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Example Plugin for SonarQube 3 | * Copyright (C) 2009-2025 SonarSource SA 4 | * mailto:contact AT sonarsource DOT com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | package org.sonarsource.plugins.example.rules; 21 | 22 | import org.sonar.api.server.rule.RulesDefinition; 23 | import org.sonar.api.server.rule.RulesDefinitionAnnotationLoader; 24 | import org.sonarsource.plugins.example.languages.FooLanguage; 25 | 26 | public final class FlagRuleDefinition implements RulesDefinition { 27 | 28 | static final String KEY = "flagLine"; 29 | public static final String REPO_KEY = FooLanguage.KEY + "-" + KEY; 30 | private static final String REPO_NAME = FooLanguage.KEY + "- " + KEY + " repo"; 31 | 32 | @Override 33 | public void define(Context context) { 34 | NewRepository repository = context.createRepository(REPO_KEY, FooLanguage.KEY).setName(REPO_NAME); 35 | 36 | RulesDefinitionAnnotationLoader rulesDefinitionAnnotationLoader = new RulesDefinitionAnnotationLoader(); 37 | rulesDefinitionAnnotationLoader.load(repository, FlagLine1Rule.class, FlagLine2Rule.class, FlagLine3Rule.class); 38 | 39 | repository.done(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/rules/FooLintIssuesLoaderSensor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Example Plugin for SonarQube 3 | * Copyright (C) 2009-2025 SonarSource SA 4 | * mailto:contact AT sonarsource DOT com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | package org.sonarsource.plugins.example.rules; 21 | 22 | import java.io.File; 23 | import java.util.Arrays; 24 | import java.util.List; 25 | import java.util.Optional; 26 | import javax.xml.stream.XMLStreamException; 27 | import org.sonar.api.batch.fs.FileSystem; 28 | import org.sonar.api.batch.fs.InputFile; 29 | import org.sonar.api.batch.sensor.Sensor; 30 | import org.sonar.api.batch.sensor.SensorContext; 31 | import org.sonar.api.batch.sensor.SensorDescriptor; 32 | import org.sonar.api.batch.sensor.issue.NewIssue; 33 | import org.sonar.api.batch.sensor.issue.NewIssueLocation; 34 | import org.sonar.api.config.Configuration; 35 | import org.sonar.api.rule.RuleKey; 36 | import org.sonar.api.utils.log.Logger; 37 | import org.sonar.api.utils.log.Loggers; 38 | import org.sonarsource.plugins.example.languages.FooLanguage; 39 | 40 | /** 41 | * The goal of this Sensor is to load the results of an analysis performed by a fictive external tool named: FooLint 42 | * Results are provided as an xml file and are corresponding to the rules defined in 'rules.xml'. 43 | * To be very abstract, these rules are applied on source files made with the fictive language Foo. 44 | */ 45 | public class FooLintIssuesLoaderSensor implements Sensor { 46 | 47 | private static final Logger LOGGER = Loggers.get(FooLintIssuesLoaderSensor.class); 48 | 49 | protected static final String REPORT_PATH_KEY = "sonar.foolint.reportPath"; 50 | 51 | protected final Configuration config; 52 | protected final FileSystem fileSystem; 53 | protected SensorContext context; 54 | 55 | /** 56 | * Use of IoC to get Settings, FileSystem, RuleFinder and ResourcePerspectives 57 | */ 58 | public FooLintIssuesLoaderSensor(final Configuration config, final FileSystem fileSystem) { 59 | this.config = config; 60 | this.fileSystem = fileSystem; 61 | } 62 | 63 | @Override 64 | public void describe(final SensorDescriptor descriptor) { 65 | descriptor.name("FooLint Issues Loader Sensor"); 66 | descriptor.onlyOnLanguage(FooLanguage.KEY); 67 | } 68 | 69 | protected String reportPathKey() { 70 | return REPORT_PATH_KEY; 71 | } 72 | 73 | protected String getReportPath() { 74 | Optional o = config.get(reportPathKey()); 75 | if (o.isPresent()) { 76 | return o.get(); 77 | } 78 | return null; 79 | } 80 | 81 | @Override 82 | public void execute(final SensorContext context) { 83 | String reportPath = getReportPath(); 84 | if (reportPath != null) { 85 | this.context = context; 86 | File analysisResultsFile = new File(reportPath); 87 | try { 88 | parseAndSaveResults(analysisResultsFile); 89 | } catch (XMLStreamException e) { 90 | throw new IllegalStateException("Unable to parse the provided FooLint file", e); 91 | } 92 | } 93 | } 94 | 95 | protected void parseAndSaveResults(final File file) throws XMLStreamException { 96 | LOGGER.info("(mock) Parsing 'FooLint' Analysis Results"); 97 | FooLintAnalysisResultsParser parser = new FooLintAnalysisResultsParser(); 98 | List errors = parser.parse(file); 99 | for (ErrorDataFromExternalLinter error : errors) { 100 | getResourceAndSaveIssue(error); 101 | } 102 | } 103 | 104 | private void getResourceAndSaveIssue(final ErrorDataFromExternalLinter error) { 105 | LOGGER.debug(error.toString()); 106 | 107 | InputFile inputFile = fileSystem.inputFile( 108 | fileSystem.predicates().and( 109 | fileSystem.predicates().hasRelativePath(error.getFilePath()), 110 | fileSystem.predicates().hasType(InputFile.Type.MAIN))); 111 | 112 | LOGGER.debug("inputFile null ? " + (inputFile == null)); 113 | 114 | if (inputFile != null) { 115 | saveIssue(inputFile, error.getLine(), error.getType(), error.getDescription()); 116 | } else { 117 | LOGGER.error("Not able to find a InputFile with " + error.getFilePath()); 118 | } 119 | } 120 | 121 | private void saveIssue(final InputFile inputFile, int line, final String externalRuleKey, final String message) { 122 | RuleKey ruleKey = RuleKey.of(getRepositoryKeyForLanguage(inputFile.language()), externalRuleKey); 123 | 124 | NewIssue newIssue = context.newIssue() 125 | .forRule(ruleKey); 126 | 127 | NewIssueLocation primaryLocation = newIssue.newLocation() 128 | .on(inputFile) 129 | .message(message); 130 | if (line > 0) { 131 | primaryLocation.at(inputFile.selectLine(line)); 132 | } 133 | newIssue.at(primaryLocation); 134 | 135 | newIssue.save(); 136 | } 137 | 138 | private static String getRepositoryKeyForLanguage(String languageKey) { 139 | return languageKey.toLowerCase() + "-" + FlagRuleDefinition.KEY; 140 | } 141 | 142 | @Override 143 | public String toString() { 144 | return "FooLintIssuesLoaderSensor"; 145 | } 146 | 147 | private class ErrorDataFromExternalLinter { 148 | 149 | private final String externalRuleId; 150 | private final String issueMessage; 151 | private final String filePath; 152 | private final int line; 153 | 154 | public ErrorDataFromExternalLinter(final String externalRuleId, final String issueMessage, final String filePath, final int line) { 155 | this.externalRuleId = externalRuleId; 156 | this.issueMessage = issueMessage; 157 | this.filePath = filePath; 158 | this.line = line; 159 | } 160 | 161 | public String getType() { 162 | return externalRuleId; 163 | } 164 | 165 | public String getDescription() { 166 | return issueMessage; 167 | } 168 | 169 | public String getFilePath() { 170 | return filePath; 171 | } 172 | 173 | public int getLine() { 174 | return line; 175 | } 176 | 177 | @Override 178 | public String toString() { 179 | StringBuilder s = new StringBuilder(); 180 | s.append(externalRuleId); 181 | s.append("|"); 182 | s.append(issueMessage); 183 | s.append("|"); 184 | s.append(filePath); 185 | s.append("("); 186 | s.append(line); 187 | s.append(")"); 188 | return s.toString(); 189 | } 190 | } 191 | 192 | private class FooLintAnalysisResultsParser { 193 | 194 | public List parse(final File file) throws XMLStreamException { 195 | LOGGER.info("Parsing file {}", file.getAbsolutePath()); 196 | 197 | // as the goal of this example is not to demonstrate how to parse an xml file we return an hard coded list of FooError 198 | 199 | ErrorDataFromExternalLinter fooError1 = new ErrorDataFromExternalLinter("ExampleRule1", "More precise description of the error", "src/MyClass.foo", 5); 200 | ErrorDataFromExternalLinter fooError2 = new ErrorDataFromExternalLinter("ExampleRule2", "More precise description of the error", "src/MyClass.foo", 9); 201 | 202 | return Arrays.asList(fooError1, fooError2); 203 | } 204 | } 205 | 206 | } 207 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/rules/JavaRulesDefinition.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Example Plugin for SonarQube 3 | * Copyright (C) 2009-2025 SonarSource SA 4 | * mailto:contact AT sonarsource DOT com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | package org.sonarsource.plugins.example.rules; 21 | 22 | import org.sonar.api.rule.RuleKey; 23 | import org.sonar.api.rule.RuleStatus; 24 | import org.sonar.api.rule.Severity; 25 | import org.sonar.api.server.rule.RuleDescriptionSection; 26 | import org.sonar.api.server.rule.RulesDefinition; 27 | 28 | import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY; 29 | import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.INTRODUCTION_SECTION_KEY; 30 | import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY; 31 | 32 | public class JavaRulesDefinition implements RulesDefinition { 33 | 34 | public static final String REPOSITORY = "java-example"; 35 | public static final String JAVA_LANGUAGE = "java"; 36 | public static final RuleKey RULE_ON_LINE_1 = RuleKey.of(REPOSITORY, "line1"); 37 | 38 | @Override 39 | public void define(Context context) { 40 | NewRepository repository = context.createRepository(REPOSITORY, JAVA_LANGUAGE).setName("My Custom Java Analyzer"); 41 | 42 | var hibernate = new org.sonar.api.server.rule.Context("hibernate", "Hibernate"); 43 | var myBatis = new org.sonar.api.server.rule.Context("mybatis", "MyBatis"); 44 | 45 | NewRule x1Rule = repository.createRule(RULE_ON_LINE_1.rule()) 46 | .setName("Stupid rule") 47 | .setHtmlDescription("Generates an issue on every line 1 of Java files") 48 | .addDescriptionSection(descriptionSection(INTRODUCTION_SECTION_KEY, "This rule is not that stupid", null)) 49 | .addDescriptionSection(descriptionSection(ROOT_CAUSE_SECTION_KEY, "The root cause of this issue is this and that.", null)) 50 | .addDescriptionSection(descriptionSection(HOW_TO_FIX_SECTION_KEY, 51 | "To fix an issue reported by this rule when using Hibernate do this and that.", hibernate)) 52 | .addDescriptionSection(descriptionSection(HOW_TO_FIX_SECTION_KEY, 53 | "To fix an issue reported by this rule when using MyBatis do this and that.", myBatis)) 54 | // optional tags 55 | .setTags("style", "stupid") 56 | 57 | // optional status. Default value is READY. 58 | .setStatus(RuleStatus.BETA) 59 | 60 | // default severity when the rule is activated on a Quality profile. Default value is MAJOR. 61 | .setSeverity(Severity.MINOR); 62 | 63 | x1Rule.setDebtRemediationFunction(x1Rule.debtRemediationFunctions().linearWithOffset("1h", "30min")); 64 | 65 | // don't forget to call done() to finalize the definition 66 | repository.done(); 67 | } 68 | 69 | private static RuleDescriptionSection descriptionSection(String sectionKey, String htmlContent, org.sonar.api.server.rule.Context context) { 70 | return RuleDescriptionSection.builder() 71 | .sectionKey(sectionKey) 72 | .htmlContent(htmlContent) 73 | //Optional context - can be any framework or component for which you want to create detailed description 74 | .context(context) 75 | .build(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/settings/FooLanguageProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Example Plugin for SonarQube 3 | * Copyright (C) 2009-2025 SonarSource SA 4 | * mailto:contact AT sonarsource DOT com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | package org.sonarsource.plugins.example.settings; 21 | 22 | import java.util.List; 23 | import org.sonar.api.config.PropertyDefinition; 24 | import org.sonar.api.resources.Qualifiers; 25 | 26 | import static java.util.Arrays.asList; 27 | 28 | public class FooLanguageProperties { 29 | 30 | public static final String FILE_SUFFIXES_KEY = "sonar.foo.file.suffixes"; 31 | public static final String FILE_SUFFIXES_DEFAULT_VALUE = ".foo"; 32 | 33 | private FooLanguageProperties() { 34 | // only statics 35 | } 36 | 37 | public static List getProperties() { 38 | return asList(PropertyDefinition.builder(FILE_SUFFIXES_KEY) 39 | .multiValues(true) 40 | .defaultValue(FILE_SUFFIXES_DEFAULT_VALUE) 41 | .category("Foo") 42 | .name("File Suffixes") 43 | .description("List of suffixes for files to analyze.") 44 | .onQualifiers(Qualifiers.PROJECT) 45 | .build()); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/settings/HelloWorldProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Example Plugin for SonarQube 3 | * Copyright (C) 2009-2025 SonarSource SA 4 | * mailto:contact AT sonarsource DOT com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | package org.sonarsource.plugins.example.settings; 21 | 22 | import java.util.List; 23 | import org.sonar.api.config.PropertyDefinition; 24 | 25 | import static java.util.Arrays.asList; 26 | 27 | public class HelloWorldProperties { 28 | 29 | public static final String HELLO_KEY = "sonar.example.hello"; 30 | public static final String CATEGORY = "Properties Example"; 31 | 32 | private HelloWorldProperties() { 33 | // only statics 34 | } 35 | 36 | public static List getProperties() { 37 | return asList( 38 | PropertyDefinition.builder(HELLO_KEY) 39 | .name("Hello") 40 | .description("Say Hello") 41 | .defaultValue(String.valueOf(false)) 42 | .category(CATEGORY) 43 | .build()); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/settings/SayHelloFromScanner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Example Plugin for SonarQube 3 | * Copyright (C) 2009-2025 SonarSource SA 4 | * mailto:contact AT sonarsource DOT com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | package org.sonarsource.plugins.example.settings; 21 | 22 | import org.sonar.api.batch.sensor.Sensor; 23 | import org.sonar.api.batch.sensor.SensorContext; 24 | import org.sonar.api.batch.sensor.SensorDescriptor; 25 | import org.sonar.api.utils.log.Loggers; 26 | 27 | public class SayHelloFromScanner implements Sensor { 28 | 29 | @Override 30 | public void describe(SensorDescriptor descriptor) { 31 | descriptor.name(getClass().getName()); 32 | } 33 | 34 | @Override 35 | public void execute(SensorContext context) { 36 | if (context.config().getBoolean(HelloWorldProperties.HELLO_KEY).orElse(false)) { 37 | // print log only if property is set to true 38 | Loggers.get(getClass()).info("Hello World!"); 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/sonarsource/plugins/example/web/MyPluginPageDefinition.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Example Plugin for SonarQube 3 | * Copyright (C) 2009-2025 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | package org.sonarsource.plugins.example.web; 21 | 22 | import org.sonar.api.web.page.Context; 23 | import org.sonar.api.web.page.Page; 24 | import org.sonar.api.web.page.PageDefinition; 25 | 26 | import static org.sonar.api.web.page.Page.Qualifier.SUB_VIEW; 27 | import static org.sonar.api.web.page.Page.Qualifier.VIEW; 28 | import static org.sonar.api.web.page.Page.Scope.COMPONENT; 29 | 30 | public class MyPluginPageDefinition implements PageDefinition { 31 | 32 | @Override 33 | public void define(Context context) { 34 | context 35 | .addPage(Page.builder("example/global_page") 36 | .setName("Global Page using Vanilla JS") 37 | .build()) 38 | .addPage(Page.builder("example/project_page") 39 | .setName("Project Page using Backbone JS") 40 | .setScope(COMPONENT) 41 | .build()) 42 | .addPage(Page.builder("example/portfolio_page") 43 | .setName("Portfolio Page using React JS") 44 | .setScope(COMPONENT) 45 | .setComponentQualifiers(VIEW, SUB_VIEW) 46 | .build()) 47 | .addPage(Page.builder("example/admin_page") 48 | .setName("Admin Page using React JS") 49 | .setAdmin(true) 50 | .build()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/js/admin_page/components/InstanceStatisticsApp.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | import React from "react"; 20 | import { 21 | findIssuesStatistics, 22 | findProjects, 23 | findQualityProfilesStatistics, 24 | findQualityQatesStatistics 25 | } from "../../common/api"; 26 | 27 | export default class InstanceStatisticsApp extends React.PureComponent { 28 | state = { 29 | loading: true, 30 | numberOfQualityProfiles: "", 31 | numberOfQualityGates: "", 32 | numberOfIssues: "", 33 | numberOfProjects: "" 34 | }; 35 | 36 | componentDidMount() { 37 | Promise.all([ 38 | findQualityProfilesStatistics(), 39 | findQualityQatesStatistics(), 40 | findIssuesStatistics(), 41 | findProjects() 42 | ]).then(([numberOfQualityProfiles, numberOfQualityGates, numberOfIssues, numberOfProjects]) => { 43 | this.setState({ 44 | loading: false, 45 | numberOfQualityProfiles, 46 | numberOfQualityGates, 47 | numberOfIssues, 48 | numberOfProjects 49 | }); 50 | }); 51 | } 52 | 53 | render() { 54 | if (this.state.loading) { 55 | return ( 56 |
57 | Loading... 58 |
59 | ); 60 | } 61 | 62 | return ( 63 |
64 | 65 | 66 | 67 | 74 | 75 | 76 | 83 | 84 | 85 | 92 | 93 | 94 | 101 | 102 | 103 |
68 | {window.tp( 69 | "example.admin_page.we_have_x_y", 70 | this.state.numberOfQualityProfiles, 71 | window.t("example.admin_page.quality_profiles") 72 | )} 73 |
77 | {window.tp( 78 | "example.admin_page.we_have_x_y", 79 | this.state.numberOfQualityGates, 80 | window.t("example.admin_page.quality_gates") 81 | )} 82 |
86 | {window.tp( 87 | "example.admin_page.we_have_x_y", 88 | this.state.numberOfIssues, 89 | window.t("example.admin_page.issues") 90 | )} 91 |
95 | {window.tp( 96 | "example.admin_page.we_have_x_y", 97 | this.state.numberOfProjects, 98 | window.t("example.admin_page.projects") 99 | )} 100 |
104 |
105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/js/admin_page/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | import React from 'react'; 20 | import '../style.css'; 21 | import InstanceStatisticsApp from './components/InstanceStatisticsApp'; 22 | 23 | // This creates a global administration page, which generates a report of the 24 | // overall number of Quality Profiles, Quality Gates, total number of issues, 25 | // and total number of projects. 26 | // 27 | // You can access it at /admin/extension/example/admin_page 28 | window.registerExtension('example/admin_page', () => { 29 | return 30 | }); 31 | -------------------------------------------------------------------------------- /src/main/js/common/api.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | // SonarRequest (referenced as sonar-request here, see the Webpack config) 20 | // Exposes helpers for managing API requests. 21 | import { getJSON } from "sonar-request"; 22 | 23 | export function findQualityProfilesStatistics(project) { 24 | return getJSON("/api/qualityprofiles/search").then(function(response) { 25 | return response.profiles.length; 26 | }); 27 | } 28 | 29 | export function findQualityQatesStatistics(project) { 30 | return getJSON("/api/qualitygates/list").then(function(response) { 31 | return response.qualitygates.length; 32 | }); 33 | } 34 | 35 | export function findIssuesStatistics(project) { 36 | return getJSON("/api/issues/search").then(function(response) { 37 | return response.total; 38 | }); 39 | } 40 | 41 | export function findProjects(project) { 42 | return getJSON("/api/projects/search").then(function(response) { 43 | return response.components.length; 44 | }); 45 | } 46 | 47 | export function findVersionsAndMeasures(project) { 48 | return getJSON("/api/project_analyses/search", { 49 | project: project.key, 50 | p: 1, 51 | ps: 500 52 | }).then(function(responseAnalyses) { 53 | const numberOfAnalyses = responseAnalyses.analyses.length; 54 | if (numberOfAnalyses > 0) { 55 | return getJSON("/api/measures/search_history", { 56 | component: project.key, 57 | metrics: "alert_status,bugs,vulnerabilities,sqale_index,reliability_rating,security_rating,sqale_rating", 58 | ps: 50 59 | }).then(function(responseMetrics) { 60 | var data = []; 61 | var numberOfVersions = 0; 62 | for (let i = 0; i < numberOfAnalyses; i++) { 63 | let result = { 64 | alert_status: "", 65 | bugs: "0", 66 | vulnerabilities: "0", 67 | sqale_index: "0", 68 | reliability_rating: "", 69 | security_rating: "", 70 | sqale_rating: "" 71 | }; 72 | const numberOfMeasuresRetrieved = 7; 73 | 74 | for (let k = 0; k < numberOfMeasuresRetrieved; k++) { 75 | for (let d = 0; d < responseMetrics.measures[k].history.length; d++) { 76 | if ( 77 | responseMetrics.measures[k].history[d].date === responseAnalyses.analyses[i].date 78 | ) { 79 | if (responseMetrics.measures[k].metric === "bugs") { 80 | result.bugs = responseMetrics.measures[k].history[d].value; 81 | } else if (responseMetrics.measures[k].metric === "vulnerabilities") { 82 | result.vulnerabilities = responseMetrics.measures[k].history[d].value; 83 | } else if (responseMetrics.measures[k].metric === "sqale_index") { 84 | result.sqale_index = responseMetrics.measures[k].history[d].value; 85 | } else if (responseMetrics.measures[k].metric === "alert_status") { 86 | result.alert_status = responseMetrics.measures[k].history[d].value; 87 | } else if (responseMetrics.measures[k].metric === "reliability_rating") { 88 | result.reliability_rating = responseMetrics.measures[k].history[d].value; 89 | } else if (responseMetrics.measures[k].metric === "security_rating") { 90 | result.security_rating = responseMetrics.measures[k].history[d].value; 91 | } else if (responseMetrics.measures[k].metric === "sqale_rating") { 92 | result.sqale_rating = responseMetrics.measures[k].history[d].value; 93 | } 94 | } 95 | } 96 | } 97 | 98 | data[numberOfVersions] = result; 99 | numberOfVersions++; 100 | } 101 | return data; 102 | }); 103 | } 104 | }); 105 | } 106 | -------------------------------------------------------------------------------- /src/main/js/global_page/app.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | .example-global_page { 20 | text-align: center; 21 | max-width: 400px; 22 | padding: 20px; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/js/global_page/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | let el, stylesTag; 20 | 21 | function init() { 22 | el.innerHTML = ` 23 |
24 | 25 |
26 | `; 27 | document 28 | .getElementById("example-global_page--button") 29 | .addEventListener("click", handleButtonClick); 30 | } 31 | 32 | function handleButtonClick() { 33 | alert("Told you so"); 34 | } 35 | 36 | export function start(element) { 37 | el = element; 38 | init(); 39 | } 40 | 41 | export function stop() { 42 | // Remove any event listeners we still have. 43 | document 44 | .getElementById("example-global_page--button") 45 | .removeEventListener("click", handleButtonClick); 46 | 47 | // The node will get removed completely by SonarQube anyway, but we can still 48 | // clean it up. 49 | el.innerHTML = ""; 50 | } 51 | -------------------------------------------------------------------------------- /src/main/js/global_page/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | import * as app from "./app"; 20 | import "./app.css"; 21 | 22 | // This creates a page for portfolios, which generates a report for all the 23 | // projects inside the portfolio. 24 | // 25 | // You can access it at /extension/example/global_page 26 | window.registerExtension('example/global_page', function (options) { 27 | // options.el contains the DOM node we can use for our app. Call the start 28 | // method to initialize the application, and pass it this DOM node. 29 | app.start(options.el); 30 | 31 | // Return the shutdown function. 32 | return function () { 33 | // When the user leaves our page, we have the opportunity to cleanly 34 | // shutdown out application. Do whatever is necessary (removing state, event 35 | // listeners, etc) in this function. 36 | app.stop(); 37 | }; 38 | }); 39 | -------------------------------------------------------------------------------- /src/main/js/portfolio_page/components/MeasuresHistory.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | import React from "react"; 20 | 21 | export default function MeasuresHistory(props) { 22 | return ( 23 | 24 | 25 |
26 | 27 | {props.measure.alert_status || "OK"} 28 | 29 |
30 | 31 | 32 |
{props.measure.bugs}
33 | 34 | 35 |
36 | 37 | {formatRating(props.measure.reliability_rating || 1)} 38 | 39 |
40 | 41 | 42 |
43 | {props.measure.vulnerabilities} 44 |
45 | 46 | 47 |
48 | 49 | {formatRating(props.measure.security_rating || 1)} 50 | 51 |
52 | 53 | 54 |
{props.measure.sqale_index}
55 | 56 | 57 |
58 | 59 | {formatRating(props.measure.sqale_rating || 1)} 60 | 61 |
62 | 63 | 64 | ); 65 | } 66 | 67 | function formatRating(rating: number) { 68 | return String.fromCharCode('A'.charCodeAt(0) - 1 + +rating); 69 | } 70 | -------------------------------------------------------------------------------- /src/main/js/portfolio_page/components/VersionsMeasuresHistoryApp.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | import React from "react"; 20 | import { findVersionsAndMeasures } from "../../common/api"; 21 | import MeasuresHistory from "./MeasuresHistory"; 22 | 23 | export default class VersionsMeasuresHistoryApp extends React.PureComponent { 24 | state = { 25 | loading: true, 26 | data: [] 27 | }; 28 | 29 | componentDidMount() { 30 | findVersionsAndMeasures(this.props.project).then(data => { 31 | this.setState({ 32 | loading: false, 33 | data 34 | }); 35 | }); 36 | } 37 | 38 | render() { 39 | if (this.state.loading) { 40 | return ( 41 |
42 | Loading... 43 |
44 | ); 45 | } 46 | 47 | return ( 48 |
49 | 50 | 51 | 52 | 55 | 58 | 61 | 62 | 65 | 68 | 69 | 72 | 75 | 76 | 77 | 78 | {this.state.data !== undefined && 79 | this.state.data.map((value, idx) => )} 80 | 81 |
53 | {window.t("example.portfolio_page.qg")} 54 | 56 | {window.t("example.portfolio_page.bugs")} 57 | 59 | {window.t("example.portfolio_page.reliability_rating")} 60 | 63 | {window.t("example.portfolio_page.vulnerabilities")} 64 | 66 | {window.t("example.portfolio_page.security_rating")} 67 | 70 | {window.t("example.portfolio_page.code_smells_effort")} 71 | 73 | {window.t("example.portfolio_page.maintainability_rating")} 74 |
82 |
83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/js/portfolio_page/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | import React from "react"; 20 | import "../style.css"; 21 | import VersionsMeasuresHistoryApp from "./components/VersionsMeasuresHistoryApp"; 22 | 23 | // This creates a page for portfolios, which generates a report for all the 24 | // projects inside the portfolio. 25 | // 26 | // You can access it at /project/extension/example/portfolio_page?id={PORTFOLIO_ID}&qualifier=VW 27 | window.registerExtension("example/portfolio_page", options => { 28 | return ; 29 | }); 30 | -------------------------------------------------------------------------------- /src/main/js/project_page/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | // Necessary for setting up, because of Webpack. 20 | import Backbone from "backbone"; 21 | import $ from 'jquery'; 22 | import _ from "underscore"; 23 | window.$ = $; 24 | window._ = _; 25 | window.Backbone = Backbone; 26 | window.app = window.app || {}; 27 | 28 | // Load the actual app logic. 29 | require("./view/AppView"); 30 | 31 | // This creates a page for any component (project, portfolio, etc). 32 | // 33 | // You can access it at /project/extension/example/project_page?id={COMPONENT_ID} 34 | window.registerExtension('example/project_page', function (options) { 35 | // options.el contains the DOM node we can use for our app. Prepare our node 36 | // so our Backbone View can correctly target it. 37 | options.el.innerHTML = `
Loading...
`; 38 | 39 | // Start the view. 40 | var view = new app.AppView({ branchLike: options.branchLike }); 41 | view.render(); 42 | 43 | // Return the shutdown function. 44 | return function () { 45 | // When the user leaves our page, we have the opportunity to cleanly 46 | // shutdown out application. Let's clean up the view by removing it 47 | // completely. 48 | view.remove(); 49 | }; 50 | }); 51 | -------------------------------------------------------------------------------- /src/main/js/project_page/view/AppView.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | (function ({ app, Backbone, $, _ }) { 20 | 21 | app.AppView = Backbone.View.extend({ 22 | 23 | initialize: function(options) { 24 | _.extend(this, _.pick(options, "branchLike")); 25 | }, 26 | 27 | tpl: _.template(` 28 |
29 | <% if (branchLike !== undefined) { %> 30 |
31 | <% if (branchLike.name !== undefined) { %> 32 | You are currently on branch <%= branchLike.name %> 33 | <% } else if (branchLike.key !== undefined) { %> 34 | You are currently on PR #<%= branchLike.key %> 35 | <% } %> 36 |
37 | <% } %> 38 | 39 |
`), 40 | 41 | el: '#example-project_page', 42 | 43 | events: { 44 | 'click': 'handleClick' 45 | }, 46 | 47 | handleClick: function() { 48 | alert("Gotcha"); 49 | }, 50 | 51 | render: function() { 52 | this.$el.html(this.tpl({ branchLike: this.branchLike, label: "Click on this" })); 53 | return this; 54 | } 55 | 56 | }); 57 | 58 | })(window); 59 | -------------------------------------------------------------------------------- /src/main/js/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 SonarSource SA 3 | * mailto:info AT sonarsource DOT com 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | .custom-abc { 20 | background-color: pink; 21 | } 22 | 23 | .sanity-check { 24 | width: 400px 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/org/sonar/l10n/example.properties: -------------------------------------------------------------------------------- 1 | # 2 | # This bundle must be stored in the package org.sonar.l10n and its name must be _.properties 3 | # 4 | example.help=Help 5 | example.plugin_property=My Plugin Property 6 | example.widget_properties=Widget properties 7 | example.portfolio_page.bugs=Bugs 8 | example.portfolio_page.qg=Quality Gate 9 | example.portfolio_page.reliability_rating=Reliability Rating 10 | example.portfolio_page.vulnerabilities=Vulnerabilities 11 | example.portfolio_page.security_rating=Security Rating 12 | example.portfolio_page.code_smells_effort=Code Smells remediation effort 13 | example.portfolio_page.maintainability_rating=Maintainability Rating 14 | example.admin_page.we_have_x_y=We have {0} {1} 15 | example.admin_page.quality_profiles=Quality Profiles 16 | example.admin_page.quality_gates=Quality Gates 17 | example.admin_page.issues=Issues 18 | example.admin_page.projects=Projects 19 | -------------------------------------------------------------------------------- /src/main/resources/org/sonar/l10n/example_fr.properties: -------------------------------------------------------------------------------- 1 | example.help=Aide 2 | example.plugin_property=Ma propriété 3 | example.widget_properties=Propriétés de ce widget 4 | --------------------------------------------------------------------------------