Sonarqube & JavaScript / Web: un nouvel espoir ?

SonarQube et le Web: un passif lourd...

Il est vrai qu'il est frustant de faire des projets en JavaScript / NodeJs, ou en Web, et que nous ayons à passer dans SonarQube. Alors que de principe, c'est important d'utiliser un outil de ce genre !

En effet, SonarQube est un outil qui permet de faire un suivi en continue de la qualité du code. Il permet ainsi de remonter (et de versionner) les "code smells". Ces derniers sont des erreurs d'écritures de code (plus ou moins critiques) et qu'il vaut mieux de corriger.

Nous pouvons voir également un code coverage associé, ainsi que le taux de duplication de code, des statistiques sur les fichiers, etc ...

Le hic survient quand on voit pour quelle technologie était faite à la base SonarQube: Java. Et le soucis est bien là.

En effet, il faut savoir que pour générer un code smell, il faut créer des plugins Sonar avec des règles non dynamiques (dans le sens où nous ne pouvons pas à notre guise ajouter de nouvelles règles à chaud). Et mine de rien, l'écriture d'un plugin SonarQube n'est pas chose aisée.

Du coup, quand on fait des application JavaScript, le plugin SonarJs était utilisé. Et c'est le drame.

Car ce dernier possède tout un tas de règles très discutable, voir totalement fausse (et avec un niveau de criticité aberrante). 

Exemple, je mets un "use strict" dans mon fichier JavaScript, je vais avoir une erreur au niveau "CRITICAL" (le plus haut), car soit disant non compatible avec tous les navigateurs. Et dans les faits, c'est vrai ... en 2005. En 2018, cela fait depuis bien longtemps que cette règle n'existe plus.

De même, oublié un ";" à la fin de la ligne ne devrait pas être considéré comme une erreur critique (à la rigueur un warning) surtout quand on sait que cela ne va pas nous protéger des erreurs. Et à l'heure d'aujourd'hui, des groupes comme Google, Airbnb, ... mettent dans leur "coding style" de ne plus le déclarer. C'est dire.

Le pire étant que ce genre d'erreurs, SonarQube le voit. En revanche voir que nous appelons une méthode qui n'existe pas, ou pas avec les bons paramètres, il n'y arrive pas.

Ainsi, nous voulions avoir la possibilité de pouvoir modifier, ajouter, adapter ces règles, et les équipes de SonarQube nous répondaient en somme qu'ils avaient beaucoup réfléchis aux règles, et que du coup, c'est celle-là qu'il fallait utiliser. Et c'était une réponse en 2013 !

Alors qu'on a les outils...

Et le plus rageant est que nous avons maintenant les outils pour définir ces règles. Au début, nous avions JsLint, qui avait peu de règles, mais c'était déjà ça. Ensuite on a eu JsHint, qui avait plus de règles et légèrement customisable. Ce dernier en plus arrivé à voir les erreurs de programmations plus poussées, dans le sens une méthode qui n'existe pas.

Mais depuis 2013, nous avons Eslint. Créé par Nicholas Zakas, un ancien tech Lead de chez Yahoo, nous avons là un outils très puissant, orienté plugin (donc extensible, et la création de plugin est plutôt simple) qui permet de gérer un grand nombre de rapport et de détecter / analyser très finement le code JavaScript grâce notamment à espree qui permet d'analyser la syntaxe.

Du coup, un très grand nombre de plugins est apparu, notamment, celui qui permet de lister les règles usuelles de coding en JavaScript (avec de très nombreuses règles), ou encore un plugin sur les coding conventions en angularJs, VueJS, etc...

Même un plugin qui permet de s'assurer que nous n'utilisons pas des features navigateurs trop récentes.

Cet outils s'intégre bien dans nos IDE (Visual Studio, Intellijea), dans Jenkins, etc ... Mais pas dans SonarQube.

Ou tout du moins depuis peu.

Et SonarQube 7.2 apparût...

Il semblerait que le message soit passé côté SonarQube car désormais, il est possible de pouvoir dans le cas du JavaScript / Web utilisé les rapports de:
  • Eslint
  • Stylelint (le stricte équivalement à Eslint, mais pour le CSS)

Ainsi, imaginons que nous avons un projet en AngularJs, 

Nous allons écrire le petit bout de NodeJs suivant, afin de collecter automatiquement certaines informations sur le projet et ainsi les fournir à SonarQube:


'use strict';

const { promisify } = require('util');
const sonarqubeScanner = require('sonarqube-scanner');

const packageJson = require('../package');
const sonarqubeScannerPromisify = promisify(sonarqubeScanner);

