Πόσες φορές έχετε χρησιμοποιήσει τη λειτουργία εύρεσης και αντικατάστασης σε έναν κατάλογο για να κάνετε αλλαγές στα αρχεία προέλευσης JavaScript; Εάν είστε καλοί, έχετε φανταστεί και χρησιμοποιείτε τυπικές εκφράσεις με ομάδες σύλληψης, γιατί αξίζει τον κόπο αν η βάση του κώδικα σας είναι αρκετά μεγάλη. Ωστόσο, το Regex έχει όρια. Για μη ασήμαντες αλλαγές, χρειάζεστε έναν προγραμματιστή που κατανοεί τον κώδικα στο πλαίσιο και είναι επίσης πρόθυμος να αναλάβει τη μακρά, κουραστική και επιρρεπή σε σφάλματα διαδικασία.
Εδώ μπαίνουν τα 'codemods'.
Τα Codemods είναι σενάρια που χρησιμοποιούνται για την επανεγγραφή άλλων σεναρίων. Σκεφτείτε τα ως λειτουργίες εύρεσης και αντικατάστασης που μπορούν να διαβάζουν και να γράφουν κώδικα. Μπορείτε να τους χρησιμοποιήσετε για να ενημερώσετε τον πηγαίο κώδικα για να ταιριάζει στις συμβάσεις κωδικοποίησης μιας ομάδας, να κάνετε ευρείες αλλαγές όταν τροποποιείται ένα API ή ακόμα και να διορθώσετε αυτόματα τον υπάρχοντα κώδικα όταν το δημόσιο πακέτο σας κάνει μια αλλαγή.
Σε αυτό το άρθρο, θα εξερευνήσουμε ένα κιτ εργαλείων για κωδικούς κωδικούς που ονομάζονται 'jscodeshift', δημιουργώντας ταυτόχρονα τρία κωδικοκώδικα με αυξανόμενη πολυπλοκότητα. Μέχρι το τέλος θα έχετε ευρεία έκθεση στις σημαντικές πτυχές του jscodeshift και θα είστε έτοιμοι να ξεκινήσετε τη σύνταξη των δικών σας κωδικών κωδικών. Θα περάσουμε από τρεις ασκήσεις που καλύπτουν κάποιες βασικές, αλλά φοβερές, χρήσεις κωδικών κωδικών και μπορείτε να δείτε τον πηγαίο κώδικα για αυτές τις ασκήσεις στο my έργο github .
Η εργαλειοθήκη jscodeshift σάς επιτρέπει να αντλείτε μια δέσμη αρχείων προέλευσης μέσω ενός μετασχηματισμού και να τα αντικαθιστάτε με αυτό που βγαίνει από το άλλο άκρο. Μέσα στον μετασχηματισμό, αναλύετε την πηγή σε ένα αφηρημένο δέντρο σύνταξης (AST), σπρώξτε γύρω για να κάνετε τις αλλαγές σας και, στη συνέχεια, αναδημιουργήστε την πηγή από το αλλοιωμένο AST.
Η διεπαφή που παρέχει το jscodeshift είναι ένα περιτύλιγμα γύρω στο recast
και ast-types
πακέτα. recast
χειρίζεται τη μετατροπή από πηγή σε AST και πίσω ενώ ast-types
χειρίζεται την αλληλεπίδραση χαμηλού επιπέδου με τους κόμβους AST.
Για να ξεκινήσετε, εγκαταστήστε το jscodeshift παγκοσμίως από το npm.
npm i -g jscodeshift
Υπάρχουν επιλογές δρομέων που μπορείτε να χρησιμοποιήσετε και μια ρύθμιση δοκιμών με γνώμονα το οποίο καθιστά την εκτέλεση μιας σειράς δοκιμών μέσω Jest (ένα πλαίσιο δοκιμών ανοιχτού κώδικα JavaScript) πολύ εύκολη, αλλά θα το παρακάμψουμε προς το παρόν υπέρ της απλότητας:
jscodeshift -t some-transform.js input-file.js -d -p
Αυτό θα εκτελεστεί input-file.js
μέσω του μετασχηματισμού some-transform.js
και εκτυπώστε τα αποτελέσματα χωρίς να αλλάξετε το αρχείο.
πώς να φτιάξετε ένα παιχνίδι σε επεξεργασία
Πριν ξεκινήσετε, ωστόσο, είναι σημαντικό να κατανοήσετε τρεις βασικούς τύπους αντικειμένων με τους οποίους ασχολείται το API jscodeshift: κόμβους, διαδρομές κόμβων και συλλογές.
Οι κόμβοι είναι τα βασικά δομικά στοιχεία του AST, που συχνά αναφέρονται ως «κόμβοι AST». Αυτά είναι αυτά που βλέπετε κατά την εξερεύνηση του κωδικού σας με τον AST Explorer. Είναι απλά αντικείμενα και δεν παρέχουν μεθόδους.
Οι διαδρομές κόμβων είναι περιτυλίγματα γύρω από έναν κόμβο AST που παρέχεται από ast-types
ως τρόπος διέλευσης της αφηρημένης σύνταξης δέντρων (AST, θυμηθείτε;). Μεμονωμένα, οι κόμβοι δεν έχουν καμία πληροφορία σχετικά με τον γονέα ή το εύρος τους, επομένως οι κόμβοι-κόμβοι το φροντίζουν. Μπορείτε να αποκτήσετε πρόσβαση στον τυλιγμένο κόμβο μέσω του node
ιδιότητα και υπάρχουν πολλές διαθέσιμες μέθοδοι για την αλλαγή του υποκείμενου κόμβου. Οι κόμβοι-κόμβοι αναφέρονται συχνά ως «μονοπάτια».
Οι συλλογές είναι ομάδες μηδενικών ή περισσότερων διαδρομών κόμβου που επιστρέφει το API jscodeshift όταν υποβάλλετε ερώτημα στο AST. Έχουν κάθε είδους χρήσιμες μεθόδους, μερικές από τις οποίες θα διερευνήσουμε.
Οι συλλογές περιέχουν κόμβους κόμβων, κόμβοι κόμβων περιέχουν κόμβους και κόμβοι είναι από τους οποίους αποτελείται το AST. Λάβετε υπόψη αυτό και θα είναι εύκολο να κατανοήσετε το API ερωτημάτων jscodeshift.
Μπορεί να είναι δύσκολο να παρακολουθείτε τις διαφορές μεταξύ αυτών των αντικειμένων και των αντίστοιχων δυνατοτήτων API τους, επομένως υπάρχει ένα καλό εργαλείο που ονομάζεται jscodeshift-βοηθός που καταγράφει τον τύπο αντικειμένου και παρέχει άλλες βασικές πληροφορίες.
Για να βραχεί τα πόδια μας, ας ξεκινήσουμε με την κατάργηση κλήσεων σε όλες τις μεθόδους κονσόλας στη βάση κώδικα. Ενώ μπορείτε να το κάνετε αυτό με εύρεση και αντικατάσταση και λίγο regex, αρχίζει να γίνεται δύσκολο με δηλώσεις πολλαπλών γραμμών, γραμματοσειρές προτύπων και πιο περίπλοκες κλήσεις, οπότε είναι ένα ιδανικό παράδειγμα για να ξεκινήσετε.
Αρχικά, δημιουργήστε δύο αρχεία, remove-consoles.js
και remove-consoles.input.js
:
//remove-consoles.js export default (fileInfo, api) => { };
//remove-consoles.input.js export const sum = (a, b) => { console.log('calling sum with', arguments); return a + b; }; export const multiply = (a, b) => { console.warn('calling multiply with', arguments); return a * b; }; export const divide = (a, b) => { console.error(`calling divide with ${ arguments }`); return a / b; }; export const average = (a, b) => { console.log('calling average with ' + arguments); return divide(sum(a, b), 2); };
Εδώ είναι η εντολή που θα χρησιμοποιούμε στο τερματικό για να το προωθήσουμε μέσω του jscodeshift:
jscodeshift -t remove-consoles.js remove-consoles.input.js -d -p
Εάν όλα έχουν ρυθμιστεί σωστά, όταν το εκτελείτε θα πρέπει να δείτε κάτι τέτοιο.
Processing 1 files... Spawning 1 workers... Running in dry mode, no files will be written! Sending 1 files to free worker... All done. Results: 0 errors 0 unmodified 1 skipped 0 ok Time elapsed: 0.514seconds
Εντάξει, αυτό ήταν λίγο αντικλιματικό, καθώς ο μετασχηματισμός μας δεν κάνει τίποτα ακόμη, αλλά τουλάχιστον γνωρίζουμε ότι όλα λειτουργούν. Εάν δεν λειτουργεί καθόλου, βεβαιωθείτε ότι έχετε εγκαταστήσει το jscodeshift παγκοσμίως. Εάν η εντολή για την εκτέλεση του μετασχηματισμού είναι λανθασμένη, θα δείτε είτε ένα μήνυμα 'ERROR Transform file ... δεν υπάρχει' ή 'TypeError: path πρέπει να είναι μια συμβολοσειρά ή Buffer' εάν το αρχείο εισαγωγής δεν μπορεί να βρεθεί. Εάν έχετε κάτι με λίγα λόγια, θα πρέπει να εντοπίσετε εύκολα τα πολύ περιγραφικά σφάλματα μετασχηματισμού.
Σχετίζεται με: Γρήγορο και πρακτικό φύλλο εξαπάτησης JavaScript του ApeeScape: ES6 και πέραΟ τελικός μας στόχος όμως, μετά από μια επιτυχημένη μετατροπή, είναι να δούμε αυτήν την πηγή:
export const sum = (a, b) => { return a + b; }; export const multiply = (a, b) => { return a * b; }; export const divide = (a, b) => { return a / b; }; export const average = (a, b) => { return divide(sum(a, b), 2); };
Για να φτάσουμε εκεί, πρέπει να μετατρέψουμε την πηγή σε AST, να βρούμε τις κονσόλες, να τις αφαιρέσουμε και, στη συνέχεια, να μετατρέψουμε το τροποποιημένο AST σε πηγή. Τα πρώτα και τελευταία βήματα είναι εύκολα, είναι απλώς:
remove-consoles.js export default (fileInfo, api) => { const j = api.jscodeshift; const root = j(fileInfo.source); return root.toSource(); };
Αλλά πώς βρίσκουμε τις κονσόλες και τις αφαιρούμε; Εάν δεν έχετε εξαιρετικές γνώσεις σχετικά με το Mozilla Parser API, πιθανότατα θα χρειαστείτε ένα εργαλείο για να κατανοήσετε πώς φαίνεται το AST. Για αυτό μπορείτε να χρησιμοποιήσετε το Εξερεύνηση AST . Επικολλήστε τα περιεχόμενα του remove-consoles.input.js
σε αυτό και θα δείτε το AST. Υπάρχουν πολλά δεδομένα ακόμη και στον απλούστερο κώδικα, επομένως βοηθά στην απόκρυψη δεδομένων και μεθόδων τοποθεσίας. Μπορείτε να αλλάξετε την ορατότητα των ιδιοτήτων στην Εξερεύνηση AST με τα πλαίσια ελέγχου πάνω από το δέντρο.
Μπορούμε να δούμε ότι οι μέθοδοι κλήσεων στην κονσόλα αναφέρονται ως CallExpressions
, οπότε πώς τις βρίσκουμε στο μετασχηματισμό μας; Χρησιμοποιούμε τα ερωτήματα του jscodeshift, θυμόμαστε την προηγούμενη συζήτησή μας σχετικά με τις διαφορές μεταξύ των Συλλογών, των κόμβων και των ίδιων των κόμβων:
//remove-consoles.js export default (fileInfo, api) => { const j = api.jscodeshift; const root = j(fileInfo.source); return root.toSource(); };
Η γραμμή const root = j(fileInfo.source);
επιστρέφει μια συλλογή μιας διαδρομής κόμβου, η οποία τυλίγει τον κόμβο AST ρίζας. Μπορούμε να χρησιμοποιήσουμε τη συλλογή find
μέθοδος για αναζήτηση κόμβων απογόνου ενός συγκεκριμένου τύπου, όπως:
const callExpressions = root.find(j.CallExpression);
Αυτό επιστρέφει μια άλλη συλλογή διαδρομών κόμβων που περιέχουν μόνο τους κόμβους που είναι CallExpressions. Στην πρώτη κοκκινίλα, αυτό μοιάζει με αυτό που θέλουμε, αλλά είναι πολύ ευρύ. Ενδέχεται να καταλήξουμε να εκτελούμε εκατοντάδες ή χιλιάδες αρχεία μέσω των μετασχηματισμών μας, οπότε πρέπει να είμαστε ακριβείς για να έχουμε οποιαδήποτε εμπιστοσύνη ότι θα λειτουργήσει όπως είχε προβλεφθεί. Το αφελές find
παραπάνω δεν θα βρει απλώς την κονσόλα CallExpressions, αλλά θα βρει κάθε CallExpression στην πηγή, συμπεριλαμβανομένης της
require('foo') bar() setTimeout(() => {}, 0)
Για να επιβάλουμε μεγαλύτερη ειδικότητα, παρέχουμε ένα δεύτερο όρισμα στο .find
: Ένα αντικείμενο πρόσθετων παραμέτρων, κάθε κόμβος πρέπει να συμπεριληφθεί στα αποτελέσματα. Μπορούμε να κοιτάξουμε στον Εξερευνητή AST για να δούμε ότι η κονσόλα μας. * Οι κλήσεις έχουν τη μορφή:
{ 'type': 'CallExpression', 'callee': { 'type': 'MemberExpression', 'object': { 'type': 'Identifier', 'name': 'console' } } }
Με αυτές τις γνώσεις, ξέρουμε να τελειοποιούμε το ερώτημά μας με έναν προσδιοριστή που θα επιστρέφει μόνο τον τύπο CallExpressions που μας ενδιαφέρει:
const callExpressions = root.find(j.CallExpression, { callee: { type: 'MemberExpression', object: { type: 'Identifier', name: 'console' }, }, });
Τώρα που έχουμε μια ακριβή συλλογή των ιστότοπων κλήσεων, ας τους καταργήσουμε από το AST. Βολικά, ο τύπος αντικειμένου συλλογής έχει remove
μέθοδος που θα κάνει ακριβώς αυτό. Το remove-consoles.js
το αρχείο θα μοιάζει τώρα με αυτό:
//remove-consoles.js export default (fileInfo, api) => { const j = api.jscodeshift; const root = j(fileInfo.source) const callExpressions = root.find(j.CallExpression, { callee: { type: 'MemberExpression', object: { type: 'Identifier', name: 'console' }, }, } ); callExpressions.remove(); return root.toSource(); };
Τώρα, εάν εκτελέσουμε τον μετασχηματισμό μας από τη γραμμή εντολών χρησιμοποιώντας το jscodeshift -t remove-consoles.js remove-consoles.input.js -d -p
, θα πρέπει να δούμε:
Processing 1 files... Spawning 1 workers... Running in dry mode, no files will be written! Sending 1 files to free worker... export const sum = (a, b) => { return a + b; }; export const multiply = (a, b) => { return a * b; }; export const divide = (a, b) => { return a / b; }; export const average = (a, b) => { return divide(sum(a, b), 2); }; All done. Results: 0 errors 0 unmodified 0 skipped 1 ok Time elapsed: 0.604seconds
Φαίνεται καλό. Τώρα που ο μετασχηματισμός μας αλλάζει το υποκείμενο AST, χρησιμοποιώντας το .toSource()
δημιουργεί μια διαφορετική συμβολοσειρά από το πρωτότυπο. Η επιλογή -p από την εντολή μας εμφανίζει το αποτέλεσμα και στο κάτω μέρος εμφανίζεται ένας αριθμός καταθέσεων για κάθε αρχείο που υποβλήθηκε σε επεξεργασία. Η κατάργηση της επιλογής -d από την εντολή μας, θα αντικαταστήσει το περιεχόμενο του remove-consoles.input.js με την έξοδο από τον μετασχηματισμό.
Η πρώτη μας άσκηση ολοκληρώθηκε… σχεδόν. Ο κώδικας είναι παράξενος και πιθανώς πολύ προσβλητικός σε οποιονδήποτε λειτουργικό καθαριστή εκεί έξω, και έτσι για να κάνει τη ροή κώδικα μετασχηματισμού καλύτερη, το jscodeshift έκανε τα περισσότερα πράγματα αλυσοδεμένα. Αυτό μας επιτρέπει να ξαναγράψουμε τον μετασχηματισμό μας έτσι:
// remove-consoles.js export default (fileInfo, api) => { const j = api.jscodeshift; return j(fileInfo.source) .find(j.CallExpression, { callee: { type: 'MemberExpression', object: { type: 'Identifier', name: 'console' }, }, } ) .remove() .toSource(); };
Πολύ καλύτερα. Για να ανακεφαλαιώσετε την άσκηση 1, τυλίξαμε την πηγή, ζητήσαμε μια συλλογή από κόμβους-κόμβους, αλλάξαμε το AST και, στη συνέχεια, αναδημιουργήσαμε αυτήν την πηγή. Βρέξαμε τα πόδια μας με ένα πολύ απλό παράδειγμα και αγγίξαμε τις πιο σημαντικές πτυχές. Τώρα, ας κάνουμε κάτι πιο ενδιαφέρον.
Για αυτό το σενάριο, έχουμε μια ενότητα 'γεωμετρίας' με μια μέθοδο που ονομάζεται 'circleArea' την οποία καταργήσαμε υπέρ του 'getCircleArea'. Θα μπορούσαμε εύκολα να τα βρούμε και να τα αντικαταστήσουμε με /geometry.circleArea/g
, αλλά τι γίνεται αν ο χρήστης έχει εισαγάγει τη λειτουργική μονάδα και της έχει εκχωρήσει διαφορετικό όνομα; Για παράδειγμα:
import g from 'geometry'; const area = g.circleArea(radius);
Πώς θα γνωρίζαμε να αντικαταστήσουμε το g.circleArea
αντί για geometry.circleArea
; Σίγουρα δεν μπορούμε να υποθέσουμε ότι όλα circleArea
οι κλήσεις είναι αυτές που αναζητούμε, χρειαζόμαστε κάποιο πλαίσιο. Εδώ ξεκινούν τα codemods την αξία τους. Ας ξεκινήσουμε κάνοντας δύο αρχεία, deprecated.js
και deprecated.input.js
.
//deprecated.js export default (fileInfo, api) => { const j = api.jscodeshift; const root = j(fileInfo.source); return root.toSource(); };
deprecated.input.js import g from 'geometry'; import otherModule from 'otherModule'; const radius = 20; const area = g.circleArea(radius); console.log(area === Math.pow(g.getPi(), 2) * radius); console.log(area === otherModule.circleArea(radius));
Τώρα εκτελέστε αυτήν την εντολή για να εκτελέσετε τον κωδικό κώδικα.
jscodeshift -t ./deprecated.js ./deprecated.input.js -d -p
Θα πρέπει να δείτε την έξοδο που δείχνει ότι ο μετασχηματισμός εκτελέστηκε, αλλά δεν έχει αλλάξει τίποτα ακόμη.
Processing 1 files... Spawning 1 workers... Running in dry mode, no files will be written! Sending 1 files to free worker... All done. Results: 0 errors 1 unmodified 0 skipped 0 ok Time elapsed: 0.892seconds
Πρέπει να γνωρίζουμε τι geometry
η μονάδα έχει εισαχθεί ως. Ας δούμε την Εξερεύνηση AST και να καταλάβουμε τι ψάχνουμε. Η εισαγωγή μας λαμβάνει αυτήν τη μορφή.
{ 'type': 'ImportDeclaration', 'specifiers': [ { 'type': 'ImportDefaultSpecifier', 'local': { 'type': 'Identifier', 'name': 'g' } } ], 'source': { 'type': 'Literal', 'value': 'geometry' } }
Μπορούμε να καθορίσουμε έναν τύπο αντικειμένου για να βρούμε μια συλλογή κόμβων όπως αυτή:
const importDeclaration = root.find(j.ImportDeclaration, { source: { type: 'Literal', value: 'geometry', }, });
Αυτό μας δίνει το ImportDeclaration που χρησιμοποιείται για την εισαγωγή «γεωμετρίας». Από εκεί, σκάψτε για να βρείτε το τοπικό όνομα που χρησιμοποιείται για τη συγκράτηση της εισαγόμενης λειτουργικής μονάδας. Δεδομένου ότι είναι η πρώτη φορά που το κάναμε, ας επισημάνουμε ένα σημαντικό και μπερδεμένο σημείο κατά την πρώτη εκκίνηση.
Σημείωση: Είναι σημαντικό να γνωρίζετε ότι root.find()
επιστρέφει μια συλλογή κόμβων-διαδρομών. Από εκεί, το .get(n)
Η μέθοδος επιστρέφει τη διαδρομή κόμβου στο ευρετήριο n
σε αυτήν τη συλλογή, και για να πάρουμε τον πραγματικό κόμβο, χρησιμοποιούμε .node
. Ο κόμβος είναι βασικά αυτό που βλέπουμε στον AST Explorer. Θυμηθείτε, η διαδρομή κόμβου είναι κυρίως πληροφορίες σχετικά με το εύρος και τις σχέσεις του κόμβου, όχι ο ίδιος ο κόμβος.
// find the Identifiers const identifierCollection = importDeclaration.find(j.Identifier); // get the first NodePath from the Collection const nodePath = identifierCollection.get(0); // get the Node in the NodePath and grab its 'name' const localName = nodePath.node.name;
Αυτό μας επιτρέπει να καταλάβουμε δυναμικά τι geometry
η μονάδα έχει εισαχθεί ως. Στη συνέχεια, βρίσκουμε τα μέρη που χρησιμοποιείται και τα αλλάζουμε. Κοιτάζοντας τον AST Explorer, μπορούμε να δούμε ότι πρέπει να βρούμε εκφράσεις μελών που μοιάζουν με αυτό:
{ 'type': 'MemberExpression', 'object': { 'name': 'geometry' }, 'property': { 'name': 'circleArea' } }
Θυμηθείτε, ωστόσο, ότι η ενότητα μας μπορεί να έχει εισαχθεί με διαφορετικό όνομα, οπότε πρέπει να το λάβουμε υπόψη, κάνοντας το ερώτημά μας να μοιάζει με αυτό:
j.MemberExpression, { object: { name: localName, }, property: { name: 'circleArea', }, })
Τώρα που έχουμε ένα ερώτημα, μπορούμε να πάρουμε μια συλλογή όλων των ιστότοπων κλήσεων στην παλιά μας μέθοδο και στη συνέχεια να χρησιμοποιήσουμε τη συλλογή replaceWith()
μέθοδο για την ανταλλαγή τους. Το replaceWith()
Η μέθοδος επαναλαμβάνεται μέσω της συλλογής, περνώντας κάθε διαδρομή κόμβου σε μια συνάρτηση επανάκλησης. Στη συνέχεια, ο κόμβος AST αντικαθίσταται με οποιονδήποτε κόμβο επιστρέφετε από την επιστροφή κλήσης.
Μόλις τελειώσουμε με την αντικατάσταση, δημιουργούμε την πηγή ως συνήθως. Εδώ είναι ο τελικός μετασχηματισμός μας:
//deprecated.js export default (fileInfo, api) => { const j = api.jscodeshift; const root = j(fileInfo.source); // find declaration for 'geometry' import const importDeclaration = root.find(j.ImportDeclaration, { source: { type: 'Literal', value: 'geometry', }, }); // get the local name for the imported module const localName = // find the Identifiers importDeclaration.find(j.Identifier) // get the first NodePath from the Collection .get(0) // get the Node in the NodePath and grab its 'name' .node.name; return root.find(j.MemberExpression, { object: { name: localName, }, property: { name: 'circleArea', }, }) .replaceWith(nodePath => { // get the underlying Node const { node } = nodePath; // change to our new prop node.property.name = 'getCircleArea'; // replaceWith should return a Node, not a NodePath return node; }) .toSource(); };
Όταν εκτελούμε την πηγή μέσω του μετασχηματισμού, βλέπουμε ότι η κλήση προς τη μέθοδο που έχει καταργηθεί στο geometry
η ενότητα άλλαξε, αλλά τα υπόλοιπα αφέθηκαν αμετάβλητα, έτσι:
import g from 'geometry'; import otherModule from 'otherModule'; const radius = 20; const area = g.getCircleArea(radius); console.log(area === Math.pow(g.getPi(), 2) * radius); console.log(area === otherModule.circleArea(radius));
Στις προηγούμενες ασκήσεις καλύψαμε συλλογές ερωτήσεων για συγκεκριμένους τύπους κόμβων, την αφαίρεση κόμβων και την αλλαγή κόμβων, αλλά τι γίνεται με τη δημιουργία εντελώς νέων κόμβων; Αυτό θα αντιμετωπίσουμε σε αυτήν την άσκηση.
Σε αυτό το σενάριο, έχουμε μια υπογραφή μεθόδου που ξεφεύγει από τον έλεγχο με μεμονωμένα ορίσματα καθώς το λογισμικό έχει αναπτυχθεί, και έτσι αποφασίστηκε ότι θα ήταν καλύτερα να αποδεχτούμε ένα αντικείμενο που περιέχει αυτά τα επιχειρήματα.
Αντί για car.factory('white', 'Kia', 'Sorento', 2010, 50000, null, true);
θα θέλαμε να δούμε
const suv = car.factory({ color: 'white', make: 'Kia', model: 'Sorento', year: 2010, miles: 50000, bedliner: null, alarm: true, });
Ας ξεκινήσουμε κάνοντας τον μετασχηματισμό και ένα αρχείο εισαγωγής για δοκιμή με:
//signature-change.js export default (fileInfo, api) => { const j = api.jscodeshift; const root = j(fileInfo.source); return root.toSource(); };
//signature-change.input.js import car from 'car'; const suv = car.factory('white', 'Kia', 'Sorento', 2010, 50000, null, true); const truck = car.factory('silver', 'Toyota', 'Tacoma', 2006, 100000, true, true);
Η εντολή μας για εκτέλεση του μετασχηματισμού θα είναι jscodeshift -t signature-change.js signature-change.input.js -d -p
και τα βήματα που πρέπει να πραγματοποιήσουμε αυτόν τον μετασχηματισμό είναι:
Χρησιμοποιώντας τον AST Explorer και τη διαδικασία που χρησιμοποιήσαμε στις προηγούμενες ασκήσεις, τα πρώτα δύο βήματα είναι εύκολα:
//signature-change.js export default (fileInfo, api) => { const j = api.jscodeshift; const root = j(fileInfo.source); // find declaration for 'car' import const importDeclaration = root.find(j.ImportDeclaration, { source: { type: 'Literal', value: 'car', }, }); // get the local name for the imported module const localName = importDeclaration.find(j.Identifier) .get(0) .node.name; // find where `.factory` is being called return root.find(j.CallExpression, { callee: { type: 'MemberExpression', object: { name: localName, }, property: { name: 'factory', }, } }) .toSource(); };
Για να διαβάσετε όλα τα επιχειρήματα που διαβιβάζονται αυτήν τη στιγμή, χρησιμοποιούμε το replaceWith()
μέθοδος στη συλλογή μας CallExpressions για ανταλλαγή καθενός από τους κόμβους. Οι νέοι κόμβοι θα αντικαταστήσουν τα node.arguments με ένα νέο όρισμα, ένα αντικείμενο.
Ας το δοκιμάσουμε με ένα απλό αντικείμενο για να βεβαιωθούμε ότι γνωρίζουμε πώς λειτουργεί πριν χρησιμοποιήσουμε τις σωστές τιμές:
.replaceWith(nodePath => { const { node } = nodePath; node.arguments = [{ foo: 'bar' }]; return node; })
Όταν το εκτελέσουμε (jscodeshift -t signature-change.js signature-change.input.js -d -p
), ο μετασχηματισμός θα εκραγεί με:
ERR signature-change.input.js Transformation error Error: {foo: bar} does not match type Printable
Αποδεικνύεται ότι δεν μπορούμε απλώς να μπλοκάρουμε απλά αντικείμενα στους κόμβους AST μας. Αντ 'αυτού, πρέπει να χρησιμοποιήσουμε τους κατασκευαστές για να δημιουργήσουμε κατάλληλους κόμβους.
Σχετίζεται με: Προσλάβετε το κορυφαίο 3% των ανεξάρτητων προγραμματιστών Javascript.Οι κατασκευαστές μας επιτρέπουν να δημιουργούμε σωστά νέους κόμβους. παρέχονται από ast-types
και εμφανίστηκε μέσω του jscodeshift. Ελέγχουν αυστηρά ότι οι διαφορετικοί τύποι κόμβων έχουν δημιουργηθεί σωστά, κάτι που μπορεί να είναι απογοητευτικό όταν χαράζετε ένα ρολό, αλλά τελικά, αυτό είναι καλό. Για να καταλάβετε πώς να χρησιμοποιείτε τους κατασκευαστές, υπάρχουν δύο πράγματα που πρέπει να έχετε υπόψη:
Όλοι οι διαθέσιμοι τύποι κόμβων AST ορίζονται στο φάκελο def
του ast-types github project , κυρίως στο core.js Υπάρχουν κατασκευαστές για όλους τους τύπους κόμβων AST, αλλά χρησιμοποιούν καμήλα έκδοση του τύπου κόμβου, όχι pascal-case . (Αυτό δεν αναφέρεται ρητά, αλλά μπορείτε να δείτε ότι ισχύει στην περίπτωση πηγή ast-types
Εάν χρησιμοποιήσουμε τον AST Explorer με ένα παράδειγμα αυτού που θέλουμε να είναι το αποτέλεσμα, μπορούμε να το συνδυάσουμε αρκετά εύκολα. Στην περίπτωσή μας, θέλουμε το νέο μεμονωμένο όρισμα να είναι ObjectExpression με πολλές ιδιότητες. Κοιτάζοντας τους ορισμούς τύπων που αναφέρονται παραπάνω, μπορούμε να δούμε τι συνεπάγεται αυτό:
def('ObjectExpression') .bases('Expression') .build('properties') .field('properties', [def('Property')]); def('Property') .bases('Node') .build('kind', 'key', 'value') .field('kind', or('init', 'get', 'set')) .field('key', or(def('Literal'), def('Identifier'))) .field('value', def('Expression'));
Έτσι, ο κώδικας για τη δημιουργία ενός κόμβου AST για το {foo: ‘bar’} θα μοιάζει με:
j.objectExpression([ j.property( 'init', j.identifier('foo'), j.literal('bar') ) ]);
Πάρτε αυτόν τον κωδικό και συνδέστε τον στον μετασχηματισμό μας έτσι:
.replaceWith(nodePath => { const { node } = nodePath; const object = j.objectExpression([ j.property( 'init', j.identifier('foo'), j.literal('bar') ) ]); node.arguments = [object]; return node; })
Τρέχοντας αυτό μας δίνει το αποτέλεσμα:
import car from 'car'; const suv = car.factory({ foo: 'bar' }); const truck = car.factory({ foo: 'bar' });
Τώρα που ξέρουμε πώς να δημιουργήσουμε έναν σωστό κόμβο AST, είναι εύκολο να βρούμε τα παλιά επιχειρήματα και να δημιουργήσουμε ένα νέο αντικείμενο για χρήση. Εδώ είναι τι signature-change.js
το αρχείο μοιάζει τώρα:
//signature-change.js export default (fileInfo, api) => { const j = api.jscodeshift; const root = j(fileInfo.source); // find declaration for 'car' import const importDeclaration = root.find(j.ImportDeclaration, { source: { type: 'Literal', value: 'car', }, }); // get the local name for the imported module const localName = importDeclaration.find(j.Identifier) .get(0) .node.name; // current order of arguments const argKeys = [ 'color', 'make', 'model', 'year', 'miles', 'bedliner', 'alarm', ]; // find where `.factory` is being called return root.find(j.CallExpression, { callee: { type: 'MemberExpression', object: { name: localName, }, property: { name: 'factory', }, } }) .replaceWith(nodePath => { const { node } = nodePath; // use a builder to create the ObjectExpression const argumentsAsObject = j.objectExpression( // map the arguments to an Array of Property Nodes node.arguments.map((arg, i) => j.property( 'init', j.identifier(argKeys[i]), j.literal(arg.value) ) ) ); // replace the arguments with our new ObjectExpression node.arguments = [argumentsAsObject]; return node; }) // specify print options for recast .toSource({ quote: 'single', trailingComma: true }); };
Εκτελέστε τον μετασχηματισμό (jscodeshift -t signature-change.js signature-change.input.js -d -p
) και θα δούμε ότι οι υπογραφές έχουν ενημερωθεί όπως αναμενόταν:
εύρεση διαρροών μνήμης στη java
import car from 'car'; const suv = car.factory({ color: 'white', make: 'Kia', model: 'Sorento', year: 2010, miles: 50000, bedliner: null, alarm: true, }); const truck = car.factory({ color: 'silver', make: 'Toyota', model: 'Tacoma', year: 2006, miles: 100000, bedliner: true, alarm: true, });
Χρειάστηκε λίγος χρόνος και προσπάθεια για να φτάσουμε σε αυτό το σημείο, αλλά τα οφέλη είναι τεράστια όταν αντιμετωπίζουμε μαζική αναδιαμόρφωση. Η διανομή ομάδων αρχείων σε διαφορετικές διαδικασίες και η παράλληλη εκτέλεση τους είναι κάτι που ξεχωρίζει το jscodeshift, επιτρέποντάς σας να εκτελείτε σύνθετους μετασχηματισμούς σε μια τεράστια βάση κώδικα σε δευτερόλεπτα. Καθώς γίνετε πιο ικανοί με τα κωδικοκώδικα, θα αρχίσετε να επανατοποθετείτε υπάρχοντα σενάρια (όπως το reposit-codemod github αποθετήριο ή να γράψετε τη δική σας για κάθε είδους εργασίες, και αυτό θα κάνει εσάς, την ομάδα σας και τους χρήστες πακέτων σας πιο αποτελεσματικούς.