Alat refactoring khusus: Swift

Setiap insinyur berusaha membuat proses kerjanya seoptimal mungkin. Sebagai pengembang iOS seluler, kami sangat sering harus bekerja dengan struktur bahasa yang seragam. Apple sedang meningkatkan alat pengembang dengan melakukan banyak upaya agar nyaman bagi kita untuk memprogram: penyorotan bahasa, metode pelengkapan otomatis dan banyak fitur IDE lainnya yang memungkinkan jari kita mengikuti ide di kepala kita.



Apa yang dilakukan seorang insinyur ketika alat yang dibutuhkan hilang? Benar, dia akan melakukan semuanya sendiri! Sebelumnya kami berbicara tentang membuat alat khusus kami, sekarang mari kita bicara tentang cara memodifikasi Xcode dan membuatnya berfungsi sesuai aturan Anda.

Kami mengambil tugas dari JIRA Swift dan membuat alat yang mengubah jika dibiarkan menjadi penjaga yang setara.



Sejak versi kesembilan, Xcode menyediakan mekanisme refactoring baru yang dapat mengkonversi kode secara lokal, dalam file sumber Swift yang sama, atau secara global ketika Anda mengganti nama metode atau properti yang terjadi dalam banyak file, bahkan jika mereka dalam bahasa yang berbeda.

Refactoring lokal sepenuhnya diimplementasikan dalam kompiler dan kerangka kerja SourceKit, fitur ini ada dalam repositori Swift open source dan ditulis dalam C ++. Modifikasi refactoring global saat ini tidak dapat diakses oleh orang biasa, karena basis kode Xcode ditutup. Karena itu, kita akan membahas sejarah lokal dan berbicara tentang cara mengulang pengalaman kita.

Apa yang Anda butuhkan untuk membuat alat sendiri untuk refactoring lokal:

  1. Memahami C ++
  2. Pengetahuan dasar tentang kompiler
  3. Memahami apa itu AST dan bagaimana cara mengatasinya
  4. Kode sumber cepat
  5. Panduan swift / docs / refactoring / SwiftLocalRefactoring.md
  6. Banyak kesabaran

Sedikit tentang AST


Sedikit dasar teoretis sebelum terjun dalam praktik. Mari kita lihat bagaimana arsitektur kompiler Swift bekerja. Pertama-tama, kompiler bertanggung jawab untuk mengubah kode menjadi kode mesin yang dapat dieksekusi.



Dari tahapan transformasi yang disajikan, yang paling menarik bagi kami adalah pembuatan pohon sintaksis abstrak (AST) - grafik di mana simpulnya adalah operator dan daunnya adalah operan mereka.



Sintaksis pohon digunakan dalam pengurai. AST digunakan sebagai representasi internal dalam kompiler / juru bahasa program komputer untuk mengoptimalkan dan menghasilkan kode.

Setelah AST dibuat, parsing dilakukan untuk membuat AST dengan pemeriksaan tipe yang telah diterjemahkan ke dalam Bahasa Menengah Swift. SIL dikonversi, dioptimalkan, diturunkan ke LLVM IR, yang akhirnya dikompilasi menjadi kode mesin.

Untuk membuat alat refactoring, kita perlu memahami AST dan dapat bekerja dengannya. Jadi alat akan dapat beroperasi dengan benar dengan bagian-bagian kode yang ingin kita proses.

Untuk menghasilkan AST file, jalankan perintah: swiftc -dump-ast MyFile.swift

Di bawah ini adalah output ke konsol AST dari fungsi if let , yang disebutkan sebelumnya.



Ada tiga jenis utama simpul di Swift AST:

  • deklarasi (subclass dari tipe Decl),
  • ekspresi (subclass dari tipe Expr),
  • operator (subkelas dari tipe Stmt).

