Kodunuzu Yeniden Yazmak için Kod Yazın: jscodeshift

Yayınlanan: 2022-03-11

jscodeshift ile kod modları

JavaScript kaynak dosyalarında değişiklik yapmak için bir dizinde bul ve değiştir işlevini kaç kez kullandınız? Eğer iyiyseniz, havalı oldunuz ve grup yakalama ile düzenli ifadeler kullandınız, çünkü kod tabanınız oldukça büyükse bu çabaya değer. Yine de Regex'in sınırları vardır. Önemsiz değişiklikler için, kodu bağlamda anlayan ve ayrıca uzun, sıkıcı ve hataya açık süreci üstlenmeye istekli bir geliştiriciye ihtiyacınız vardır.

İşte bu noktada “codemods” devreye giriyor.

Codemod'lar, diğer komut dosyalarını yeniden yazmak için kullanılan komut dosyalarıdır. Bunları kod okuyabilen ve yazabilen bul ve değiştir işlevi olarak düşünün. Bunları, kaynak kodunu bir ekibin kodlama kurallarına uyacak şekilde güncellemek, bir API değiştirildiğinde yaygın değişiklikler yapmak ve hatta genel paketiniz önemli bir değişiklik yaptığında mevcut kodu otomatik olarak düzeltmek için kullanabilirsiniz.

jscodeshift araç takımı, kod modlarıyla çalışmak için harikadır.

Codemod'ları, kod okuyabilen ve yazabilen, komut dosyasıyla yazılmış bul ve değiştir işlevi olarak düşünün.
Cıvıldamak

Bu makalede, artan karmaşıklıkta üç kod modu oluştururken “jscodeshift” adlı kod modları için bir araç seti keşfedeceğiz. Sonunda, jscodeshift'in önemli yönlerine geniş ölçüde maruz kalacaksınız ve kendi kod modlarınızı yazmaya başlamaya hazır olacaksınız. Codemod'ların bazı temel ama harika kullanımlarını kapsayan üç alıştırmayı gözden geçireceğiz ve bu alıştırmaların kaynak kodunu github projemde görüntüleyebilirsiniz.

jscodeshift nedir?

jscodeshift araç takımı, bir dönüşüm yoluyla bir grup kaynak dosyayı pompalamanıza ve bunları diğer uçtan çıkanlarla değiştirmenize olanak tanır. Dönüşümün içinde, kaynağı soyut bir sözdizimi ağacına (AST) ayrıştırırsınız, değişikliklerinizi yapmak için etrafta gezinirsiniz, ardından kaynağı değiştirilmiş AST'den yeniden oluşturursunuz.

jscodeshift'in sağladığı arabirim, recast ve ast-types paketlerinin etrafındaki bir sarmalayıcıdır. recast , kaynaktan AST'ye ve geriye dönüşümü işlerken ast-types , AST düğümleriyle düşük seviyeli etkileşimi işler.

Kurmak

Başlamak için jscodeshift'i global olarak npm'den yükleyin.

 npm i -g jscodeshift

Kullanabileceğiniz koşucu seçenekleri ve Jest (açık kaynaklı bir JavaScript test çerçevesi) aracılığıyla bir dizi test çalıştırmayı gerçekten kolaylaştıran fikirli bir test kurulumu var, ancak şimdilik basitlik lehine bunu atlayacağız:

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

Bu, input-file.js transform some-transform.js aracılığıyla çalıştıracak ve dosyayı değiştirmeden sonuçları yazdıracaktır.

Ancak konuya girmeden önce, jscodeshift API'sinin ilgilendiği üç ana nesne türünü anlamak önemlidir: düğümler, düğüm yolları ve koleksiyonlar.

düğümler

Düğümler, genellikle "AST düğümleri" olarak adlandırılan AST'nin temel yapı taşlarıdır. AST Explorer ile kodunuzu keşfederken bunları görürsünüz. Bunlar basit nesnelerdir ve herhangi bir yöntem sağlamazlar.

düğüm yolları

