There are a ton of posts on StackOverflow and Medium and the rest of the internet on setting up SonarQube, but I couldn’t find a definitive guide on configuring it with a React web application (using react-scripts/create react app) written in TypeScript. Turns out that it’s not that hard once you know all the pieces that need to be pulled together.

This article was written in February, 2020. These are the versions of the different components I’m using. Your mileage may vary.

  • Yarn 1.2.1
  • Node 12.4.1
  • React 16.12.0
  • Sonar 7.9.2
  • TypeScript 3.7.5
  • Docker Desktop (Windows) 2.2.0.0
  • Docker 19.03.5
  • react-scripts 3.3.0
  • sonarqube-scanner 2.5.0
  • jest-sonar-reporter 2.0.0

First of all, I’m going to assume nothing is ejected from the CRA configuration and that you have a working application written in TypeScript. For example if you cannot successfully run npm test or yarn test then this guide will not work. I’m also assuming you’re comfortable with Docker.

SonarQube

Get SonarQube up. I used Docker for this because it was the quickest way.

First, set your memory allocation for Docker to at least 4GB for the Sonar container to be able to run correctly. ElasticSearch needs this, I guess.

Next, I used this docker-compose file from https://github.com/SonarSource/docker-sonarqube/blob/master/example-compose-files/sq-with-postgres/docker-compose.yml. Just docker-compose up. This will start a SonarQube instance using Postgres on http://localhost:9000

Open that URL in a browser, log in with admin/admin, and make sure everything is looking good. There should be a wizard that guides you through creating a security token for your user. If not, you can always do so in Administration > Security and creating a Sonar user just for project analysis (this is probably a good idea anyway).

Analyzing your React application with Sonar

Tools

You’ll need a couple tools to simplify this process. Install sonarqube-scanner and jest-sonar-reporter into your React project using either yarn or npm. Save it as a development dependency.

yarn add -D sonarqube-scanner
yarn add -D jest-sonar-reporter

Sonar Configuration

I was expecting to do a bunch of configuration within Sonar itself, but it can all be contained within your React project. In the root of your project (at the same level as your package.json) create a file named sonar-project.js with the following contents. I’ll go over the details next.

const sonarqubeScanner = require("sonarqube-scanner");
sonarqubeScanner(
  {
    serverUrl: "http://localhost:9000",
    token: "YOUR-TOKEN-HERE",
    options: {
      "sonar.sources": "./src",
      "sonar.exclusions": "**/__tests__/**",
      "sonar.tests": "./src/__tests__",
      "sonar.test.inclusions": "./src/__tests__/**/*.test.tsx,./src/__tests__/**/*.test.ts",
      "sonar.typescript.lcov.reportPaths": "coverage/lcov.info",
      "sonar.testExecutionReportPaths": "reports/test-report.xml",
    },
  },
  () => {},
);

Each of these settings is a standard Sonar configuration property, but they weren’t immediately clear to me.

  • serverUrl is the URL to your SonarQube instance
  • token is the security token assigned to your Sonar user
  • sonar.sources is the base directory for all of your code. This is where your React application lives (in my case the *.tsx files). By default, CRA puts __tests__ within the src directory. We’ll deal with that next.
  • sonar.exclusions is everything you do not want Sonar to analyze. The most important one for me is that we don’t want to be analysis on our tests. In fact, if there is overlap between sonar.sources and sonar.tests then Sonar will throw an indexing error. So I exclude anything in any __tests__ folder.
  • sonar.tests is the location of all of your tests. By default, CRA puts this in /src/__tests__
  • sonar.test.inclusions is a comma separated list of all files that should be treated as test files. I have a mix of .tsx and .ts tests, but they all follow the standard jest pattern of testname.test.ts*
  • sonar.typescript.lcov.reportPaths is the path to the test coverage output file from jest. By default this will be coverage/lcov.info
  • sonar.testExecutionReportPaths is the path to the jest-sonar-reporter output file. We’ll configure this next.

Test Coverage Configuration

The first thing to note is that we use the sonar.typescript.lcov.reportPaths property in our sonar-project.js configuration, not the javascript property.

By default jest-sonar-reporter outputs a file called test-report.xml to the root directory. I don’t like littering that directory with unrelated files, so I added this to the end of my package.json file in order to put the report in a reports directory.

"jestSonar": {
  "reportPath": "reports",
  "reportFile": "test-report.xml",
  "indent": 4
}

The last thing you need to do is tell react-scripts to use this test report generator rather than the default. I assume you have something like this in your package.json scripts definition.

"test": "react-scripts test --silent --env=jsdom"

We need to change that so we process the results in a Sonar-friendly format

"test": "react-scripts test --silent --env=jsdom --coverage --testResultsProcessor jest-sonar-reporter"

To be honest, I do one more thing so that we’re not watching tests, so my test script is, which tells react-scripts that we’re running in a continuous integration mode and should not watch.

"test": "cross-env CI=true react-scripts test --silent --env=jsdom --coverage --testResultsProcessor jest-sonar-reporter",

Running Sonar Analysis

We’ll add another script to our package.json file to initiate the Sonar analysis.

"sonar": "node sonar-project.js"

At this point we have all of our configuration done and just need to run everything.

First run your tests

yarn test

This should run all of your tests with coverage. You’ll have files in /coverage and /reports.

Next, run your sonar analysis

yarn sonar

This will take a couple minutes, depending on the size of your project. But when it’s complete, refresh your Sonar projects and you should see your project show up. By default, it will use the name defined in your package.json file.

Continuous Integration

If you wanted to (and why wouldn’t you) integrate this into your CI environment so every push triggers a Sonar analysis, you can just add a build step that invokes yarn sonar after your test stage.

Other Projects

I’m using this base set of instructions for all of my TypeScript based projects. The only thing that really changes is the location of source, tests, and the inclusion/exclusion list. For example, in my api, where I have __tests_ at the same level as src (instead of nested within), my sonar-project.js looks like this.

const sonarqubeScanner = require("sonarqube-scanner");

sonarqubeScanner(
  {
    serverUrl: "http://localhost:9000",
    token: "MY-TOKEN",
    options: {
      "sonar.sources": "./src",
      "sonar.tests": "./__tests__",
      "sonar.test.inclusions": "./__tests__/**/*.test.ts",
      "sonar.typescript.lcov.reportPaths": "coverage/lcov.info",
      "sonar.testExecutionReportPaths": "reports/test-report.xml",
    },
  },
  () => {},
);

Some Cleanup

This whole thing will create some cruft in your project. You want to ignore /.scannerwork, /coverage, /reports from Git.