go-critic: analisa statis paling keras kepala untuk Go


Kami mengumumkan linter baru (penganalisis statis) untuk Go , yang juga merupakan kotak pasir untuk membuat prototipe ide-ide Anda di dunia analisis statis.


go-kritik dibangun di sekitar pengamatan berikut:


  • Lebih baik memiliki implementasi tes yang "cukup baik" daripada tidak memilikinya sama sekali
  • Jika cek itu kontroversial, ini tidak berarti bahwa itu tidak berguna. Kami menandai sebagai "berpendirian" dan menuangkan
  • Menulis linter dari awal biasanya lebih sulit daripada menambahkan cek baru ke kerangka yang ada jika kerangka itu sendiri mudah dimengerti.

Dalam posting ini kita akan melihat penggunaan dan arsitektur go-kritik, beberapa pemeriksaan diimplementasikan di dalamnya , dan juga menjelaskan langkah-langkah utama untuk menambahkan fungsi analisa kami ke dalamnya.


Mulai cepat


$ cd $GOPATH $ go get -u github.com/go-critic/go-critic/... $ ./bin/gocritic check-package strings $GOROOT/src/strings/replace.go:450:22: unslice: could simplify s[:] to s $GOROOT/src/strings/replace.go:148:2: elseif: should rewrite if-else to switch statement $GOROOT/src/strings/replace.go:156:3: elseif: should rewrite if-else to switch statement $GOROOT/src/strings/replace.go:219:3: elseif: should rewrite if-else to switch statement $GOROOT/src/strings/replace.go:370:1: paramTypeCombine: func(pattern string, value string) *singleStringReplacer could be replaced with func(pattern, value string) *singleStringReplacer $GOROOT/src/strings/replace.go:259:2: rangeExprCopy: copy of r.mapping (256 bytes) can be avoided with &r.mapping $GOROOT/src/strings/replace.go:264:2: rangeExprCopy: copy of r.mapping (256 bytes) can be avoided with &r.mapping $GOROOT/src/strings/strings.go:791:1: paramTypeCombine: func(s string, cutset string) string could be replaced with func(s, cutset string) string $GOROOT/src/strings/strings.go:800:1: paramTypeCombine: func(s string, cutset string) string could be replaced with func(s, cutset string) string $GOROOT/src/strings/strings.go:809:1: paramTypeCombine: func(s string, cutset string) string could be replaced with func(s, cutset string) string $GOROOT/src/strings/strings.go:44:1: unnamedResult: consider to give name to results $GOROOT/src/strings/strings.go:61:1: unnamedResult: consider to give name to results $GOROOT/src/strings/export_test.go:28:3: rangeExprCopy: copy of r.mapping (256 bytes) can be avoided with &r.mapping $GOROOT/src/strings/export_test.go:42:1: unnamedResult: consider to give name to results 

(Format peringatan telah diedit; aslinya tersedia di intisari .)


Utilitas gocritic dapat memeriksa masing-masing paket dengan jalur impor mereka ( check-package ), dan juga secara traverse melintasi semua direktori ( check-project ). Misalnya, Anda dapat memeriksa seluruh $GOROOT atau $GOPATH dengan satu perintah:


 $ gocritic check-project $GOROOT/src $ gocritic check-project $GOPATH/src 

Ada dukungan untuk "daftar putih" untuk pemeriksaan untuk secara eksplisit mendaftar pemeriksaan mana yang harus dilakukan (flag -enable ). Secara default, semua pemeriksaan yang tidak ditandai dengan ikon Experimental atau VeryOpinionated .


Integrasi dalam golangci-lint dan gometalinter direncanakan .


Bagaimana semuanya dimulai


Melakukan tinjauan kode selanjutnya dari proyek Go, atau saat mengaudit perpustakaan pihak ke-3, Anda dapat melihat masalah yang sama berulang-ulang.


Untuk penyesalan Anda, tidak mungkin menemukan linter yang akan mendiagnosis kelas masalah ini.


Langkah pertama Anda mungkin merupakan upaya untuk mengelompokkan masalah dan menghubungi penulis linter yang ada, menyarankan mereka menambahkan cek baru. Kemungkinan proposal Anda akan diterima sangat tergantung pada proyek dan bisa sangat rendah. Selanjutnya, kemungkinan besar, bulan harapan akan mengikuti.


Tetapi bagaimana jika cek itu sepenuhnya ambigu dan dapat dianggap oleh seseorang sebagai terlalu subyektif atau kurang akurat?


Mungkin masuk akal untuk mencoba menulis cek ini sendiri?


go-critic ada untuk menjadi rumah bagi tes eksperimental yang lebih mudah untuk diterapkan oleh diri kita sendiri daripada untuk melampirkannya ke analisis statis yang ada. Perangkat go-critic sendiri meminimalkan jumlah konteks dan tindakan yang diperlukan untuk menambahkan cek baru - kita dapat mengatakan bahwa Anda perlu menambahkan hanya satu file (tidak termasuk tes).