Düğüm yolları, soyut sözdizimi ağacında geçiş yapmanın bir yolu olarak ast-types tarafından sağlanan bir AST düğümünün etrafındaki sarmalayıcılardır (AST, hatırladınız mı?). Yalıtımda, düğümler üst öğeleri veya kapsamları hakkında herhangi bir bilgiye sahip değildir, bu nedenle düğüm yolları bununla ilgilenir. Sarılmış düğüme node özelliği aracılığıyla erişebilirsiniz ve temeldeki düğümü değiştirmek için kullanılabilecek birkaç yöntem vardır. düğüm yollarına genellikle yalnızca "yollar" denir.

Koleksiyonlar

Koleksiyonlar, AST'yi sorguladığınızda jscodeshift API'sinin döndürdüğü sıfır veya daha fazla düğüm yolundan oluşan gruplardır. Bazılarını keşfedeceğimiz her türlü yararlı yönteme sahipler.

Koleksiyonlar düğüm yollarını içerir, düğüm yolları düğümleri içerir ve düğümler AST'nin yapıldığı şeydir. Bunu aklınızda bulundurun ve jscodeshift sorgu API'sini anlamak kolay olacaktır.

Bu nesneler ve ilgili API yetenekleri arasındaki farkları takip etmek zor olabilir, bu nedenle nesne türünü kaydeden ve diğer önemli bilgileri sağlayan jscodeshift-helper adlı şık bir araç var.

Düğümler, düğüm yolları ve koleksiyonlar arasındaki farkı bilmek önemlidir.

Düğümler, düğüm yolları ve koleksiyonlar arasındaki farkı bilmek önemlidir.

Alıştırma 1: Konsol Çağrılarını Kaldırın

Ayaklarımızı ıslatmak için kod tabanımızdaki tüm konsol yöntemlerine yapılan çağrıları kaldırarak başlayalım. Bunu bul ve değiştir ve küçük bir normal ifade ile yapabilirsiniz, ancak çok satırlı ifadeler, şablon değişmezleri ve daha karmaşık çağrılar ile zorlaşmaya başlar, bu nedenle başlamak için ideal bir örnektir.

İlk önce iki dosya oluşturun, remove-consoles.js ve 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); };

Terminalde onu jscodeshift'ten geçirmek için kullanacağımız komut:

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

Her şey doğru ayarlanmışsa, çalıştırdığınızda böyle bir şey görmelisiniz.

 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

Tamam, dönüşümümüz henüz bir şey yapmadığı için bu biraz antiklimaktikti, ama en azından her şeyin işe yaradığını biliyoruz. Hiç çalışmıyorsa, global olarak jscodeshift'i yüklediğinizden emin olun. Dönüştürmeyi çalıştırma komutu yanlışsa, “HATA Dönüştürme dosyası … mevcut değil” mesajını veya giriş dosyası bulunamazsa “TypeError: yol bir dize veya Tampon olmalıdır” mesajını görürsünüz. Bir şeye parmak bastıysanız, çok açıklayıcı dönüşüm hatalarıyla bunu fark etmeniz kolay olacaktır.

İlgili: Toptal'ın Hızlı ve Pratik JavaScript Hile Sayfası: ES6 ve Ötesi

Başarılı bir dönüşümün ardından nihai hedefimiz şu kaynağı görmektir:

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

Oraya ulaşmak için kaynağı bir AST'ye dönüştürmemiz, konsolları bulmamız, onları kaldırmamız ve sonra değiştirilmiş AST'yi tekrar kaynağa dönüştürmemiz gerekiyor. İlk ve son adımlar kolaydır, sadece:

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

Peki konsolları nasıl bulup kaldıracağız? Mozilla Ayrıştırıcı API'si hakkında olağanüstü bir bilgiye sahip değilseniz, muhtemelen AST'nin nasıl göründüğünü anlamanıza yardımcı olacak bir araca ihtiyacınız olacaktır. Bunun için AST Explorer'ı kullanabilirsiniz. remove-consoles.input.js içeriğini buna yapıştırın ve AST'yi göreceksiniz. En basit kodda bile çok fazla veri var, bu nedenle konum verilerini ve yöntemlerini gizlemeye yardımcı oluyor. Ağacın üzerindeki onay kutularını kullanarak AST Explorer'da özelliklerin görünürlüğünü değiştirebilirsiniz.

