Formater son code avec Prettier-java

Vous êtes fraîchement arrivé sur un nouveau projet, bien motivé, vous décidez de vous promener dans le code source de l’application. Cependant, vous commencez à sourciller, du code mal indenté, des enchaînements d’appel de méthode sur une ligne à n’en pas finir, trop de retours à la ligne et bien sûr toujours différents d’un fichier à l’autre ! Pas facile de lire le code, mais ne vous inquiétez pas, Prettier-Java arrive à la rescousse !

Le but de cet article est de présenter ce formateur de code d’un point de vue technique et aussi son utilisation.

Prettier-java

Prettier-Java est un formateur de code Java open source. Il prend en entrée du code et le transforme pour le rendre subjectivement plus agréable à lire mais aussi pour uniformiser le style d'écriture au sein d’un projet. Cela a donc pour but de garder une certaine cohérence de forme mais aussi de régler certains types de problèmes tels que les soucis d’indentation par tabulation ou par espaces.

Prenons par exemple le code ci-dessous non formaté :

public class Prettier {
  
  public boolean prettierIsAwesome(int myFirstArgument, int mySecondArgument, int myThirdArgument, int myFourthArgument, int myFifthArgument) {
    if(myFirstArgument == 1 && mySecondArgument == 2 && myThirdArgument == 3 && myFourthArgument == 4 && myFifthArgument == 5) {
      return true;
    }

    // if you do not want to format the next expression
    // prettier-ignore
    if(myFirstArgument == 1 && mySecondArgument == 1 && myThirdArgument == 1 && myFourthArgument == 1 && myFifthArgument == 1) {
      return true;
    }

    MyObject mo = new MyObject();
    return mo.very().very().very().very().very().very().very().very().very().very().very().very().very().longMethod();
  }
}

Appliquons le formatage par Prettier-Java et observons le résultat. Il s’agit là d’un exemple volontairement exagéré. Il est à noter que dans le cas où une expression n’est pas trop longue, elle ne va pas être “cassée” sur plusieurs lignes. De manière générale, Prettier-Java suit dans les grandes lignes Google Java style guide mais ce n’est pas la règle.

public class Prettier {

  public boolean prettierIsAwesome(
    int myFirstArgument,
    int mySecondArgument,
    int myThirdArgument,
    int myFourthArgument,
    int myFifthArgument
  ) {
    if (
      myFirstArgument == 1 &&
      mySecondArgument == 2 &&
      myThirdArgument == 3 &&
      myFourthArgument == 4 &&
      myFifthArgument == 5
    ) {
      return true;
    }

    // if do not want to format the next expression
    // prettier-ignore
    if(myFirstArgument == 1 && mySecondArgument == 1 && myThirdArgument == 1 && myFourthArgument == 1 && myFifthArgument == 1) {
      return true;
    }

    MyObject mo = new MyObject();
    return mo
      .very()
      .very()
      .very()
      .very()
      .very()
      .very()
      .very()
      .very()
      .very()
      .very()
      .very()
      .very()
      .very()
      .longMethod();
  }
}

Fonctionnement

Prettier-Java n’est pas un formateur de code en soi mais un plugin d’un outil appelé Prettier. Il s’agit d’une application connue qui formate de nombreux langages et formats de données (JavaScript, TypeScript, JSON...) et Prettier-java est officiellement recensé comme projet communautaire qui ajoute le support de Java dans Prettier.

Le plugin correspond à l’association de deux projets, Java-Parser et Prettier-plugin-java. En effet, deux grandes étapes sont nécessaires pour formater le code Java. Dans un premier temps, le code doit être transformé en une donnée structurée par analyse syntaxique. Pour ce faire, il génère un arbre syntaxique qui est ensuite visité par Prettier-plugin-java, qui, lui, est chargé de rendre le code sous une nouvelle forme. Le fait d’avoir découpé le projet de cette manière permet de mettre à disposition un analyseur syntaxique Java pour d’autres cas d’usage.

Java-Parser

Java-Parser est un analyseur syntaxique (parser) de code Java écrit en JavaScript mais à la différence de la plupart des parsers existants, celui-ci génère un Arbre Syntaxique Concret (Concrete Syntax Tree ou CST) et non un Arbre Syntaxique Abstrait (Abstract Syntax Tree ou AST) comme on aurait l’habitude d’en voir dans un compilateur ou un interpreteur. La différence étant qu'un CST est une représentation en arbre de la structure syntaxique du code texte Java. À partir d'un CST, il est possible de recréer le code texte Java sans ambiguïté. Un AST est quant à lui le résultat de la simplification d’un CST par réduction de l’information, pour n’en conserver que le nécessaire afin de donner un sens sémantique au code Java.



Exemple d’un AST pour l’expression (4 - 8) + (3 x 7)



Exemple d’un CST pour l’expression (4 - 8) + (3 x 7)

Prettier-java-plugin

Prettier-plugin-java correspond au plugin de Prettier qui permet de passer d'un arbre de syntaxe concret généré à l’aide du parser précédemment présenté à un code formaté via l'API de Prettier. En effet, Prettier offre un éventail d'outils permettant de formater relativement simplement le code, gérer l’indentation, la concaténation, etc…

Comme dans l’écrasante majorité des interpréteurs et analyseurs, celui-ci se base sur le design pattern visitor afin de parcourir l’arbre et d'effectuer les traitements de formatage pour chaque nœud.

Utilisation

Installation et utilisation

