Bagikan melalui


Tutorial: Menulis penganalisis dan perbaikan kode pertama Anda

.NET Compiler Platform SDK menyediakan alat yang Anda butuhkan untuk membuat diagnostik kustom (penganalisis), perbaikan kode, pemfaktoran ulang kode, dan suppressor 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 bisa apa saja mulai dari struktur kode hingga gaya pengkodean hingga konvensi penamaan dan banyak lagi. Platform .NET Compiler menyediakan kerangka kerja untuk menjalankan analisis saat pengembang menulis kode, dan semua fitur UI Visual Studio untuk memperbaiki kode: menampilkan garis bergelombang di editor, mengisi Daftar Kesalahan Visual Studio, membuat saran "bola lampu" dan menunjukkan pratinjau yang mendetail dari perbaikan yang disarankan.

Dalam tutorial ini, Anda akan menjelajahi pembuatan penganalisis dan perbaikan kode yang menyertainya 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 menemukan deklarasi variabel lokal yang seharusnya menggunakan pengubah const tetapi belum dinyatakan demikian. Perbaikan kode yang menyertainya memodifikasi deklarasi tersebut untuk menambahkan pengubah const .

Prasyarat

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

Instruksi penginstalan - Penginstal Visual Studio

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

Menginstal menggunakan Visual Studio Installer - Tampilan beban kerja

.NET Compiler Platform SDK tidak dipilih secara otomatis 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 editor DGML menampilkan grafik di visualizer:

  1. Buka node Komponen individual di pohon ringkasan.
  2. Centang kotak untuk editor DGML

Menginstal menggunakan Alat Penginstal Visual Studio - tab Komponen individual

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

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

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

Ada beberapa langkah untuk membuat dan memvalidasi penganalisis Anda:

  1. Buat solusinya.
  2. Daftarkan nama dan deskripsi penganalisis.
  3. Melaporkan peringatan dan rekomendasi penganalisis.
  4. Terapkan perbaikan kode untuk menerima rekomendasi.
  5. Meningkatkan kualitas analisis melalui pengujian unit.

Membuat solusi

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

Nota

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

Menjelajahi 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 menghasilkan paket NuGet untuk penganalisis dan perbaikan kode.
  • MakeConst.Test, yang merupakan proyek pengujian unit.
  • MakeConst.Vsix, yang merupakan proyek startup default yang memulai instans kedua Visual Studio yang telah memuat penganalisis baru Anda. Tekan F5 untuk memulai proyek VSIX.

Nota

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

Petunjuk / Saran

Saat menjalankan penganalisis, Anda memulai salinan kedua Visual Studio. Salinan kedua ini menggunakan sarang registri yang berbeda untuk menyimpan pengaturan. Hal ini memungkinkan Anda membedakan pengaturan visual di dua salinan Visual Studio. Anda dapat memilih tema yang berbeda untuk eksekusi eksperimental Visual Studio. Selain itu, jangan mengubah pengaturan atau masuk ke akun Visual Studio Anda menggunakan mode percobaan Visual Studio. Itu membuat pengaturan tetap berbeda.

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

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 mouse ke atas token dengan garis bawah bergelombang, dan teks peringatan yang disediakan oleh penganalisis muncul.

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

Peringatan laporan analis

Templat juga menyediakan perbaikan kode yang mengubah nama jenis apa pun yang berisi karakter huruf kecil ke semua huruf besar. Anda dapat mengeklik bola lampu yang ditampilkan dengan peringatan untuk melihat perubahan yang disarankan. Dengan menerima perubahan yang disarankan, nama jenis dan semua referensi ke jenis tersebut dalam solusi akan diperbarui. Sekarang setelah Anda melihat penganalisis awal beraksi, 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 di penganalisis Anda. Templat juga secara otomatis membuat projek pengujian unit untuk Anda. Proyek tersebut berisi dua tes. 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 analisis kode dan perbaikan kode, Anda akan menulis pengujian untuk berbagai struktur kode guna memverifikasi hasil pekerjaan Anda. Pengujian unit untuk penganalisis jauh lebih cepat daripada mengujinya secara interaktif dengan Visual Studio.

Petunjuk / Saran

Pengujian unit penganalisis adalah alat yang bagus ketika Anda mengetahui konstruksi kode apa yang harus dan tidak boleh memicu penganalisis Anda. Memuat penganalisis Anda di salinan lain Visual Studio adalah alat yang bagus untuk menjelajahi dan menemukan konstruk yang mungkin belum Anda pikirkan.

Dalam tutorial ini, Anda menulis penganalisis yang melaporkan kepada pengguna setiap deklarasi variabel lokal yang dapat dikonversi ke konstanta lokal. Misalnya, pertimbangkan kode berikut:

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

