Bagikan melalui


Mulai menggunakan analisis sintaksis

Dalam tutorial ini, Anda akan menjelajahi SINTAKS API. SINTAKS API menyediakan akses ke struktur data yang menjelaskan program C# atau Visual Basic. Struktur data ini memiliki detail yang cukup sehingga dapat sepenuhnya mewakili program apa pun dalam berbagai ukuran. Struktur ini dapat menjelaskan program lengkap yang mengkompilasi dan berjalan dengan benar. Mereka juga dapat menggambarkan program yang tidak lengkap saat sedang ditulis di editor.

Untuk mengaktifkan ekspresi kaya ini, struktur data dan API yang membentuk SINTAKS API selalu kompleks. Mari kita mulai dengan seperti apa struktur data untuk program "Halo Dunia" khas:

using System;
using System.Collections.Generic;
using System.Linq;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

Lihat teks program sebelumnya. Anda mengenali elemen yang akrab. Seluruh teks mewakili satu file sumber, atau unit kompilasi. Tiga baris pertama dari file sumber tersebut menggunakan arahan. Sumber yang tersisa terkandung dalam deklarasi namespace. Deklarasi namespace berisi sebuah deklarasi kelas sebagai elemen turunan. Deklarasi kelas berisi satu deklarasi metode.

SINTAKS API membuat struktur pohon dengan akar yang mewakili unit kompilasi. Simpul di pohon mewakili using arahan, deklarasi namespace, dan semua elemen program lainnya. Struktur pohon berlanjut ke tingkat terendah: string "Hello World!" adalah token harfiah string yang merupakan turunan dari argumen. SINTAKS API menyediakan akses ke struktur program. Anda dapat mengkueri praktik kode tertentu, memanjat seluruh pohon untuk memahami kode, dan membuat pohon baru dengan memodifikasi pohon yang ada.

Deskripsi singkat tersebut memberikan gambaran umum tentang jenis informasi yang dapat diakses menggunakan API Sintaks. API Sintaks tidak lebih dari API formal yang menjelaskan konstruksi kode yang sudah dikenal yang Anda ketahui dari C#. Kemampuan lengkap mencakup informasi tentang bagaimana kode diformat termasuk pemisah baris, spasi kosong, dan inden. Dengan menggunakan informasi ini, Anda dapat sepenuhnya mewakili kode seperti yang ditulis dan dibaca oleh pemrogram manusia atau pengkompilasi. Menggunakan struktur ini memungkinkan Anda berinteraksi dengan kode sumber pada tingkat yang sangat bermakna. Ini bukan lagi string teks, tetapi data yang mewakili struktur program C#.

Untuk memulai, Anda harus menginstal .NET Compiler Platform SDK:

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. Pastikan kotak untuk .NET Compiler Platform SDK dicentang.
  6. Pilih Ubah.

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 .
  5. Pilih Ubah.

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 .

Memahami pohon sintaks

Anda menggunakan SINTAKS API untuk analisis apa pun dari struktur kode C#. API Sintaks mengekspos pengurai, pohon sintaksis, dan utilitas untuk menganalisis dan membangun pohon sintaksis. Ini adalah cara Anda mencari kode untuk elemen sintaks tertentu atau membaca kode untuk program.

Pohon sintaks adalah struktur data yang digunakan oleh pengkompilasi C# dan Visual Basic untuk memahami program C# dan Visual Basic. Pohon sintaks diproduksi oleh parser yang sama yang berjalan ketika sebuah proyek dibangun atau pengembang menekan F5. Pohon sintaks memiliki keakuratan penuh dengan bahasa; setiap bit informasi dalam file kode diwakili di pohon. Menulis pohon sintaks ke teks mereproduksi teks asli yang tepat yang diurai. Pohon sintaksis juga tidak dapat diubah; setelah dibuat, pohon sintaksis tidak pernah dapat diubah. Pengguna pohon dapat menganalisis pohon pada beberapa utas, tanpa penguncian atau langkah-langkah lain yang berkaitan dengan konkurensi, karena data tersebut tidak pernah berubah. Anda dapat menggunakan API untuk membuat pohon baru yang merupakan hasil dari memodifikasi pohon yang ada.

