Referências retrospectivas em regex: \n e \k<name>
Entenda as referências retrospectivas em expressões regulares no JavaScript: como usar \1, \k<name> e $1 em replace() com exemplos práticos.
A maior parte de uma expressão regular corresponde a texto fixo, mas às vezes você precisa corresponder a um texto que deve ser idêntico a algo que você capturou antes — sem saber antecipadamente qual é esse texto. Uma referência retrospectiva resolve exatamente isso. Ela permite que um padrão diga "corresponda à mesma coisa que o grupo capturou há pouco."
Usos clássicos para referências retrospectivas incluem detectar uma palavra duplicada (the the), corresponder a uma string envolvida em aspas balanceadas ("..." ou '...', mas não "...'), ou verificar se uma tag HTML-like é fechada pelo mesmo nome de tag. Nada disso é possível com padrões literais comuns, pois o texto a corresponder não é conhecido até que a regex seja executada.
Este guia aborda referências retrospectivas numeradas (\1, \2, …), referências retrospectivas nomeadas (\k<name>), como os grupos são numerados, as armadilhas mais comuns e como reutilizar texto capturado em String.prototype.replace().
Como usar referências retrospectivas
Dentro de um padrão, uma barra invertida seguida de um número refere-se ao texto capturado por um grupo de captura. \1 é qualquer coisa que o grupo 1 correspondeu, \2 é o grupo 2, e assim por diante. O ponto-chave: ele corresponde ao texto capturado, não ao padrão do grupo novamente.
Aqui (\w+) captura uma palavra no grupo 1, \s corresponde ao espaço, e \1 exige a mesma palavra novamente. Então hello hello corresponde, mas hello world não — \1 deve ser igual ao que o grupo 1 capturou, não apenas corresponder a \w+ uma segunda vez.
Como os grupos são numerados
Os números dos grupos são atribuídos pela posição do parêntese de abertura de cada grupo, da esquerda para a direita. Isso importa quando você tem múltiplos grupos ou grupos aninhados:
A correspondência inteira é o grupo 0 (m[0]), por isso o primeiro grupo de captura é \1, não \0. Para grupos aninhados, o grupo externo recebe o número menor porque seu ( vem primeiro.
Usando grupos nomeados
As referências numeradas ficam difíceis de ler conforme o padrão cresce. Em vez disso, você pode nomear um grupo com (?<name>…) e referenciá-lo com \k<name>. Para mais detalhes sobre como declarar grupos nomeados, consulte Grupos de Captura.
Aqui (?<word>\w+) é um grupo nomeado e \k<word> o referencia retrospectivamente. Após uma correspondência bem-sucedida, o texto capturado também está disponível no objeto match.groups. Grupos nomeados e \k<name> funcionam em todos os navegadores modernos e no Node.js atual sem nenhuma flag.
Reutilizando capturas em replace()
O uso mais comum do dia a dia de referências retrospectivas não é dentro do padrão — é na string de substituição de String.prototype.replace(). Lá você referencia o texto capturado com $1, $2, … (ou $<name> para grupos nomeados).
Um exemplo elegante colapsa uma palavra duplicada acidentalmente em uma só:
Observe a distinção: \1 (barra invertida) é usado dentro do padrão, enquanto $1 (cifrão) é usado na string de substituição. Confundi-los é uma fonte frequente de bugs.
Armadilha: grupos não participantes
Uma referência retrospectiva a um grupo que não participou da correspondência tem um comportamento especial. Se o grupo nunca correspondeu (por exemplo, estava dentro de uma alternativa não utilizada), seu valor capturado é undefined, e em JavaScript a referência retrospectiva então corresponde à string vazia — ela tem sucesso sem consumir nada.
Isso é fácil de deixar passar: você pode esperar que \1 falhe quando o grupo não correspondeu, mas em vez disso ele silenciosamente corresponde a nada. Estruture sua alternância com cuidado se você depende de um grupo ter sempre capturado algo.
Uma referência retrospectiva genuína: aspas balanceadas
Um padrão prático que requer uma referência retrospectiva é corresponder a uma string entre aspas onde a aspa de fechamento deve ser o mesmo caractere que a de abertura — "..." e '...' são válidos, mas "...' não é.
O grupo (['"]) captura qualquer caractere de aspa que abriu a string, e \1 força o fechamento a ser exatamente esse caractere. Um simples ["'].*?["'] não poderia garantir isso — ele corresponderia felizmente a "...'. Esta é a diferença entre um lookahead/lookbehind (que apenas afirma) e uma referência retrospectiva (que corresponde ao texto capturado novamente).
Conclusão
Use uma referência retrospectiva sempre que uma parte posterior da correspondência precisar ser igual ao texto correspondido anteriormente — palavras duplicadas, aspas balanceadas, tags com o mesmo nome, ou regras do tipo "caracteres adjacentes devem ser diferentes". Lembre-se dos três pontos essenciais:
- Grupos numerados são contados pelo seu
(de abertura, começando em\1; a correspondência inteira é o grupo 0. - Use
\1/\k<name>dentro do padrão, e$1/$<name>emreplace(). - Um grupo não participante faz sua referência retrospectiva corresponder à string vazia, portanto proteja suas alternâncias.
Para os blocos de construção, revise Grupos de Captura, Classes de Caracteres e Lookahead e Lookbehind.