Bekerja dengan pohon sintaksis JavaScript abstrak

Mengapa menguraikan kode Anda? Misalnya, untuk menemukan console.log yang terlupakan sebelum melakukan. Tetapi bagaimana jika Anda perlu mengubah tanda tangan fungsi dalam ratusan entri dalam kode? Akankah ekspresi reguler mengatasi di sini? Artikel ini akan menunjukkan kepada Anda kemungkinan apa yang ditawarkan pohon sintaksis abstrak kepada pengembang.



Di bawah potongan - video dan transkrip teks dari laporan oleh Kirill Cherkashin ( z6Dabrata ) dari konferensi HolyJS 2018 Piter .


Tentang penulis
Cyril lahir di Moskow, sekarang tinggal di New York dan bekerja di Firebase. Mengajar Angular tidak hanya di Google, tetapi di seluruh dunia. Penyelenggara mitap Angular terbesar di dunia adalah AngularNYC (serta VueNYC dan ReactNYC). Di waktu luangnya dari pemrograman, ia menyukai tango, buku, dan percakapan yang menyenangkan.

Gergaji besi atau kayu?


Mari kita mulai dengan sebuah contoh: katakanlah Anda mendebug sebuah program dan mengirimkan perubahan yang dibuat ke git, setelah itu Anda pergi tidur dengan tenang. Di pagi hari ternyata kolega Anda mengunduh perubahan Anda dan, karena Anda lupa menghapus output informasi debug ke konsol sehari sebelumnya, itu menampilkannya dan menyumbat output. Banyak yang menghadapi masalah ini.

Ada alat, seperti EsLint , untuk memperbaiki situasi, tetapi untuk tujuan pendidikan, mari kita coba mencari solusi sendiri.
Alat mana yang harus saya gunakan untuk menghapus semua console.log() dari kode?
Kami memilih antara ekspresi reguler dan penggunaan Abstract Sitax trees (ASD). Mari kita coba selesaikan ini dengan ekspresi reguler dengan menulis beberapa fungsi findConsoleLog . Pada input, ia akan menerima kode program sebagai argumen dan menampilkan true jika console.log () ditemukan di suatu tempat dalam teks program.

 function findConsoleLog(code) { return !!code.match(/console.log/); } 

Saya menulis 17 tes, mencoba menemukan berbagai cara untuk merusak fungsi kami. Daftar ini jauh dari lengkap.



Tes paling sederhana berlalu.
Dan bagaimana jika ada fungsi yang berisi string "console.log" dalam namanya?

 function findConsoleLog(code) { return !!code.match(/\bconsole.log/); } 

Menambahkan karakter yang menunjukkan bahwa console.log harus muncul di awal kata.



Hanya dua tes yang lulus, tetapi bagaimana jika console.log ada di komentar dan tidak perlu dihapus?

Kami menulis ulang sehingga parser tidak menyentuh komentar.

 function findConsoleLog(code) { return !!code   .replace(/\/\/.*/)   .match(/\bconsole.log/); } 



Kami mengecualikan penghapusan "console.log" dari baris:

 function findConsoleLog(code) { return !!code   .replace(/\/\/.*|'.*'/, '')   .match(/\bconsole.log/); } 



Jangan lupa bahwa kami masih memiliki spasi dan karakter lain yang dapat mencegah beberapa tes lulus:



