Tulis Kode untuk Menulis Ulang Kode Anda: jscodeshift

Diterbitkan: 2022-03-11

Codemods dengan jscodeshift

Berapa kali Anda menggunakan fungsi temukan-dan-ganti di seluruh direktori untuk membuat perubahan pada file sumber JavaScript? Jika Anda mahir, Anda sudah terbiasa dan menggunakan ekspresi reguler dengan grup penangkap, karena itu sepadan dengan usaha jika basis kode Anda cukup besar. Regex memiliki batasan. Untuk perubahan non-sepele, Anda memerlukan pengembang yang memahami kode dalam konteks dan juga bersedia untuk mengambil proses yang panjang, membosankan, dan rawan kesalahan.

Di sinilah "codemods" masuk.

Codemods adalah skrip yang digunakan untuk menulis ulang skrip lain. Anggap saja mereka sebagai fungsi temukan dan ganti yang dapat membaca dan menulis kode. Anda dapat menggunakannya untuk memperbarui kode sumber agar sesuai dengan konvensi pengkodean tim, membuat perubahan luas saat API dimodifikasi, atau bahkan memperbaiki otomatis kode yang ada saat paket publik Anda membuat perubahan yang melanggar.

Toolkit jscodeshift sangat bagus untuk bekerja dengan codemods.

Pikirkan codemods sebagai skrip temukan dan ganti fungsionalitas yang dapat membaca dan menulis kode.
Menciak

Dalam artikel ini, kita akan menjelajahi toolkit untuk codemod yang disebut “jscodeshift” sambil membuat tiga codemod dengan kompleksitas yang meningkat. Pada akhirnya Anda akan memiliki paparan luas terhadap aspek-aspek penting jscodeshift dan akan siap untuk mulai menulis codemods Anda sendiri. Kita akan melalui tiga latihan yang mencakup beberapa dasar, tapi mengagumkan, penggunaan codemods, dan Anda dapat melihat kode sumber untuk latihan ini di proyek github saya.

Apa itu jscodeshift?

Toolkit jscodeshift memungkinkan Anda untuk memompa banyak file sumber melalui transformasi dan menggantinya dengan apa yang keluar dari ujung yang lain. Di dalam transformasi, Anda mengurai sumber menjadi pohon sintaksis abstrak (AST), melihat-lihat untuk membuat perubahan, lalu membuat ulang sumber dari AST yang diubah.

Antarmuka yang disediakan jscodeshift adalah pembungkus di sekitar paket recast dan ast-types . recast menangani konversi dari sumber ke AST dan kembali sementara ast-types menangani interaksi tingkat rendah dengan node AST.

Mempersiapkan

Untuk memulai, instal jscodeshift secara global dari npm.

 npm i -g jscodeshift

Ada opsi runner yang dapat Anda gunakan dan pengaturan pengujian yang membuat menjalankan serangkaian pengujian melalui Jest (kerangka pengujian JavaScript open source) sangat mudah, tetapi kami akan mengabaikannya untuk saat ini demi kesederhanaan:

jscodeshift -t some-transform.js input-file.js -d -p

Ini akan menjalankan input-file.js melalui transform some-transform.js dan mencetak hasilnya tanpa mengubah file.

Namun, sebelum masuk, penting untuk memahami tiga tipe objek utama yang ditangani oleh jscodeshift API: node, node-path, dan collections.

Node

Node adalah blok bangunan dasar AST, sering disebut sebagai "node AST." Inilah yang Anda lihat saat menjelajahi kode Anda dengan AST Explorer. Mereka adalah objek sederhana dan tidak menyediakan metode apa pun.

Jalur-simpul

Jalur simpul adalah pembungkus di sekitar simpul AST yang disediakan oleh ast-types sebagai cara untuk melintasi pohon sintaksis abstrak (AST, ingat?). Secara terpisah, node tidak memiliki informasi apapun tentang parent atau scopenya, jadi node-path menanganinya. Anda dapat mengakses simpul yang dibungkus melalui properti node dan ada beberapa metode yang tersedia untuk mengubah simpul yang mendasarinya. jalur-simpul sering disebut hanya sebagai "jalur".

Koleksi

Koleksi adalah grup dari nol atau lebih jalur simpul yang dikembalikan oleh jscodeshift API saat Anda menanyakan AST. Mereka memiliki segala macam metode yang berguna, beberapa di antaranya akan kita jelajahi.