Mereka sesuai dengan tiga entitas yang digunakan dalam bahasa Swift itu sendiri. Nama fungsi, struktur, parameter adalah deklarasi. Ekspresi adalah entitas yang mengembalikan nilai; misalnya fungsi panggilan. Operator adalah bagian dari bahasa yang menentukan aliran kontrol eksekusi kode, tetapi tidak mengembalikan nilai (misalnya, jika atau lakukan-tangkap).

Ini adalah minimum yang cukup yang perlu Anda ketahui tentang AST untuk pekerjaan mendatang Anda.

Bagaimana alat refactoring bekerja secara teori


Untuk menerapkan alat refactoring, Anda memerlukan informasi spesifik tentang area kode yang akan Anda ubah. Pengembang diberikan entitas tambahan yang mengakumulasikan data. Yang pertama, ResolvedCursorInfo (refactoring berbasis kursor), memberi tahu Anda jika kita berada di awal ekspresi. Jika demikian, objek kompilator yang sesuai dari ekspresi ini dikembalikan. Entitas kedua, RangeInfo (refactoring berbasis rentang), merangkum data tentang rentang asli (misalnya, berapa banyak titik masuk dan keluar yang dimilikinya).

Refactoring berbasis kursor dimulai oleh lokasi kursor dalam file sumber. Tindakan refactoring menerapkan metode yang digunakan mekanisme refactoring untuk menampilkan tindakan yang tersedia di IDE dan untuk melakukan transformasi. Contoh tindakan berdasarkan kursor: Melompat ke definisi, bantuan cepat, dll.



Pertimbangkan tindakan biasa dari sisi teknis:

  1. Saat Anda memilih lokasi dari editor Xcode, permintaan dibuat ke sourcekitd (kerangka kerja yang bertanggung jawab untuk penyorotan, penyelesaian kode, dll.) Untuk menampilkan tindakan refactoring yang tersedia.
  2. Setiap tindakan yang tersedia diminta oleh objek ResolvedCursorInfo untuk memeriksa apakah tindakan ini berlaku untuk kode yang dipilih.
  3. Daftar tindakan yang berlaku dikembalikan sebagai respons dari sourcekitd dan ditampilkan dalam Xcode.
  4. Xcode kemudian menerapkan perubahan pada alat refactoring.

Refactoring berbasis rentang dimulai dengan memilih rentang kode kontinu dalam file sumber.



Dalam hal ini, alat refactoring akan melalui rantai panggilan serupa yang dijelaskan. Perbedaannya adalah bahwa ketika diimplementasikan, inputnya adalah RangeInfo, bukan ResolvedCursorInfo. Pembaca yang tertarik dapat merujuk ke Refactoring.cpp untuk informasi lebih lanjut tentang contoh-contoh toolkit Apple.

Dan sekarang untuk praktik menciptakan alat.

Persiapan


Pertama-tama, Anda perlu mengunduh dan membangun kompiler Swift. Instruksi terperinci ada di repositori resmi ( readme.md ). Berikut adalah perintah utama untuk kloning kode:

mkdir swift-source cd swift-source git clone https://github.com/apple/swift.git ./swift/utils/update-checkout --clone 

Cmake digunakan untuk menggambarkan struktur dan dependensi proyek. Dengan menggunakannya, Anda dapat menghasilkan proyek untuk Xcode (lebih mudah) atau untuk ninja (lebih cepat) karena salah satu perintah:

 ./utils/build-script --debug --xcode 

atau
 swift/utils/build-script --debug-debuginfo 

Kompilasi yang berhasil membutuhkan versi terbaru Xcode beta (10.2.1 pada saat penulisan) - tersedia di situs web resmi Apple . Untuk menggunakan Xcode baru untuk membangun proyek, Anda harus mendaftarkan jalur menggunakan utilitas pilih-xcode:

 sudo xcode-select -s /Users/username/Xcode.app 

Jika kita menggunakan flag --xcode untuk membangun proyek untuk Xcode, masing-masing, kemudian setelah beberapa jam kompilasi (kita mendapat lebih dari dua) di folder build, kita akan menemukan file Swift.xcodeproj. Membuka proyek, kita akan melihat Xcode yang akrab dengan pengindeksan, breakpoints.

