
Dalam artikel ini, saya akan berbicara tentang perpustakaan analisis statis go-ruleguard
(dan utilitas) yang mengadaptasi gogrep
untuk digunakan di dalam linter.
Fitur khas: Anda menguraikan aturan analisis statis pada DSL khusus Go-like, yang pada awal ruleguard
berubah menjadi seperangkat diagnostik. Mungkin ini adalah salah satu alat yang paling mudah dikonfigurasi untuk menerapkan inspeksi khusus untuk Go.
Sebagai bonus, kita akan membicarakan go/analysis
dan pendahulunya .
Extensibility Analisis Statis
Ada banyak linter untuk Go, beberapa di antaranya dapat diperluas. Biasanya, untuk memperpanjang linter, Anda perlu menulis kode Go menggunakan API linter khusus.
Ada dua cara utama: Go plugins and monolith. Monolith menyiratkan bahwa semua cek (termasuk yang pribadi Anda) tersedia pada tahap kompilasi.
revive
membutuhkan pemeriksaan baru untuk dimasukkan ke dalam kernel untuk ekspansi. go-critic
selain plug-in ini, yang memungkinkan Anda untuk mengumpulkan ekstensi terlepas dari kode utama. Kedua pendekatan ini menyiratkan bahwa Anda menerapkan manipulasi go/ast
dan go/types
on Go menggunakan linter API. Bahkan cek sederhana membutuhkan banyak kode .
go/analysis
bertujuan untuk menyederhanakan gambar dengan fakta bahwa "kerangka kerja" linter menjadi hampir identik, tetapi itu tidak menyelesaikan masalah kompleksitas implementasi teknis dari diagnostik itu sendiri.
Digresi pada `loader` dan` go / packages`
Saat Anda menulis penganalisis untuk Go, tujuan akhir Anda adalah untuk berinteraksi dengan AST dan tipe, tetapi sebelum Anda dapat melakukan ini, kode sumber perlu "dimuat" dengan cara yang benar. Untuk menyederhanakan, konsep memuat termasuk penguraian , pemeriksaan jenis, dan impor dependensi .
Langkah pertama dalam menyederhanakan pipa ini adalah paket go/loader
, yang memungkinkan Anda untuk "mengunduh" semua yang Anda butuhkan melalui beberapa panggilan. Semuanya hampir baik-baik saja, dan kemudian ia menjadi usang demi go/packages
. go/packages
memiliki API yang sedikit ditingkatkan dan, secara teori, bekerja dengan baik dengan modul.
Sekarang, yang terbaik adalah tidak menggunakan salah satu di atas secara langsung untuk menulis analisis, karena go/analysis
memberikan go/packages
sesuatu yang tidak dimiliki oleh solusi sebelumnya - sebuah struktur untuk program Anda. Sekarang kita dapat menggunakan paradigma go/analysis
didiktekan dan menggunakan kembali analisis secara lebih efisien. Paradigma ini memiliki poin kontroversial, misalnya, go/analysis
cocok untuk analisis pada tingkat satu paket dan dependensinya, tetapi untuk membuat analisis global pada itu tanpa trik teknik licik tidak akan mudah.
go/analysis
juga menyederhanakan pengujian analyzer .
Apa itu peraturan?

