3 puntos por GN⁺ 2024-03-22 | 1 comentarios | Compartir por WhatsApp
  • Difftastic es una herramienta CLI de diff que compara archivos no por líneas, sino según la estructura sintáctica del código
  • Internamente analiza el código con tree-sitter, por lo que incluso si el formateo cambia las líneas, distingue mejor qué expresiones realmente cambiaron
  • Incluso cuando un formateador divide elementos en varias líneas o se agrega un wrapper, puede mostrar por separado la estructura modificada y el contenido interno que se mantiene
  • Ofrece guía de configuración para Git y documentación de instalación, y soporta varios lenguajes de programación y formatos de texto estructurado como JSON y YAML
  • Se publica bajo licencia MIT, por lo que puede descargarse, modificarse y compartirse, y es ideal para agregar comparación estructural a flujos de diff existentes

Compara la estructura sintáctica, no las líneas

  • Difftastic es una herramienta CLI de diff que compara archivos con base en la sintaxis
  • Se enfoca en mostrar resultados que entienden la estructura del código, en lugar de un diff tradicional por líneas
  • El código se analiza con tree-sitter
    • Incluso expresiones internas que en un diff centrado en líneas podrían parecer modificadas pueden distinguirse según si realmente cambiaron o no
  • Incluso cuando un formateador de código divide el código en varias líneas, puede mostrar los cambios reales
  • Si se agrega un wrapper, empareja los delimitadores con precisión
    • Incluso si el contenido interno también cambia, puede señalar el wrapper agregado
  • En lugar de usar la sintaxis de hunk de diff como @@ -5,6 +5,7 @@, muestra los números de línea reales de los archivos antes y después del cambio

Lenguajes compatibles e integración con Git

  • Los recursos del proyecto están disponibles en GitHub, Manual e Install
  • Algunos ejemplos de lenguajes de programación compatibles son:
    • C++, C#, Clojure, Dart, Erlang, Go, Haskell, Java, JavaScript, Kotlin, Lisp, Lua, OCaml, PHP, Python, R, Ruby, Rust, Scala, TypeScript
    • La lista completa puede consultarse en la lista de lenguajes compatibles del manual
  • Algunos ejemplos de formatos de archivo compatibles son:
  • Puede usarse junto con Git, y el manual incluye una guía de configuración de Git
  • Difftastic se publica bajo licencia MIT, por lo que puede descargarse, modificarse y compartirse