Koleksi berisi jalur-simpul, jalur-simpul berisi simpul-simpul, dan dari simpul-simpul itulah AST dibuat. Ingatlah hal itu dan akan mudah untuk memahami API kueri jscodeshift.

Mungkin sulit untuk melacak perbedaan antara objek ini dan kemampuan API masing-masing, jadi ada alat bagus yang disebut jscodeshift-helper yang mencatat jenis objek dan menyediakan informasi penting lainnya.

Mengetahui perbedaan antara node, node-path, dan collection adalah penting.

Mengetahui perbedaan antara node, node-path, dan collection adalah penting.

Latihan 1: Hapus Panggilan ke Konsol

Untuk membuat kaki kita basah, mari kita mulai dengan menghapus panggilan ke semua metode konsol di basis kode kita. Meskipun Anda dapat melakukan ini dengan find and replace dan sedikit regex, ini mulai menjadi rumit dengan pernyataan multiline, literal template, dan panggilan yang lebih kompleks, jadi ini adalah contoh ideal untuk memulai.

Pertama, buat dua file, remove-consoles.js dan 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); };

Inilah perintah yang akan kita gunakan di terminal untuk mendorongnya melalui jscodeshift:

jscodeshift -t remove-consoles.js remove-consoles.input.js -d -p

Jika semuanya sudah diatur dengan benar, ketika Anda menjalankannya Anda akan melihat sesuatu seperti ini.

 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

Oke, itu agak antiklimaks karena transformasi kita belum melakukan apa-apa, tapi setidaknya kita tahu semuanya berhasil. Jika tidak berjalan sama sekali, pastikan Anda menginstal jscodeshift secara global. Jika perintah untuk menjalankan transformasi salah, Anda akan melihat pesan "ERROR Transform file ... not existing" atau "TypeError: path must be a string or Buffer" jika file input tidak dapat ditemukan. Jika Anda memiliki sesuatu yang gemuk, itu akan mudah dikenali dengan kesalahan transformasi yang sangat deskriptif.

Terkait: Lembar Cheat JavaScript Cepat Dan Praktis Toptal: ES6 Dan Selanjutnya

Namun, tujuan akhir kami, setelah transformasi yang berhasil, adalah melihat sumber ini:

 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); };

Untuk sampai ke sana, kita perlu mengubah sumber menjadi AST, menemukan konsol, menghapusnya, dan kemudian mengubah AST yang diubah kembali menjadi sumber. Langkah pertama dan terakhir mudah, hanya saja:

 remove-consoles.js export default (fileInfo, api) => { const j = api.jscodeshift; const root = j(fileInfo.source); return root.toSource(); };

Tapi bagaimana kita menemukan konsol dan menghapusnya? Kecuali Anda memiliki pengetahuan luar biasa tentang Mozilla Parser API, Anda mungkin memerlukan alat untuk membantu memahami seperti apa AST itu. Untuk itu Anda bisa menggunakan AST Explorer. Rekatkan konten remove-consoles.input.js ke dalamnya dan Anda akan melihat AST. Ada banyak data bahkan dalam kode yang paling sederhana, sehingga membantu menyembunyikan data dan metode lokasi. Anda dapat mengaktifkan visibilitas properti di AST Explorer dengan kotak centang di atas pohon.

Kita dapat melihat bahwa panggilan ke metode konsol disebut sebagai CallExpressions , jadi bagaimana kita menemukannya dalam transformasi kita? Kami menggunakan kueri jscodeshift, mengingat diskusi kami sebelumnya tentang perbedaan antara Koleksi, jalur simpul, dan simpul itu sendiri:

 //remove-consoles.js export default (fileInfo, api) => { const j = api.jscodeshift; const root = j(fileInfo.source); return root.toSource(); };

Baris const root = j(fileInfo.source); mengembalikan kumpulan satu jalur simpul, yang membungkus simpul AST akar. Kita dapat menggunakan metode find koleksi untuk mencari node turunan dari tipe tertentu, seperti:

 const callExpressions = root.find(j.CallExpression);