Untuk membuat instrumen baru, kita perlu menambahkan kode dengan logika instrumen ke file: lib / IDE / Refactoring.cpp dan tentukan dua metode, dapat diterapkan dan performChange. Pada metode pertama, kami memutuskan apakah akan mengeluarkan opsi refactoring untuk kode yang dipilih. Dan yang kedua - bagaimana mengkonversi kode yang dipilih untuk menerapkan refactoring.

Setelah persiapan selesai, tetap menerapkan langkah-langkah berikut:

  1. Mengembangkan logika alat (pengembangan dapat dilakukan dengan beberapa cara - melalui toolchain, melalui Ninja, melalui Xcode; semua opsi akan dijelaskan di bawah)
  2. Menerapkan dua metode: isApplicable dan performChange (mereka bertanggung jawab untuk akses ke alat dan operasinya)
  3. Diagnosis dan uji alat yang sudah jadi sebelum mengirim PR ke repositori Swift resmi.

Uji operasi alat melalui toolchain


Metode pengembangan ini akan membawa Anda banyak waktu karena perakitan komponen yang lama, tetapi hasilnya langsung terlihat dalam Xcode - cara untuk memverifikasi secara manual.

Untuk memulai, mari kita membangun rantai alat Swift menggunakan perintah:

 ./utils/build-toolchain some_bundle_id 

Mengkompilasi toolchain akan lebih lama dari kompilasi dan dependensi. Outputnya adalah file swift-LOCAL-yyyy-mm-dd.xctoolchain di folder swift-nightly-install, yang perlu Anda transfer ke Xcode: / Library / Developer / Toolchains /. Selanjutnya, dalam pengaturan IDE, pilih toolchain baru, restart Xcode.



Pilih sepotong kode yang harus diproses alat, dan cari alat di menu konteks.

Pengembangan melalui tes dengan Ninja


Jika proyek ini dibangun untuk Ninja dan Anda memilih jalur TDD, maka pengembangan melalui tes dengan Ninja adalah salah satu opsi yang cocok untuk Anda. Kontra - Anda tidak dapat mengatur breakpoints, seperti dalam pengembangan melalui Xcode.

Jadi, kita perlu memeriksa bahwa alat baru ditampilkan dalam Xcode ketika pengguna memilih konstruksi penjaga dalam kode sumber. Kami menulis tes dalam file test / refactoring / RefactoringKind / basic.swift yang ada:

 func testConvertToGuardExpr(idxOpt: Int?) {    if let idx = idxOpt {        print(idx)    } } //     . // RUN: %refactor -source-filename %s -pos=266:3 -end-pos=268:4 | %FileCheck %s -check-prefix=CHECK-CONVERT-TO-GUARD-EXPRESSION // CHECK-CONVERT-TO-GUARD-EXPRESSION: Convert To Guard Expression 


Kami menunjukkan bahwa saat menyorot kode antara 266 kolom 3 baris dan 268 baris 4 kolom, kami mengharapkan tampilan item menu dengan alat baru.

Menggunakan skrip lit.py dapat memberikan umpan balik yang lebih cepat ke siklus pengembangan Anda. Anda dapat menentukan jenis tes yang diinginkan. Dalam kasus kami, suite ini akan menjadi RefactoringKind:

./llvm/utils/lit/lit.py -sv ./build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/test-macosx-x86_64/refactoring/RefactoringKind/
Akibatnya, tes hanya file ini yang akan diluncurkan. Implementasi mereka akan memakan waktu beberapa detik. Informasi lebih lanjut tentang lit.py akan dibahas nanti di bagian Diagnostik dan Pengujian.
Tes gagal, yang normal untuk paradigma TDD. Bagaimanapun, sejauh ini kami belum menulis satu baris kode pun dengan logika alat.

Pengembangan melalui debugging dan Xcode


