Bagikan melalui


Tutorial: Tulis penganalisis dan perbaikan kode pertama Anda

.NET Compiler Platform SDK menyediakan peralatan yang Anda butuhkan untuk membuat diagnostik kustom (penganalisis), perbaikan kode, pemfaktoran ulang kode, dan penekan diagnostik yang menargetkan kode C# atau Visual Basic. Penganalisis berisi kode yang mengenali pelanggaran aturan Anda. Perbaikan kode Anda berisi kode yang memperbaiki pelanggaran. Aturan yang Anda terapkan dapat berupa apa pun mulai dari struktur kode hingga gaya pengodean, konvensi penamaan, dan banyak lagi. .NET Compiler Platform menyediakan kerangka kerja untuk menjalankan analisis saat pengembang menulis kode, dan semua fitur UI Visual Studio untuk memperbaiki kode: menampilkan coretan di editor, mengisi Daftar Kesalahan Visual Studio, membuat saran "bola lampu", dan menunjukkan pratinjau kaya dari perbaikan yang disarankan.

Dalam tutorial ini, Anda akan menjelajahi pembuatan penganalisis dan perbaikan kode yang menyertainya dengan menggunakan API Roslyn. Penganalisis adalah cara untuk melakukan analisis kode sumber dan melaporkan masalah kepada pengguna. Secara opsional, perbaikan kode dapat dikaitkan dengan penganalisis untuk mewakili modifikasi pada kode sumber pengguna. Tutorial ini membuat penganalisis yang akan menemukan deklarasi variabel lokal yang dapat dideklarasikan dengan, tetapi bukan merupakan modifier const. Perbaikan kode yang menyertainya memodifikasi deklarasi tersebut untuk menambahkan pengubah const.

Prasyarat

Anda harus memasang .NET Compiler Platform SDK melalui Alat Penginstal Visual Studio:

Petunjuk penginstalan - Alat penginstal Visual Studio

Ada dua cara berbeda untuk menemukan .NET Compiler Platform SDK di Alat penginstal Visual Studio:

Instal menggunakan Alat penginstal Visual Studio - Tampilan beban kerja

.NET Compiler Platform SDK tidak secara otomatis dipilih sebagai bagian dari beban kerja pengembangan ekstensi Visual Studio. Anda harus memilihnya sebagai komponen opsional.

  1. Jalankan Alat penginstal Visual Studio
  2. Pilih Ubah
  3. Periksa beban kerja pengembangan ekstensi Visual Studio.
  4. Buka simpul Pengembangan ekstensi Visual Studio di pohon ringkasan.
  5. Centang kotak untuk .NET Compiler Platform SDK. Anda akan menemukannya terakhir di bawah komponen opsional.

Secara opsional, Anda juga ingin penyunting DGML menampilkan grafik di visualizer:

  1. Buka simpul Komponen individu di pohon ringkasan.
  2. Centang kotak untuk Penyunting DGML

Instal menggunakan Alat penginstal Visual Studio - tab komponen individu

  1. Jalankan Alat penginstal Visual Studio
  2. Pilih Ubah
  3. Pilih tab Komponen individu
  4. Centang kotak untuk .NET Compiler Platform SDK. Anda akan menemukannya di bagian atas di bawah bagian Pengompilasi, alat build, dan runtime.

Secara opsional, Anda juga ingin penyunting DGML menampilkan grafik di visualizer:

  1. Centang kotak untuk Penyunting DGML. Anda akan menemukannya di bagian Alat kode.

Ada beberapa langkah untuk membuat dan memvalidasi penganalisis Anda:

  1. Buat solusinya.
  2. Daftarkan nama penganalisis dan deskripsinya.
  3. Laporkan peringatan dan rekomendasi penganalisis.
  4. Terapkan perbaikan kode untuk menerima rekomendasi.
  5. Tingkatkan analisis melalui pengujian unit.

Buat solusinya

  • Di Visual Studio, pilih File > Proyek > Baru... untuk menampilkan dialog Proyek Baru.
  • Di bawah Ekstensibilitas > Visual C#, pilih Penganalisis dengan perbaikan kode (.NET Standard).
  • Namai proyek Anda "MakeConst" dan klik OK.

Catatan