Ini mengembalikan kumpulan jalur simpul lain yang hanya berisi simpul yang merupakan CallExpressions. Pada awalnya blush on, sepertinya ini yang kita inginkan, tapi terlalu luas. Kami mungkin akhirnya menjalankan ratusan atau ribuan file melalui transformasi kami, jadi kami harus tepat untuk memiliki keyakinan bahwa itu akan berfungsi sebagaimana dimaksud. find naif di atas tidak hanya akan menemukan CallExpressions konsol, itu akan menemukan setiap CallExpression di sumbernya, termasuk

 require('foo') bar() setTimeout(() => {}, 0)

Untuk memaksa spesifisitas yang lebih besar, kami memberikan argumen kedua untuk .find : Objek parameter tambahan, setiap node harus disertakan dalam hasil. Kita dapat melihat AST Explorer untuk melihat bahwa panggilan console.* kita memiliki bentuk:

 { "type": "CallExpression", "callee": { "type": "MemberExpression", "object": { "type": "Identifier", "name": "console" } } }

Dengan pengetahuan itu, kami tahu untuk menyaring kueri kami dengan specifier yang hanya akan mengembalikan jenis CallExpressions yang kami minati:

 const callExpressions = root.find(j.CallExpression, { callee: { type: 'MemberExpression', object: { type: 'Identifier', name: 'console' }, }, });

Sekarang setelah kita mendapatkan kumpulan situs panggilan yang akurat, mari kita hapus dari AST. Mudahnya, tipe objek koleksi memiliki metode remove yang akan melakukan hal itu. File remove-consoles.js kami sekarang akan terlihat seperti ini:

 //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(); };

Sekarang, jika kita menjalankan transformasi dari baris perintah menggunakan jscodeshift -t remove-consoles.js remove-consoles.input.js -d -p , kita akan melihat:

 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

Itu terlihat bagus. Sekarang transformasi kami mengubah AST yang mendasarinya, menggunakan .toSource() menghasilkan string yang berbeda dari aslinya. Opsi -p dari perintah kami menampilkan hasilnya, dan penghitungan disposisi untuk setiap file yang diproses ditampilkan di bagian bawah. Menghapus opsi -d dari perintah kita, akan mengganti konten remove-consoles.input.js dengan output dari transformasi.

Latihan pertama kami selesai… hampir. Kodenya terlihat aneh dan mungkin sangat menyinggung semua puritan fungsional di luar sana, dan untuk membuat aliran kode transformasi lebih baik, jscodeshift telah membuat sebagian besar hal dapat dirantai. Ini memungkinkan kita untuk menulis ulang transformasi kita seperti:

 // 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(); };

Jauh lebih baik. Untuk rekap latihan 1, kami membungkus sumbernya, menanyakan kumpulan jalur simpul, mengubah AST, dan kemudian membuat ulang sumber itu. Kami telah membuat kaki kami basah dengan contoh yang cukup sederhana dan menyentuh aspek yang paling penting. Sekarang, mari kita lakukan sesuatu yang lebih menarik.

Latihan 2: Mengganti Panggilan Metode yang Diimpor

Untuk skenario ini, kami memiliki modul "geometri" dengan metode bernama "circleArea" yang tidak lagi digunakan untuk "getCircleArea." Kita dapat dengan mudah menemukan dan menggantinya dengan /geometry\.circleArea/g , tetapi bagaimana jika pengguna telah mengimpor modul dan memberinya nama yang berbeda? Sebagai contoh:

 import g from 'geometry'; const area = g.circleArea(radius);

Bagaimana kita tahu untuk mengganti g.circleArea alih-alih geometry.circleArea ? Kami tentu tidak dapat berasumsi bahwa semua panggilan circleArea adalah yang kami cari, kami memerlukan beberapa konteks. Di sinilah codemod mulai menunjukkan nilainya. Mari kita mulai dengan membuat dua file, deprecated.js dan 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));

Sekarang jalankan perintah ini untuk menjalankan codemod.

jscodeshift -t ./deprecated.js ./deprecated.input.js -d -p

Anda akan melihat output yang menunjukkan transformasi berjalan, tetapi belum mengubah apa pun.

 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

Kita perlu tahu apa modul geometry kita telah diimpor. Mari kita lihat AST Explorer dan cari tahu apa yang kita cari. Impor kami mengambil bentuk ini.

 { "type": "ImportDeclaration", "specifiers": [ { "type": "ImportDefaultSpecifier", "local": { "type": "Identifier", "name": "g" } } ], "source": { "type": "Literal", "value": "geometry" } }

