3 pontos por GN⁺ 2024-03-22 | 1 comentários | Compartilhar no WhatsApp
  • Difftastic é uma ferramenta CLI de diff que compara arquivos com base na estrutura sintática do código, não linha por linha
  • Internamente, usa tree-sitter para fazer o parsing do código, distinguindo melhor as expressões que realmente mudaram mesmo quando a formatação altera as linhas
  • Mesmo quando um formatador de código divide itens em várias linhas ou adiciona um wrapper, consegue separar e mostrar a estrutura alterada e o conteúdo interno preservado
  • Há documentação de instalação e instruções de configuração para Git, com suporte a várias linguagens de programação e formatos de texto estruturado como JSON e YAML
  • Disponível sob a licença MIT, pode ser baixado, modificado e compartilhado, sendo adequado para adicionar comparação estrutural a fluxos de diff existentes

Compara a estrutura sintática, não as linhas

  • Difftastic é uma ferramenta CLI de diff que compara arquivos com base na sintaxe
  • Seu foco é mostrar resultados que entendem a estrutura do código melhor do que um diff tradicional linha por linha
  • O código é analisado com tree-sitter
    • Isso permite distinguir se expressões internas que poderiam parecer alteradas em um diff centrado em linhas realmente mudaram ou não
  • Mesmo quando um formatador de código divide o código em várias linhas, consegue mostrar as mudanças reais
  • Quando um wrapper é adicionado, ele faz a correspondência correta dos delimitadores
    • Mesmo que o conteúdo interno também mude, consegue indicar o wrapper adicionado
  • Em vez da sintaxe de hunk de diff como @@ -5,6 +5,7 @@, mostra os números de linha reais dos arquivos antes e depois da mudança

Linguagens compatíveis e integração com Git

  • Os materiais do projeto estão disponíveis no GitHub, no Manual e em Install
  • Exemplos de linguagens de programação compatíveis incluem:
    • C++, C#, Clojure, Dart, Erlang, Go, Haskell, Java, JavaScript, Kotlin, Lisp, Lua, OCaml, PHP, Python, R, Ruby, Rust, Scala, TypeScript
    • A lista completa pode ser consultada na lista de linguagens compatíveis do manual
  • Exemplos de formatos de arquivo compatíveis incluem:
  • Pode ser usado com Git, e o manual traz instruções de configuração do Git
  • O Difftastic é disponibilizado sob a licença MIT, permitindo download, modificação e compartilhamento