go-ruleguard
adalah utilitas analisis statis yang secara default tidak menyertakan pemeriksaan tunggal.
ruleguard
aturan dimuat pada awalnya, dari file gorules
khusus yang secara gorules
menggambarkan pola kode yang harus dikeluarkan peringatan. File ini dapat diedit secara bebas oleh pengguna ruleguard
.
Tidak perlu gorules
program kontrol untuk menghubungkan pemeriksaan baru, sehingga aturan dari gorules
dapat disebut dinamis .
Program kontrol pengawas terlihat seperti ini:
package main import ( "github.com/quasilyte/go-ruleguard/analyzer" "golang.org/x/tools/go/analysis/singlechecker" ) func main() { singlechecker.Main(analyzer.Analyzer) }
Pada saat yang sama, analyzer
diimplementasikan melalui paket ruleguard
, yang harus Anda gunakan jika Anda ingin menggunakannya sebagai perpustakaan.
memerintah kembali VS menghidupkan kembali
Ambil contoh sederhana namun nyata: misalkan kita ingin menghindari panggilan runtime.GC()
di program kami. Dalam menghidupkan kembali sudah ada diagnostik terpisah untuk ini, itu disebut "call-to-gc"
.
Implementasi panggilan-ke-gc (70 jalur di Elven)
package rule import ( "go/ast" "github.com/mgechev/revive/lint" )
Sekarang bandingkan dengan bagaimana hal ini dilakukan di go-ruleguard
:
package gorules import "github.com/quasilyte/go-ruleguard/dsl/fluent" func callToGC(m fluent.Matcher) { m.Match(`runtime.GC()`).Report(`explicit call to the garbage collector`) }
Tidak lebih, hanya yang benar-benar penting - runtime.GC
dan pesan yang perlu dikeluarkan jika aturan dipicu.
Anda mungkin bertanya: apakah hanya itu? Saya secara khusus mulai dengan contoh sederhana untuk menunjukkan berapa banyak kode yang mungkin diperlukan untuk diagnosis yang sangat sepele dalam kasus pendekatan tradisional. Saya berjanji akan ada contoh yang lebih menarik.
Mulai cepat
go-critic
memiliki diagnostik rangeExprCopy
yang menemukan salinan array berpotensi tak terduga dalam kode.
Kode ini diulang pada salinan array:
var xs [2048]byte for _, x := range xs {
Perbaikan untuk masalah ini adalah menambahkan satu karakter:
var xs [2048]byte - for _, x := range xs { // Copies 2048 bytes + for _, x := range &xs { // No copy // Loop body. }
Kemungkinan besar, Anda tidak perlu menyalin ini, dan kinerja versi yang diperbaiki selalu lebih baik. Anda dapat menunggu hingga kompiler Go menjadi lebih baik, atau Anda dapat mendeteksi tempat-tempat tersebut dalam kode dan memperbaikinya hari ini menggunakan go-critic
.
Diagnosis ini dapat diimplementasikan dalam bahasa rules.go
(file rules.go
):
package gorules import "github.com/quasilyte/go-ruleguard/dsl/fluent" func _(m fluent.Matcher) { m.Match(`for $_, $_ := range $x { $*_ }`, `for $_, $_ = range $x { $*_ }`). Where(m["x"].Addressable && m["x"].Type.Size >= 128). Report(`$x copy can be avoided with &$x`). At(m["x"]). Suggest(`&$x`) }
Aturan menemukan semua loop for-range
mana kedua variabel iterable digunakan (ini adalah kasus yang mengarah ke menyalin). Ekspresi iterable $x
harus addressable
dan harus lebih besar dari ambang yang dipilih dalam byte.
Report()
mendefinisikan pesan yang akan dikeluarkan untuk pengguna, dan Suggest()
menguraikan templat quickfix
yang dapat digunakan dalam editor Anda melalui gopls (LSP), serta secara interaktif jika ruleguard
dipanggil dengan argumen- -fix
(kami akan kembali ke ini). At()
melampirkan peringatan dan quickfix
ke bagian tertentu dari templat. Kita membutuhkan ini untuk mengganti $x
dengan &$x
, daripada menulis ulang seluruh loop.
Baik Report()
dan Suggest()
menerima string ke mana ekspresi yang diambil oleh templat dari Match()
dapat diinterpolasi. Variabel predefined $$
berarti "semua fragmen yang ditangkap" (sebagai $0
dalam ekspresi reguler).
Buat file rangecopy.go
:
package example
Sekarang kita bisa menjalankan ruleguard
:
$ ruleguard -rules rules.go -fix rangecopy.go rangecopy.go:12:20: builtins copy can be avoided with &builtins
Jika setelah itu kita melihat rangecopy.go
, kita akan melihat versi yang tetap, karena ruleguard
dipanggil dengan parameter -fix
.
Aturan paling sederhana dapat gorules
tanpa membuat file gorules
:
$ ruleguard -c 1 -e 'm.Match(`return -1`)' rangecopy.go rangecopy.go:17:2: return -1 16 } 17 return -1 18 }
Berkat penggunaan go/analysis/singlechecker
, kami memiliki opsi -c
, yang memungkinkan kami untuk menampilkan garis konteks yang ditentukan bersama dengan peringatan itu sendiri. Mengontrol parameter ini sedikit berlawanan dengan intuisi: nilai defaultnya adalah -c=-1
, yang berarti "tidak ada konteks", dan -c=0
akan menampilkan satu baris konteks (yang ditunjukkan oleh diagnostik).
Berikut adalah beberapa gorules
lebih menarik:
- Ketik templat yang memungkinkan Anda menentukan jenis yang diharapkan. Misalnya,
map[$t]$t
ekspresi map[$t]$t
menjelaskan semua peta dengan tipe nilai yang cocok dengan jenis kunci, dan *[$len]$elem
menangkap semua pointer ke array. - Dalam satu fungsi, mungkin ada beberapa aturan,
dan fungsi itu sendiri harus disebut kelompok aturan . - Aturan dalam grup diterapkan satu demi satu, sesuai dengan urutannya. Aturan pertama yang dipicu membatalkan perbandingan dengan aturan lainnya. Ini penting bukan untuk optimasi tetapi untuk aturan khusus untuk kasus-kasus tertentu. Contoh di mana ini berguna adalah aturan penulisan ulang
$x=$x+$y
hingga $x+=$y
, untuk kasus dengan $y=1
Anda ingin menawarkan $x++
, bukan $x+=1
.
Informasi lebih lanjut tentang DSL yang digunakan dapat ditemukan di docs/gorules.md
.
Lebih banyak contoh
package gorules import "github.com/quasilyte/go-ruleguard/dsl/fluent" func exampleGroup(m fluent.Matcher) {
Jika tidak ada panggilan Report()
untuk aturan, maka output pesan dari Suggest()
akan digunakan. Dalam beberapa kasus ini memungkinkan untuk menghindari duplikasi.
Ketik filter dan subekspresi dapat memeriksa berbagai properti. Misalnya, properti Pure
dan Const
berguna:
Var.Pure
berarti bahwa ekspresi tidak memiliki efek samping.Var.Const
berarti bahwa ekspresi dapat digunakan dalam konteks konstan (misalnya, dimensi array).
Untuk nama yang package-qualified
dalam kondisi Where()
, Anda perlu menggunakan metode Import()
. Untuk kenyamanan, semua paket standar diimpor untuk Anda, jadi dalam contoh di atas kami tidak perlu melakukan impor tambahan.
go/analysis
tindakan perbaikan cepat
Dukungan untuk quickfix
oleh go/analysis
untuk kami.
Dalam model go/analysis
, alat analisa menghasilkan diagnostik dan fakta . Diagnostik dikirimkan kepada pengguna, dan fakta-fakta dimaksudkan untuk digunakan oleh analis lain.
Diagnostik dapat memiliki satu set perbaikan yang disarankan , yang masing-masing menjelaskan cara mengubah kode sumber dalam kisaran yang ditentukan untuk memperbaiki masalah yang ditemukan oleh diagnostik.
Deskripsi resmi tersedia di go/analysis/doc/suggested_fixes.md
.
Kesimpulan

Coba ruleguard
proyek Anda, dan jika Anda menemukan bug atau ingin meminta fitur baru, buka masalah .
Jika Anda masih merasa kesulitan untuk membuat ruleguard
, berikut adalah beberapa contoh:
- Terapkan diagnostik Anda sendiri untuk Go.
- Secara otomatis memutakhirkan atau memperbaiki kode dengan
-fix
. - Koleksi statistik kode dengan
-json
pemrosesan hasil -json
.
Rencana pengembangan untuk ruleguard
dalam waktu dekat:
Tautan dan sumber daya yang bermanfaat