Skip to content

Instantly share code, notes, and snippets.

@erickgnavar
Created November 13, 2024 07:16
Show Gist options
  • Save erickgnavar/72872ede576151173cd929b86a17584f to your computer and use it in GitHub Desktop.
Save erickgnavar/72872ede576151173cd929b86a17584f to your computer and use it in GitHub Desktop.

Integra cualquier linter a Emacs con Flymake

Agenda

  • Conocer que es un linter y como funciona
  • Conocer flymake
  • Integrar ruff, linter de python, en emacs

¿Qué es un linter?

Una herramienta que permite hacer análisis a código y mostrar diagósticos, existen múltiples herramientas para diferentes lenguajes de programación

¿Qué es flymake?

Es un paquete incluido en emacs que permite integrar linters de forma fácil

¿Cómo hacemos la integración?

Requisitos

  • La herramienta debe poder leer el código a analizar desde stdin y mostrar los resultados a stdout
  • Los resultados deben tener un formato estándarizado para poder extraer su información

Programa de ejemplo para revisar con nuestro linter

import os
import sys

for i in range(10):
    print(f"{fooo}")

value = 1

value2 += value + value3

Explorando ruff

Ejecución básica

ruff check demo.py

Obtenemos los errores pero están un poco dispersos, necesitamos una forma de tenerlos formateados.

Mejorando la salida

ruff check --output-format concise demo.py

Ya tenemos la salida con un mejor formato, pero tenemos elementos que no necesitamos, como:

Found 5 errors.
[*] 2 fixable with the `--fix` option.

Salida limpia y solo con lo necesario

ruff check --output-format concise --quiet demo.py

Ahora leemos desde stdin

cat demo.py | ruff check --output-format concise --quiet -

Haciendo la integración con flymake

Extraer los elementos de análisis

Para integrar con flymake necesitamos poder parsear la salida del linter, en este caso vamos a usar regexs para poder obtener los diferentes elementos del análisis realizado

(defvar flymake-ruff--output-regex "\\(.*\\):\\([0-9]+\\):\\([0-9]+\\): \\([A-Za-z0-9]+\\):? \\(.*\\)")

Podemos usar regexp-builder para construir de forma interactiva el regex

Definir los argumentos para la ejecución de ruff

(defvar flymake-ruff-program-args '("check" "--output-format" "concise" "--exit-zero" "--quiet" "-"))

Definir una función que ejecute el análisis y retorne los diagnósticos

(defun flymake-ruff--check-buffer ()
  (let ((code-buffer (current-buffer))
        (start-line (line-number-at-pos (point-min) t))
        (code-content (without-restriction
                        (buffer-substring-no-properties (point-min) (point-max))))
        (dxs '()))
    (with-temp-buffer
      (insert code-content)
      (apply #'call-process-region (point-min) (point-max) "ruff" t t nil flymake-ruff-program-args)
      (goto-char (point-min))
      (while (search-forward-regexp flymake-ruff--output-regex (point-max) t)
        (when (match-string 2)
          (let* ((line (string-to-number (match-string 2)))
                 (col (string-to-number (match-string 3)))
                 (code (match-string 4))
                 (msg (match-string 5))
                 (description (format "Ruff: %s %s" code msg))
                 (region (flymake-diag-region code-buffer (1+ (- line start-line)) col))
                 (dx (flymake-make-diagnostic code-buffer (car region) (cdr region)
                                              :error description)))
            (add-to-list 'dxs dx)))))
    dxs))

Definir una función wrapper para integrar flymake

(defun flymake-ruff--run-checker (report-fn &rest _args)
  "Run checker using REPORT-FN."
  (funcall report-fn (flymake-ruff--check-buffer)))

Crear una función para registrar nuestra función de análisis con flymake

;;;###autoload
(defun flymake-ruff-load ()
  "Load hook for the current buffer to tell flymake to run checker."
  (interactive)
  (when (derived-mode-p 'python-mode 'python-ts-mode)
    (add-hook 'flymake-diagnostic-functions #'flymake-ruff--run-checker nil t)))

Enlazar nuestro código con un major-mode

(add-hook 'python-ts-mode-hook 'flymake-ruff-load)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment