Unifica el estilo del código de un proyecto con Editorconfig

Dicen que en la variedad está el gusto. Y efectivamente, siempre es mejor tener muchas opciones para poder elegir entre ellas la que más nos guste. Pero cuando por variedad no nos referimos a los sabores de un helado sino a que en un mismo proyecto tenemos gente que usa un editor de código que finaliza los archivos con una nueva linea mientras que otros usan otro editor que no, unos indentan los archivos con espacios y otros con tabuladores, unos tienen el tamaño máximo de cada línea de 80 carácteres y otros de 120... al final acabamos con un popurrí de estilos que en el mejor de los casos hará que las revisiones de código sean un infierno y que haya cambios en el código cuya aprobación dependerá subjetivamente de la persona que esté revisándolos en ese momento. La variedad ahora ya no está tan bien, ¿verdad?

Por suerte, para intentar solucionar estos problemas de convivencia entre los integrantes de un equipo existen herramientas (o mejor dicho, convenciones o acuerdos) como Editorconfig, los cuales permiten definir una serie de configuraciones para que independientemente de la persona (o del editor de código que ésta utilice) el estilo del proyecto sea siempre el mismo.

Vamos a ver en qué consiste:

  1. ¿Qué es Editorconfig?
  2. Configuración
  3. Referencias

¿Qué es Editorconfig?

Editorconfig es una especificación que intenta definir una serie de configuraciones acerca del estilo de un proyecto de forma que cualquier persona que participe en él pueda hacerlo siguiendo las reglas definidas.

Por ejemplo, hay personas que utilizan espacios en blanco para indentar el código, sin embargo otras prefieren utilizar tabuladores. Si en un proyecto cuyos archivos están indentados con espacios llega una persona y modifica uno de esos archivos para implementar una nueva funcionalidad o corregir algún bug, debería respetar el estilo del proyecto y continuar utilizando espacios en blanco para indentar el código. Pero a veces esto no es así debido a las preferencias personales de cada uno, o incluso aunque esa persona no lo haga a propósito, si el IDE que utiliza está configurado para usar tabuladores, estará añadiendo su parte de código utilizando estos tabuladores en lugar de espacios en blanco como en el resto del proyecto.

Si definimos todas estas reglas de estilo utilizando la especificación de Editorconfig dará igual que cada uno tenga unas preferencias personales distintas o que cada IDE tenga una configuración por defecto diferente, ya que al abrir el proyecto todos los IDEs sabrán que deben usar espacios en blanco para indentar el código.

Existen multitud de editores de código e IDEs compatibles con Editorconfig, ya sea de forma nativa (como por ejemplo el maravilloso IntelliJ IDEA o esa guarrería de Visual Studio que utilizáis algunos) o por medio de plugins (como el maravilloso VIM, Sublime o esa guarrería de Emacs que solo gente con 6 dedos en cada mano puede utilizar).

Basta con definir estas reglas en un archivo llamado .editorconfig en la raíz de nuestro proyecto para que cualquier editor de código o IDE compatible entienda qué reglas de estilo debe utilizar.

Configuración

La configuración no podría ser más sencilla. Como he comentado antes, basta con añadir a la raíz de nuestro proyecto un archivo de texto plano llamado .editorconfig con la configuración de reglas que queramos definir.

Realmente el archivo .editorconfig podría estar en cualquier directorio de nuestro proyecto, ya que al abrir un archivo, los IDEs compatibles buscarán este archivo en el mismo directorio en el que se encuentre el archivo que acabamos de abrir y en caso de no encontrarlo ahí, buscaría en el directorio padre, y así sucesivamente hasta que o bien encuentre un archivo .editorconfig con una directiva especial que le haga dejar de buscar o bien llegue al directorio raíz del disco.

Supongo que en alguna extraña circunstancia (aunque realmente no se me ocurre cual) podría darse el caso en el que queramos tener una configuración de reglas de estilo definidas para nuestro proyecto, pero que en uno de los subdirectorios del proyecto quisiéramos tener una configuración diferente. En ese caso sí que podría tener sentido tener diferentes archivos .editorconfig, pero para el 99% de los casos es suficiente (y recomendable) tener únicamente un archivo .editorconfig en la raíz del proyecto.