Anda mungkin mendapatkan kesalahan kompilasi (MSB4062: Tugas "CompareBuildTaskVersion" tidak dapat dimuat"). Untuk memperbaikinya, perbarui paket NuGet di dalam solusi dengan Manajer Paket NuGet atau gunakan Update-Package di jendela Konsol Manajer Paket.

Jelajahi templat penganalisis

Penganalisis dengan templat perbaikan kode membuat lima proyek:

  • MakeConst, yang berisi penganalisis.
  • MakeConst.CodeFixes, yang berisi perbaikan kode.
  • MakeConst.Package, yang digunakan untuk memproduksi paket NuGet bagi penganalisis dan perbaikan kode.
  • MakeConst.Test, yang merupakan proyek pengujian unit.
  • MakeConst.Vsix, yang merupakan proyek awal default yang memulai instans kedua Visual Studio, yang telah memuat penganalisis baru Anda. Tekan F5 untuk memulai proyek VSIX.

Catatan

Penganalisis harus menargetkan .NET Standard 2.0 karena dapat berjalan di lingkungan .NET Core (build baris perintah) dan lingkungan .NET Framework (Visual Studio).

Tip

Saat menjalankan penganalisis, Anda memulai salinan kedua Visual Studio. Salinan kedua ini menggunakan hive registri yang berbeda untuk menyimpan pengaturan. Hal ini akan memungkinkan Anda membedakan pengaturan visual dalam dua salinan Visual Studio. Anda dapat memilih tema yang berbeda untuk eksekusi eksperimental Visual Studio. Selain itu, jangan menjelajahi pengaturan Anda atau masuk ke akun Visual Studio Anda dengan menggunakan eksekusi eksperimental Visual Studio. Hal itu akan membuat pengaturan tetap berbeda.

Hive tidak hanya mencakup penganalisis yang sedang dikembangkan, tetapi juga penganalisis yang telah dibuka sebelumnya. Untuk mengatur ulang hive Roslyn, Anda perlu menghapusnya secara manual dari %LocalAppData%\Microsoft\VisualStudio. Nama folder hive Roslyn akan berakhiran Roslyn, misalnya, 16.0_9ae182f9Roslyn. Perhatikan bahwa Anda mungkin perlu membersihkan solusi dan membangunnya kembali setelah menghapus hive.

Dalam instans Visual Studio kedua yang baru saja Anda mulai, buat proyek Aplikasi Konsol C# baru (kerangka kerja target apa pun akan berfungsi -- penganalisis berfungsi di tingkat sumber.) Arahkan kursor ke atas token dengan garis bawah bergelombang, dan teks peringatan yang disediakan oleh penganalisis akan muncul.

Templat ini membuat penganalisis yang akan melaporkan peringatan pada setiap deklarasi jenis dengan nama jenis berisi huruf kecil, seperti yang ditunjukkan pada gambar berikut:

Peringatan pelaporan penganalisis

Templat ini juga menyediakan perbaikan kode yang mengubah semua nama jenis yang berisi karakter huruf kecil menjadi berhuruf besar. Anda dapat mengeklik bola lampu yang ditampilkan dengan peringatan untuk melihat perubahan yang disarankan. Menerima perubahan yang disarankan akan memperbarui nama jenis dan semua referensi ke jenis tersebut di solusinya. Sekarang, setelah Anda melihat penganalisis awal melakukan tindakan, tutup instans Visual Studio kedua dan kembali ke proyek penganalisis Anda.

Anda tidak perlu memulai salinan kedua Visual Studio dan membuat kode baru untuk menguji setiap perubahan dalam penganalisis Anda. Templat ini juga membuat proyek pengujian unit untuk Anda. Proyek tersebut berisi dua pengujian. TestMethod1 menunjukkan format khas pengujian yang menganalisis kode tanpa memicu diagnostik. TestMethod2 menunjukkan format pengujian yang memicu diagnostik, lalu menerapkan perbaikan kode yang disarankan. Saat Anda membangun penganalisis dan perbaikan kode, Anda akan menulis pengujian untuk struktur kode yang berbeda guna memverifikasi pekerjaan Anda. Pengujian unit untuk penganalisis jauh lebih cepat daripada mengujinya secara interaktif dengan Visual Studio.

Tip

Pengujian unit penganalisis adalah alat yang bagus jika Anda mengetahui konstruksi kode apa yang akan dan tidak akan memicu penganalisis Anda. Memuat penganalisis Anda di salinan Visual Studio lain adalah alat yang bagus untuk menjelajahi dan menemukan konstruksi yang mungkin belum terpikirkan oleh Anda sebelumnya.

Dalam tutorial ini, Anda menulis penganalisis yang akan melaporkan setiap deklarasi variabel lokal yang dapat dikonversi ke konstanta lokal kepada pengguna. Sebagai contoh, perhatikan kode berikut:

int x = 0;
Console.WriteLine(x);

Dalam kode di atas, x diberi nilai konstanta dan tidak pernah diubah. Kode dapat dideklarasikan dengan menggunakan pengubah const:

const int x = 0;
Console.WriteLine(x);

Analisis untuk menentukan jika variabel dapat dibuat konstanta dilibatkan, sehingga membutuhkan analisis sintaksis, analisis konstanta dari ekspresi penginisialisasi, dan analisis aliran data untuk memastikan bahwa variabel tidak pernah ditulis. .NET Compiler Platform menyediakan API yang memudahkan untuk melakukan analisis ini.

Buat pendaftaran penganalisis

Templat tersebut membuat kelas DiagnosticAnalyzer awal, dalam file MakeConstAnalyzer.cs. Penganalisis awal ini menunjukkan dua properti penting dari setiap penganalisis.

  • Setiap penganalisis diagnostik harus menyediakan atribut [DiagnosticAnalyzer] yang menjelaskan bahasa yang digunakan dalam operasinya.
  • Setiap penganalisis diagnostik harus berasal (secara langsung atau tidak langsung) dari kelas DiagnosticAnalyzer.

Templat tersebut juga menunjukkan fitur dasar yang merupakan bagian dari semua penganalisis:

  1. Daftarkan tindakan. Tindakan tersebut mewakili perubahan kode yang akan memicu penganalisis Anda untuk memeriksa kode guna mencari pelanggaran. Saat Visual Studio mendeteksi pengeditan kode yang cocok dengan tindakan terdaftar, Visual Studio akan memanggil metode terdaftar penganalisis Anda.
  2. Buat diagnostik. Saat penganalisis Anda mendeteksi pelanggaran, penganalisis akan membuat objek diagnostik yang digunakan oleh Visual Studio untuk memberi tahu pengguna tentang pelanggaran tersebut.

Anda mendaftarkan tindakan dalam pengambilalihan metode DiagnosticAnalyzer.Initialize(AnalysisContext) Anda. Dalam tutorial ini, Anda akan mengunjungi node sintaksis yang mencari deklarasi lokal, dan melihat mana yang memiliki nilai konstanta. Jika deklarasi dapat menjadi konstanta, penganalisis Anda akan membuat dan melaporkan diagnostik.

Langkah pertama adalah memperbarui konstanta dan metode pendaftaran Initialize, sehingga konstanta ini akan menunjukkan penganalisis "Make Const" Anda. Sebagian besar konstanta string ditentukan dalam file sumber daya string. Anda harus mengikuti praktik tersebut untuk pelokalan yang lebih mudah. Buka file Resources.resx untuk proyek penganalisis MakeConst. File ini menampilkan editor sumber daya. Perbarui sumber daya string sebagai berikut:

  • Ubah AnalyzerDescription ke "Variables that are not modified should be made constants.".
  • Ubah AnalyzerMessageFormat ke "Variable '{0}' can be made constant".
  • Ubah AnalyzerTitle ke "Variable can be made constant".

Setelah Anda selesai, editor sumber daya akan muncul seperti yang ditunjukkan pada gambar berikut:

Memperbarui sumber daya string

Perubahan yang tersisa ada di file penganalisis. Buka MakeConstAnalyzer.cs di Visual Studio. Ubah tindakan terdaftar dari satu tindakan yang bertindak berdasarkan simbol menjadi tindakan yang bertindak berdasarkan sintaksis. Dalam metode MakeConstAnalyzerAnalyzer.Initialize, temukan baris yang mendaftarkan tindakan berdasarkan simbol:

context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);

