Przepisywanie AST
Przepisywanie AST to wyszukiwanie AST z szablonem zastępczym. Greph używa tego samego mechanizmu dopasowywania co wyszukiwanie AST, a następnie wstawia przepisany szablon do źródła w dopasowanych zakresach bajtów. Otaczające białe znaki, wcięcia i komentarze są zachowywane; sam dopasowany kod jest drukowany ponownie (zob. Wewnątrz dopasowania).
Szybkie przykłady
# 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
Szablony zastępcze
Szablon zastępczy używa tej samej składni metazmiennych co wzorzec wyszukiwania. Przechwycone węzły są wstawiane z powrotem według nazwy:
| We wzorcu | W szablonie | Efekt |
|---|---|---|
$X |
$X |
Wstaw przechwycony węzeł |
$$$ARGS |
$$$ARGS |
Wstaw przechwyconą sekwencję węzłów |
$NAME (identyfikator) |
$NAME |
Wstaw przechwycony identyfikator |
Metazmienne występujące we wzorcu, ale nie w szablonie, są pomijane w przepisanym kodzie. Metazmienna występująca w szablonie, ale nie we wzorcu, nie jest obecnie odrzucana: zostaje wypisana dosłownie, więc -r '$o->$m($$$A, $EXTRA)' zapisuje zmienną $EXTRA w kodzie (#4). Najpierw sprawdź szablony za pomocą --dry-run.
Tryby
| Flaga | Działanie |
|---|---|
| (brak) | Zastosuj przepisywanie do każdego pasującego pliku |
--dry-run |
Wypisz zawartość przepisanych plików na standardowe wyjście, ale niczego nie zapisuj |
--interactive |
Poproś o potwierdzenie Rewrite <file>? [y/N] przed zapisaniem każdego pliku |
Domyślnie „zapisuj wszystkie zmiany”. Najpierw użyj --dry-run podczas dopracowywania wzorca, a następnie usuń flagę, gdy uznasz różnice za odpowiednie.
Zachowanie formatowania
Greph nie formatuje ponownie pliku. Mechanizm dopasowywania zapisuje zakres bajtów każdego dopasowania w oryginalnym źródle, a mechanizm przepisujący wstawia wyrenderowany szablon bezpośrednio w tych zakresach. Nietknięty kod pozostaje dokładnie taki jak wcześniej, w tym:
- Wcięcia i końcowe białe znaki
- Komentarze w wierszu i samodzielne
- Układ pustych wierszy
- Styl cudzysłowów i konkatenacji w ciągach znaków
- Styl znaczników otwierających i zamykających PHP
To celowy kompromis w porównaniu z pełnym formatowaniem: Greph nie „porządkuje” kodu, którego nie dopasowało, ale też nie wprowadza niepotrzebnych różnic w niezwiązanych wierszach.
Wewnątrz dopasowania
Sam dopasowany zakres zostaje zastąpiony szablonem przetworzonym przez drukarkę AST w PHP-Parser; przechwycone węzły są drukowane ponownie, a nie kopiowane jako tekst źródłowy. Obecnie oznacza to (#2):
- Komentarz, który nie jest przypisany do kolejnego węzła wewnątrz dopasowania, zostaje usunięty, na przykład komentarz na końcu po ostatnim elemencie tablicy lub komentarz w pustej tablicy.
- Komentarze przypisane do przechwyconych węzłów są zachowywane, ale przenoszone do osobnego wiersza, co może zamienić jednowierszowe wyrażenie w kilka wierszy.
- Podziały wierszy wewnątrz dopasowania nie są zachowywane: wielowierszowe
array(...)bez takich komentarzy staje się jednowierszowym[...]. - Zapis literałów jest normalizowany:
0x1Fstaje się0x1f,1_000staje się1000, sekwencje ucieczki takie jak"\x41"są zapisywane jako znak ("A"), a elastyczne heredoki tracą wcięcia.
// before
$d = array(
"a" => 1, // trailing comment
);
// after -p 'array($$$ITEMS)' -r '[$$$ITEMS]'
$d = ["a" => 1];
Sprawdź wynik --dry-run pod kątem dopasowań zawierających komentarze lub ręcznie sformatowane literały.
Dopasowania wielowierszowe i zagnieżdżone
Wzorce wielowierszowe działają tak samo jak jednowierszowe. Zastępnik jest renderowany przez drukarkę AST po podstawieniu metazmiennych, więc układ zastępowanego kodu pochodzi z drukarki, a nie z oryginalnego dopasowania.
Greph przepisuje każdy plik w jednym przebiegu i nigdy nie stosuje dwóch nakładających się dopasowań. Przechodzi po dopasowaniach od końca pliku do początku i pomija te, które nakładają się na już przepisane. W przypadku zagnieżdżonych dopasowań zaczynających się w różnych przesunięciach przepisuje dopasowanie wewnętrzne, a zewnętrzne pozostawia bez zmian:
// 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]];
Gdy dwa dopasowania zaczynają się na tej samej pozycji (na przykład łańcuch metod dopasowany przez $obj->$method($$$ARGS)), przepisywane jest dopasowanie zewnętrzne. Jeśli wzorzec może być zagnieżdżony, uruchamiaj przepisywanie ponownie, aż --dry-run niczego nie wypisze (#3). ast-grep przepisuje zewnętrzne z dwóch zagnieżdżonych dopasowań, więc vendor/bin/sg i nadrzędny sg dają tu różne wyniki.
Użycie programowe
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() samodzielnie nie zapisuje plików. Za zatwierdzanie zmian (lub pomijanie ich w przypadkach --dry-run i odrzuconych --interactive) odpowiada CLI. Dzięki temu z fasady mogą bezpiecznie korzystać zautomatyzowani agenci, którzy chcą sprawdzić różnice przed zastosowaniem zmian.
RewriteResult udostępnia:
file: ścieżka bezwzględnaoriginalContents: zawartość pliku przed przepisaniemrewrittenContents: zawartość pliku po przepisaniureplacementCount: liczba zamian strukturalnychchanged(): funkcja pomocnicza zwracającatrue, gdyoriginalContents !== rewrittenContents
Kiedy używać przepisywania
Przepisywanie sprawdzi się, gdy:
- Transformacja jest lokalna i strukturalna (nie obejmuje przebudowy całego pliku).
- Chcesz deterministycznego, powtarzalnego refaktoryzowania, które możesz ponawiać, aż nic się nie zmieni.
- Chcesz podejrzeć zmianę przed jej zastosowaniem.
- Chcesz zintegrować refaktoryzację ze skryptem lub zadaniem CI bez uruchamiania zewnętrznego pliku binarnego.
Przepisywanie nie sprawdzi się, gdy:
- Transformacja zależy od informacji o typach, których PHP-Parser nie potrafi ustalić na podstawie pojedynczego pliku.
- Transformacja wymaga dodania lub usunięcia importów poza granicami pliku.
- Potrzebujesz formatowania, a nie refaktoryzacji (użyj PHP-CS-Fixer).