Empat blok penyusun utama pohon sintaksis adalah:

Trivia, token, dan node disusun secara hierarkis untuk membentuk pohon yang sepenuhnya mewakili semuanya dalam fragmen kode Visual Basic atau C#. Anda dapat melihat struktur ini menggunakan jendela Sintaks Visualizer . Di Visual Studio, pilih Lihat>Windows Lainnya>Visualizer Sintaks. Misalnya, file sumber C# sebelumnya yang diperiksa menggunakan Sintaks Visualizer terlihat seperti gambar berikut:

SintaksNode: Biru | SintaksToken: Hijau | SintaksTrivia: File Kode C# Merah

Dengan menavigasi struktur pohon ini, Anda dapat menemukan pernyataan, ekspresi, token, atau sedikit spasi kosong dalam file kode.

Meskipun Anda dapat menemukan apa pun dalam file kode menggunakan API Sintaks, sebagian besar skenario melibatkan pemeriksaan cuplikan kecil kode, atau mencari pernyataan atau fragmen tertentu. Dua contoh berikut menunjukkan penggunaan umum untuk menelusuri struktur kode, atau mencari pernyataan tunggal.

Penelusuran pohon

Anda dapat memeriksa simpul dalam pohon sintaksis dengan dua cara. Anda dapat melintasi pohon untuk memeriksa setiap simpul, atau Anda dapat mengkueri elemen atau simpul tertentu.

Penelusuran Manual

Anda dapat melihat kode yang sudah selesai untuk sampel ini di repositori GitHub kami.

Nota

Jenis Pohon Sintaks menggunakan pewarisan untuk menggambarkan berbagai elemen sintaks yang valid di lokasi yang berbeda dalam program. Menggunakan API ini sering berarti mentransmisikan properti atau anggota koleksi ke jenis turunan tertentu. Dalam contoh berikut, penugasan dan cast adalah pernyataan terpisah yang menggunakan variabel dengan tipe yang ditentukan secara eksplisit. Anda dapat membaca kode untuk melihat jenis pengembalian API dan jenis runtime objek yang dikembalikan. Dalam praktiknya, lebih umum untuk menggunakan variabel yang diketik secara implisit dan mengandalkan nama API untuk menjelaskan jenis objek yang sedang diperiksa.

Buat proyek C# Stand-Alone Code Analysis Tool baru:

  • Di Visual Studio, pilih File>Proyek> untuk menampilkan dialog Proyek Baru.
  • Di bawah Visual C#>Extensibility, pilih Stand-Alone Code Analysis Tool.
  • Beri nama proyek Anda "SintaksTreeManualTraversal" dan klik OK.

Anda akan menganalisis program dasar "Halo Dunia!" yang ditunjukkan sebelumnya. Tambahkan teks untuk program Halo Dunia sebagai konstanta di kelas Anda Program :

        const string programText =
@"using System;
using System.Collections;
using System.Linq;
using System.Text;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(""Hello, World!"");
        }
    }
}";

Selanjutnya, tambahkan kode berikut untuk membangun pohon sintaks untuk teks kode dalam programText konstanta. Tambahkan baris berikut ke metode Anda Main :

SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

Kedua baris itu menciptakan pohon dan mengambil simpul akar pohon itu. Anda sekarang dapat memeriksa simpul di pohon. Tambahkan baris ini ke metode Anda Main untuk menampilkan beberapa properti simpul akar di pohon:

WriteLine($"The tree is a {root.Kind()} node.");
WriteLine($"The tree has {root.Members.Count} elements in it.");
WriteLine($"The tree has {root.Usings.Count} using directives. They are:");
foreach (UsingDirectiveSyntax element in root.Usings)
    WriteLine($"\t{element.Name}");