Ganti dengan baris berikut:

context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.LocalDeclarationStatement);

Setelah perubahan itu, Anda dapat menghapus metode AnalyzeSymbol. Penganalisis ini memeriksa SyntaxKind.LocalDeclarationStatement, bukan pernyataan SymbolKind.NamedType. Perhatikan bahwa AnalyzeNode memiliki coretan merah di bawahnya. Kode yang baru saja Anda tambahkan merujuk pada metode AnalyzeNode yang belum dideklarasikan. Deklarasikan metode tersebut dengan menggunakan kode berikut:

private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
}

Ubah Category ke "Usage" di MakeConstAnalyzer.cs seperti yang ditunjukkan dalam kode berikut:

private const string Category = "Usage";

Temukan deklarasi lokal yang dapat menjadi konstanta

Saatnya menulis versi pertama metode AnalyzeNode ini. Harus mencari satu deklarasi lokal yang dapat menjadi, tetapi bukan merupakan const, seperti kode berikut:

int x = 0;
Console.WriteLine(x);

Langkah pertama adalah menemukan deklarasi lokal. Tambahkan kode berikut ke AnalyzeNode di MakeConstAnalyzer.cs:

var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;

Konversi eksplisit ini selalu berhasil karena penganalisis Anda mendaftar untuk perubahan pada deklarasi lokal, dan hanya deklarasi lokal. Tidak ada jenis node lain yang memicu panggilan ke metode AnalyzeNode Anda. Selanjutnya, periksa deklarasi untuk mencari setiap pengubah const. Jika Anda menemukannya, segera kembali. Kode berikut mencari semua pengubah const pada deklarasi lokal:

// make sure the declaration isn't already const:
if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
{
    return;
}

Terakhir, Anda perlu memeriksa jika variabel bisa menjadi const. Hal tersebut berarti memastikan bahwa variabel tidak pernah ditetapkan setelah diinisialisasi.

Anda akan melakukan beberapa analisis semantik dengan menggunakan SyntaxNodeAnalysisContext. Anda menggunakan argumen context untuk menentukan apakah deklarasi variabel lokal dapat dibuat const. Microsoft.CodeAnalysis.SemanticModel mewakili semua informasi semantik dalam satu file sumber. Anda dapat mempelajari selengkapnya dalam artikel yang mencakup model semantik. Anda akan menggunakan Microsoft.CodeAnalysis.SemanticModel untuk melakukan analisis aliran data pada pernyataan deklarasi lokal. Kemudian, Anda akan menggunakan hasil analisis aliran data ini untuk memastikan bahwa variabel lokal tidak ditulis dengan nilai baru di tempat lain. Panggil metode ekstensi GetDeclaredSymbol untuk mengambil ILocalSymbol bagi variabel dan periksa apakah itu tidak dimuat dengan koleksi analisis aliran data DataFlowAnalysis.WrittenOutside. Tambahkan kode berikut di akhir metode AnalyzeNode:

// Perform data flow analysis on the local declaration.
DataFlowAnalysis dataFlowAnalysis = context.SemanticModel.AnalyzeDataFlow(localDeclaration);

// Retrieve the local symbol for each variable in the local declaration
// and ensure that it is not written outside of the data flow analysis region.
VariableDeclaratorSyntax variable = localDeclaration.Declaration.Variables.Single();
ISymbol variableSymbol = context.SemanticModel.GetDeclaredSymbol(variable, context.CancellationToken);
if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
{
    return;
}