Konsola çağrı yöntemlerinin CallExpressions olarak adlandırıldığını görebiliriz, peki bunları dönüşümümüzde nasıl bulabiliriz? Koleksiyonlar, düğüm yolları ve düğümlerin kendileri arasındaki farklar hakkındaki önceki tartışmamızı hatırlayarak jscodeshift'in sorgularını kullanıyoruz:

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

Satır const root = j(fileInfo.source); kök AST düğümünü saran bir düğüm yolu koleksiyonu döndürür. Belirli bir türdeki alt düğümleri aramak için koleksiyonun find yöntemini kullanabiliriz, örneğin:

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

Bu, yalnızca CallExpressions olan düğümleri içeren başka bir düğüm yolu koleksiyonu döndürür. İlk bakışta, istediğimiz bu gibi görünüyor, ancak çok geniş. Dönüşümlerimiz aracılığıyla yüzlerce veya binlerce dosya çalıştırabiliriz, bu nedenle amaçlandığı gibi çalışacağından emin olmak için kesin olmalıyız. Yukarıdaki saf find , yalnızca CallExpressions konsolunu bulmakla kalmaz, kaynaktaki her CallExpression da dahil olmak üzere bulur.

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

Daha fazla özgünlüğü zorlamak için, .find ikinci bir argüman sağlıyoruz: Ek parametrelerin bir nesnesi, her düğümün sonuçlara dahil edilmesi gerekir. Konsolumuzun* çağrılarının şu şekilde olduğunu görmek için AST Explorer'a bakabiliriz:

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

Bu bilgiyle, yalnızca ilgilendiğimiz CallExpressions türünü döndürecek bir belirteçle sorgumuzu hassaslaştırmayı biliyoruz:

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

Artık çağrı sitelerinin doğru bir koleksiyonuna sahip olduğumuza göre, onları AST'den çıkaralım. Uygun bir şekilde, toplama nesnesi türü, tam da bunu yapacak bir remove yöntemine sahiptir. remove-consoles.js dosyamız şimdi şöyle görünecek:

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

Şimdi, jscodeshift -t remove-consoles.js remove-consoles.input.js -d -p kullanarak dönüşümümüzü komut satırından çalıştırırsak, şunu görmeliyiz:

 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

Güzel görünüyor. Artık dönüşümümüz temeldeki AST'yi değiştirdiğine göre, .toSource() kullanmak orijinalden farklı bir dize oluşturur. Komutumuzdaki -p seçeneği sonucu görüntüler ve işlenen her dosya için bir düzenleme listesi altta gösterilir. Komutumuzdan -d seçeneğini kaldırmak, remove-consoles.input.js içeriğini dönüştürmenin çıktısıyla değiştirir.

İlk egzersizimiz tamamlandı… neredeyse. Kod tuhaf görünüyor ve muhtemelen herhangi bir işlevsel saflık için çok rahatsız edici ve bu nedenle dönüşüm kodu akışını daha iyi hale getirmek için jscodeshift çoğu şeyi zincirlenebilir hale getirdi. Bu, dönüşümümüzü şu şekilde yeniden yazmamızı sağlar:

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

Çok daha iyi. Alıştırma 1'i tekrarlamak için kaynağı sardık, düğüm yolları koleksiyonunu sorguladık, AST'yi değiştirdik ve sonra bu kaynağı yeniden oluşturduk. Oldukça basit bir örnekle ayaklarımızı ıslattık ve en önemli noktalara değindik. Şimdi daha ilginç bir şey yapalım.

Alıştırma 2: İçe Aktarılan Yöntem Çağrılarını Değiştirme

Bu senaryo için, "getCircleArea" lehine kullanımdan kaldırdığımız "circleArea" adlı bir yönteme sahip bir "geometri" modülümüz var. Bunları /geometry\.circleArea/g ile kolayca bulabilir ve değiştirebiliriz, ancak ya kullanıcı modülü içe aktardıysa ve ona farklı bir ad atadıysa? Örneğin:

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

geometry.circleArea yerine g.circleArea değiştirmeyi nasıl bilebiliriz? Kesinlikle tüm circleArea çağrılarının aradığımız çağrılar olduğunu varsayamayız, bir bağlama ihtiyacımız var. Burası kod modlarının değerlerini göstermeye başladığı yerdir. deprecated.input.js deprecated.js üzere iki dosya oluşturarak başlayalım.

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

