Μετάβαση στο περιεχόμενο

Δομική αναδιάρθρωση κώδικα

Αυτός ο οδηγός δείχνει πώς να κάνετε έναν πράκτορα να πραγματοποιήσει μια ευρεία μηχανική αλλαγή — ένα codemod, μια μαζική μετεγκατάσταση API, ένα μοτίβο που θέλετε να εξαφανιστεί — χρησιμοποιώντας δομική αναζήτηση και επανεγγραφή, με την αλλαγή να προετοιμάζεται για έλεγχο πριν γραφτεί οτιδήποτε.

Το ast_grep αντιστοιχίζει κώδικα με βάση το σχήμα. Το ast_edit τον επανεγγράφει. Το resolve εφαρμόζει ή απορρίπτει τις αλλαγές.

Τα δομικά εργαλεία καλύπτουν τις γλώσσες που αναλύει το γράφημα κώδικα: Dart, JavaScript, TypeScript, TSX, PHP, Python, Rust, Zig, C, C++, Go, Java, Ruby, C#, Swift, Kotlin, R, Assembly, MATLAB και Ada. Χρειάζονται τις γραμματικές του tree-sitter προετοιμασμένες, κάτι που συμβαίνει πάντα σε μια έκδοση που διανέμεται — μια γραμματική που λείπει σταματά τον διακομιστή, αντί να υποβαθμίσει την αναζήτηση.

Μην ξαναγράφετε ποτέ κάτι που δεν έχετε κοιτάξει. Ζητήστε από τον πράκτορα να κάνει αναζήτηση:

Βρείτε κάθε σημείο όπου καλούμε dispose() σε μια nullable μεταβλητή που μόλις ελέγξαμε για null.

ast_grep(pattern: "if ($X != null) $X.dispose()", language: "dart")

Η έξοδος είναι file:line μαζί με τον πηγαίο κώδικα που αντιστοιχίστηκε σε κάθε σημείο.

Τι σας προσφέρει η σύνταξη των μοτίβων

Ενότητα με τίτλο «Τι σας προσφέρει η σύνταξη των μοτίβων»
Στοιχείο Αντιστοιχεί σε
$NAME Οποιονδήποτε μεμονωμένο κόμβο, καταγεγραμμένο
$$$NAME Οποιαδήποτε ακολουθία αδελφών κόμβων — ορίσματα, εντολές
$_ Οτιδήποτε, χωρίς να καταγράφεται με όνομα

Αυτό που έχει σημασία: ένα $NAME που επαναλαμβάνεται πρέπει να καταγράφει το ίδιο κείμενο.

if ($X != null) $X.dispose()

βρίσκει μια προστατευτική συνθήκη που κάνει dispose σε ό,τι εξέτασε. Δεν βρίσκει το if (a != null) b.dispose() — που συνήθως είναι το σφάλμα που ψάχνατε. Καμία αναζήτηση κειμένου δεν μπορεί να το εκφράσει αυτό.

Δύο πράγματα να δοκιμάσετε, με αυτή τη σειρά:

  1. Κάντε το πιο μικρό. foo($X) πριν από το if (a) { foo($X); }. Ένα μοτίβο που δεν είναι από μόνο του έγκυρος κώδικας συχνά δεν είναι η δομή που είχατε στο μυαλό σας.
  2. Δηλώστε τη γλώσσα. Το language: συμπεραίνεται μόνο όταν το path δείχνει ένα μεμονωμένο αρχείο.

Ένα μοτίβο που κανένα πλαίσιο ανάλυσης δεν αποδέχεται αναφέρεται ως μη αναλύσιμο — το εργαλείο το λέει, αντί να αναφέρει «καμία αντιστοίχιση» για κάτι που ποτέ δεν διαβαζόταν όπως φαίνεται.

Επανεγγράψτε, και κοιτάξτε πριν εφαρμοστεί

Ενότητα με τίτλο «Επανεγγράψτε, και κοιτάξτε πριν εφαρμοστεί»
ast_edit(
pattern: "if ($X != null) $X.dispose()",
rewrite: "$X?.dispose()",
language: "dart",
)

Το αποτέλεσμα είναι μια πρόταση:

(proposed) ast_edit: if ($X != null) $X.dispose() → $X?.dispose()
12 replacements in 4 files:
lib/features/a/x.dart (3)
lib/features/b/y.dart (5)
…
Nothing has been written. Call resolve with edit_id "edit_1" and action
"accept" to apply it, or "discard" to drop it.

Τίποτα δεν βρίσκεται ακόμη στον δίσκο. Διαβάστε τη λίστα, ελέγξτε δύο-τρία από τα σημεία που ανέφερε η αναζήτηση και μετά:

resolve(edit_id: "edit_1", action: "accept")

ή

resolve(edit_id: "edit_1", action: "discard")

Ελέγξτε τη δουλειά σας με τον μεταγλωττιστή

Ενότητα με τίτλο «Ελέγξτε τη δουλειά σας με τον μεταγλωττιστή»

Το νόημα του να το κάνετε αυτό δομικά είναι ότι η αλλαγή είναι ευρεία. Οι ευρείες αλλαγές είναι ακριβώς εκεί όπου μια επανεγγραφή που φαίνεται λογική σπάει έναν τύπο. Ζητήστε διαγνωστικά αφού κάνετε resolve:

lsp(action: "diagnostics", file: "*")

Το file: "*" εκτελεί τον δικό του ελεγκτή του έργου (dart analyze, tsc --noEmit, cargo check, go build) αντί για ερώτημα ανά αρχείο — το σωστό είδος ερώτησης μετά από μια αλλαγή που άγγιξε σαράντα αρχεία.

Λυμένο παράδειγμα: μετεγκατάσταση μιας κλήσης

Ενότητα με τίτλο «Λυμένο παράδειγμα: μετεγκατάσταση μιας κλήσης»

Ας πούμε ότι το log(msg, level) γίνεται log(level: level, message: msg).

ast_grep(pattern: "log($MSG, $LEVEL)", language: "dart")

Κοιτάξτε τα αποτελέσματα. Αν κάποια από αυτά είναι ένα διαφορετικό log με δύο ορίσματα, περιορίστε το μοτίβο ορίζοντας το path στον φάκελο που περιέχει αυτό που εννοείτε, αντί να προσπαθήσετε να εκφράσετε τη διαφορά μέσα στο μοτίβο.

ast_edit(
pattern: "log($MSG, $LEVEL)",
rewrite: "log(level: $LEVEL, message: $MSG)",
path: "lib/features/telemetry",
language: "dart",
)

Έπειτα resolve, μετά lsp(action: "diagnostics", file: "*").

Κατάσταση Καλύτερο εργαλείο
Μετονομασία ενός συμβόλου lsp_rename — ακολουθεί αναφορές, όχι σχήματα
Εύρεση συμβολοσειράς, σχολίου ή ονόματος αρχείου search (περιεχόμενα) / search_files (ονόματα)
«Τι σπάει αν αλλάξω αυτό;» Το γράφημα κώδικα
Γλώσσα που δεν καλύπτουν οι γραμματικές search μαζί με edit