Kode yang baru saja ditambahkan akan memastikan bahwa variabel tidak diubah, dan karenanya dapat dibuat const. Saatnya menaikkan diagnostik. Tambahkan kode berikut sebagai baris terakhir di AnalyzeNode:

context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation(), localDeclaration.Declaration.Variables.First().Identifier.ValueText));

Anda dapat memeriksa kemajuan Anda dengan menekan F5 untuk menjalankan penganalisis Anda. Anda dapat memuat aplikasi konsol yang Anda buat sebelumnya, lalu menambahkan kode pengujian berikut:

int x = 0;
Console.WriteLine(x);

Bola lampu akan muncul, dan penganalisis Anda akan melaporkan diagnostik. Namun, tergantung versi Visual Studio Anda, Anda mungkin akan melihat:

  • Bola lampu, yang masih menggunakan perbaikan kode yang dihasilkan templat, akan memberi tahu Anda bahwa tulisan dapat dibuat menjadi berhuruf besar.
  • Pesan banner di bagian atas penyunting yang menyatakan 'MakeConstCodeFixProvider' menemukan kesalahan dan telah dinonaktifkan.'. Ini karena penyedia perbaikan kode belum diubah dan masih mengharapkan menemukan elemen TypeDeclarationSyntax alih-alih elemen LocalDeclarationStatementSyntax.

Bagian berikutnya menjelaskan cara menulis perbaikan kode.

Tulis perbaikan kode

Penganalisis dapat menyediakan satu atau beberapa perbaikan kode. Perbaikan kode menentukan pengeditan yang akan mengatasi masalah yang dilaporkan. Untuk penganalisis yang Anda buat, Anda dapat memberikan perbaikan kode yang menyisipkan kata kunci konstanta:

- int x = 0;
+ const int x = 0;
Console.WriteLine(x);

Pengguna memilihnya dari antarmuka pengguna bola lampu di penyunting dan Visual Studio mengubah kode.

Buka file CodeFixResources.resx dan ubah CodeFixTitle menjadi "Make constant".

Buka file MakeConstCodeFixProvider.cs yang ditambahkan oleh templat. Perbaikan kode ini sudah terhubung ke ID Diagnostik yang diproduksi oleh penganalisis diagnostik Anda, tetapi belum menerapkan transformasi kode yang tepat.

Selanjutnya, hapus metode MakeUppercaseAsync. Ini tidak lagi berlaku.

Semua penyedia perbaikan kode berasal dari CodeFixProvider. Semua penyedia mengambil alih CodeFixProvider.RegisterCodeFixesAsync(CodeFixContext) untuk melaporkan perbaikan kode yang tersedia. Di RegisterCodeFixesAsync, ubah jenis node pendahulu yang Anda cari menjadi LocalDeclarationStatementSyntax agar sesuai dengan diagnostik:

var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<LocalDeclarationStatementSyntax>().First();

Selanjutnya, ubah baris terakhir untuk mendaftarkan perbaikan kode. Perbaikan Anda akan membuat dokumen baru yang dihasilkan dari penambahan pengubah const ke deklarasi yang sudah ada:

// Register a code action that will invoke the fix.
context.RegisterCodeFix(
    CodeAction.Create(
        title: CodeFixResources.CodeFixTitle,
        createChangedDocument: c => MakeConstAsync(context.Document, declaration, c),
        equivalenceKey: nameof(CodeFixResources.CodeFixTitle)),
    diagnostic);

Anda akan melihat coretan merah dalam kode yang baru saja Anda tambahkan pada simbol MakeConstAsync. Tambahkan deklarasi untuk MakeConstAsync seperti kode berikut:

private static async Task<Document> MakeConstAsync(Document document,
    LocalDeclarationStatementSyntax localDeclaration,
    CancellationToken cancellationToken)
{
}

Metode MakeConstAsync baru Anda akan mengubah Document yang mewakili file sumber pengguna menjadi Document baru yang sekarang berisi deklarasi const.

Anda membuat token kata kunci const baru untuk disisipkan di bagian depan pernyataan deklarasi. Berhati-hatilah untuk terlebih dahulu menghapus trivia terkemuka dari token pertama pernyataan deklarasi dan melampirkannya ke token const. Tambahkan kode berikut ke metode MakeConstAsync:

// Remove the leading trivia from the local declaration.
SyntaxToken firstToken = localDeclaration.GetFirstToken();
SyntaxTriviaList leadingTrivia = firstToken.LeadingTrivia;
LocalDeclarationStatementSyntax trimmedLocal = localDeclaration.ReplaceToken(
    firstToken, firstToken.WithLeadingTrivia(SyntaxTriviaList.Empty));

// Create a const token with the leading trivia.
SyntaxToken constToken = SyntaxFactory.Token(leadingTrivia, SyntaxKind.ConstKeyword, SyntaxFactory.TriviaList(SyntaxFactory.ElasticMarker));

Selanjutnya, tambahkan token const ke deklarasi dengan menggunakan kode berikut:

// Insert the const token into the modifiers list, creating a new modifiers list.
SyntaxTokenList newModifiers = trimmedLocal.Modifiers.Insert(0, constToken);
// Produce the new local declaration.
LocalDeclarationStatementSyntax newLocal = trimmedLocal
    .WithModifiers(newModifiers)
    .WithDeclaration(localDeclaration.Declaration);

