npm, Cargo, Composer e pip interpretam a mesma abreviação de semver de formas diferentes, e apenas duas das treze expressões de intervalo que testei se comportam de forma idêntica em todos os quatro.

Escrevi parsers separados para cada gerenciador, segui suas especificações oficiais e, em seguida, testei cada intervalo contra uma matriz de dezenove números de versão. A grade de 13 × 19 (247 células) revelou um cenário fragmentado: apenas 17 células deram a mesma resposta “sim” ou “não” em todas as ferramentas, e a maioria dessas 17 foram rejeições unânimes. Em resumo, as ferramentas concordam com o “não” muito mais frequentemente do que com o “sim”.

Por que as ferramentas divergem

Todos os quatro gerenciadores afirmam seguir o Semantic Versioning, mas cada um adiciona suas próprias regras de abreviação.

  • Caret (^) e dot-x – o npm suporta caret; o pip ignora tanto o caret quanto o dot-x. Isso, por si só, cria uma divergência de 49 células entre o npm e o pip.
  • Versões puras (Bare versions) – o Cargo trata uma versão simples como 1.2.3 como um intervalo caret, significando “compatível com 1.x”. O npm e o Composer leem a mesma string como uma correspondência exata, aceitando apenas 1.2.3.
  • Tilde (~) – o tilde do Cargo fixa a versão minor (~1.2 corresponde a 1.2.*, mas não a 1.3.0). O tilde do Composer fixa a versão major (~1.2 corresponde a 1.*). As duas interpretações divergem em qualquer versão que altere o componente minor.

O tratamento de pré-lançamentos (pre-release) traz outra surpresa. Um intervalo como ^1.2.3 não inclui 2.0.0-rc.1, embora a parte numérica seja inferior ao limite superior. A regra é: pré-lançamentos são excluídos, a menos que o intervalo mencione explicitamente um identificador de pré-lançamento.

A única sintaxe segura entre ferramentas

O experimento mostra que intervalos de desigualdade explícitos — por exemplo, >=1.2.0 <2.0.0 — comportam-se da mesma maneira no npm, Cargo, Composer e pip. Cada gerenciador trata os dois limites como limites numéricos literais, sem semânticas ocultas de caret ou tilde.

Se você precisar expressar “qualquer versão 1.x que seja pelo menos 1.2”, escreva-a por extenso. Isso custa alguns caracteres extras, mas garante uma resolução previsível onde quer que o código seja executado.

Quando a abreviação ainda faz sentido

A abreviação continua sendo atraente para projetos de uma única linguagem. Dentro de um ecossistema puramente npm, ^1.2.3 captura de forma sucinta “compatível com futuros lançamentos minor e patch”. O mesmo vale para o caret do Cargo ou o tilde do Composer quando você sabe que a ferramenta consumidora não mudará. O perigo surge quando o mesmo manifesto é reutilizado entre diferentes linguagens, ou quando um job de CI puxa dependências de múltiplos ecossistemas.

Conclusão

Confiar em abreviações de semver para compatibilidade entre linguagens é um risco. A única maneira confiável de garantir que uma restrição de versão signifique a mesma coisa em todos os lugares é escrever desigualdades explícitas. Quando precisar de brevidade, permaneça dentro de um único ecossistema; quando precisar de consistência, troque a abreviação pela clareza.