Bagaimana go-critic bekerja


Seorang kritikus adalah seperangkat aturan yang menggambarkan properti pemeriksaan dan pemeriksa mikro yang menerapkan inspeksi kode untuk kepatuhan terhadap aturan.


Aplikasi yang menyematkan linter (misalnya, cmd / gocritic atau golangci-lint ) menerima daftar aturan yang didukung, memfilternya dengan cara tertentu, membuat fungsi pemeriksaan untuk setiap aturan yang dipilih, dan meluncurkan masing-masing dari paket yang sedang dipelajari.


Pekerjaan menambahkan checker baru bermuara pada tiga langkah utama:


  1. Menambahkan tes.
  2. Pelaksanaan verifikasi itu sendiri.
  3. Menambahkan dokumentasi untuk linter.

Kami akan membahas semua poin ini menggunakan aturan captLocal contoh, yang mengharuskan tidak adanya nama lokal yang dimulai dengan huruf kapital.



Menambahkan Tes


Untuk menambahkan data uji untuk pemeriksaan baru, Anda perlu membuat direktori baru di lint / testdata .


Setiap direktori tersebut harus memiliki file positive_tests.go , yang menjelaskan contoh-contoh kode di mana cek harus bekerja. Untuk menguji tidak adanya false positive, tes dilengkapi dengan kode "benar", di mana pemeriksaan baru seharusnya tidak menemukan masalah ( negative_tests.go ).


Contoh:


 // lint/testdata/positive_tests.go /// consider `in' name instead of `IN' /// `X' should not be capitalized /// `Y' should not be capitalized /// `Z' should not be capitalized func badFunc1(IN, X int) (Y, Z int) { /// `V' should not be capitalized V := 1 return V, 0 } 

 // lint/testdata/negative_tests.go func goodFunc1(in, x int) (x, y int) { v := 1 return v, 0 } 

Anda dapat menjalankan tes setelah menambahkan linter baru.


Implementasi Verifikasi


Buat file dengan nama pemeriksa: lint/captLocal_checker.go .
Secara konvensional, semua file micro-linter memiliki akhiran _checker .


 package lint //  β€œChecker”    . type captLocalChecker struct { checkerBase upcaseNames map[string]bool } 

checkerBase adalah tipe yang harus disematkan di setiap pemeriksa.
Ini memberikan implementasi standar, yang memungkinkan Anda untuk menulis lebih sedikit kode di setiap linter.
Antara lain, checkerBase menyertakan pointer ke lint.context , yang berisi informasi jenis dan metadata lain tentang file yang sedang diperiksa.


Bidang upcaseNames akan berisi tabel nama yang dikenal, yang akan kami tawarkan untuk diganti dengan versi strings.ToLower(name) . Untuk nama-nama yang tidak terkandung dalam peta, disarankan untuk tidak menggunakan huruf kapital, tetapi tidak ada penggantian yang benar akan diberikan.


Keadaan internal diinisialisasi sekali untuk setiap instance.
Metode Init() harus didefinisikan hanya untuk mereka yang butuh melakukan inisialisasi awal.


 func (c *captLocalChecker) Init() { c.upcaseNames = map[string]bool{ "IN": true, "OUT": true, "INOUT": true, } } 

Sekarang Anda perlu mendefinisikan fungsi pemeriksaan itu sendiri.
Dalam kasus captLocal , kita perlu memeriksa semua ast.Ident lokal yang memperkenalkan variabel baru.


Untuk memeriksa semua definisi nama lokal, Anda harus menerapkan metode dengan tanda tangan berikut di pemeriksa Anda:


 VisitLocalDef(name astwalk.Name, initializer ast.Expr) 

Daftar antarmuka pengunjung yang tersedia dapat ditemukan di file lint / internal / visitor.go .
captLocal mengimplementasikan LocalDefVisitor .


 //  ast.Expr,         //  .      . func (c *captLocalChecker) VisitLocalDef(name astwalk.Name, _ ast.Expr) { switch { case c.upcaseNames[name.ID.String()]: c.warnUpcase(name.ID) case ast.IsExported(name.ID.String()): c.warnCapitalized(name.ID) } } func (c *captLocalChecker) warnUpcase(id *ast.Ident) { c.ctx.Warn(id, "consider `%s' name instead of `%s'", strings.ToLower(id.Name), id) } func (c *captLocalChecker) warnCapitalized(id ast.Node) { c.ctx.Warn(id, "`%s' should not be capitalized", id) } 

Dengan konvensi, metode yang menghasilkan peringatan biasanya diletakkan dalam metode yang terpisah. Ada pengecualian langka, tetapi mengikuti aturan ini dianggap praktik yang baik.


Menambahkan dokumentasi


