
# Reescrita AST

A reescrita AST é uma pesquisa AST com um modelo de substituição. Greph executa o mesmo comparador da [pesquisa AST](https://greph.inline0.dev/pt/docs/modes/ast) e, em seguida, insere o modelo reescrito no código-fonte nos intervalos de bytes correspondentes. Os espaços em branco, a indentação e os comentários ao redor são preservados; o próprio código correspondente é impresso novamente (consulte [Dentro de uma correspondência](#inside-a-match)).

## Exemplos rápidos

```bash
# Convert array() literals to []
./vendor/bin/greph -p 'array($$$ITEMS)' -r '[$$$ITEMS]' --dry-run src
./vendor/bin/greph -p 'array($$$ITEMS)' -r '[$$$ITEMS]' src

# isset ternary -> null coalesce
./vendor/bin/greph -p 'isset($X) ? $X : $Y' -r '$X ?? $Y' --dry-run src

# Rename a method call
./vendor/bin/greph -p '$obj->oldName($$$ARGS)' -r '$obj->newName($$$ARGS)' src

# Confirm each file before writing
./vendor/bin/greph -p 'array($$$ITEMS)' -r '[$$$ITEMS]' --interactive src
```

## Modelos de substituição

O modelo de substituição usa a mesma sintaxe de metavariáveis do padrão de pesquisa. Os nós capturados são inseridos novamente pelo nome:

| No padrão | No modelo | Efeito |
|---|---|---|
| `$X` | `$X` | Insere o nó capturado |
| `$$$ARGS` | `$$$ARGS` | Insere a sequência de nós capturada |
| `$NAME` (identificador) | `$NAME` | Insere o identificador capturado |

As metavariáveis que aparecem no padrão, mas não no modelo, são removidas do código reescrito. Uma metavariável que aparece no modelo, mas não no padrão, não é rejeitada atualmente: ela é impressa literalmente, então `-r '$o->$m($$$A, $EXTRA)'` grava uma variável `$EXTRA` no código ([#4](https://github.com/inline0/greph/issues/4)). Verifique os modelos com `--dry-run` primeiro.

## Modos

| Sinalizador | Comportamento |
|---|---|
| (nenhum) | Aplica a reescrita a todos os arquivos correspondentes |
| `--dry-run` | Imprime o conteúdo do arquivo reescrito na saída padrão, sem gravá-lo |
| `--interactive` | Solicita confirmação `Rewrite <file>? [y/N]` antes de gravar cada arquivo |

O padrão é “gravar tudo o que mudar”. Use `--dry-run` primeiro ao ajustar um padrão e remova o sinalizador quando estiver satisfeito com as diferenças.

## Preservação da formatação

Greph não reformata o arquivo. O comparador registra o intervalo de bytes de cada correspondência no código-fonte original, e o reescritor insere o modelo renderizado diretamente nesses intervalos. O código não alterado permanece exatamente como estava, incluindo:

- Indentação e espaços em branco no fim da linha
- Comentários em linha e isolados
- Disposição das linhas em branco
- Estilo de aspas e concatenação de strings
- Estilo das tags de abertura/fechamento PHP

Essa é uma escolha deliberada em troca da reformatação completa: Greph não vai “arrumar” o código que não correspondeu, mas também não introduz diferenças ruidosas em linhas sem relação.

### Dentro de uma correspondência

O próprio intervalo correspondente é substituído pelo modelo renderizado usando o impressor de código de PHP-Parser, que imprime novamente os nós capturados em vez de copiá-los como texto-fonte. Atualmente, isso significa ([#2](https://github.com/inline0/greph/issues/2)):

- Um comentário que não esteja associado a um nó seguinte dentro da correspondência é removido. Por exemplo, um comentário após o último item de array ou um comentário em um array vazio.
- Comentários associados a nós capturados são mantidos, mas passam para a própria linha, o que pode transformar uma expressão de uma linha em várias linhas.
- Quebras de linha dentro da correspondência não são mantidas: um `array(...)` de várias linhas sem esses comentários se torna um `[...]` de uma linha.
- A grafia de literais é normalizada: `0x1F` se torna `0x1f`, `1_000` se torna `1000`, sequências de escape como `"\x41"` são escritas como o caractere (`"A"`), e heredocs flexíveis perdem a indentação.

```php
// before
$d = array(
    "a" => 1,  // trailing comment
);

// after -p 'array($$$ITEMS)' -r '[$$$ITEMS]'
$d = ["a" => 1];
```

Revise a saída de `--dry-run` para encontrar correspondências que contenham comentários ou literais formatados manualmente.

## Correspondências multilinha e aninhadas

Padrões multilinha funcionam da mesma forma que os de uma linha. A substituição é renderizada pelo impressor de código com as metavariáveis inseridas, então a disposição do código substituído vem do impressor, não da correspondência original.

Greph reescreve cada arquivo em uma única passagem e nunca aplica duas correspondências sobrepostas. Percorre as correspondências de trás para a frente e ignora qualquer uma que se sobreponha a outra já reescrita. Para correspondências aninhadas que começam em posições diferentes, a interna é reescrita e a externa permanece como estava:

```php
// before
$b = array(1, array(2));

// after one run of -p 'array($$$ITEMS)' -r '[$$$ITEMS]'
$b = array(1, [2]);

// after a second run
$b = [1, [2]];
```

Quando duas correspondências começam na mesma posição (por exemplo, uma cadeia de métodos correspondente a `$obj->$method($$$ARGS)`), a correspondência externa é reescrita. Quando um padrão pode ser aninhado, execute a reescrita novamente até que `--dry-run` não imprima nada ([#3](https://github.com/inline0/greph/issues/3)). ast-grep reescreve a correspondência externa de duas aninhadas, então `vendor/bin/sg` e o `sg` upstream produzem resultados diferentes aqui.

## Uso programático

```php
use Greph\Greph;
use Greph\Ast\AstSearchOptions;

$rewrites = Greph::rewriteAst(
    'array($$$ITEMS)',
    '[$$$ITEMS]',
    'src',
    new AstSearchOptions(jobs: 4),
);

foreach ($rewrites as $result) {
    if (!$result->changed()) {
        continue;
    }

    echo "{$result->file}: {$result->replacementCount} replacement(s)\n";
    file_put_contents($result->file, $result->rewrittenContents);
}
```

`Greph::rewriteAst()` não grava arquivos por conta própria. O CLI é responsável por confirmar as alterações (ou ignorá-las em casos de `--dry-run` e `--interactive` recusado). Isso torna a fachada segura para chamadas de agentes automatizados que queiram inspecionar as diferenças antes de aplicá-las.

`RewriteResult` expõe:

- `file`: caminho absoluto
- `originalContents`: o conteúdo do arquivo antes da reescrita
- `rewrittenContents`: o conteúdo do arquivo após a reescrita
- `replacementCount`: número de substituições estruturais
- `changed()`: auxiliar que retorna `true` quando `originalContents !== rewrittenContents`

## Quando usar rewrite

Rewrite é a ferramenta certa quando:

- A transformação é local e estrutural (não uma reestruturação do arquivo inteiro).
- Você quer uma refatoração determinística e repetível, que possa executar novamente até que nada mude.
- Você quer visualizar a alteração antes de aplicá-la.
- Você quer integrar a refatoração a um script ou trabalho de CI sem invocar um binário externo.

Rewrite não é a ferramenta certa quando:

- A transformação depende de informações de tipos que PHP-Parser não consegue resolver a partir de um único arquivo.
- A transformação precisa adicionar ou remover imports além dos limites do arquivo.
- Você quer um formatador em vez de uma refatoração (use PHP-CS-Fixer).