Dan akhirnya, metode pengembangan terakhir ketika proyek dibangun di bawah Xcode. Kelebihan utama adalah kemampuan untuk mengatur breakpoint dan mengontrol debugging.

Ketika membangun proyek di bawah Xcode, file Swift.xcodeproj dibuat di folder build / Xcode-DebugAssert / swift-macosx-x86_64 /. Saat Anda membuka file ini untuk pertama kalinya, lebih baik memilih untuk membuat skema secara manual untuk menghasilkan ALL_BUILD dan swift-refactor sendiri:



Selanjutnya, kami membangun proyek dengan ALL_BUILD sekali, setelah itu kami menggunakan skema swift-refactor.

Alat refactoring dikompilasi menjadi file yang dapat dieksekusi terpisah - swift-refactor. Bantuan untuk file ini dapat ditampilkan menggunakan flag –help. Parameter yang paling menarik bagi kami adalah:

 -source-filename=<string> //   -pos=<string> //   -end-pos=<string> //   -kind //   

Mereka dapat ditentukan dalam skema sebagai argumen. Sekarang Anda dapat mengatur breakpoint untuk berhenti di tempat-tempat menarik ketika memulai alat. Dengan cara biasa, menggunakan perintah p dan po di konsol Xcode, menampilkan nilai-nilai variabel yang sesuai.



Implementasi yang berlaku


Metode isApplicable menerima ResolvedRangeInfo dengan informasi tentang AST node dari fragmen kode yang dipilih pada input. Pada keluaran metode, diputuskan apakah akan menampilkan alat atau tidak dalam menu konteks Xcode. Antarmuka ResolvedRangeInfo lengkap dapat ditemukan di file include / swift / IDE / Utils.h .

Pertimbangkan bidang kelas ResolvedRangeInfo yang paling berguna dalam kasus kami:

  • RangeKind - hal pertama yang harus dilakukan adalah memeriksa jenis area yang dipilih. Jika area tidak valid (Tidak Valid), Anda dapat mengembalikan false. Jika jenisnya sesuai dengan kita, misalnya, SingleStatement atau MultiStatement, maka pindah;

  • ContainedNodes - array elemen AST yang termasuk dalam rentang yang dipilih. Kami ingin memastikan bahwa pengguna memilih rentang yang akan dimasukkan oleh konstruksi if. Untuk melakukan ini, kita mengambil elemen pertama dari array dan memeriksa apakah elemen ini sesuai dengan IfStmt (kelas yang mendefinisikan simpul AST dari simpul pernyataan dari subtipe if). Selanjutnya, lihat kondisinya. Untuk menyederhanakan implementasi, kami akan menampilkan alat hanya untuk ekspresi dengan satu syarat. Berdasarkan jenis kondisi (CK_PatternBinding) kami menentukan bahwa ini dibiarkan.



Untuk menguji apakah dapat diterapkan, tambahkan kode sampel ke file test / refactoring / RefactoringKind / basic.swift .



Agar tes dapat mensimulasikan panggilan ke alat kami, Anda perlu menambahkan baris di file tools / swift-refactor / swift-refactor.cpp .  



Kami menerapkan performChange


Metode ini dipanggil ketika alat refactoring dipilih dalam menu konteks. Metode ini memiliki akses ke ResolvedRangeInfo, serta di isApplicable. Kami menggunakan ResolvedRangeInfo dan menulis logika alat konversi kode.

Saat membuat kode untuk token statis (diatur oleh sintaks bahasa), Anda dapat menggunakan entitas dari tok namespace. Misalnya, untuk kata kunci penjaga, gunakan tok :: kw_guard. Untuk token dinamis (dimodifikasi oleh pengembang, misalnya, nama fungsi), Anda harus memilihnya dari larik elemen AST.

Untuk menentukan di mana kode yang dikonversi dimasukkan, kami menggunakan rentang yang dipilih penuh menggunakan konstruk RangeInfo.ContentRange.



Diagnostik dan pengujian