1 comentarios

 
GN⁺ 2024-03-22
Comentarios de Hacker News
  • Quizá algunos ya lo sepan, pero esta herramienta está construida sobre tree-sitter(https://tree-sitter.github.io/tree-sitter/), y hace para el parsing algo parecido a lo que LSP hizo por el análisis
    Convierte el código en un árbol de sintaxis abstracta (AST) y proporciona una interfaz estándar para que clientes como editores o herramientas de diff puedan usar ese AST
    Herramientas como esta no necesitan dar soporte directo a decenas de lenguajes; con soportar tree-sitter pueden funcionar automáticamente con todos los lenguajes que tree-sitter soporte
    Si desarrollas un lenguaje nuevo, basta con crear un parser de tree-sitter, y entonces todas las herramientas que usan tree-sitter podrán soportar ese lenguaje
    Gracias a estas dos grandes innovaciones, las mejoras en este tipo de herramientas se han disparado. Ahora editores, herramientas de diff y demás pueden soportar decenas o cientos de lenguajes sin repetir el mismo trabajo entre sí, lo cual es realmente impresionante

    • Totalmente de acuerdo, y retomando un comentario que escribí el año pasado, también vale la pena destacar que tree-sitter tiene una arquitectura sin dependencias
      Como referencia, algunos compañeros de mi equipo mantienen los bindings de tree-sitter para OCaml y también contribuyen con frecuencia a las gramáticas como parte del trabajo en Semgrep. Semgrep usa tree-sitter para la búsqueda de código y para parsear consultas, que son fragmentos de código en sí mismos, como matchers de AST
      Al crear un linter, muchas veces hay que llevarse también el runtime del lenguaje objetivo. Por ejemplo, si construyes un parser en Python con el módulo ast integrado, tienes que hacer coincidir la versión y las características del lenguaje, así que Pylint ejecutándose en Python 2.7 no puede parsear código Python 3
      Cuando apuntas a varios lenguajes, eso se vuelve mucho más molesto de lo que uno imagina al principio
      Antes de tree-sitter, a menudo lo mejor era usar las herramientas AST integradas del lenguaje, porque al menos había garantía de seguir la sintaxis más reciente
      Personalmente, creo que la genialidad de tree-sitter está en que hizo mucho más fácil mantener actualizados los parsers de los lenguajes que con los enfoques gramaticales tradicionales
      Si quieren conocer más sobre las decisiones de diseño de tree-sitter, recomiendo muchísimo la charla de Strange Loop de Max Brunsfield: https://www.youtube.com/watch?v=Jes3bD6P0To
      Como resultado, además de difftastic, varias herramientas nuevas como neovim, Zed, Semgrep y la búsqueda de código de GitHub se han construido sobre tree-sitter
    • Estoy de acuerdo en que tree-sitter es una herramienta excelente, pero escribir gramáticas por cuenta propia puede ser extremadamente difícil
      Intenté escribir una gramática de tree-sitter y un conjunto de consultas de resaltado para VHDL, y hubo muchas dificultades para expresar la gramática de VHDL en tree-sitter
    • Creo que esta explicación no es precisa. No existe una interfaz común del tipo “hablar tree-sitter”, y cada parser de tree-sitter emite un árbol de sintaxis concreta distinto, no un árbol de sintaxis abstracta estándar
      LSP sí resuelve de verdad el problema de las M×N integraciones necesarias entre M editores y N lenguajes usando una interfaz estándar para compiladores orientados por consultas
      tree-sitter, más que resolver ese problema, hace mucho más fácil escribir esas N integraciones desde el editor o la herramienta
    • Lo que más me frena con tree-sitter es que este enfoque no funciona para varios lenguajes que me interesan
      Common Lisp no se puede parsear sin una implementación completa de Lisp, la gramática de Haskell es lo bastante compleja como para que la gramática quede incompleta, C/C++ ya es difícil de parsear correctamente solo por el preprocesador, y parsear Perl es turing-completo
      En vez de hacer que en un ecosistema nuevo se reescriban parsers, como sugieren algunas propuestas por ahí, me parece más correcto definir un formato que los parsers existentes puedan generar como salida adicional
    • ¿Se podría crear una gramática de tree-sitter para lenguaje natural como el inglés, de modo que cada oración quede etiquetada como una unidad de oración?
      Así difftastic podría mostrar cambios por oración en lugar de por línea visual
      En ensayos, hacer diff por líneas visuales se siente raro. Normalmente la oración cambiada empieza a mitad de una línea visual
  • Si lo instalaste con cargo, así puedes actualizar difftastic y herramientas relacionadas
    cargo install cargo-update
    cargo install-update --list
    cargo install-update --all
    También hay proyectos interesantes en Rust que se pueden instalar con cargo
    https://mise.jdx.dev/ mise-en-place es una alternativa rápida y flexible a https://asdf-vm.com/
    https://github.com/ajeetdsouza/zoxide es un excelente reemplazo de cd: guarda los lugares a los que te moviste y luego puede llevarte a "~/projects/helloworld" con solo una coincidencia parcial como "z hel"
    https://github.com/bootandy/dust complementa a du mostrando qué directorios están usando mucho espacio en disco

    • Si agregara tres herramientas más elegantes: https://github.com/eza-community/eza es un ls visualmente más agradable, https://github.com/ClementTsang/bottom es un htop con gráficos, y https://github.com/sharkdp/bat es un cat con resaltado de sintaxis
    • Me encanta zoxide. También valdría la pena agregar lsd a la lista. Es un ls más bonito
    • Probé instalar mise-en-place y es exactamente lo que siempre quise que fuera asdf
    • Entre los nuevos tipos de du, dua-cli es el que más me gusta. Tiene una UI interactiva de terminal estilo clon de ncdu
      Lo encontré porque estaba buscando una opción con modo claro, ya que ncdu no lo tiene
    • ncdu es por mucho el mejor reemplazo de du
  • Casi no entiendo por qué herramientas como git no ofrecen algo así por defecto
    Llevo como un año usando difft, y mi mayor queja es que hace difícil volver a otras herramientas de diff cuando estás en un entorno donde difft no está disponible
    Me pregunto si también se ha investigado alguna herramienta de diff semántico. O sea, una que maneje casos donde cambió la sintaxis pero el significado sigue siendo el mismo
    Como problema general suena intratable, pero quizá podría ser posible o útil en DSL pequeños o en subconjuntos de algunos lenguajes

    • Creo que tiene sentido usar el viejo diff como valor predeterminado
      Ya está presente en casi cualquier sistema donde quieras ejecutar git, es rápido, pequeño y todo el mundo conoce su uso básico
      Aun así, está bien que ese valor predeterminado se pueda cambiar fácilmente
    • Estoy creando https://semanticdiff.com/ y detecta cambios semánticos básicos, como convertir literales decimales a hexadecimales o cambiar el orden de las claves dentro de un objeto JSON
      No es una utilidad de línea de comandos, sino una extensión de VS Code y una GitHub App
      Si te interesa cómo funciona y en qué se diferencia de difftastic, puedes ver https://semanticdiff.com/blog/semanticdiff-vs-difftastic/
    • Difftastic es una herramienta útil, pero en mi experiencia es demasiado lenta para convertirla en la opción predeterminada de una herramienta tan ampliamente usada como git
    • Para hacer algo así, la herramienta de diff en la práctica se convierte en un compilador
    • Ahorita estoy probando usar difft con git y me está gustando bastante
      Aun así, una de las razones por las que no creo que deba ser el valor predeterminado es que oculta las diferencias de espacios en blanco. Todavía no he revisado si eso se puede configurar
  • Me identifiqué totalmente con la pregunta de si sabes leer la sintaxis @@ -5,6 +5,7 @@, y con la explicación de que Difftastic muestra los números de línea reales de los archivos antes y después del cambio
    Lo instalé de inmediato e hice git diff; funciona muy bien

    • ¿No puedes leer esa sintaxis?
  • Que GitHub no haya sacado algo así parece un gran vacío de innovación de producto
    Ni siquiera ayuda cuando solo cambia la indentación, y normalmente lo muestra como una eliminación enorme y una adición enorme
    El visor de diff de GitHub podría ser más inteligente y debería serlo

    • GitHub ni siquiera maneja bien el reconocimiento sintáctico, mucho menos el diff semántico
      De hecho, ni puede distinguir que foo.cpp.in y foo.mk.in son distintos
      Cualquier foo.t se declara como Perl y no hay forma de corregirlo. Hay un ticket de hace 10 años
    • tree-sitter está optimizado para rendimiento en editores, no para precisión
      De hecho, los propios desarrolladores principales de TS recomiendan no preocuparse demasiado por la exactitud de la gramática[1]
      Parece que esta limitación podría ser fatal para GitHub o servicios en una posición similar
      [1] https://github.com/tree-sitter/tree-sitter/issues/130#issuec...
    • GitHub sí tiene una opción para ignorar espacios en blanco en el diff
  • Intenté cambiarme a esta herramienta, pero usa un formato raro incluso en partes que no cambiaron, así que se sintió demasiado ruidosa
    Al final regresé a icdiff[0]
    [0]: https://github.com/jeffkaufman/icdiff

  • Hace tiempo encontré Semantic Merge[1], pero no era open source
    Este no hace merge, solo diff, pero al menos es open source y el diff se ve mucho mejor, así que ya lo puse como valor predeterminado
    Me pregunto si hay planes de extenderlo con una función de merge
    [1] https://docs.plasticscm.com/semanticmerge

    • Según el README de GitHub, difftastic no hace merge
      Dice que el merge de AST es un problema difícil y que difftastic no lo aborda
      Además, el diff de AST es un proceso con pérdida desde la perspectiva del diff de texto. difftastic ignora espacios en blanco que no tienen significado sintáctico, pero para hacer merge sí necesitas rastrear esos espacios
    • Yo también iba a recomendar Semantic Merge
      Cuando trabajaba en refactorizaciones desordenadas de proyectos C# en un equipo grande, de verdad fue una herramienta salvadora
  • Debates anteriores:
    https://news.ycombinator.com/item?id=27768861 (297 points | hace 3 años | 61 comments)
    https://news.ycombinator.com/item?id=32746258 (698 points | hace 2 años | 90 comments)
    https://news.ycombinator.com/item?id=30841244 (983 points | hace 2 años | 219 comments)

  • Parece una gran herramienta, pero al probarla con algunos archivos XML grandes, las líneas modificadas se mostraban en verde normal y los atributos modificados en verde en negritas, así que visualmente era difícil distinguirlos
    No encontré en la documentación cómo cambiar el estilo del diff ni cómo asignar otro color a los casos en negritas
    Me pregunto si alguien sabe cómo hacerlo

    • Por desgracia, parece que todavía no se admite la personalización de colores
      Dejé un comentario en este issue[1] diciendo que apoyo esa función
      Si lo que quieres entra en la misma categoría, podrías sumar tu opinión ahí; y si crees que es una función suficientemente distinta, quizá valga la pena abrir un issue nuevo
      [1]: https://github.com/Wilfred/difftastic/issues/611
  • Es interesante que el paquete de Arch Linux pesa 7 MB, pero después de instalarse se expande a 80 MB, y solo el binario difft ya ocupa 78 MB
    En un dataset de ZFS con compresión LZ4 activada, du dice que son 17 MB
    Me pregunto por qué algo que se comprime tan bien dentro del binario no se deja simplemente comprimido. Parece que si se descomprime en RAM hasta podría cargar más rápido

    • Ya que salió el tema de la compresión, me dio curiosidad y lo revisé: en btrfs zstd:1 queda en unos 10 MB
      Processed 1 file, 614 regular extents (614 refs), 0 inline.
      Type Perc Disk Usage Uncompressed Referenced
      TOTAL 14% 10M 77M 77M
      none 100% 1.1M 1.1M 1.1M
      zstd 12% 9.8M 76M 76M
    • Probablemente tenga que ver con el comentario de que el parser de Nim es un archivo C de 60 MB que no se puede fusionar
      Parece que Tree-sitter necesita una cantidad enorme de código, y una buena parte de eso se ve redundante o comprimible
    • Los ejecutables no se cargan completos en RAM antes de arrancar
      Se mapean en memoria y solo se cargan las partes que realmente se usan cuando hacen falta
      Por eso, si comprimes un binario grande, la paginación por demanda deja de funcionar y en realidad se vuelve más lento
      Hace unos años hice una prueba con un EXE de Windows de 280 MB, y aunque sí se comprimía a unos 70 MB, tardaba varios segundos más en iniciar que el original
      En algunos escenarios, como ejecutar binarios a través de la red, puede tener sentido, pero en la mayoría de los casos un binario sin comprimir arranca más rápido