Selanjutnya, format deklarasi baru agar sesuai dengan aturan pemformatan C#. Memformat perubahan Anda agar sesuai dengan kode yang ada akan menciptakan pengalaman yang lebih baik. Setelah kode yang ada muncul, segera tambahkan pernyataan berikut:

// Add an annotation to format the new local declaration.
LocalDeclarationStatementSyntax formattedLocal = newLocal.WithAdditionalAnnotations(Formatter.Annotation);

Namespace baru diperlukan untuk kode ini. Tambahkan direktif using berikut ke bagian atas file:

using Microsoft.CodeAnalysis.Formatting;

Langkah terakhir adalah melakukan pengeditan. Ada tiga langkah dalam proses ini:

  1. Pahami dan tangani dokumen yang sudah ada.
  2. Buat dokumen baru dengan mengganti deklarasi yang ada dengan deklarasi baru.
  3. Kembalikan dokumen baru.

Tambahkan kode berikut di akhir metode MakeConstAsync:

// Replace the old local declaration with the new local declaration.
SyntaxNode oldRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
SyntaxNode newRoot = oldRoot.ReplaceNode(localDeclaration, formattedLocal);

// Return document with transformed tree.
return document.WithSyntaxRoot(newRoot);

Perbaikan kode Anda siap untuk dicoba. Tekan F5 untuk menjalankan proyek penganalisis dalam instans kedua Visual Studio. Dalam instans Visual Studio kedua, buat proyek Aplikasi Konsol C# baru dan tambahkan beberapa deklarasi variabel lokal yang diinisialisasi dengan nilai konstanta ke metode Utama. Anda akan melihat bahwa ini akan dilaporkan sebagai peringatan seperti di bawah ini.

Dapat membuat peringatan const

Anda telah membuat banyak kemajuan. Terdapat coretan di bawah deklarasi yang dapat dibuat const. Namun, masih ada pekerjaan yang harus dilakukan. Ini berfungsi dengan baik jika Anda menambahkan const ke deklarasi yang dimulai dengan i, lalu j, dan terakhir k. Tetapi, jika Anda menambahkan pengubah const dalam urutan yang berbeda, dimulai dengan k, penganalisis Anda akan membuat kesalahan: k tidak dapat dideklarasikan sebagai const, kecuali jika i dan j sudah menjadi const. Anda harus melakukan lebih banyak analisis untuk memastikan bahwa Anda menangani berbagai cara deklarasi dan inisialisasi variabel.

Pengujian unit build

Penganalisis dan perbaikan kode Anda berfungsi pada kasus sederhana dari satu deklarasi yang dapat dibuat konstanta. Ada banyak kemungkinan pernyataan deklarasi yang menunjukkan penerapan ini membuat kesalahan. Anda akan mengatasi kasus-kasus ini dengan bekerja dengan pustaka pengujian unit yang ditulis oleh templat. Ini jauh lebih cepat daripada berulang kali membuka salinan kedua Visual Studio.

Buka file MakeConstUnitTests.cs dalam proyek pengujian unit. Templat membuat dua pengujian yang mengikuti dua pola umum untuk pengujian unit penganalisis dan perbaikan kode. TestMethod1 menunjukkan pola untuk pengujian yang memastikan bahwa penganalisis tidak melaporkan diagnostik ketika tidak diharuskan melapor. TestMethod2 menunjukkan pola untuk melaporkan diagnostik dan menjalankan perbaikan kode.

Templat menggunakan paket Microsoft.CodeAnalysis.Testing untuk pengujian unit.

Tip

Pustaka pengujian mendukung sintaksis markup khusus, termasuk berikut ini:

  • [|text|]: menunjukkan bahwa diagnostik dilaporkan untuk text. Secara default, formulir ini hanya dapat digunakan untuk menguji penganalisis dengan satu DiagnosticDescriptor, yang disediakan oleh DiagnosticAnalyzer.SupportedDiagnostics.
  • {|ExpectedDiagnosticId:text|}: menunjukkan bahwa diagnostik dengan IdExpectedDiagnosticId dilaporkan untuk text.