Un ejemplo de uno de estos archivos sería algo así:

# EditorConfig is awesome: https://EditorConfig.org
root = true

[*]
charset = utf-8
end_of_line = LF
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
max_line_length = 120

[*.{yml,yaml,yml.j2,yaml.j2}]
indent_size = 2

[*.md]
max_line_length = 140
trim_trailing_whitespace = false

[Makefile]
indent_style = tab

Como podéis ver, se trata de un archivo INI en el que se definen configuraciones generales y configuraciones específicas según el nombre o extensión de determinados archivos.

Según este archivo de ejemplo, el proyecto tendría definidas unas configuraciones generales (las que están bajo el bloque [*]) y otras específicas para algunos archivos determinados que redefinirían algunas de esas configuraciones generales (las que están bajo los bloques [*.{yml,yaml,yml.j2,yaml.j2}], [*.md] y [Makefile]).

Por ejemplo, de forma general para todo el proyecto (bloque [*]) tenemos definido entre otras cosas que la indentación se debe hacer con espacios en blanco (indent_style = space) y que el tamaño máximo de linea debe ser de 120 carácteres (max_line_length = 120). Sin embargo, los archivos Markdown (aquellos con extensión .md) tendrán un tamaño máximo de linea de 140 carácteres (max_line_length = 140) y en los archivos Makefile se deberá utilizar tabuladores para indentar el código (indent_style = tab).

Para definir los archivos sobre los que aplicarán las configuraciones se puede simplemente poner el nombre del archivo (como en el ejemplo anterior, en el bloque Makefile) o utilizar algunos de los comodines compatibles. Estos comodines son los siguientes:

Comodín Descripción
* Equivale a cualquier carácter (o conjunto de carácteres) excepto a /, por lo que no se puede utilizar para indicar un path.
** Equivale a cualquier carácter (o conjunto de carácteres).
? Equivale a cualquier carácter (solo a uno).
[nombre] Equivale al nombre especificado.
[!nombre] Equivale a cualquier nombre excepto al especificado.
{s1, s2, s3} Equivale a cualquiera de los carácteres especificados.
{num1..num2} Equivale a cualquier número entero entre num1 y num2 (pueden ser positivos o negativos).

Se puede usar la barra invertida (\) para escapar cualquiera de éstos carácteres y evitar así que sean interpretados como comodines.

Si te has fijado bien en el archivo que he puesto de ejemplo quizás te hayas dado cuenta de 2 cosas:

La primera es que se pueden escribir comentarios utilizando el símbolo #. Cualquier línea que empiece por ese carácter será ignorada al parsear el archivo.

Y la segunda es que hay una directiva que no está dentro de ningún bloque: root = true.

Al principio del artículo comenté que en un mismo proyecto se podían tener tantos archivos .editorconfig como quisiéramos y que al abrir uno de los archivos de nuestro proyecto, el IDE primero intentaría leer el .editorconfig que hubiera en el mismo directorio donde estuviera el archivo que estamos abriendo, y después intentaría leer el archivo .editorconfig que hubiera en el directorio padre, y así iría subiendo en el árbol de directorios sucesivamente hasta llegar o bien al directorio raíz de nuestro disco o bien hasta que se encuentre con un archivo .editorconfig que tenga ésta directiva configurada a true.

Por lo tanto, dado que mi recomendación es que únicamente tengáis un archivo .editorconfig en el directorio raíz de vuestro proyecto, este archivo debería tener siempre la directiva root = true para evitar que el IDE continue buscando archivos .editorconfig fuera de vuestro proyecto.

