Επανεγγραφή AST
Η επανεγγραφή AST είναι αναζήτηση AST με πρότυπο αντικατάστασης. Το Greph εκτελεί τον ίδιο μηχανισμό αντιστοίχισης με την αναζήτηση AST και, στη συνέχεια, εισάγει το τροποποιημένο πρότυπο στον πηγαίο κώδικα, στις περιοχές byte που αντιστοιχίστηκαν. Τα κενά, οι εσοχές και τα σχόλια γύρω από αυτές διατηρούνται· ο κώδικας που αντιστοιχίστηκε εκτυπώνεται ξανά (δείτε την ενότητα Μέσα σε μια αντιστοιχία).
Γρήγορα παραδείγματα
# 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
Πρότυπα αντικατάστασης
Το πρότυπο αντικατάστασης χρησιμοποιεί την ίδια σύνταξη μεταβλητών μετά-επιπέδου με το μοτίβο αναζήτησης. Οι κόμβοι που έχουν συλληφθεί εισάγονται ξανά βάσει ονόματος:
| Στο μοτίβο | Στο πρότυπο | Αποτέλεσμα |
|---|---|---|
$X |
$X |
Εισαγωγή του κόμβου που έχει συλληφθεί |
$$$ARGS |
$$$ARGS |
Εισαγωγή της ακολουθίας κόμβων που έχει συλληφθεί |
$NAME (αναγνωριστικό) |
$NAME |
Εισαγωγή του αναγνωριστικού που έχει συλληφθεί |
Οι μεταβλητές μετά-επιπέδου που υπάρχουν στο μοτίβο αλλά όχι στο πρότυπο αφαιρούνται από τον κώδικα που προκύπτει μετά την επανεγγραφή. Προς το παρόν, δεν απορρίπτεται μια μεταβλητή μετά-επιπέδου που υπάρχει στο πρότυπο αλλά όχι στο μοτίβο: εκτυπώνεται κυριολεκτικά, επομένως το -r '$o->$m($$$A, $EXTRA)' γράφει μια μεταβλητή $EXTRA στον κώδικα (#4). Ελέγξτε πρώτα τα πρότυπα με το --dry-run.
Λειτουργίες
| Σημαία | Συμπεριφορά |
|---|---|
| (καμία) | Εφαρμογή της επανεγγραφής σε κάθε αρχείο που αντιστοιχίζεται |
--dry-run |
Εκτύπωση των περιεχομένων του τροποποιημένου αρχείου στην τυπική έξοδο, χωρίς εγγραφή |
--interactive |
Ερώτηση προς το Rewrite <file>? [y/N] πριν από την εγγραφή κάθε αρχείου |
Η προεπιλογή είναι «εγγραφή όλων των αλλαγών». Χρησιμοποιήστε πρώτα το --dry-run όσο επεξεργάζεστε ένα μοτίβο και, όταν είστε ικανοποιημένοι με τη διαφορά, αφαιρέστε τη σημαία.
Διατήρηση μορφοποίησης
Το Greph δεν αλλάζει τη μορφοποίηση του αρχείου. Ο μηχανισμός αντιστοίχισης καταγράφει την περιοχή byte κάθε αντιστοιχίας στον αρχικό πηγαίο κώδικα και ο μηχανισμός επανεγγραφής εισάγει απευθείας το αποδοσμένο πρότυπο σε αυτές τις περιοχές. Ο κώδικας που δεν τροποποιείται παραμένει ακριβώς όπως ήταν, συμπεριλαμβανομένων των εξής:
- Εσοχές και κενά στο τέλος γραμμών
- Ενσωματωμένα και αυτόνομα σχόλια
- Διάταξη κενών γραμμών
- Στυλ εισαγωγικών συμβολοσειρών και συνένωσης
- Στυλ ετικετών ανοίγματος και κλεισίματος PHP
Αυτός είναι ένας σκόπιμος συμβιβασμός σε σχέση με την πλήρη αναδιαμόρφωση: το Greph δεν «συμμαζεύει» κώδικα που δεν αντιστοίχισε, αλλά επίσης δεν δημιουργεί περιττές διαφορές σε άσχετες γραμμές.
Μέσα σε μια αντιστοιχία
Η ίδια η περιοχή που αντιστοιχίστηκε αντικαθίσταται από το πρότυπο, το οποίο αποδίδεται μέσω του όμορφου εκτυπωτή του PHP-Parser· οι κόμβοι που έχουν συλληφθεί εκτυπώνονται ξανά αντί να αντιγράφονται ως κείμενο πηγαίου κώδικα. Προς το παρόν, αυτό σημαίνει τα εξής (#2):
- Ένα σχόλιο που δεν είναι προσαρτημένο σε επόμενο κόμβο μέσα στην αντιστοιχία αφαιρείται· για παράδειγμα, ένα σχόλιο μετά το τελευταίο στοιχείο πίνακα ή ένα σχόλιο μέσα σε κατά τα άλλα κενό πίνακα.
- Τα σχόλια που είναι προσαρτημένα σε κόμβους που έχουν συλληφθεί διατηρούνται, αλλά μεταφέρονται στη δική τους γραμμή, κάτι που μπορεί να μετατρέψει μια έκφραση μίας γραμμής σε πολλές.
- Οι αλλαγές γραμμής μέσα στην αντιστοιχία δεν διατηρούνται: ένα πολυγραμμικό
array(...)χωρίς τέτοια σχόλια γίνεται μονογραμμικό[...]. - Η γραφή των κυριολεκτικών τιμών κανονικοποιείται: το
0x1Fγίνεται0x1f, το1_000γίνεται1000, ακολουθίες διαφυγής όπως το"\x41"γράφονται ως χαρακτήρας ("A"), ενώ οι ευέλικτες heredoc χάνουν την εσοχή τους.
// before
$d = array(
"a" => 1, // trailing comment
);
// after -p 'array($$$ITEMS)' -r '[$$$ITEMS]'
$d = ["a" => 1];
Ελέγξτε την έξοδο --dry-run για αντιστοιχίες που περιέχουν σχόλια ή κυριολεκτικές τιμές με χειροκίνητη μορφοποίηση.
Πολυγραμμικές και εμφωλευμένες αντιστοιχίες
Τα πολυγραμμικά μοτίβα λειτουργούν όπως και τα μονογραμμικά. Ο όμορφος εκτυπωτής αποδίδει την αντικατάσταση αφού εισαχθούν οι μεταβλητές μετά-επιπέδου, επομένως η διάταξη του κώδικα που αντικαθίσταται προκύπτει από τον εκτυπωτή και όχι από την αρχική αντιστοιχία.
Το Greph επανεγγράφει κάθε αρχείο σε ένα πέρασμα και δεν εφαρμόζει ποτέ δύο επικαλυπτόμενες αντιστοιχίες. Διατρέχει τις αντιστοιχίες από το τέλος του αρχείου προς τα πίσω και παραλείπει κάθε αντιστοιχία που επικαλύπτεται με μία που έχει ήδη επανεγγραφεί. Για εμφωλευμένες αντιστοιχίες που αρχίζουν σε διαφορετικές θέσεις, επανεγγράφεται η εσωτερική και η εξωτερική μένει ως έχει:
// 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]];
Όταν δύο αντιστοιχίες αρχίζουν στην ίδια θέση (για παράδειγμα, μια αλυσίδα μεθόδων που αντιστοιχίζεται από το $obj->$method($$$ARGS)), επανεγγράφεται η εξωτερική. Όταν ένα μοτίβο μπορεί να εμφωλευτεί, εκτελέστε ξανά την επανεγγραφή μέχρι το --dry-run να μην εμφανίζει τίποτα (#3). Το ast-grep επανεγγράφει την εξωτερική από δύο εμφωλευμένες αντιστοιχίες, επομένως τα vendor/bin/sg και το upstream sg δίνουν διαφορετικά αποτελέσματα εδώ.
Προγραμματιστική χρήση
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() δεν γράφει τα ίδια τα αρχεία. Το CLI είναι υπεύθυνο για την καταχώριση των αλλαγών (ή την παράλειψή τους σε περιπτώσεις --dry-run και απόρριψης του --interactive). Έτσι, το facade είναι ασφαλές για κλήση από αυτοματοποιημένους πράκτορες που θέλουν να ελέγξουν τη διαφορά πριν την εφαρμόσουν.
Το RewriteResult παρέχει:
file: απόλυτη διαδρομήoriginalContents: τα περιεχόμενα του αρχείου πριν από την αναδιαγραφήrewrittenContents: τα περιεχόμενα του αρχείου μετά την αναδιαγραφήreplacementCount: αριθμός δομικών αντικαταστάσεωνchanged(): βοηθητική συνάρτηση που επιστρέφειtrueόταν ισχύει τοoriginalContents !== rewrittenContents
Πότε να χρησιμοποιείτε την αναδιαγραφή
Η αναδιαγραφή είναι το κατάλληλο εργαλείο όταν:
- Ο μετασχηματισμός είναι τοπικός και δομικός (όχι αναδιάρθρωση ολόκληρου του αρχείου).
- Θέλετε έναν ντετερμινιστικό, επαναλήψιμο ανασχηματισμό που μπορείτε να εκτελείτε ξανά μέχρι να μην αλλάζει τίποτα.
- Θέλετε να δείτε σε προεπισκόπηση την αλλαγή πριν την εφαρμόσετε.
- Θέλετε να ενσωματώσετε τον ανασχηματισμό σε ένα script ή μια εργασία CI χωρίς να καλέσετε εξωτερικό εκτελέσιμο αρχείο.
Η αναδιαγραφή δεν είναι το κατάλληλο εργαλείο όταν:
- Ο μετασχηματισμός εξαρτάται από πληροφορίες τύπων που το PHP-Parser δεν μπορεί να επιλύσει από ένα μόνο αρχείο.
- Ο μετασχηματισμός χρειάζεται προσθήκη ή αφαίρεση εισαγωγών σε όριο αρχείων.
- Θέλετε μορφοποιητή και όχι ανασχηματισμό (χρησιμοποιήστε το PHP-CS-Fixer).