if (process.env.SONAR_TOKEN) {
  const sonarqubeOptions = {
    serverUrl : 'http://localhost:9000',
    token : process.env.SONAR_TOKEN,
    options : {
      'sonar.host.url': 'http://localhost:9000',
      'sonar.host.login': process.env.SONAR_TOKEN,
      'sonar.verbose': 'true',

      'sonar.projectKey': packageJson.name,
      'sonar.projectName': packageJson.name,
      'sonar.projectVersion': packageJson.version,
      'sonar.projectDescription': packageJson.description,
      'sonar.scm.provider': 'git',

      'sonar.sources': './app',
      'sonar.exclusions': 'node_modules/**,bower_components/**,jspm_packages/**,typings/**,lib-cov/**,scripts/**,karma.conf.js,Gruntfile.js,gulpfile.js',
      'sonar.sourceEncoding': 'UTF-8',

      'sonar.javascript.file.suffixes': '.js,.jsx,.vue',
      'sonar.javascript.jQueryObjectAliases': '$,jQuery',
      'sonar.javascript.environments': 'browser',
      'sonar.javascript.globals': 'angular',

      // Test & code coverage
      'sonar.coverage.exclusions': 'node_modules/**,bower_components/**,jspm_packages/**,typings/**,lib-cov/**,scripts/**,karma.conf.js,Gruntfile.js,gulpfile.js',
      'sonar.test.exclusions': 'node_modules/**,bower_components/**,jspm_packages/**,typings/**,lib-cov/**,scripts/**,karma.conf.js,Gruntfile.js,gulpfile.js',
      'sonar.test.inclusions': './test',
      'sonar.javascript.lcov.reportPaths': './target/reports/coverage/report-lcov/lcov.info',
      'sonar.javascript.jstestdriver.reportsPath':' ./target/reports/junit',

      // Code duplication
      'sonar.cp.exclusions': 'node_modules/**,bower_components/**,jspm_packages/**,typings/**,lib-cov/**,scripts/**,karma.conf.js,Gruntfile.js,gulpfile.js',
      'sonar.cpd.javascript.minimumtokens': '100',
      'sonar.cpd.javascript.minimumLines': '10'
    }
  };

  if (packageJson.homepage) {
    sonarqubeOptions.options['sonar.links.homepage'] = packageJson.homepage;
  }

  if (packageJson.bugs) {
    sonarqubeOptions.options['sonar.links.issue'] = packageJson.bugs.url;
  }
  if (packageJson.repository) {
    sonarqubeOptions.options['sonar.links.scm'] = packageJson.repository.url;
  }

  console.info('Contact sonar for a new analysis');

  sonarqubeScannerPromisify(sonarqubeOptions)
    .then(() => {
      console.info('Sonar analysis done');
    })
    .catch((err) => {
      console.error('Sonar analysis failed');
      console.error(err);
    });

} else {
  console.error('SONAR_TOKEN was not provided and so we cannot communicate with the sonarqube instance');
}


On lance le script (ici, on a associé le npm run-script "metrics:sonar", et on obtient cela:


On voit que Sonar a bien pris en compte l'analyse du code (car on voit le nombre de lignes du projet) et qu'on est bien sur le profil Sonar JavaScript ... avec aucune erreur. Ce qui peur paraître curieux de prime abord.

Lançons une couverture de test avec Karma, et relançons l'analyse Sonar (dans le script NodeJS, on indique déjà où trouver le rapport lcov). Ce qui donne ceci:


On voit bien la couverture de code.


Activons la prise en compte du rapport Eslint. Pour cela, rajoutons ceci dans le script NodeJS:

// Metrics
'sonar.eslint.reportPaths': './target/reports/eslint/eslint-reporter-javascript.json',


Ensuite configurons de la sorte la génération du rapport JSON par Eslint via npm run-script:

"metrics:js": "(node node_modules/eslint/bin/eslint.js --config=.eslintrc --output-file ./target/reports/eslint/eslint-reporter-javascript.json --format json ./app || exit 0)",


Lançons l'analyse Eslint, et ensuite l'analyse Sonar, on obtient cela:



 Nous voyons bien que les metrics Eslint sont pris en compte.

Nous pouvons alors aller plus loin en intégrer des plugins comme eslint-plugin-angular, mais il faut faire attention.

En effet, comme il est précisé dans la documentation Sonarqube, certains plugins Eslint (les 4 plus populaires, notamment eslint-plugin-angular) sont pris en compte "directement", dans le sens où le binding de certains erreurs / warnings de ces plugins sont liés aux niveaux de criticités de SonarQube.

En revanche toutes les métriques des autres plugins seront considérés en tant que "code smell" très basique (même les erreurs).

Commentaires

Posts les plus consultés de ce blog

ISO: liens & outils utiles

NodeJs et SSL: une petite analyse

Créer sa commande slack en quelques minutes