Setting up SonarQube with Angular
By: Anirudh Ramesh
Tags: Static Code Analysis, Ci Cd Pipeline, Angular, Sonarqube, Code Review

Recently, I wanted to perform static code analysis on an Angular project. In my team, we want to analyze code quality, test coverage, code-smells, etc. Sonarqube was an easy choice for us since it provides all of the above with a nice UI.

An example Java project which is integrated with SonarQube

You can also drill down and check the coverage vs tech-debt graph for each file in your project:

Ideally, you would want all your circles to be in the bottom-left

I had a community edition of Sonarqube running on a dedicated instance. However, integrating it with Angular was not really straightforward. I also found that a few of the tutorials on the internet were either obsolete or not working (due to various problems).

Hence I decided to document the steps that have worked for me.

I am currently working on Angular 11.2.2 (Though most of this should work with previous angular versions also).

Step 1: Figure out if all tests are running properly

To run Angular tests and generate coverage information, you can use the command:

ng test --watch=false --code-coverage

Additionally, if you want to speed up the process a bit and you don’t plan to debug the tests, you can use source-map=false parameter.

Note: — watch=false parameter used here will kill the Karma server once the tests are done running.

If you get any errors in this test run, you will not see the coverage info (in the terminal). Additionally, coverage info will also not be present in the coverage folder inside src/ (this step will not happen if your tests are failing or throwing warnings that may break ng test ).

Pro-tip: you can choose to always generate coverage with ng test command by modifying your angular.json file, and modifying the options object in the JSON document, like this:

"options": {

"main": "src/test.ts",

"polyfills": "src/polyfills.ts",

"codeCoverage": true, //This option

"tsConfig": "src/tsconfig.spec.json",

"karmaConfig": "src/karma.conf.js",

"styles": ["src/styles.scss"],

....

.... // Rest of the options document

}

Please fix all tests that are breaking. Additionally, you can choose to xdescribe or xit those tests.

Check for step 1: Your src/ folder should have a coverage folder inside and it should contain a lcov.info file (at some level* — not necessarily at the coverage/ folder level — depending on your karma config 🙂).

Step 2: Add SonarQube plugin to Karma.Conf

SonarQube will use the lcov.info file to get information about the typescript/javascript code quality. However, to map a TS/JS file with its Unit-Test file (Spec file) and make it understand coverage details, you need to use SonarQube plugin with Karma.

Install the plugin using the command

And head over to karma.conf.js and make the following changes:

import the plugin

require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), require('@angular-devkit/build-angular/plugins/karma'), require('karma-sonarqube-reporter'), // This one

2. Add sonarqube to the array of reporters:

reporters: [‘progress’, ‘kjhtml’, ‘sonarqube’]

3. Configure Sonarqube in Karma.conf like this:

sonarqubeReporter: {

basePath: 'src/app', // test files folder

filePattern: '**/*spec.ts', // test files glob pattern

encoding: 'utf-8', // test files encoding

outputFolder: 'reports', // report destination

legacyMode: false, // report for Sonarqube < 6.2 (disabled)

reportName: function (metadata) {

// report name callback, but accepts also a

// string (file name) to generate a single file

/**

* Report metadata array:

* - metadata[0] = browser name

* - metadata[1] = browser version

* - metadata[2] = plataform name

* - metadata[3] = plataform version

*/

return 'sonarqube_report.xml';

},

},

Check for Step 2: If you run ng test --code-coverage --watch=false now, there is a folder reports/ generated at the project root with sonarqube_report.xml file inside the folder.

This part I figured out, thanks to: https://morioh.com/p/6971eef66101

Step 3: Update your .gitignore

Add these to your .gitignore since you never want them on version-tracking.

.scannerwork/*

reports/*

Step 4: Install “sonar-scanner”

npm install -D sonarqube-scanner

This is the wrapper over SonarQube Scanner .jar. However, you will not need Java to run this (therefore, your docker images are also lighter).

Sonar-Scanner works exactly to the JAR also. The NPM package also has very good docs (https://www.npmjs.com/package/sonarqube-scanner). You can refer to either docs.

Step 5: Create a sonar-project.properties file in your project root

sonar.host.url=SONAR-QUBE-URL sonar.projectKey=Project_Key # Must be unique (Primary key) sonar.projectName=PROJECT_NAME # name that will be displayed on the dashboard sonar.projectVersion=1.0 sonar.sourceEncoding=UTF-8 sonar.sources=src sonar.exclusions=**/node_modules/** sonar.tests=src # Where to pickup test files from sonar.test.inclusions=**/*.spec.ts # only collect these for test-coverage sonar.javascript.lcov.reportPaths=src/coverage/PROJECT_NAME/lcov.info sonar.testExecutionReportPaths=reports/sonarqube_report.xml

Additionally, add the sonar.login and sonar.password if you have password-protected your SonarQube instance (Only when running locally!).

Note: this is for testing purposes only. When running on production, please generate a token . This way, when running the code, you do not need a sonar.password property, and you can pass in the token to sonar.login option.

Step 6: Run the SonarQube Scanner:

Run

npm run sonar-scanner

at the root of your project (or simply sonar-scanner if it is in your path ). This will fire up the process and you should see a new entry for your project in your sonarqube instance. You can also choose run a local version of SonarQube on Docker.

Step 6a (Optional): Debugging if sonar-scanner breaks midway.

If Sonar-Scanner breaks mid-way due to memory issues, the process usually shows a detailed stack trace and the options you need to set.

Usually, it is:

sonar.javascript.node.maxspace=4096 (Or Whatever value you choose)

However, sometimes it can also be vm-options . The error message is usually very clear. Follow that and add them to your .properties file.

If it fails even after this, try increasing the value a bit more.

On my MacBook Pro, Killing docker , closing down a few tabs in the browser and doing a watch over the process really helped me figure out what was going on (99% of the cases, it is memory issues — on mine, the memory for the process kept increasing but my laptop memory was full).

Step 7: Putting it all together

Update your package.json with two new tasks:

{

....

"test:headless": "ng test --watch=false --code-coverage --source-map=false",

"sonar-scanner": "sonar-scanner"

}

2. If running sonar-scanner is a separate step in your CI pipeline (after ng-test), do not forget to mark required artifacts for the next step 🚨. For my Gitlab pipelines, I have done it like so:

runUnitTestsMaster:

stage: test

image: ${UI_TEST_IMAGE}

before_script:

- npm config set cache /tmp/gitlab/cache/npm --global

script:

- ng run

artifacts:

paths:

- src/coverage

- reports

expire_in: 1 day

only:

- master

- RELEASE

- HOTFIX

3. You can run sonarqube using the command:

npm run sonar-scanner -- -Dsonar.login=${SONARQUBE_REPORTER_TOKEN}

The token you use here is generated from SonarQube administration (It is not a User-name password pair but a single token). You can store it in your CI/CD secrets or choose to fetch it from any secret manager this way.

Additionally, you can choose to fail the pipeline if the code does not pass your quality standards using the parameter -Dsonar.qualitygate.wait=true .

Step 8 (Optional): Use SonarQube as build-breaker:

If you are on SonarQube 8.1 or later versions, SonarQube provides a Quality-Gate by default — https://docs.sonarqube.org/latest/user-guide/quality-gates/. You can also define your own set of rules to check if your project is “release” ready.