Dalam kode di atas, x diberi nilai konstanta dan tidak pernah dimodifikasi. Ini dapat dinyatakan menggunakan pengubah const :

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

Analisis untuk menentukan apakah variabel dapat dibuat konstan terlibat, membutuhkan analisis sintaktik, analisis konstan dari ekspresi inisialisasi dan analisis aliran data untuk memastikan bahwa variabel tidak pernah ditulis. Platform Compiler .NET menyediakan API yang memudahkan untuk melakukan analisis ini.

Membuat pendaftaran penganalisis

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

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

Templat juga menunjukkan fitur dasar yang merupakan bagian dari penganalisis apa pun:

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

Anda mendaftarkan tindakan dalam penimpaan metode DiagnosticAnalyzer.Initialize(AnalysisContext). Dalam tutorial ini, Anda akan mengunjungi simpul sintaksis yang mencari deklarasi lokal, dan melihat mana yang memiliki nilai konstanta. Jika suatu deklarasi bisa menjadi konstan, penganalisis Anda akan membuat dan melaporkan diagnosa.

Langkah pertama adalah memperbarui konstanta dan metode registrasi Initialize agar konstanta ini mengindikasikan penganalisis "Make Const" Anda. Sebagian besar konstanta string didefinisikan dalam file sumber daya string. Anda harus mengikuti praktek ini untuk memudahkan pelokalan. Buka file Resources.resx untuk proyek penganalisis MakeConst . Ini menampilkan editor sumber daya. Perbarui sumber daya string sebagai berikut:

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

Setelah 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 tindakan yang bertindak pada simbol menjadi tindakan yang bertindak pada sintaksis. Dalam metode MakeConstAnalyzerAnalyzer.Initialize, temukan baris yang mendaftarkan tindakan pada simbol:

context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);

Ganti dengan baris berikut:

context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.LocalDeclarationStatement);

Setelah perubahan itu, Anda dapat menghapus AnalyzeSymbol metode . Penganalisis ini memeriksa SyntaxKind.LocalDeclarationStatement, bukan pernyataan SymbolKind.NamedType. Perhatikan bahwa terdapat garis merah bergelombang di bawah AnalyzeNode. Kode yang baru saja Anda tambahkan mereferensikan AnalyzeNode metode yang belum dideklarasikan. Nyatakan metode tersebut menggunakan kode berikut:

private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
}

Ganti Category dengan "Usage" di MakeConstAnalyzer.cs sebagaimana ditunjukkan dalam kode berikut ini:

private const string Category = "Usage";

Temukan deklarasi lokal yang dapat dijadikan const

Saatnya menulis versi pertama metode AnalyzeNode . Ini harus mencari satu deklarasi lokal yang mungkin const tetapi tidak demikian, seperti kode berikut:

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

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

var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;

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

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

Akhirnya, Anda perlu memeriksa bahwa variabel bisa menjadi const. Itu berarti memastikan tidak pernah ditetapkan setelah diinisialisasi.

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

// 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 memastikan bahwa variabel tidak dimodifikasi, dan oleh karena itu dapat dibuat const. Sudah waktunya untuk melakukan 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 seharusnya muncul, dan penganalisis Anda seharusnya melaporkan hasil diagnosa. Namun, tergantung pada versi Visual Studio, Anda akan melihat:

  • Bola lampu tersebut, yang masih menggunakan perbaikan kode yang dihasilkan oleh templat, akan memberi tahu Anda bahwa itu dapat dibuat huruf besar.
  • Pesan banner di bagian atas editor yang mengatakan 'MakeConstCodeFixProvider' mengalami kesalahan dan telah dinonaktifkan.'. Ini karena penyedia perbaikan kode belum diubah dan masih mengharapkan untuk menemukan TypeDeclarationSyntax elemen alih-alih LocalDeclarationStatementSyntax elemen.

Bagian berikutnya menjelaskan cara menulis perbaikan kode.

Menulis perbaikan kode

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

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

Pengguna memilihnya dari ikon bola lampu di antarmuka pengguna editor dan Visual Studio memodifikasi 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 MakeUppercaseAsync metode . Ini tidak lagi berlaku.

Semua penyedia perbaikan kode berasal dari CodeFixProvider. Mereka semua mengambil alih CodeFixProvider.RegisterCodeFixesAsync(CodeFixContext) untuk melaporkan perbaikan kode yang tersedia. Di RegisterCodeFixesAsync, ubah jenis node leluhur 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 menambahkan const pengubah 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 garis bergelombang 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 baru MakeConstAsync Anda akan mengubah Document yang mewakili file sumber pengguna menjadi sebuah Document baru yang sekarang berisi sebuah const deklarasi.