Jalankan aplikasi untuk melihat apa yang telah ditemukan kode Anda tentang simpul akar di pohon ini.

Biasanya, Anda akan menelusuri pohon untuk memahami kode. Dalam contoh ini, Anda menganalisis kode yang Anda ketahui untuk menjelajahi API. Tambahkan kode berikut untuk memeriksa anggota pertama simpul root :

MemberDeclarationSyntax firstMember = root.Members[0];
WriteLine($"The first member is a {firstMember.Kind()}.");
var helloWorldDeclaration = (NamespaceDeclarationSyntax)firstMember;

Anggota itu adalah Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax. Ini mewakili segala sesuatu dalam cakupan deklarasi namespace HelloWorld. Tambahkan kode berikut untuk memeriksa simpul apa yang dideklarasikan di dalam HelloWorld namespace:

WriteLine($"There are {helloWorldDeclaration.Members.Count} members declared in this namespace.");
WriteLine($"The first member is a {helloWorldDeclaration.Members[0].Kind()}.");

Jalankan program untuk melihat apa yang telah Anda pelajari.

Sekarang setelah Anda tahu deklarasi adalah Microsoft.CodeAnalysis.CSharp.Syntax.ClassDeclarationSyntax, deklarasikan variabel baru dari jenis itu untuk memeriksa deklarasi kelas. Kelas ini hanya berisi satu anggota: Main metode . Tambahkan kode berikut untuk menemukan metode Main dan ubah tipenya ke Microsoft.CodeAnalysis.CSharp.Syntax.MethodDeclarationSyntax.

var programDeclaration = (ClassDeclarationSyntax)helloWorldDeclaration.Members[0];
WriteLine($"There are {programDeclaration.Members.Count} members declared in the {programDeclaration.Identifier} class.");
WriteLine($"The first member is a {programDeclaration.Members[0].Kind()}.");
var mainDeclaration = (MethodDeclarationSyntax)programDeclaration.Members[0];

Node deklarasi metode berisi semua informasi sinactik tentang metode . Mari kita tampilkan jenis Main pengembalian metode, jumlah dan jenis argumen, dan teks isi metode. Tambahkan kode berikut:

WriteLine($"The return type of the {mainDeclaration.Identifier} method is {mainDeclaration.ReturnType}.");
WriteLine($"The method has {mainDeclaration.ParameterList.Parameters.Count} parameters.");
foreach (ParameterSyntax item in mainDeclaration.ParameterList.Parameters)
    WriteLine($"The type of the {item.Identifier} parameter is {item.Type}.");
WriteLine($"The body text of the {mainDeclaration.Identifier} method follows:");
WriteLine(mainDeclaration.Body?.ToFullString());

var argsParameter = mainDeclaration.ParameterList.Parameters[0];

Jalankan program untuk melihat semua informasi yang Anda temukan tentang program ini:

The tree is a CompilationUnit node.
The tree has 1 elements in it.
The tree has 4 using directives. They are:
        System
        System.Collections
        System.Linq
        System.Text
The first member is a NamespaceDeclaration.
There are 1 members declared in this namespace.
The first member is a ClassDeclaration.
There are 1 members declared in the Program class.
The first member is a MethodDeclaration.
The return type of the Main method is void.
The method has 1 parameters.
The type of the args parameter is string[].
The body text of the Main method follows:
        {
            Console.WriteLine("Hello, World!");
        }

Metode kueri

Selain menelusuri pohon, Anda juga dapat menjelajahi pohon sintaksis menggunakan metode kueri yang ditentukan pada Microsoft.CodeAnalysis.SyntaxNode. Metode ini akan langsung dikenali oleh siapa pun yang sudah familiar dengan XPath. Anda dapat menggunakan metode ini dengan LINQ untuk menemukan hal-hal di pohon dengan cepat. memiliki metode kueri seperti DescendantNodes, AncestorsAndSelf, dan ChildNodes pada SyntaxNode.