Metode implementasi lain yang diperlukan adalah InitDocumentation :


 func (c *captLocalChecker) InitDocumentation(d *Documentation) { d.Summary = "Detects capitalized names for local variables" d.Before = `func f(IN int, OUT *int) (ERR error) {}` d.After = `func f(in int, out *int) (err error) {}` } 

Biasanya, cukup isi 3 bidang:


  • Summary - deskripsi tindakan validasi dalam satu kalimat.
  • Before - kode sebelum koreksi.
  • Kode setelah koreksi (tidak boleh menyebabkan peringatan).

Pembuatan Dokumentasi

Pembuatan ulang dokumentasi bukanlah prasyarat untuk linter baru, mungkin dalam waktu dekat langkah ini akan sepenuhnya otomatis. Tetapi jika Anda masih ingin memeriksa bagaimana tampilan file penurunan harga, gunakan perintah make docs . File docs/overview.md akan diperbarui.


Daftarkan linter baru dan jalankan tes


Sentuhan terakhir sedang mendaftarkan linter baru:


 //   captLocal_checker.go init . func init() { addChecker(&captLocalChecker{}, attrExperimental, attrSyntaxOnly) } 

addChecker mengharapkan pointer ke nilai nol linter baru. Berikutnya adalah argumen variadik, yang memungkinkan Anda untuk meloloskan nol atau lebih atribut yang menggambarkan properti implementasi aturan.


attrSyntaxOnly adalah penanda opsional untuk linter yang tidak menggunakan informasi jenis dalam implementasinya, yang memungkinkan Anda untuk menjalankannya tanpa melakukan pemeriksaan jenis. golangci-lint menandai linters dengan bendera β€œfast” (karena mereka berlari lebih cepat).


attrExperimental adalah atribut yang ditetapkan untuk semua implementasi baru. Menghapus atribut ini hanya mungkin setelah stabilisasi pemeriksaan yang diterapkan.


Sekarang linter baru terdaftar melalui addChecker, Anda dapat menjalankan tes:


 #  GOPATH: $ go test -v github.com/go-critic/go-critic/lint #  GOPATH/src/github.com/go-critic/go-critic: $ go test -v ./lint #  ,      make: $ make test 

Penggabungan yang optimis (hampir)


Saat mempertimbangkan permintaan tarik, kami mencoba mematuhi strategi penggabungan yang optimis . Ini terutama dinyatakan dalam penerimaan PR yang kepadanya pengkaji mungkin memiliki beberapa, khususnya murni subjektif, klaim. Segera setelah injeksi tambalan tersebut, PR dapat mengikuti dari pengulas, yang memperbaiki kekurangan ini, penulis tambalan asli ditambahkan ke CC (salinan).


Kami juga memiliki dua penanda linter yang dapat digunakan untuk menghindari bendera merah tanpa adanya konsensus penuh:


  1. Experimental : suatu implementasi dapat memiliki sejumlah besar false positive, tidak efektif (sumber masalah diidentifikasi), atau "jatuh" dalam beberapa situasi. Anda dapat memasukkan implementasi seperti itu jika Anda menandainya dengan atribut attrExperimental . Kadang-kadang, dengan bantuan percobaan, cek tersebut diindikasikan gagal menemukan nama baik dari komit pertama.
  2. VeryOpinionated : jika cek dapat memiliki bek dan musuh, perlu menandainya dengan atribut attrVeryOpinionated . Dengan cara ini, kita dapat menghindari penolakan ide tentang gaya kode yang mungkin tidak sesuai dengan selera sebagian penjual.

Experimental adalah properti implementasi yang berpotensi sementara dan dapat diperbaiki. VeryOpinionated adalah properti aturan yang lebih mendasar yaitu implementasi independen.


Disarankan untuk membuat tiket [checker-request] di github sebelum mengirimkan implementasi, tetapi jika Anda telah mengirimkan permintaan tarik, Anda dapat membuka masalah yang sesuai untuk Anda.


Untuk detail lebih lanjut tentang proses pengembangan, lihat CONTRIBUTING.md .
Aturan dasar tercantum di bagian aturan utama .


Kata perpisahan


Anda dapat berpartisipasi dalam proyek ini tidak hanya dengan menambahkan linter baru.
Ada banyak cara lain:


  • Cobalah di proyek Anda atau proyek sumber terbuka besar / terkenal dan laporkan positif palsu, negatif palsu, dan kekurangan lainnya. Kami akan berterima kasih jika Anda juga menambahkan catatan tentang masalah yang ditemukan / diperbaiki ke halaman piala .
  • Sarankan ide untuk inspeksi baru. Cukup membuat masalah pada pelacak kami.
  • Tambahkan tes untuk linter yang ada.

go-critic mengkritik kode Go Anda dengan suara-suara dari semua programmer yang terlibat dalam pengembangannya. Karena itu, setiap orang dapat mengkritik - bergabunglah!


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


All Articles