Anda membuat token kata kunci baru const untuk disisipkan di bagian depan pernyataan deklarasi. Berhati-hatilah untuk terlebih dahulu menghapus trivia yang ada di depan 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 const token ke deklarasi 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 Agar sesuai dengan kode yang ada menciptakan pengalaman yang lebih baik. Tambahkan pernyataan berikut segera setelah kode yang ada:

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

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

using Microsoft.CodeAnalysis.Formatting;

Langkah terakhir adalah membuat pengeditan Anda. Ada tiga langkah untuk proses ini:

  1. Dapatkan referensi ke dokumen yang sudah ada.
  2. Buat dokumen baru dengan mengganti deklarasi yang ada dengan deklarasi baru.
  3. Mengembalikan dokumen baru.

Tambahkan kode berikut ke akhir MakeConstAsync metode:

// 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. Di 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 mereka dilaporkan sebagai peringatan seperti di bawah ini.

Dapat membuat peringatan konstanta

Anda telah membuat banyak kemajuan. Ada garis berlekuk-lekuk di bawah deklarasi yang dapat dibuat const. Tapi masih ada pekerjaan yang harus dilakukan. Ini berfungsi dengan baik jika Anda menambahkan const ke deklarasi yang dimulai dengan i, lalu j dan akhirnya k. Tetapi, jika Anda menambahkan pengubah const dalam urutan yang berbeda, dimulai dengan k, penganalisis Anda membuat kesalahan: k tidak dapat dinyatakan const, kecuali i dan j keduanya sudah const. Anda harus melakukan lebih banyak analisis untuk memastikan Anda menangani berbagai cara variabel dapat dideklarasikan dan diinisialisasi.

Membangun pengujian unit

Penganalisis dan perbaikan kode Anda berfungsi pada kasus sederhana dari satu deklarasi yang dapat dibuat const. Ada banyak kemungkinan pernyataan deklarasi di mana implementasi ini membuat kesalahan. Anda akan mengatasi kasus ini dengan bekerja dengan perpustakaan 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 penganalisis tidak melaporkan diagnostik ketika seharusnya tidak. TestMethod2 menunjukkan pola untuk melaporkan diagnostik dan menjalankan perbaikan kode.

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

Petunjuk / Saran

Pustaka pengujian mendukung sintaks markup khusus, termasuk yang berikut ini:

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

Ganti pengujian templat di MakeConstUnitTest kelas 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 pengujian tersebut berhasil. Di Visual Studio, buka Test Explorer dengan memilih Uji>Windows>Test Explorer. Lalu pilih Jalankan Semua.

Membuat pengujian untuk deklarasi yang valid

Sebagai aturan umum, penganalisis harus keluar secepat mungkin, melakukan pekerjaan minimal. Visual Studio memanggil penganalisis terdaftar saat pengguna mengedit kode. Responsivitas adalah persyaratan utama. Ada beberapa kasus uji untuk kode yang seharusnya tidak memicu diagnostik Anda. Penganalisis Anda sudah menangani beberapa pengujian tersebut. Tambahkan metode pengujian berikut untuk mewakili kasus-kasus tersebut:

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

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

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

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

Pengujian ini lulus karena penganalisis Anda sudah menangani kondisi ini:

  • Variabel yang ditetapkan setelah inisialisasi terdeteksi oleh analisis aliran data.
  • Deklarasi yang sudah const difilter dengan memeriksa const kata kunci.
  • Deklarasi tanpa penginisialisasi ditangani oleh analisis aliran data yang mendeteksi penugasan di luar deklarasi.

Selanjutnya, tambahkan metode pengujian untuk kondisi yang belum Anda tangani:

  • Deklarasi di mana penginisialisasi bukan 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. Oleh karena itu, pernyataan ini tidak dapat dijadikan deklarasi const.

Jalankan pengujian Anda lagi, dan Anda akan melihat dua kasus pengujian terakhir ini gagal.

Perbarui penganalisis Anda untuk mengabaikan deklarasi yang benar

Anda memerlukan beberapa penyempurnaan pada metode penganalisis AnalyzeNode Anda untuk memfilter kode yang cocok dengan kondisi ini. Mereka 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 harus 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 Anda AnalyzeNode, ganti analisis semantik asli dengan:

// 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 pertama foreach memeriksa setiap deklarasi variabel menggunakan analisis sintaktis. Pemeriksaan pertama menjamin bahwa variabel memiliki penginisialisasi. Pemeriksaan kedua menjamin bahwa penginisialisasi adalah konstanta. Perulangan kedua memiliki analisis semantik asli. Pemeriksaan semantik berada dalam perulangan terpisah karena memiliki dampak yang lebih signifikan pada kinerja. Jalankan pengujian Anda lagi, dan Anda akan melihat semuanya lulus.