Ganti pengujian templat di kelas MakeConstUnitTest dengan metode pengujian berikut:

        [TestMethod]
        public async Task LocalIntCouldBeConstant_Diagnostic()
        {
            await VerifyCS.VerifyCodeFixAsync(@"
using System;

class Program
{
    static void Main()
    {
        [|int i = 0;|]
        Console.WriteLine(i);
    }
}
", @"
using System;

class Program
{
    static void Main()
    {
        const int i = 0;
        Console.WriteLine(i);
    }
}
");
        }

Jalankan pengujian ini untuk memastikan lulus pengujian. Di Visual Studio, buka Test Explorer dengan memilih Uji>Windows>Test Explorer. Lalu pilih Jalankan Semua.

Buat pengujian untuk deklarasi yang valid

Sebagai aturan umum, penganalisis harus keluar secepat mungkin dan melakukan pekerjaan minimal. Visual Studio memanggil penganalisis terdaftar saat pengguna melakukan pengeditan kode. Responsivitas adalah persyaratan utama. Ada beberapa kasus pengujian untuk kode yang tidak akan meningkatkan diagnostik Anda. Penganalisis Anda sudah menangani salah satu pengujian tersebut, kasus saat variabel ditetapkan setelah diinisialisasi. Tambahkan metode pengujian berikut untuk mewakili kasus tersebut:

        [TestMethod]
        public async Task VariableIsAssigned_NoDiagnostic()
        {
            await VerifyCS.VerifyAnalyzerAsync(@"
using System;

class Program
{
    static void Main()
    {
        int i = 0;
        Console.WriteLine(i++);
    }
}
");
        }

Tes ini juga lulus. Selanjutnya, tambahkan metode pengujian untuk kondisi yang belum pernah Anda tangani:

  • Deklarasi yang sudah menjadi const, karena sudah menjadi konstanta:

            [TestMethod]
            public async Task VariableIsAlreadyConst_NoDiagnostic()
            {
                await VerifyCS.VerifyAnalyzerAsync(@"
    using System;
    
    class Program
    {
        static void Main()
        {
            const int i = 0;
            Console.WriteLine(i);
        }
    }
    ");
            }
    
  • Deklarasi yang tidak memiliki penginisialisasi, karena tidak ada nilai untuk digunakan:

            [TestMethod]
            public async Task NoInitializer_NoDiagnostic()
            {
                await VerifyCS.VerifyAnalyzerAsync(@"
    using System;
    
    class Program
    {
        static void Main()
        {
            int i;
            i = 0;
            Console.WriteLine(i);
        }
    }
    ");
            }
    
  • Deklarasi ketika penginisialisasi bukan merupakan konstanta, karena tidak dapat menjadi konstanta waktu kompilasi:

            [TestMethod]
            public async Task InitializerIsNotConstant_NoDiagnostic()
            {
                await VerifyCS.VerifyAnalyzerAsync(@"
    using System;
    
    class Program
    {
        static void Main()
        {
            int i = DateTime.Now.DayOfYear;
            Console.WriteLine(i);
        }
    }
    ");
            }
    

Ini bisa lebih rumit karena C# memungkinkan beberapa deklarasi sebagai satu pernyataan. Pertimbangkan konstanta string kasus pengujian berikut:

        [TestMethod]
        public async Task MultipleInitializers_NoDiagnostic()
        {
            await VerifyCS.VerifyAnalyzerAsync(@"
using System;

class Program
{
    static void Main()
    {
        int i = 0, j = DateTime.Now.DayOfYear;
        Console.WriteLine(i);
        Console.WriteLine(j);
    }
}
");
        }

Variabel i dapat dibuat konstanta, tetapi variabel j tidak dapat dibuat konstanta. Oleh karena itu, pernyataan ini tidak dapat dijadikan deklarasi konstanta.

Jalankan lagi pengujian dan Anda akan melihat bahwa kasus pengujian baru ini gagal.

Perbarui penganalisis Anda untuk mengabaikan deklarasi yang benar

Anda memerlukan beberapa penyempurnaan pada metode AnalyzeNode penganalisis Anda untuk memfilter kode yang cocok dengan kondisi ini. Itu semua adalah kondisi terkait, sehingga perubahan serupa akan memperbaiki semua kondisi ini. Buat perubahan berikut pada AnalyzeNode:

  • Analisis semantik Anda memeriksa satu deklarasi variabel. Kode ini perlu ada dalam perulangan foreach yang memeriksa semua variabel yang dideklarasikan dalam pernyataan yang sama.
  • Setiap variabel yang dideklarasikan harus memiliki penginisialisasi.
  • Setiap penginisialisasi variabel yang dideklarasikan harus berupa konstanta waktu kompilasi.

Dalam metode AnalyzeNode Anda, ganti analisis semantik asli:

// Perform data flow analysis on the local declaration.
DataFlowAnalysis dataFlowAnalysis = context.SemanticModel.AnalyzeDataFlow(localDeclaration);

// Retrieve the local symbol for each variable in the local declaration
// and ensure that it is not written outside of the data flow analysis region.
VariableDeclaratorSyntax variable = localDeclaration.Declaration.Variables.Single();
ISymbol variableSymbol = context.SemanticModel.GetDeclaredSymbol(variable, context.CancellationToken);
if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
{
    return;
}

dengan cuplikan kode berikut:

// Ensure that all variables in the local declaration have initializers that
// are assigned with constant values.
foreach (VariableDeclaratorSyntax variable in localDeclaration.Declaration.Variables)
{
    EqualsValueClauseSyntax initializer = variable.Initializer;
    if (initializer == null)
    {
        return;
    }

    Optional<object> constantValue = context.SemanticModel.GetConstantValue(initializer.Value, context.CancellationToken);
    if (!constantValue.HasValue)
    {
        return;
    }
}

// Perform data flow analysis on the local declaration.
DataFlowAnalysis dataFlowAnalysis = context.SemanticModel.AnalyzeDataFlow(localDeclaration);

foreach (VariableDeclaratorSyntax variable in localDeclaration.Declaration.Variables)
{
    // Retrieve the local symbol for each variable in the local declaration
    // and ensure that it is not written outside of the data flow analysis region.
    ISymbol variableSymbol = context.SemanticModel.GetDeclaredSymbol(variable, context.CancellationToken);
    if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
    {
        return;
    }
}

Perulangan foreach pertama memeriksa setiap deklarasi variabel dengan menggunakan analisis sintaksis. Pemeriksaan pertama menjamin bahwa variabel memiliki penginisialisasi. Pemeriksaan kedua menjamin bahwa inisialisasi merupakan konstanta. Perulangan kedua memiliki analisis semantik asli. Pemeriksaan semantik berada dalam perulangan terpisah karena memiliki dampak yang lebih besar pada performa. Jalankan lagi pengujian Anda dan Anda akan melihat semuanya lulus.

Tambahkan polesan akhir

Anda hampir selesai. Ada beberapa kondisi lagi untuk ditangani oleh penganalisis Anda. Visual Studio memanggil penganalisis saat pengguna menulis kode. Sering kali penganalisis Anda akan dipanggil untuk kode yang tidak dikompilasi. Metode AnalyzeNode penganalisis diagnostik tidak memeriksa untuk melihat apakah nilai konstanta dapat dikonversi ke jenis variabel. Jadi, penerapan saat ini akan dengan mudah mengonversi deklarasi yang salah seperti int i = "abc" ke konstanta lokal. Tambahkan metode pengujian untuk kasus ini:

        [TestMethod]
        public async Task DeclarationIsInvalid_NoDiagnostic()
        {
            await VerifyCS.VerifyAnalyzerAsync(@"
using System;

class Program
{
    static void Main()
    {
        int x = {|CS0029:""abc""|};
    }
}
");
        }

Selain itu, jenis referensi tidak ditangani dengan benar. Satu-satunya nilai konstanta yang diizinkan untuk jenis referensi adalah null, kecuali dalam kasus System.String, yang dapat memungkinkan harfiah string. Dengan kata lain, const string s = "abc" adalah legal, tetapi const object s = "abc" tidak. Cuplikan kode ini akan memverifikasi kondisi tersebut:

        [TestMethod]
        public async Task DeclarationIsNotString_NoDiagnostic()
        {
            await VerifyCS.VerifyAnalyzerAsync(@"
using System;

class Program
{
    static void Main()
    {
        object s = ""abc"";
    }
}
");
        }

Agar menyeluruh, Anda perlu menambahkan pengujian lain untuk memastikan bahwa Anda dapat membuat deklarasi konstanta untuk string. Cuplikan berikut menetapkan kode yang menaikkan diagnostik dan kode setelah perbaikan diterapkan:

        [TestMethod]
        public async Task StringCouldBeConstant_Diagnostic()
        {
            await VerifyCS.VerifyCodeFixAsync(@"
using System;

class Program
{
    static void Main()
    {
        [|string s = ""abc"";|]
    }
}
", @"
using System;

class Program
{
    static void Main()
    {
        const string s = ""abc"";
    }
}
");
        }

Terakhir, jika variabel ditetapkan dengan kata kunci var, perbaikan kode akan melakukan hal yang salah dan menghasilkan deklarasi const var yang tidak didukung oleh bahasa C#. Untuk memperbaiki bug ini, perbaikan kode harus mengganti kata kunci var dengan nama jenis yang disimpulkan:

        [TestMethod]
        public async Task VarIntDeclarationCouldBeConstant_Diagnostic()
        {
            await VerifyCS.VerifyCodeFixAsync(@"
using System;

class Program
{
    static void Main()
    {
        [|var item = 4;|]
    }
}
", @"
using System;

class Program
{
    static void Main()
    {
        const int item = 4;
    }
}
");
        }

        [TestMethod]
        public async Task VarStringDeclarationCouldBeConstant_Diagnostic()
        {
            await VerifyCS.VerifyCodeFixAsync(@"
using System;

class Program
{
    static void Main()
    {
        [|var item = ""abc"";|]
    }
}
", @"
using System;

class Program
{
    static void Main()
    {
        const string item = ""abc"";
    }
}
");
        }

Untungnya, semua bug di atas dapat ditangani dengan menggunakan teknik yang sama dengan yang baru saja Anda pelajari.

Untuk memperbaiki bug pertama, pertama-tama, buka MakeConstAnalyzer.cs dan temukan perulangan foreach tempat tiap-tiap inisialisasi deklarasi lokal diperiksa untuk memastikan bahwa inisialisasi ditetapkan dengan nilai konstanta. Segera lakukan sebelum perulangan foreach pertama, panggil context.SemanticModel.GetTypeInfo() untuk mengambil informasi terperinci tentang jenis deklarasi lokal yang dideklarasikan:

TypeSyntax variableTypeName = localDeclaration.Declaration.Type;
ITypeSymbol variableType = context.SemanticModel.GetTypeInfo(variableTypeName, context.CancellationToken).ConvertedType;

Kemudian, di dalam perulangan foreach Anda, periksa setiap penginisialisasi untuk memastikannya dapat dikonversi ke jenis variabel. Tambahkan pemeriksaan berikut setelah memastikan bahwa inisialisasi merupakan konstanta:

// Ensure that the initializer value can be converted to the type of the
// local declaration without a user-defined conversion.
Conversion conversion = context.SemanticModel.ClassifyConversion(initializer.Value, variableType);
if (!conversion.Exists || conversion.IsUserDefined)
{
    return;
}

Perubahan berikutnya didasarkan pada yang terakhir. Sebelum kurung kurawal tutup dari perulangan foreach pertama, tambahkan kode berikut untuk memeriksa jenis deklarasi lokal ketika konstantanya adalah string atau null.

// Special cases:
//  * If the constant value is a string, the type of the local declaration
//    must be System.String.
//  * If the constant value is null, the type of the local declaration must
//    be a reference type.
if (constantValue.Value is string)
{
    if (variableType.SpecialType != SpecialType.System_String)
    {
        return;
    }
}
else if (variableType.IsReferenceType && constantValue.Value != null)
{
    return;
}

Anda harus menulis sedikit lebih banyak kode di penyedia perbaikan kode Anda untuk mengganti kata kunci var dengan nama jenis yang benar. Kembali ke MakeConstCodeFixProvider.cs. Kode yang akan Anda tambahkan akan menjalankan langkah-langkah berikut:

  • Periksa jika deklarasi merupakan deklarasi var, jika benar, maka:
  • Buat jenis baru untuk jenis yang disimpulkan.
  • Pastikan bahwa deklarasi jenis bukan sebuah alias. Jika iya, menjadi legal untuk menyatakan const var.
  • Pastikan bahwa var bukan nama jenis dalam program ini. (Jika iya, const var adalah legal).
  • Sederhanakan nama jenis lengkap

Yang terdengar seperti banyak kode. Namun sebenarnya bukan kode. Ganti baris yang mendeklarasi dan menginisialisasi newLocal dengan kode berikut. Ini akan berjalan segera setelah inisialisasi newModifiers:

// If the type of the declaration is 'var', create a new type name
// for the inferred type.
VariableDeclarationSyntax variableDeclaration = localDeclaration.Declaration;
TypeSyntax variableTypeName = variableDeclaration.Type;
if (variableTypeName.IsVar)
{
    SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

    // Special case: Ensure that 'var' isn't actually an alias to another type
    // (e.g. using var = System.String).
    IAliasSymbol aliasInfo = semanticModel.GetAliasInfo(variableTypeName, cancellationToken);
    if (aliasInfo == null)
    {
        // Retrieve the type inferred for var.
        ITypeSymbol type = semanticModel.GetTypeInfo(variableTypeName, cancellationToken).ConvertedType;

        // Special case: Ensure that 'var' isn't actually a type named 'var'.
        if (type.Name != "var")
        {
            // Create a new TypeSyntax for the inferred type. Be careful
            // to keep any leading and trailing trivia from the var keyword.
            TypeSyntax typeName = SyntaxFactory.ParseTypeName(type.ToDisplayString())
                .WithLeadingTrivia(variableTypeName.GetLeadingTrivia())
                .WithTrailingTrivia(variableTypeName.GetTrailingTrivia());

            // Add an annotation to simplify the type name.
            TypeSyntax simplifiedTypeName = typeName.WithAdditionalAnnotations(Simplifier.Annotation);

            // Replace the type in the variable declaration.
            variableDeclaration = variableDeclaration.WithType(simplifiedTypeName);
        }
    }
}
// Produce the new local declaration.
LocalDeclarationStatementSyntax newLocal = trimmedLocal.WithModifiers(newModifiers)
                           .WithDeclaration(variableDeclaration);

Anda akan perlu menambahkan satu direktif using untuk menggunakan jenis Simplifier:

using Microsoft.CodeAnalysis.Simplification;

Jalankan tes Anda dan semuanya harus lulus. Beri ucapan selamat kepada diri Anda sendiri dengan menjalankan penganalisis yang sudah selesai. Tekan Ctrl+F5 untuk menjalankan proyek penganalisis dalam instans kedua Visual Studio dengan ekstensi Pratinjau Roslyn yang dimuat.

  • Dalam instans Visual Studio kedua, buat proyek Aplikasi Konsol C# baru dan tambahkan int x = "abc"; ke metode Utama. Berkat perbaikan bug pertama, tidak ada peringatan yang akan dilaporkan untuk deklarasi variabel lokal ini (meskipun ada kesalahan pengompilasi seperti yang diduga).
  • Selanjutnya, tambahkan object s = "abc"; ke metode Utama. Karena perbaikan bug kedua, tidak ada peringatan yang akan dilaporkan.
  • Terakhir, tambahkan variabel lokal lain yang menggunakan kata kunci var. Anda akan melihat bahwa peringatan dilaporkan dan saran muncul di bawahnya, di sebelah kiri.
  • Pindahkan tanda sisipan penyunting ke garis bawah coretan dan tekan Ctrl+.. untuk menampilkan perbaikan kode yang disarankan. Setelah memilih perbaikan kode Anda, perhatikan bahwa sekarang kata kunci var akan ditangani dengan benar.

Terakhir, tambahkan kode berikut:

int i = 2;
int j = 32;
int k = i + j;

Setelah perubahan ini, Anda hanya mendapatkan coretan merah pada dua variabel pertama. Tambahkan const ke i dan j, dan Anda akan mendapatkan peringatan baru di k karena sekarang itu bisa menjadi const.

Selamat! Anda telah membuat ekstensi .NET Compiler Platform pertama Anda yang akan melakukan analisis kode saat ini untuk mendeteksi masalah dan menyediakan perbaikan cepat. Dalam proses ini, Anda telah mempelajari banyak API kode yang merupakan bagian dari .NET Compiler Platform SDK (API Roslyn). Anda dapat membandingkan pekerjaan Anda dengan sampel yang telah selesai dalam sampel repositori GitHub kami.

Sumber daya lainnya