Anda dapat menggunakan metode kueri tersebut untuk menemukan argumen untuk metode Main sebagai alternatif untuk menavigasi pohon. Tambahkan kode berikut ke bagian bawah metode Anda Main :

var firstParameters = from methodDeclaration in root.DescendantNodes()
                                        .OfType<MethodDeclarationSyntax>()
                      where methodDeclaration.Identifier.ValueText == "Main"
                      select methodDeclaration.ParameterList.Parameters.First();

var argsParameter2 = firstParameters.Single();

WriteLine(argsParameter == argsParameter2);

Pernyataan pertama menggunakan ekspresi LINQ dan DescendantNodes metode untuk menemukan parameter yang sama seperti pada contoh sebelumnya.

Jalankan program, dan Anda dapat melihat bahwa ekspresi LINQ menemukan parameter yang sama dengan menavigasi pohon secara manual.

Sampel menggunakan WriteLine instruksi untuk menampilkan informasi tentang pohon sintaksis saat pohon tersebut dilintasi. Anda juga dapat mempelajari lebih lanjut dengan menjalankan program yang sudah selesai di bawah debugger. Anda dapat memeriksa lebih banyak properti dan metode yang merupakan bagian dari pohon sintaksis yang dibuat untuk program hello world.

Penganalisis Sintaksis

Seringkali Anda ingin menemukan semua simpul jenis tertentu di pohon sintaks, misalnya, setiap deklarasi properti dalam file. Dengan memperluas kelas Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker dan meng-override metode VisitPropertyDeclaration(PropertyDeclarationSyntax), Anda memproses setiap deklarasi properti di pohon sintaks tanpa mengetahui strukturnya terlebih dahulu. CSharpSyntaxWalker adalah jenis CSharpSyntaxVisitor tertentu yang secara rekursif mengunjungi node dan masing-masing anaknya.

Contoh ini mengimplementasikan CSharpSyntaxWalker yang memeriksa pohon sintaksis. Ini mengumpulkan using direktif yang ditemukannya yang tidak mengimpor System namespace.

Buat proyek C# Stand-Alone Code Analysis Tool baru; beri nama "SintaksWalker."

Anda dapat melihat kode yang sudah selesai untuk sampel ini di repositori GitHub kami. Sampel di GitHub berisi kedua proyek yang dijelaskan dalam tutorial ini.

Seperti pada sampel sebelumnya, Anda dapat menentukan konstanta string untuk menahan teks program yang akan Anda analisis:

        const string programText =
@"using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace TopLevel
{
    using Microsoft;
    using System.ComponentModel;

    namespace Child1
    {
        using Microsoft.Win32;
        using System.Runtime.InteropServices;

        class Foo { }
    }

    namespace Child2
    {
        using System.CodeDom;
        using Microsoft.CSharp;

        class Bar { }
    }
}";

Teks sumber ini berisi using direktif yang tersebar di empat lokasi berbeda: tingkat file, di namespace tingkat atas, dan di dua namespace berlapis. Contoh ini menyoroti skenario inti untuk menggunakan CSharpSyntaxWalker kelas untuk mengkueri kode. Akan rumit untuk mengunjungi setiap simpul di pohon sintaks akar untuk menemukan deklarasi 'using'. Sebagai gantinya, Anda membuat kelas turunan dan meng-override metode yang hanya dipanggil ketika simpul saat ini di pohon adalah using arahan. Pengunjung Anda tidak melakukan pekerjaan apa pun pada jenis node lainnya. Metode tunggal ini memeriksa masing-masing arahan using dan membangun kumpulan namespace yang tidak ada di System namespace. Anda membangun CSharpSyntaxWalker yang memeriksa semua using arahan, tetapi hanya memeriksa arahan using.

Sekarang setelah Anda mendefinisikan teks program, Anda perlu membuat SyntaxTree dan mendapatkan akar pohon itu.

SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

Selanjutnya, buat kelas baru. Di Visual Studio, pilih TambahkanItem Baru>. Dalam tipe dialog Tambahkan Item BaruUsingCollector.cs sebagai nama file.