Sebelum Anda selesai mengerjakan alat, Anda perlu memeriksa kebenaran pekerjaannya lagi. Tes akan membantu kita lagi. Tes dapat dijalankan satu per satu atau dengan semua cakupan yang tersedia. Cara termudah untuk menjalankan seluruh rangkaian uji Swift adalah dengan perintah --test pada utils / build-script, yang akan menjalankan rangkaian uji utama. Menggunakan utils / build-script akan membangun kembali semua target, yang secara signifikan dapat meningkatkan waktu siklus debugging.

Pastikan untuk menjalankan utils / build-script - tes validasi uji validasi sebelum membuat perubahan besar pada kompiler atau API.

Ada cara lain untuk menjalankan semua unit test dari kompiler - melalui ninja, ninja check-swift dari build / preset / swift-macosx-x86_64. Ini akan memakan waktu sekitar 15 menit.

Dan akhirnya, opsi ketika Anda perlu menjalankan tes secara terpisah. Untuk secara langsung memanggil skrip lit.py dari LLVM, Anda harus mengonfigurasinya untuk menggunakan direktori build lokal. Sebagai contoh:

 % $ {LLVM_SOURCE_ROOT} /utils/lit/lit.py -sv $ {SWIFT_BUILD_DIR} / test-macosx-x86_64 / Parse / 

Ini akan menjalankan tes di direktori 'test / Parse /' untuk macOS 64-bit. Opsi -sv menyediakan indikator pelaksanaan tes dan menunjukkan hasil hanya tes yang gagal.

Lit.py memiliki beberapa fitur berguna lainnya, seperti tes timing dan pengujian latensi. Anda dapat melihat ini dan fitur lainnya dengan lit.py -h. Yang paling berguna dapat ditemukan di sini .

Untuk menjalankan satu tes, tulis:

  ./llvm/utils/lit/lit.py -sv ./build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/test-macosx-x86_64/refactoring/RefactoringKind/basic.swift 

Jika kita perlu menarik perubahan kompiler terbaru, maka kita perlu memperbarui semua dependensi dan melakukan rebase. Untuk meningkatkan, jalankan ./utils/update-checkout.

Kesimpulan


Kami berhasil mencapai tujuan kami - untuk membuat alat yang sebelumnya tidak ada dalam IDE untuk mengoptimalkan pekerjaan. Jika Anda juga memiliki ide tentang cara meningkatkan produk Apple dan membuat hidup lebih mudah untuk seluruh komunitas iOS, silakan mengambil counter-branding, karena lebih mudah daripada yang terlihat pada pandangan pertama!

Pada 2015, Apple mengunggah kode sumber Swift ke domain publik, yang memungkinkannya masuk ke detail implementasi kompilernya. Selain itu, dengan Xcode 9, Anda dapat menambahkan alat refactoring lokal. Pengetahuan dasar tentang C ++ dan perangkat kompiler sudah cukup untuk membuat IDE favorit Anda sedikit lebih nyaman.

Pengalaman yang dijelaskan bermanfaat bagi kami - selain menciptakan alat yang menyederhanakan proses pengembangan, kami memperoleh pengetahuan yang benar-benar hardcore tentang bahasa tersebut. Kotak Pandora yang sedikit terbuka dengan proses tingkat rendah memungkinkan Anda melihat tugas sehari-hari dari sudut pandang baru.

Kami berharap bahwa pengetahuan yang diperoleh juga akan memperkaya pemahaman Anda tentang pengembangan!

Materi ini ditulis bersama dengan @victoriaqb - Victoria Kashlina, pengembang iOS.

Sumber


  1. Perangkat kompiler Swift. Bagian 2
  2. Bagaimana cara membuat alat berbasis Swift Compiler? Panduan langkah demi langkah
  3. Membuang AST Swift untuk Proyek iOS
  4. Memperkenalkan Stress Tester sourcekitd
  5. Menguji dengan cepat
  6. [SR-5744] Tindakan refactoring untuk mengonversi if-let to guard-let dan sebaliknya # 24566

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


All Articles