Şimdi codemod'u çalıştırmak için bu komutu çalıştırın.

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

Dönüşümün çalıştığını, ancak henüz hiçbir şeyi değiştirmediğini gösteren çıktı görmelisiniz.

 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 modülümüzün ne olarak import edildiğini bilmemiz gerekiyor. AST Explorer'a bakalım ve ne aradığımızı anlayalım. İthalatımız bu şekli alır.

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

Bunun gibi bir düğüm koleksiyonu bulmak için bir nesne türü belirtebiliriz:

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

Bu bize "geometri" almak için kullanılan ImportDeclaration'ı verir. Buradan, içe aktarılan modülü tutmak için kullanılan yerel adı bulmak için aşağı inin. Bunu ilk defa yaptığımız için ilk başlarken önemli ve kafa karıştıran bir noktaya dikkat çekelim.

Not: root.find() bir düğüm yolları koleksiyonu döndürdüğünü bilmek önemlidir. Buradan, .get(n) yöntemi, o koleksiyondaki n dizinindeki düğüm yolunu döndürür ve asıl düğümü almak için .node kullanırız. Düğüm, temelde AST Explorer'da gördüğümüz şeydir. Unutmayın, düğüm yolu çoğunlukla düğümün kendisi değil, düğümün kapsamı ve ilişkileri hakkında bilgidir.

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

Bu, geometry modülümüzün ne olarak içe aktarıldığını dinamik olarak bulmamızı sağlar. Daha sonra kullanıldığı yerleri bulup değiştiriyoruz. AST Explorer'a bakarak, şuna benzeyen Üye İfadeleri bulmamız gerektiğini görebiliriz:

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

Yine de, modülümüzün farklı bir adla içe aktarılmış olabileceğini unutmayın, bu nedenle sorgumuzu bunun yerine şöyle yaparak hesaba katmalıyız:

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

Artık bir sorgumuz olduğuna göre, eski yöntemimize yönelik tüm çağrı sitelerinin bir koleksiyonunu alabilir ve ardından bunları değiştirmek için koleksiyonun replaceWith() yöntemini kullanabiliriz. replaceWith() yöntemi, her düğüm yolunu bir geri çağırma işlevine geçirerek koleksiyon boyunca yinelenir. AST Düğümü daha sonra geri aramadan döndürdüğünüz Düğüm ile değiştirilir.

Codemod'lar, yeniden düzenleme için 'akıllı' değerlendirmeleri yazmanıza izin verir.

Yine, bunun anlamlı olması için koleksiyonlar, düğüm yolları ve düğümler arasındaki farkı anlamak gerekir.

Değiştirmeyi bitirdiğimizde, kaynağı her zamanki gibi oluştururuz. İşte bitmiş dönüşümümüz:

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

Kaynağı dönüşüm üzerinden çalıştırdığımızda, geometry modülündeki kullanımdan kaldırılan yönteme yapılan çağrının değiştirildiğini ancak geri kalanının değişmeden kaldığını görüyoruz, şöyle:

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

Alıştırma 3: Yöntem İmzasını Değiştirme

Önceki alıştırmalarda, belirli düğüm türleri için koleksiyonları sorgulamayı, düğümleri kaldırmayı ve düğümleri değiştirmeyi ele aldık, peki ya tamamen yeni düğümler oluşturmaya ne dersiniz? Bu alıştırmada ele alacağımız şey bu.

Bu senaryoda, yazılım büyüdükçe bireysel argümanlarla kontrolden çıkan bir yöntem imzamız var ve bu nedenle bu argümanları içeren bir nesneyi kabul etmenin daha iyi olacağına karar verildi.

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

görmek isteriz

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

Dönüşümü ve test edilecek bir girdi dosyası yaparak başlayalım:

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

Dönüşümü çalıştırmak için jscodeshift -t signature-change.js signature-change.input.js -d -p ve bu dönüşümü gerçekleştirmek için ihtiyacımız olan adımlar:

  • İçe aktarılan modülün yerel adını bulun
  • Tüm çağrı sitelerini .factory yöntemiyle bulun
  • İletilen tüm argümanları okuyun
  • Bu çağrıyı orijinal değerlere sahip bir nesne içeren tek bir argümanla değiştirin