Kita dapat menentukan tipe objek untuk menemukan kumpulan node seperti ini:

 const importDeclaration = root.find(j.ImportDeclaration, { source: { type: 'Literal', value: 'geometry', }, });

Ini memberi kami ImportDeclaration yang digunakan untuk mengimpor "geometri". Dari sana, gali untuk menemukan nama lokal yang digunakan untuk menyimpan modul yang diimpor. Karena ini adalah pertama kalinya kami melakukannya, mari tunjukkan poin penting dan membingungkan saat pertama kali memulai.

Catatan: Penting untuk diketahui bahwa root.find() mengembalikan kumpulan jalur-simpul. Dari sana, metode .get(n) mengembalikan jalur simpul pada indeks n dalam kumpulan itu, dan untuk mendapatkan simpul yang sebenarnya, kita menggunakan .node . Node pada dasarnya adalah apa yang kita lihat di AST Explorer. Ingat, jalur simpul sebagian besar adalah informasi tentang ruang lingkup dan hubungan simpul, bukan simpul itu sendiri.

 // 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;

Ini memungkinkan kita untuk mengetahui secara dinamis apa modul geometry kita telah diimpor. Selanjutnya, kami menemukan tempat yang digunakan dan mengubahnya. Dengan melihat AST Explorer, kita dapat melihat bahwa kita perlu menemukan MemberExpressions yang terlihat seperti ini:

 { "type": "MemberExpression", "object": { "name": "geometry" }, "property": { "name": "circleArea" } }

Namun, ingat bahwa modul kita mungkin telah diimpor dengan nama yang berbeda, jadi kita harus memperhitungkannya dengan membuat kueri kita terlihat seperti ini:

 j.MemberExpression, { object: { name: localName, }, property: { name: "circleArea", }, })

Sekarang setelah kita memiliki kueri, kita bisa mendapatkan koleksi semua situs panggilan ke metode lama kita dan kemudian menggunakan metode replaceWith() koleksi untuk menukarnya. Metode replaceWith() berulang melalui koleksi, meneruskan setiap jalur simpul ke fungsi panggilan balik. Node AST kemudian diganti dengan Node apa pun yang Anda kembalikan dari panggilan balik.

Codemods memungkinkan Anda untuk membuat skrip pertimbangan 'cerdas' untuk refactoring.

Sekali lagi, memahami perbedaan antara koleksi, jalur simpul, dan simpul diperlukan agar ini masuk akal.

Setelah kami selesai dengan penggantian, kami menghasilkan sumber seperti biasa. Inilah transformasi kami yang telah selesai:

 //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(); };

Ketika kami menjalankan sumber melalui transformasi, kami melihat bahwa panggilan ke metode yang tidak digunakan lagi dalam modul geometry telah diubah, tetapi sisanya dibiarkan tidak berubah, seperti:

 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));

Latihan 3: Mengubah Tanda Tangan Metode

Dalam latihan sebelumnya, kita telah membahas kumpulan kueri untuk jenis node tertentu, menghapus node, dan mengubah node, tetapi bagaimana dengan membuat node yang sama sekali baru? Itulah yang akan kita atasi dalam latihan ini.

Dalam skenario ini, kami memiliki tanda tangan metode yang menjadi tidak terkendali dengan argumen individual seiring dengan perkembangan perangkat lunak, dan karena itu telah diputuskan bahwa akan lebih baik untuk menerima objek yang berisi argumen tersebut sebagai gantinya.

Alih-alih car.factory('white', 'Kia', 'Sorento', 2010, 50000, null, true);

kami ingin melihat

 const suv = car.factory({ color: 'white', make: 'Kia', model: 'Sorento', year: 2010, miles: 50000, bedliner: null, alarm: true, });

Mari kita mulai dengan membuat transformasi dan file input untuk diuji dengan:

 //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);

Perintah kita untuk menjalankan transformasi adalah jscodeshift -t signature-change.js signature-change.input.js -d -p dan langkah-langkah yang kita perlukan untuk melakukan transformasi ini adalah:

  • Temukan nama lokal untuk modul yang diimpor
  • Temukan semua situs panggilan ke metode .factory
  • Baca semua argumen yang diteruskan
  • Ganti panggilan itu dengan satu argumen yang berisi objek dengan nilai aslinya