Étant donné que Prettier-java est un plugin de Prettier écrit en Javascript, celui-ci requiert Node.js, il faut donc préalablement installer une version de Node >= 10.

Pour installer prettier-java-plugin, il suffit d'exécuter cette commande :

npm install prettier-plugin-java

Il faut ensuite exécuter la commande suivante afin de formater tous les fichiers contenus dans le dossier courant (et sous dossiers) :

npx prettier --write "**/*.java"

Prettier-ignore

Dans certains cas, Prettier-Java peut donner un résultat moins pertinent ou tout simplement pas satisfaisant. Il est possible d’ignorer le formatage de certaines parties de code avec les commentaires :

  • // prettier-ignore qui ignore l’expression en dessous du commentaire
  • // @formatter:on et // @formatter:off permettant d’activer/désactiver le formatage sur un bloc de code

Voici un exemple d’utilisation avec le code ci-dessous :

public class Prettier {
  
  public boolean prettierIsAwesome(int myFirstArgument, int mySecondArgument, int myThirdArgument, int myFourthArgument, int myFifthArgument) {
    mo.very().very().very().very().very().very().very().very().very().very().very().very().very().longMethod();
    // prettier-ignore
    mo.very().very().very().very().very().very().very().very().very().very().very().very().very().longMethod();
    
    // @formatter:off
    mo.very().very().very().very().very().very().very().very().very().very().very().very().very().longMethod();
    mo.very().very().very().very().very().very().very().very().very().very().very().very().very().longMethod();
    // @formatter:on
  }
}

Nous avons en sortie le code suivant :

public class Prettier {

  public boolean prettierIsAwesome(
    int myFirstArgument,
    int mySecondArgument,
    int myThirdArgument,
    int myFourthArgument,
    int myFifthArgument
  ) {
    mo
      .very()
      .very()
      .very()
      .very()
      .very()
      .very()
      .very()
      .very()
      .very()
      .very()
      .very()
      .very()
      .very()
      .longMethod();
    // prettier-ignore
    mo.very().very().very().very().very().very().very().very().very().very().very().very().very().longMethod();

    // @formatter:off
    mo.very().very().very().very().very().very().very().very().very().very().very().very().very().longMethod();
    mo.very().very().very().very().very().very().very().very().very().very().very().very().very().longMethod();
    // @formatter:on
  }
}

IntelliJ

Bien que passer par la ligne de commande soit simple, cela reste cependant fastidieux et nous utilisons très souvent des IDE pour coder, notamment IntelliJ. Il est possible d’appliquer automatiquement Prettier-Java à chaque changement dans cet IDE.

Il faut d’abord installer le plugin Intellij File Watchers et aller dans les paramètres Tools/File Watchers et configurer le watcher suivant :

Name: Prettier-java

File type: Java

Program: Le chemin vers l'exécutable Prettier

Arguments: --write $FilePathRelativeToProjectRoot$

Output paths to refresh: $FilePathRelativeToProjectRoot

Auto-save edited files to trigger the watcher: coché

Trigger the watcher on external changes: coché

Git Hook

Pour ceux n’utilisant pas d’IDE et/ou souhaitant appliquer automatiquement Prettier-Java avant de commiter, il est possible de le faire avec Husky et Pretty-quick.

Voici un exemple complet de la mise en place de Prettier-Java via Git Hook.

# Création du projet cible
mkdir myProject
cd myProject
git init
npm init

# Mise en place du git hook
npm install prettier prettier-plugin-java pretty-quick husky --save-dev
npx husky install
npx husky set .husky/pre-commit "npx pretty-quick --staged"

# Exemple d'application du git hook
echo "public    class MyClass {}" > MyClass.java
git add MyClass.java
git commit -m "commit de MyClass formaté !"

CI/CD

Il est possible d’intégrer dans un pipeline CI/CD la vérification que tous les fichiers ont bien été formatés par Prettier-Java. En effet, le CLI de prettier possède une option de “check” qui retourne 0 si les fichiers ont été préalablement formatés et des valeurs supérieures à 0 en cas d’erreurs. Nous allons ici utiliser Github Actions pour vérifier que tous les fichiers Java ont été formatés et voici ce que l’on peut obtenir :

name: CI
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2


      - uses: actions/setup-node@v2
        with:
          node-version: '12'

      - name: Install Prettier-Java
        run: npm install prettier prettier-plugin-java

      - name: Check Prettier-Java format
        run: npx prettier --check "**/*.java"

Cette configuration lance un job à chaque fois que l’on pousse ou ouvre une Pull Request sur la branche main et vérifie que tous les fichiers Java ont été formatés. Dans le cas contraire, le job se termine en erreur. Vous pouvez consulter ce projet pour avoir un exemple fonctionnel.

Conclusion

Prettier-java est donc un outil simple d’utilisation (une simple commande à lancer) qui permet donc de formater du code en forçant un certain style d’écriture. Cela permet notamment d’améliorer la lisibilité du code et de ramener un certain standard au sein d’un projet. Cependant la nécessité de Node.js peut en rebuter certains et le formatage imposé n’est pas forcément au goût de tous.

Il est intéressant de noter qu’il existe d'autres alternatives comme google-java-format ou le plugin IntelliJ Save Actions. Chacun ayant ses avantages et inconvénients mais la grande force de Prettier-Java réside dans sa polyvalence. Il s’adapte à de nombreux cas d’usage que ce soit dans l'utilisation d’un IDE (IntelliJ), la mise en place de git hooks afin de l’exécuter avant un commit ou encore dans l’intégration dans un pipeline de CI/CD.