AST Explorer'ı ve önceki alıştırmalarda kullandığımız süreci kullanarak, ilk iki adım kolaydır:

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

Halihazırda iletilen tüm argümanları okumak için, her bir düğümü değiştirmek için CallExpressions koleksiyonumuzda replaceWith() yöntemini kullanırız. Yeni düğümler, node.arguments'ı yeni bir tek argümanla, bir nesneyle değiştirecektir.

jscodeshift ile yöntem argümanlarını kolayca değiştirin!

'replacewith()' ile yöntem imzalarını değiştirin ve tüm düğümleri değiştirin.

Doğru değerleri kullanmadan önce bunun nasıl çalıştığını bildiğimizden emin olmak için basit bir nesneyle deneyelim:

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

Bunu çalıştırdığımızda ( jscodeshift -t signature-change.js signature-change.input.js -d -p ), dönüşüm şu şekilde patlayacak:

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

AST düğümlerimize düz nesneleri sıkıştıramayacağımız ortaya çıktı. Bunun yerine, uygun düğümler oluşturmak için oluşturucuları kullanmamız gerekiyor.

İlgili: Serbest Javascript geliştiricilerinin ilk %3'ünü işe alın.

Düğüm Oluşturucular

Oluşturucular, yeni düğümleri düzgün bir şekilde oluşturmamıza izin verir; bunlar ast-types tarafından sağlanır ve jscodeshift aracılığıyla ortaya çıkar. Farklı tipteki düğümlerin doğru bir şekilde oluşturulup oluşturulmadığını katı bir şekilde kontrol ederler, bu da bir ruloyu hacklerken sinir bozucu olabilir, ancak sonuçta bu iyi bir şeydir. İnşaatçıların nasıl kullanılacağını anlamak için aklınızda bulundurmanız gereken iki şey vardır:

Mevcut tüm AST düğüm türleri, çoğunlukla core.js'de olmak üzere, ast-types github projesinin def klasöründe tanımlanır. -durum. (Bu açıkça belirtilmemiştir, ancak ast-types kaynağında durumun böyle olduğunu görebilirsiniz.

AST Explorer'ı sonucun ne olmasını istediğimize dair bir örnekle kullanırsak, bunu kolayca bir araya getirebiliriz. Bizim durumumuzda, yeni tek argümanın bir grup özelliği olan bir ObjectExpression olmasını istiyoruz. Yukarıda bahsedilen tip tanımlarına baktığımızda bunun ne anlama geldiğini görebiliriz:

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

Bu nedenle, { foo: 'bar' } için bir AST düğümü oluşturma kodu şöyle görünür:

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

Bu kodu alın ve şu şekilde dönüşümümüze ekleyin:

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

Bunu çalıştırmak bize sonucu verir:

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

Artık uygun bir AST düğümünün nasıl oluşturulacağını bildiğimize göre, eski argümanlar arasında dolaşmak ve bunun yerine kullanılacak yeni bir nesne oluşturmak kolaydır. signature-change.js dosyamız şu anda böyle görünüyor:

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

Dönüşümü çalıştırın ( jscodeshift -t signature-change.js signature-change.input.js -d -p ) ve imzaların beklendiği gibi güncellendiğini göreceğiz:

 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 Özeti ile Codemod'lar

Bu noktaya gelmek biraz zaman ve çaba gerektirdi, ancak toplu yeniden düzenleme ile karşı karşıya kalındığında faydaları çok büyük. Dosya gruplarını farklı işlemlere dağıtmak ve bunları paralel olarak çalıştırmak jscodeshift'in mükemmel olduğu bir şeydir ve karmaşık dönüşümleri devasa bir kod tabanında saniyeler içinde çalıştırmanıza olanak tanır. Codemod'larda daha yetkin hale geldikçe, mevcut komut dosyalarını (reaksiyon-codemod github deposu gibi veya her türlü görev için kendinizinkini yazmak gibi) yeniden kullanmaya başlayacaksınız ve bu sizi, ekibinizi ve paket kullanıcılarınızı daha verimli hale getirecektir. .