Con respecto a las diferentes directivas de configuración que podemos utilizar en dichos archivos, éstas son todas las que existen actualmente (los valores alfanuméricos NO son sensibles a mayúsculas/minúsculas, aunque la recomendación es utilizar siempre minúsculas):

  • indent_style: Se utiliza para especificar si queremos indentar el código usando espacios o tabuladores. Sus posibles valores son space o tab respectivamente.

  • indent_size: Aquí podemos indicar el número de carácteres que se usarán en cada indentación. Por ejemplo, si en indent_style hemos especificado indentar el código con espacios, cada vez que indentemos el código un nivel se usarán tantos espacios como hayamos configurado aquí. Lo normal suelen ser 2 o 4, pero por supuesto puedes usar tantos como quieras.

    Si en lugar de poner un valor numérico como 2 o 4, pones el valor tab, el valor de indent_size será el que hayamos definido en tab_width (y en caso de no tener definido ningún valor en tab_width se usará el valor que tenga configurado el editor que estemos utilizando).

  • tab_width: Especifica el tamaño de un tabulador.

  • end_of_line: Indica el formato que hay que utilizar para indicar el final de linea (también llamado retorno de carro, en referencia a su origen en las antiguas máquinas de escribir). Sus posibles valores son:

    • lf: Normalmente utilizado por Linux.
    • crlf: Normalmente utilizado por Windows.
    • cr: Normalmente utilizado por MacOS.

    Según la propia documentación de Editorconfig, tienen pensado añadir un nuevo valor aparte de los 3 anteriores llamado native para dejar que sea el propio programa de gestión de versiones que usemos (git o similar) quien se encargue de utilizar un final de línea u otro dependiendo del sistema operativo que estemos utilizando. Pero mientras implementan este nuevo valor, si quieres que sea el control de versiones quien se encargue, no definas este valor en tu .editorconfig.

  • charset: Especifica el juego de carácteres (encoding) que queremos utilizar en nuestros archivos. Sus posibles valores pueden ser utf-8, latin1, etc.

  • trim_trailing_whitespace: Este valor nos permite indicar si queremos eliminar aquellos espacios en blanco que haya al final de una linea. Si lo establecemos a true los espacios en blanco que pudiera haber al final de cualquier línea serán eliminados, y con false obviamente NO se eliminarán.

  • insert_final_newline: Si establecemos este valor a true el editor de código que estemos utilizando intentará dejar una linea en blanco al final de cada archivo. En caso contrario (false) los archivos terminarán inmediatamente después del último carácter de la última linea que haya en el archivo.

  • max_line_length: Especifica el tamaño máximo que podrán tener las líneas en los archivos que abramos con nuestro editor de código. La idea es no tener líneas muy largas que obliguen a hacer scroll horizontal y que permitan leer el código de forma mucho más cómoda. Esta directiva NO es compatible con todos los editores compatibles con Editorconfig, por lo que si utilizas un editor de código un poco raro quizás no haga caso a esta propiedad aunque la tengas definida en tu .editorconfig.

Este es el ejemplo completo del archivo .editorconfig que suelo utilizar en todos mis proyectos, por si te sirve de ayuda a la hora de hacerte una idea antes de escribir el tuyo (o si simplemente quieres cogerlo y usarlo tal cual en tus proyectos):

# EditorConfig is awesome: https://EditorConfig.org

# top-most EditorConfig file
root = true

# Unix-style newlines with a newline ending every file
[*]
charset = utf-8
end_of_line = LF
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
max_line_length = 120

[*.{yml,yaml,yml.j2,yaml.j2}]
indent_size = 2

[*.{json,json.j2}]
indent_size = 2

[*.{tf,tfvars}]
indent_size = 2

[*.md]
max_line_length = 140
trim_trailing_whitespace = false

[Makefile]
indent_style = tab

[*.go]
indent_style = tab

[COMMIT_EDITMSG]
max_line_length = 0

Como habrás comprobado, Editorconfig es algo muy sencillo de implementar, no require la instalación de ningún software para poder utilizarlo (a no ser que tu IDE no lo soporte de forma nativa y tengas que instalar algún plugin) y te permitirá mantener un mismo estilo en aquellos proyectos en los que participen diferentes personas con diferentes sistemas operativos, IDEs o preferencias personales.

Como siempre, espero que el artículo te haya resultado de utilidad. Y por supuesto, no dudes en distribuirlo y compartirlo con todo el mundo (pero por favor, cita siempre la fuente original de este artículo).

Alaaaaaaaaaaaaaaaaaaaaaaa!

Referencias