Terlepas dari kenyataan bahwa ide itu tidak cukup sederhana, semua 17 tes menggunakan ekspresi reguler dapat dilewati. Di sini, dalam hal ini, kode solusi akan terlihat:

 function findConsoleLog(code) { return code   .replace(/\/\/.*|'.*?[^\\]'|".*?"|`[\s\S]*`|\/\*[\s\S]*\*\//)   .match(/\bconsole\s*.log\(/); } 


Masalahnya adalah bahwa kode ini tidak mencakup semua kasus yang mungkin, dan agak sulit untuk mempertahankannya.

Pertimbangkan cara mengatasi masalah ini menggunakan ASD.

Bagaimana pohon-pohon tumbuh?


Pohon sintaksis abstrak diperoleh sebagai hasil parser yang bekerja dengan kode aplikasi Anda. Parser @ babel / parser digunakan untuk demonstrasi .
Sebagai contoh, ambil string console.log('holy') , sampaikan melalui parser.

 import { parse } from 'babylon'; parse("console.log('holy')"); 

Sebagai hasil karyanya, file JSON sekitar 300 baris diperoleh. Kami mengecualikan dari garis nomor mereka dengan informasi layanan. Kami tertarik pada bagian tubuh. Meta-informasi juga tidak menarik bagi kita. Hasilnya sekitar 100 baris. Dibandingkan dengan struktur apa yang dihasilkan browser untuk satu variabel body (sekitar 300 baris), ini tidak banyak.

Mari kita lihat beberapa contoh bagaimana berbagai literal direpresentasikan dalam kode di pohon sintaks:



Ini adalah ungkapan di mana ada Numeric Literal, literal numerik.



Ekspresi console.log sudah akrab. Ia memiliki objek yang memiliki properti.



Jika log adalah panggilan fungsi, maka deskripsinya adalah sebagai berikut: ada ekspresi panggilan, ia memiliki argumen - literal numerik. Pada saat yang sama, ekspresi panggilan memiliki log nama.

Literal dapat berbeda: angka, string, ekspresi reguler, boolean, null.
Kembali ke panggilan console.log



Ini adalah ekspresi panggilan yang memiliki Ekspresi Anggota di dalamnya. Dari sini jelas bahwa objek konsol di dalamnya memiliki properti yang disebut log.

Bypass ASD


Sekarang mari kita coba bekerja dengan struktur ini dalam kode. Perpustakaan babel-traverse akan digunakan untuk melintasi pohon .

17 tes yang sama diberikan. Kode tersebut diperoleh dengan menganalisis pohon sintaks program dan mencari entri "console.log":

 function traverseConsoleLog(code, {babylon, babelTraverse, types, log}) { const ast = babylon.parse(code); let hasConsoleLog = false; babelTraverse(ast, {   MemberExpression(path){     if (       path.node.property.type === 'Identifier' &&       path.node.property.name === 'log' &&       path.node.object.type === 'Identifier' &&       path.node.object.name === 'console' &&       path.parent.type === 'CallExpression' &&       path.Parentkey === 'callee'     ) {       hasConsoleLog = true;     }   } }) return hasConsoleLog; } 

Mari kita analisis apa yang tertulis di sini. const ast = babylon.parse(code); ke dalam variabel ast kita menguraikan pohon sintaksis dari kode. Selanjutnya kita berikan pustaka babel-parse pohon ini untuk diproses. Kami mencari simpul dan properti dengan nama yang cocok di dalam ekspresi panggilan. Setel variabel hasConsoleLog menjadi true jika kombinasi node dan nama yang diperlukan ditemukan.

Kita dapat bergerak di sekitar pohon, mengambil orang tua dari node, keturunan, mencari argumen dan properti apa yang mereka miliki, melihat nama-nama properti ini, jenis - ini sangat nyaman.

Ada nuansa yang tidak menyenangkan yang dapat dengan mudah diperbaiki menggunakan perpustakaan tipe-babel. Untuk menghindari kesalahan ketika mencari di pohon karena nama yang salah, misalnya, alih-alih path.parent.type === 'CallExpression' Anda tidak sengaja menulis path.parent.type === 'callExpression' , dengan tipe-babel Anda dapat menulis seperti ini :

 // Before path.node.property.type === 'Identifier' path.node.property.name === 'log' // with babel types import {isIdentifier} from 'babel-types'; isIdentifier(path.node.property, {name: log}) //         ,  ,    isIdentifier,      

Kami menulis ulang kode sebelumnya menggunakan tipe-babel:
 function traverseConsoleLogSolved2(code, {babylon, babelTraverse, types}) { const ast = babylon.parse(code); let hasConsoleLog = false; babelTraverse(ast, {   MemberExpression(path) {     if (       types.isIdentifier(path.node.object, { name: 'console'}) &&       types.isIdentifier(path.node.property, { name: 'log'}) &&       types.isCallExpression(path.parent) &&       path.parentKey === 'callee'     ) {       hasConsoleLog = true;     }   } }); return hasConsoleLog; } 

Transform ASD menggunakan babel-traverse


Untuk mengurangi biaya tenaga kerja, kita perlu console.log segera dihapus dari kode - alih-alih sinyal bahwa kode itu ada dalam kode.

Karena kita perlu menghapus bukan MemberExpression itu sendiri, tetapi induknya, di tempat hasConsoleLog = true; kami menulis path.parentPath.remove(); .

Dari fungsi removeConsoleLog , kami masih mengembalikan nilai Boolean. Kami mengganti outputnya dengan kode yang akan menghasilkan generator-babel, seperti ini:
hasConsoleLog => babelGenerator(ast).code

Babel-generator menerima pohon sintaksis abstrak yang dimodifikasi sebagai parameter, mengembalikan objek dengan properti kode, di dalam objek ini kode dibuat ulang tanpa console.log . Omong-omong, jika kita ingin mendapatkan peta kode, kita dapat memanggil properti sourceMaps untuk objek ini.

Dan jika Anda perlu menemukan debugger?


Kali ini kita akan menggunakan ASTexplorer untuk menyelesaikan tugas. Debugger adalah jenis node pernyataan debugger. Kita tidak perlu melihat keseluruhan struktur, karena ini adalah jenis simpul khusus, cukup temukan pernyataan debugger. Kami akan menulis sebuah plugin untuk ESLint (di ASTexplorer).

ASTexplorer dirancang sedemikian rupa sehingga Anda menulis kode di sebelah kiri, dan di sebelah kanan Anda mendapatkan ASD yang sudah selesai. Anda dapat memilih dalam format mana Anda ingin menerimanya: JSON atau dalam format pohon.



Karena kita menggunakan ESLint, itu akan melakukan semua pekerjaan mencari file untuk kita dan memberi kita file yang diperlukan sehingga kita dapat menemukan baris debugger di dalamnya. Alat ini menggunakan parser ASD yang berbeda. Namun, ada beberapa jenis ASD dalam JavaScript. Sesuatu yang mengingatkan masa lalu, ketika browser yang berbeda mengimplementasikan spesifikasi dengan cara yang berbeda. Dengan demikian, kami menerapkan pencarian debugger:

 export default function(context) { return {   DebuggerStatement(node) { // ,     console.log    path,    -  ,     path         context.report(node, 'LOL Debugger!!!'); //   ESLint ,   debugger, node     ,    ,    debugger   } } } 

Memeriksa pekerjaan plugin tertulis:



Demikian pula, Anda dapat menghapus debugger dari kode.

Apa lagi ASD yang berguna


Saya pribadi menggunakan ASD untuk mempermudah bekerja dengan Angular dan kerangka kerja front-end lainnya. Anda dapat mengimpor, memperluas, menambahkan antarmuka, metode, dekorator, dan hal lainnya dengan mengklik tombol. Meskipun kita berbicara tentang Javascript dalam kasus ini, bagaimanapun, TypeScript juga memiliki ASD sendiri, satu-satunya perbedaan adalah perbedaan antara nama-nama jenis simpul dan struktur. Dalam ASTExplorer yang sama dapat dipilih sebagai bahasa TypeScript.

Jadi:

  • Kami memiliki kontrol lebih besar atas kode, refactoring lebih mudah, codemods. Misalnya, sebelum melakukan, Anda dapat menekan satu tombol untuk memformat seluruh kode sesuai dengan pedoman. Codemods menyiratkan pencocokan kode otomatis sesuai dengan versi kerangka kerja yang diperlukan.
  • Lebih sedikit perselisihan tentang desain kode.
  • Anda dapat membuat proyek game. Misalnya, secara otomatis memberikan umpan balik kepada programmer tentang kode yang ia tulis.
  • Pemahaman yang lebih baik tentang JavaScript.

Beberapa tautan bermanfaat untuk Babel


  1. Semua transformasi Babel menggunakan API ini: plugins dan preset .
  2. Bagian dari proses menambahkan fungsionalitas baru ke ECMAScript adalah membuat plugin untuk Babel. Ini diperlukan agar orang dapat menguji fungsionalitas baru. Jika Anda mengikuti tautan , Anda dapat melihat bahwa di dalam yang sama digunakan kemampuan ASD. Misalnya, operator penugasan-logis .
  3. Babel Generator kehilangan pemformatan saat membuat kode. Ini sebagian baik, karena jika alat ini digunakan dalam tim pengembangan, maka setelah menghasilkan kode dari ASD, itu akan terlihat sama untuk semua orang. Tetapi jika Anda ingin tetap memformat, Anda dapat menggunakan salah satu dari alat ini: Recast atau Babel CodeMod .
  4. Dari tautan ini Anda dapat menemukan banyak informasi tentang Babel Awesome Babel .
  5. Babel adalah proyek sumber terbuka dan tim sukarelawan sedang mengerjakannya. Kamu bisa membantu. Ada tiga cara untuk melakukan ini: bantuan keuangan, Anda dapat mendukung situs web patreon, tempat Henry Zhu, salah satu kontributor utama babel, bekerja, membantu dengan kode pada opencollective.com/babel .

Bonus


Bagaimana lagi kita bisa menemukan kode console.log kita? Gunakan IDE Anda! Menggunakan alat temukan dan ganti, setelah memilih tempat mencari kode.
Intellij IDEA juga memiliki alat "pencarian struktural" yang dapat membantu Anda menemukan tempat yang tepat dalam kode Anda, dengan cara itu, ia menggunakan ASD.

Pada 24-25 November, Kirill akan memberikan presentasi tentang JavaScript * LOVES * data biner di Moscow HolyJS : kita akan turun ke tingkat data biner, menggali file biner menggunakan file * .gif sebagai contoh, dan berurusan dengan kerangka kerja serialisasi seperti Protobuf atau Thrift. Setelah laporan, akan mungkin untuk berbicara dengan Cyril dan membahas semua masalah yang menarik di bidang diskusi.

Source: https://habr.com/ru/post/id428628/


All Articles