戻る Retour au journal

Rebundler : remettre de l'ordre dans vos Gemfile automatiquement

La gestion des dépendances en Ruby repose sur un fichier un peu particulier : le Gemfile. Ce n'est pas un simple fichier de configuration, c'est un vrai programme Ruby, interprété directement par Bundler. Cette liberté a un revers : rien n'impose de convention de mise en forme. On peut y écrire des conditions, des blocs, des appels de méthode... mais aussi n'importe quoi d'autre, on pourrait y ajouter la liste entière des Pokémons si on le souhaitait.

Dans la pratique, on fait comme le gros flemmard que je suis, on ajoute ses dépendances au fil de l'eau, en bas du fichier, ou à côté d'une gem qui semble vaguement liée, sans jamais revenir réorganiser l'ensemble. Résultat : au bout de quelques mois, le fichier ne raconte plus aucune histoire cohérente, et il devient difficile de deviner ce que fait telle ou telle dépendance, surtout quand son nom ne parle pas de lui-même.

C'est ce problème qu'attaque Rebundler, un outil que j'ai découvert récemment. Il automatise deux choses :

  • Le tri et le regroupement des dépendances, en respectant leur contexte d'origine (haut du fichier, ou bloc group) ;
  • L'annotation de chaque ligne avec une courte description du paquet, récupérée depuis les métadonnées locales ou, à défaut, depuis RubyGems.

Un mode de vérification permet aussi de l'intégrer à une CI : il détecte un fichier mal formaté sans le modifier.

Le défi technique : analyser sans pouvoir régénérer

Pour comprendre un Gemfile, Rebundler s'appuie sur Prism, l'analyseur syntaxique officiel de Ruby. Prism transforme le code en arbre syntaxique (AST), et associe à chaque élément de cet arbre sa position exacte dans le fichier source : ligne, colonne, décalage en octets.

Le souci, c'est que Prism sait lire du code, mais pas le réécrire. Contrairement à des outils comme syntax_tree, qui savent reconstituer du code formaté depuis un AST, Prism reste un pur analyseur. Reformuler le code depuis zéro aurait d'ailleurs un coût : on risque de perdre le style original du développeur (espacement, guillemets, options en ligne...).

Comment Rebundler contourne le problème

L'astuce est simple mais efficace : plutôt que de demander à Prism de régénérer le code, Rebundler l'utilise uniquement pour localiser chaque déclaration dans le fichier. Une fois qu'on connaît les coordonnées exactes d'une ligne, il suffit de la découper directement dans le texte source pas besoin de la reconstruire depuis l'arbre.

Concrètement, le traitement se fait en quatre étapes :

  1. Analyse du fichier avec Prism, pour obtenir l'AST ;
  2. Classification des nœuds : instructions génériques (source, ruby), dépendances de premier niveau, dépendances dans des blocs group ;
  3. Extraction du texte brut de chaque déclaration, via ses coordonnées ligne/colonne ;
  4. Reconstruction du fichier : tri alphabétique par groupe, fusion des blocs dupliqués, ajout des annotations.

L'avantage de cette méthode par coordonnées, c'est qu'elle préserve à 100 % le style de code d'origine, puisque le texte n'est jamais réécrit, seulement recopié au bon endroit. La contrepartie : les commentaires en fin de ligne (trailing comments) ne font pas partie du nœud de déclaration au sens strict de la grammaire Ruby. Il faut donc les retrouver séparément, en fouillant dans le flux de tokens.

Regroupement et annotation

Toutes les dépendances de premier niveau sont rassemblées, peu importe leur position d'origine dans le fichier. Celles qui vivent dans un même type de bloc (par exemple group :development, :test do ... end) restent groupées ensemble, et si plusieurs blocs portent les mêmes paramètres, ils sont fusionnés en un seul.

Pour l'annotation, Rebundler cherche d'abord la description du paquet dans les métadonnées locales du bundle ; si elle est absente, il va la chercher sur RubyGems. Les commentaires de fin de ligne déjà présents sont conservés par défaut. En revanche, les commentaires isolés (ceux placés au-dessus d'une déclaration) sont pour l'instant supprimés lors de la reconstruction.

En pratique

Voici un exemple minimal de transformation :

diff
- gem "rails"
- gem "puma"
- gem "avo"
-
- group :development do
-   gem "herb"
- end
+ gem "avo" # Admin panel framework and Content Management System for Ruby on Rails.
+ gem "puma" # A Ruby/Rack web server built for parallelism.
+ gem "rails" # Full-stack web application framework.
+
+ group :development do
+   gem "herb" # The modern HTML+ERB Toolchain
+ end

Sur un vrai Gemfile de plus de quatre-vingts dépendances, non trié, aux commentaires disparates, avec des gems commentées en pagaille, le résultat est net : les dépendances de premier niveau se retrouvent triées alphabétiquement et annotées, les blocs group sont conservés et fusionnés proprement. Et surtout, le style d'origine (versions, sources github:, options diverses) reste intact, grâce à la stratégie d'extraction par coordonnées.

Les limites

Rebundler part du principe que le fichier suit des conventions à peu près standard (gem, group, source, ruby). Or un Gemfile reste un programme Ruby arbitraire : des constructions dynamiques ou peu conventionnelles (conditions complexes, génération programmatique de déclarations) échappent à sa détection. Autre point à garder en tête : supprimer les commentaires isolés fait perdre de l'information quand ceux-ci documentaient un choix d'architecture plutôt qu'une simple description de gem.

Pour aller plus loin

Rebundler montre qu'on peut obtenir des transformations de code fiables, respectueuses du style existant, sans avoir besoin d'un moteur de régénération de code, juste un bon analyseur syntaxique et une stratégie d'extraction par coordonnées. Parmi les pistes d'évolution : conserver les commentaires isolés, et tirer parti des futures capacités de reconstruction annoncées pour Prism, qui pourraient simplifier une partie du pipeline actuel.

L'outil est disponible en tant que gem Ruby (gem install rebundler), utilisable seul ou via Bundler. Un playground en ligne permet aussi de le tester sans rien installer, l'état du fichier soumis étant encodé directement dans l'URL.

https://github.com/dennispaagman/rebundler
https://rebundler.dev/