Menggunakan AST Explorer dan proses yang kami gunakan dalam latihan sebelumnya, dua langkah pertama mudah:

 //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(); };

Untuk membaca semua argumen yang saat ini diteruskan, kami menggunakan metode replaceWith() pada koleksi CallExpressions kami untuk menukar setiap node. Node baru akan menggantikan node.arguments dengan argumen tunggal baru, sebuah objek.

Tukar argumen metode dengan mudah dengan jscodeshift!

Ubah tanda tangan metode dengan 'replacewith()' dan tukar seluruh node.

Mari kita coba dengan objek sederhana untuk memastikan kita tahu cara kerjanya sebelum kita menggunakan nilai yang tepat:

 .replaceWith(nodePath => { const { node } = nodePath; node.arguments = [{ foo: 'bar' }]; return node; })

Ketika kita menjalankan ini ( jscodeshift -t signature-change.js signature-change.input.js -d -p ), transformasi akan meledak dengan:

 ERR signature-change.input.js Transformation error Error: {foo: bar} does not match type Printable

Ternyata kita tidak bisa hanya memasukkan objek biasa ke dalam node AST kita. Sebagai gantinya, kita perlu menggunakan builder untuk membuat node yang tepat.

Terkait: Pekerjakan 3% pengembang Javascript lepas teratas.

Pembangun Node

Builder memungkinkan kita membuat node baru dengan benar; mereka disediakan oleh ast-types dan muncul melalui jscodeshift. Mereka secara kaku memeriksa bahwa berbagai jenis node dibuat dengan benar, yang dapat membuat frustasi ketika Anda melakukan hacking pada roll, tetapi pada akhirnya, ini adalah hal yang baik. Untuk memahami cara menggunakan builder, ada dua hal yang harus Anda ingat:

Semua tipe node AST yang tersedia didefinisikan dalam folder def dari proyek github tipe ast, kebanyakan di core.js Ada builder untuk semua tipe node AST, tetapi mereka menggunakan versi tipe node unta, bukan pascal -kasus. (Ini tidak dinyatakan secara eksplisit, tetapi Anda dapat melihat ini terjadi di sumber tipe ast

Jika kita menggunakan AST Explorer dengan contoh hasil yang kita inginkan, kita dapat menggabungkannya dengan cukup mudah. Dalam kasus kami, kami ingin argumen tunggal baru menjadi ObjectExpression dengan banyak properti. Melihat definisi tipe yang disebutkan di atas, kita dapat melihat apa yang dimaksud:

 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"));

Jadi, kode untuk membangun simpul AST untuk { foo: 'bar' } akan terlihat seperti:

 j.objectExpression([ j.property( 'init', j.identifier('foo'), j.literal('bar') ) ]);

Ambil kode itu dan hubungkan ke transformasi kami seperti ini:

 .replaceWith(nodePath => { const { node } = nodePath; const object = j.objectExpression([ j.property( 'init', j.identifier('foo'), j.literal('bar') ) ]); node.arguments = [object]; return node; })

Menjalankan ini memberi kita hasil:

 import car from 'car'; const suv = car.factory({ foo: "bar" }); const truck = car.factory({ foo: "bar" });

Sekarang kita tahu cara membuat node AST yang tepat, mudah untuk mengulang argumen lama dan menghasilkan objek baru untuk digunakan. Berikut tampilan file signature-change.js kami sekarang:

 //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 }); };

Jalankan transformasi ( jscodeshift -t signature-change.js signature-change.input.js -d -p ) dan kita akan melihat tanda tangan telah diperbarui seperti yang diharapkan:

 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, });

Codemods dengan Rekap jscodeshift

Butuh sedikit waktu dan upaya untuk mencapai titik ini, tetapi manfaatnya sangat besar ketika dihadapkan dengan refactoring massal. Mendistribusikan grup file ke berbagai proses dan menjalankannya secara paralel adalah keunggulan jscodeshift, memungkinkan Anda menjalankan transformasi kompleks di seluruh basis kode besar dalam hitungan detik. Saat Anda menjadi lebih mahir dengan codemods, Anda akan mulai menggunakan kembali skrip yang ada (seperti repositori github react-codemod atau menulis Anda sendiri untuk semua jenis tugas, dan itu akan membuat Anda, tim Anda, dan pengguna paket Anda lebih efisien .