Menambahkan polesan akhir

Anda hampir selesai. Ada beberapa kondisi lagi yang harus ditangani oleh penganalisis Anda. Visual Studio memanggil penganalisis saat pengguna menulis kode. Sering kali penganalisis Anda akan dipanggil untuk kode yang tidak dikompilasi. Metode penganalisis AnalyzeNode diagnostik tidak memeriksa untuk melihat apakah nilai konstanta dapat dikonversi ke jenis variabel. Jadi, implementasi saat ini akan dengan senang hati 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 memungkinkan literal string. Dengan kata lain, const string s = "abc" adalah hukum, tetapi const object s = "abc" tidak. Cuplikan kode ini memverifikasi kondisi tersebut:

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

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

Untuk menyeluruh, Anda perlu menambahkan pengujian lain untuk memastikan bahwa Anda dapat membuat deklarasi konstan untuk string. Cuplikan berikut mendefinisikan 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"";
    }
}
");
        }

Akhirnya, jika variabel dinyatakan dengan var kata kunci, perbaikan kode melakukan hal yang salah dan menghasilkan const var deklarasi, yang tidak didukung oleh bahasa C#. Untuk memperbaiki bug ini, perbaikan kode harus mengganti var kata kunci 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 diatasi menggunakan teknik yang sama yang baru saja Anda pelajari.

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

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

Kemudian, di dalam perulangan Anda foreach , periksa setiap penginisialisasi untuk memastikannya dapat dikonversi ke jenis variabel. Tambahkan pemeriksaan berikut setelah memastikan bahwa inisialisasi adalah 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 menindaklanjuti perubahan sebelumnya. Sebelum kurung kurawal penutup dari perulangan foreach pertama, tambahkan kode berikut untuk memeriksa jenis deklarasi lokal saat konstanta 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 var kata kunci dengan nama jenis yang benar. Kembali ke MakeConstCodeFixProvider.cs. Kode yang akan Anda tambahkan melakukan langkah-langkah berikut:

  • Periksa apakah deklarasi adalah var deklarasi, dan jika ya:
  • Buat jenis baru untuk jenis yang disimpulkan.
  • Pastikan deklarasi jenis bukan alias. Jika demikian, sah untuk menyatakan const var.
  • Pastikan bahwa var bukan nama tipe dalam program ini. (Jika demikian, const var adalah sah).
  • Menyederhanakan nama tipe lengkap

Kedengarannya seperti banyak kode. Bukan begitu. Ganti baris yang menyatakan dan menginisialisasi newLocal dengan kode berikut. Ini 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 harus menambahkan satu using direktif untuk menggunakan jenis Simplifier.

using Microsoft.CodeAnalysis.Simplification;

Jalankan tes Anda, dan semuanya harus lulus. Ucapan selamat untuk diri sendiri dengan menjalankan penganalisis yang telah selesai. Tekan Ctrl+F5 untuk menjalankan proyek penganalisis pada instans Visual Studio yang kedua dengan ekstensi Pratinjau Roslyn terpasang.

  • Di 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 harus dilaporkan untuk deklarasi variabel lokal ini (meskipun ada kesalahan kompilator seperti yang diharapkan).
  • Selanjutnya, tambahkan object s = "abc"; ke metode Utama. Karena perbaikan bug kedua, tidak ada peringatan yang harus dilaporkan.
  • Terakhir, tambahkan variabel lokal lain yang menggunakan var kata kunci. Anda akan melihat bahwa peringatan diberikan dan saran muncul di kiri bawah.
  • Pindahkan kursor editor pada garis bawah berlekuk dan tekan Ctrl+.. untuk menampilkan perbaikan kode yang disarankan. Setelah memilih perbaikan kode Anda, perhatikan bahwa var kata kunci sekarang ditangani dengan benar.

Terakhir, tambahkan kode berikut:

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

Setelah perubahan ini, Anda hanya mendapatkan garis keriting merah pada dua variabel pertama. Tambahkan const ke masing-masing i dan j, dan Anda mendapatkan peringatan k baru karena sekarang bisa const.

Selamat! Anda telah membuat ekstensi .NET Compiler Platform pertama yang melakukan analisis kode on-the-fly untuk mendeteksi masalah dan menyediakan perbaikan cepat untuk memperbaikinya. Sepanjang jalan, Anda telah mempelajari banyak API kode yang merupakan bagian dari .NET Compiler Platform SDK (Roslyn API). Anda dapat memeriksa pekerjaan Anda terhadap sampel yang telah selesai di repositori GitHub sampel kami.

Sumber daya lainnya