Anda menerapkan fungsi visitor using dalam kelas UsingCollector. Mulailah dengan membuat UsingCollector kelas berasal dari CSharpSyntaxWalker.

class UsingCollector : CSharpSyntaxWalker

Anda memerlukan penyimpanan untuk menyimpan node namespace yang Anda kumpulkan. Deklarasikan properti baca-saja di kelas UsingCollector publik; Anda menggunakan variabel ini untuk menyimpan simpul UsingDirectiveSyntax yang Anda temukan:

public ICollection<UsingDirectiveSyntax> Usings { get; } = new List<UsingDirectiveSyntax>();

Kelas dasar, CSharpSyntaxWalker mengimplementasikan logika untuk mengunjungi setiap simpul di pohon sintaks. Kelas turunan menggantikan metode yang dipanggil untuk simpul spesifik yang Anda minati. Dalam hal ini, Anda tertarik terhadap arahan apa pun using. Itu berarti Anda harus mengambil alih VisitUsingDirective(UsingDirectiveSyntax) metode . Satu argumen untuk metode ini adalah Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax objek. Itu adalah keuntungan penting dari menggunakan pola Visitor: mereka memanggil metode yang dilampaui dengan argumen yang sudah di-cast ke jenis node tertentu. Kelas Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax memiliki Name properti yang menyimpan nama namespace yang sedang diimpor. Ini adalah Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax. Tambahkan kode berikut ke dalam penggantian VisitUsingDirective(UsingDirectiveSyntax) :

public override void VisitUsingDirective(UsingDirectiveSyntax node)
{
    WriteLine($"\tVisitUsingDirective called with {node.Name}.");
    if (node.Name.ToString() != "System" &&
        !node.Name.ToString().StartsWith("System."))
    {
        WriteLine($"\t\tSuccess. Adding {node.Name}.");
        this.Usings.Add(node);
    }
}

Seperti contoh sebelumnya, Anda telah menambahkan berbagai WriteLine pernyataan untuk membantu memahami metode ini. Anda dapat melihat kapan fungsi itu dipanggil, dan argumen apa yang diteruskan kepadanya setiap kali.

Terakhir, Anda perlu menambahkan dua baris kode untuk membuat UsingCollector agar mengunjungi simpul akar dan mengumpulkan semua perintah using. Kemudian, tambahkan perulangan foreach untuk menampilkan semua arahan yang ditemukan pengumpul Anda:

var collector = new UsingCollector();
collector.Visit(root);
foreach (var directive in collector.Usings)
{
    WriteLine(directive.Name);
}

Kompilasi dan jalankan program. Anda akan melihat output berikut:

        VisitUsingDirective called with System.
        VisitUsingDirective called with System.Collections.Generic.
        VisitUsingDirective called with System.Linq.
        VisitUsingDirective called with System.Text.
        VisitUsingDirective called with Microsoft.CodeAnalysis.
                Success. Adding Microsoft.CodeAnalysis.
        VisitUsingDirective called with Microsoft.CodeAnalysis.CSharp.
                Success. Adding Microsoft.CodeAnalysis.CSharp.
        VisitUsingDirective called with Microsoft.
                Success. Adding Microsoft.
        VisitUsingDirective called with System.ComponentModel.
        VisitUsingDirective called with Microsoft.Win32.
                Success. Adding Microsoft.Win32.
        VisitUsingDirective called with System.Runtime.InteropServices.
        VisitUsingDirective called with System.CodeDom.
        VisitUsingDirective called with Microsoft.CSharp.
                Success. Adding Microsoft.CSharp.
Microsoft.CodeAnalysis
Microsoft.CodeAnalysis.CSharp
Microsoft
Microsoft.Win32
Microsoft.CSharp
Press any key to continue . . .

Selamat! Anda telah menggunakan SINTAKS API untuk menemukan jenis arahan dan deklarasi tertentu dalam kode sumber C#.