├── .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 [](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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------