1 comentários

 
GN⁺ 2024-03-22
Comentários do Hacker News
  • Talvez alguns já saibam, mas esta ferramenta foi construída sobre o tree-sitter(https://tree-sitter.github.io/tree-sitter/) e, de certa forma, faz para parsing o que o LSP fez para análise
    Ela converte código em uma árvore de sintaxe abstrata (AST) e fornece uma interface padrão para que clientes como editores ou ferramentas de diff possam usar essa AST
    Assim, essas ferramentas não precisam oferecer suporte direto a dezenas de linguagens; basta suportar o tree-sitter, e elas passam a funcionar automaticamente com todas as linguagens que o tree-sitter suporta
    Se você estiver desenvolvendo uma nova linguagem, basta criar um parser para o tree-sitter, e então todas as ferramentas que trabalham com tree-sitter poderão suportar essa linguagem
    Graças a essas duas grandes inovações, as melhorias nesse tipo de ferramenta estão crescendo de forma explosiva. Agora editores, ferramentas de diff etc. conseguem suportar dezenas ou centenas de linguagens sem repetir o mesmo trabalho entre si, o que é realmente incrível

    • Concordo totalmente e, para recuperar um comentário que escrevi no ano passado, também vale destacar que o tree-sitter tem uma arquitetura sem dependências
      Para contextualizar, alguns membros da nossa equipe mantêm os bindings de tree-sitter para OCaml e também contribuem com frequência para gramáticas como parte do trabalho no Semgrep. O Semgrep usa tree-sitter para busca de código e para fazer parsing de consultas, que são em si trechos de código, como matchers de AST
      Ao criar um linter, muitas vezes é preciso trazer junto o runtime da linguagem alvo. Por exemplo, se você construir um parser em Python usando o módulo embutido ast, precisa alinhar versão e recursos da linguagem, então um Pylint executando em Python 2.7 não consegue fazer parsing de código Python 3
      Quando você lida com várias linguagens, isso fica muito mais incômodo do que parece à primeira vista
      Antes do tree-sitter, muitas vezes a melhor opção era usar ferramentas de AST embutidas na própria linguagem, porque havia a garantia de acompanhar a sintaxe mais recente
      Na minha opinião, a genialidade do tree-sitter está em ter tornado muito mais fácil manter parsers de linguagens atualizados do que com abordagens gramaticais tradicionais
      Se quiser entender melhor as escolhas de projeto do tree-sitter, recomendo fortemente a palestra do Max Brunsfield na Strange Loop: https://www.youtube.com/watch?v=Jes3bD6P0To
      Como resultado, além do difftastic, várias ferramentas novas foram construídas sobre o tree-sitter, como neovim, Zed, Semgrep e a busca de código do GitHub
    • Concordo que o tree-sitter é uma ferramenta excelente, mas escrever gramáticas manualmente pode ser extremamente difícil
      Já tentei escrever uma gramática de tree-sitter para VHDL e um conjunto de queries de highlighting, e houve muitas dificuldades para expressar a gramática de VHDL no tree-sitter
    • Acho que essa explicação não está correta. Não existe uma interface comum do tipo "falar tree-sitter", e cada parser de tree-sitter produz sua própria árvore de sintaxe concreta, não uma árvore de sintaxe abstrata padrão
      O LSP realmente resolve o problema das integrações M×N necessárias entre M editores e N linguagens, usando uma interface padrão para compiladores orientados a consultas
      O tree-sitter, em vez de resolver esse problema, torna muito mais fácil escrever as N integrações no editor ou na ferramenta
    • O principal ponto que me incomoda no tree-sitter é que essa abordagem não funciona para várias linguagens que me interessam
      Common Lisp não pode ser parseado sem uma implementação completa de Lisp, a gramática de Haskell é complexa o bastante para a gramática ficar incompleta, C/C++ já são difíceis de parsear corretamente só por causa do pré-processador, e o parsing de Perl é Turing-completo
      Como foi sugerido em outro lugar, em vez de fazer cada novo ecossistema reescrever parsers, talvez fosse melhor definir um formato que parsers existentes possam gerar como saída adicional
    • Será que daria para criar uma gramática de tree-sitter para linguagem natural como inglês, rotulando cada frase como uma unidade de sentença?
      Assim talvez o difftastic pudesse mostrar mudanças por frase em vez de por linha visual
      Em ensaios, diff por linha visual é estranho. Normalmente a frase alterada começa no meio de uma linha visual
  • Se você instalou via cargo, aqui vai uma forma de atualizar o difftastic e ferramentas relacionadas
    cargo install cargo-update
    cargo install-update --list
    cargo install-update --all
    Também existem alguns projetos interessantes em Rust que podem ser instalados via cargo
    https://mise.jdx.dev/ mise-en-place é uma alternativa rápida e flexível ao https://asdf-vm.com/
    https://github.com/ajeetdsouza/zoxide é um excelente substituto para cd: ele guarda os lugares por onde você passou e depois permite ir para "~/projects/helloworld" usando apenas uma correspondência parcial como "z hel"
    https://github.com/bootandy/dust complementa o du, mostrando quais diretórios estão consumindo mais espaço em disco

    • Para citar mais três ferramentas elegantes, https://github.com/eza-community/eza é um ls visualmente melhor, https://github.com/ClementTsang/bottom é um htop com gráficos, e https://github.com/sharkdp/bat é um cat com destaque de sintaxe
    • Gosto muito do zoxide. Também vale adicionar o lsd à lista. É um ls mais bonito
    • Instalei o mise-en-place, e é exatamente como eu gostaria que o asdf fosse
    • Entre as novas ferramentas do tipo du, a de que mais gosto é o dua-cli. É uma interface interativa de terminal no estilo clone do ncdu
      Descobri isso enquanto procurava uma alternativa porque o ncdu não tem modo claro
    • O ncdu continua sendo, de longe, o melhor substituto para du
  • Quase não consigo entender por que ferramentas como o git não oferecem isso por padrão
    Uso o difft há cerca de um ano, e minha maior reclamação é que ele dificulta voltar para outras ferramentas de diff em ambientes onde o difft não existe
    Fico curioso se já existe alguma pesquisa sobre ferramentas de diff semântico. Quero dizer, ferramentas que lidem com casos em que a sintaxe mudou, mas o significado continua o mesmo
    Parece um problema geral difícil, mas talvez seja possível ou útil em DSLs pequenas ou em subconjuntos de algumas linguagens

    • Acho certo usar o velho diff como padrão
      Ele já está presente em praticamente qualquer sistema onde alguém vai querer rodar git, é rápido, pequeno, e todo mundo conhece o uso básico
      Ainda assim, é bom que esse padrão possa ser trocado com facilidade
    • Estou criando o https://semanticdiff.com/, que detecta mudanças semânticas básicas, como trocar literais decimais por hexadecimais ou mudar a ordem das chaves em um objeto JSON
      Não é um utilitário de linha de comando, e sim uma extensão do VS Code e um GitHub App
      Se você quiser saber como funciona e qual é a diferença em relação ao difftastic, veja https://semanticdiff.com/blog/semanticdiff-vs-difftastic/
    • Difftastic é uma ferramenta útil, mas, na minha experiência, é lento demais para ser a opção padrão em uma ferramenta tão amplamente usada quanto o git
    • Para fazer isso, a ferramenta de diff praticamente vira um compilador
    • Estou usando o difft com git agora e gostei bastante
      Mesmo assim, uma das razões pelas quais acho que ele não deveria ser o padrão é que ele esconde diferenças de espaço em branco. Ainda não verifiquei se isso é configurável
  • Concordo totalmente com a pergunta sobre saber ler a sintaxe @@ -5,6 +5,7 @@ e com a explicação de que o Difftastic mostra os números reais das linhas dos arquivos antes e depois da mudança
    Instalei na hora e rodei git diff, e funcionou muito bem

    • Você não consegue ler essa sintaxe?
  • O fato de o GitHub não ter lançado algo assim parece uma grande lacuna de inovação de produto
    Ele não ajuda nem quando só a indentação muda, e normalmente mostra isso como uma exclusão enorme seguida de uma adição enorme
    O visualizador de diff do GitHub poderia e deveria ser mais inteligente

    • O GitHub não consegue lidar direito nem com reconhecimento sintático, quanto mais com diff semântico
      Na prática, ele nem consegue distinguir foo.cpp.in de foo.mk.in
      Qualquer foo.t é classificado como Perl, e não há como corrigir isso. Existe até um ticket com 10 anos
    • O tree-sitter é otimizado para desempenho no editor, não para precisão
      Na verdade, os próprios desenvolvedores principais do TS recomendam não se preocupar demais com a precisão da gramática[1]
      Para serviços como o GitHub, ou em posição semelhante, essa limitação pode ser fatal
      [1] https://github.com/tree-sitter/tree-sitter/issues/130#issuec...
    • O GitHub tem uma opção para ignorar espaços em branco no diff
  • Tentei mudar para essa ferramenta, mas achei ela barulhenta demais porque aplica uma formatação estranha até nas partes que não mudaram
    No fim, voltei para o icdiff[0]
    [0]: https://github.com/jeffkaufman/icdiff

  • Encontrei o Semantic Merge[1] há algum tempo, mas ele não era open source
    Este aqui não faz merge, só diff, mas pelo menos é open source e o diff é muito mais agradável de ver, então já troquei meu padrão por ele
    Fico curioso se há planos de expandi-lo com uma função de merge
    [1] https://docs.plasticscm.com/semanticmerge

    • Segundo o README do GitHub, o difftastic não faz merge
      Merge de AST é um problema difícil, e o difftastic não lida com isso
      Além disso, diff de AST é um processo com perdas do ponto de vista de diff textual. O difftastic ignora espaços em branco que não têm significado sintático, mas merge precisa rastrear espaços em branco
    • Eu também ia recomendar o Semantic Merge
      Quando eu fazia refatorações bagunçadas em projetos C# numa equipe grande, ele era realmente uma ferramenta salvadora
  • Discussões anteriores:
    https://news.ycombinator.com/item?id=27768861 (297 points | 3 years ago | 61 comments)
    https://news.ycombinator.com/item?id=32746258 (698 points | 2 years ago | 90 comments)
    https://news.ycombinator.com/item?id=30841244 (983 points | 2 years ago | 219 comments)

  • Parece uma ótima ferramenta, mas testei com alguns arquivos XML grandes e as linhas modificadas apareciam em verde normal, enquanto os atributos modificados apareciam em verde negrito, então ficou visualmente difícil distinguir
    Não consegui encontrar na documentação uma forma de mudar o estilo do diff ou definir outra cor para os casos em negrito
    Queria saber se alguém conhece um jeito

    • Infelizmente, parece que ainda não há suporte para personalização de cores
      Comentei nessa issue[1] dizendo que apoio esse recurso
      Se o que você quer estiver na mesma categoria, pode acrescentar sua opinião lá; se achar que é algo suficientemente diferente, talvez valha abrir uma nova issue
      [1]: https://github.com/Wilfred/difftastic/issues/611
  • É interessante que o pacote do Arch Linux tem 7MB, mas depois da instalação se expande para 80MB, e só o binário difft já tem 78MB
    Em um dataset ZFS com compressão LZ4 ativada, dizem que o du mostra 17MB
    Fico curioso por que algo que comprime tão bem dentro do binário não é simplesmente mantido comprimido. Parece até que, ao descomprimir na RAM, poderia carregar mais rápido

    • Como surgiu o assunto de compressão, fui conferir por curiosidade e, em btrfs zstd:1, fica em cerca de 10MB
      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
    • Isso provavelmente tem relação com o comentário sobre o parser de Nim ser um arquivo C de 60MB impossível de mesclar
      O Tree-sitter parece precisar de uma quantidade enorme de código, e boa parte disso parece redundante ou compressível
    • O executável não é carregado inteiro na RAM antes de iniciar
      Ele é mapeado em memória e só as partes realmente usadas são carregadas quando necessário
      Por isso, comprimir um binário grande acaba desativando a paginação sob demanda e pode até deixá-lo mais lento
      Alguns anos atrás, testei isso com um EXE de 280MB no Windows; ele realmente comprimia para algo em torno de 70MB, mas demorava alguns segundos a mais para iniciar do que o original
      Em alguns cenários, como executar o binário pela rede, isso pode fazer sentido, mas na maioria dos casos um binário sem compressão inicia mais rápido