Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Tutorial ini dibangun berdasarkan konsep dan teknik yang dieksplorasi dalam memulai dengan analisis sintaksis dan panduan cepat analisis semantik. Jika Anda belum melakukannya, Anda harus menyelesaikan panduan memulai cepat sebelum memulai yang satu ini.
Dalam panduan cepat ini, Anda mengeksplorasi teknik untuk membuat dan mengubah pohon sintaks. Dengan menggabungkan teknik yang Anda pelajari dari quickstarts sebelumnya, Anda membuat refaktorisasi baris perintah pertama Anda!
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.
- Jalankan Alat Penginstal Visual Studio
- Pilih Ubah
- Periksa beban kerja pengembangan ekstensi Visual Studio .
- Buka simpul pengembangan ekstensi Visual Studio di pohon ringkasan.
- 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:
- Buka node Komponen individual di pohon ringkasan.
- Centang kotak untuk editor DGML
Menginstal menggunakan Alat Penginstal Visual Studio - tab Komponen Individual
- Jalankan Alat Penginstal Visual Studio
- Pilih Ubah
- Pilih tab Komponen individual
- 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:
- Centang kotak untuk editor DGML. Anda akan menemukannya di bawah bagian Alat kode .
Kekekalan dan platform kompilator .NET
Kekekalan adalah tenet mendasar dari platform kompilator .NET. Struktur data yang tidak dapat diubah tidak dapat diubah setelah dibuat. Struktur data yang tidak dapat diubah dapat dibagikan dengan aman dan dianalisis oleh beberapa konsumen secara bersamaan. Tidak ada bahaya bahwa satu konsumen mempengaruhi konsumen lain dengan cara yang tidak dapat diprediksi. Penganalisis Anda tidak memerlukan kunci atau tindakan konkurensi lainnya. Aturan ini berlaku untuk pohon sintaksis, kompilasi, simbol, model semantik, dan setiap struktur data lainnya yang Anda temui. Alih-alih memodifikasi struktur yang ada, API membuat objek baru berdasarkan perbedaan yang ditentukan dengan yang lama. Anda menerapkan konsep ini ke pohon sintaksis untuk membuat pohon baru menggunakan transformasi.
Membuat dan mengubah pohon
Anda memilih salah satu dari dua strategi untuk transformasi sintaksis. Metode factory paling baik digunakan saat Anda mencari simpul tertentu untuk diganti, atau lokasi tertentu di mana Anda ingin menyisipkan kode baru. Penulis ulang adalah yang terbaik ketika Anda ingin memindai seluruh proyek untuk pola kode yang ingin Anda ganti.
Membuat simpul dengan metode pabrik
Transformasi sintaks pertama menunjukkan metode pabrik. Kau akan mengganti pernyataan using System.Collections; dengan using System.Collections.Generic; pernyataan. Contoh ini menunjukkan cara Anda membuat Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode objek menggunakan Microsoft.CodeAnalysis.CSharp.SyntaxFactory metode pabrik. Untuk setiap jenis node, token, atau trivia, ada metode pabrik yang membuat instans jenis tersebut. Anda membuat pohon sintaksis dengan menyusun simpul secara hierarkis dengan cara bottom-up. Kemudian, Anda akan mengubah program yang ada dengan mengganti simpul yang ada dengan pohon baru yang telah Anda buat.
Mulai Visual Studio, dan buat proyek C# Stand-Alone Code Analysis Tool baru. Di Visual Studio, pilih File>Proyek> untuk menampilkan dialog Proyek Baru. Di bawah Visual C#>Ekstensibilitas pilih Alat Analisis Kode Stand-Alone. Panduan mulai cepat ini memiliki dua contoh proyek, jadi beri nama solusi SyntaxTransformationQuickStart, dan beri nama proyek ConstructionCS. Klik OK.
Proyek ini menggunakan metode kelas Microsoft.CodeAnalysis.CSharp.SyntaxFactory untuk membuat Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax yang mewakili namespace System.Collections.Generic.
Tambahkan direktif berikut using ke bagian Program.csatas .
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static System.Console;
Anda akan membuat simpul sintaks nama untuk membangun pohon yang mewakili using System.Collections.Generic; pernyataan.
NameSyntax adalah kelas dasar untuk empat jenis nama yang muncul di C#. Anda menyusun keempat jenis nama ini bersama-sama untuk membuat nama apa pun yang dapat muncul dalam bahasa C#:
-
Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax, yang mewakili nama pengidentifikasi tunggal sederhana seperti
SystemdanMicrosoft. -
Microsoft.CodeAnalysis.CSharp.Syntax.GenericNameSyntax, yang mewakili jenis generik atau nama metode seperti
List<int>. -
Microsoft.CodeAnalysis.CSharp.Syntax.QualifiedNameSyntax, yang mewakili nama formulir
<left-name>.<right-identifier-or-generic-name>yang memenuhi syarat sepertiSystem.IO. -
Microsoft.CodeAnalysis.CSharp.Syntax.AliasQualifiedNameSyntax, yang mewakili nama dengan menggunakan alias extern assembly seperti
LibraryV2::Foo.
Anda menggunakan IdentifierName(String) metode untuk membuat simpul NameSyntax . Tambahkan kode berikut dalam metode Anda Main di Program.cs:
NameSyntax name = IdentifierName("System");
WriteLine($"\tCreated the identifier {name}");
Kode sebelumnya membuat IdentifierNameSyntax objek dan menetapkannya ke variabel name. Banyak API Roslyn mengembalikan kelas dasar untuk mempermudah bekerja dengan jenis terkait. Variabel name, NameSyntax, dapat digunakan kembali saat Anda membangun QualifiedNameSyntax. Jangan gunakan inferensi jenis saat Anda membuat sampel. Anda akan mengotomatiskan langkah tersebut dalam proyek ini.
Anda telah membuat nama. Sekarang, saatnya untuk menambahkan lebih banyak simpul ke pohon dengan membuat QualifiedNameSyntax. Pohon baru menggunakan name sebagai bagian kiri dari nama, dan IdentifierNameSyntax baru untuk namespace Collections sebagai bagian kanan dari QualifiedNameSyntax. Tambahkan kode berikut ke program.cs:
name = QualifiedName(name, IdentifierName("Collections"));
WriteLine(name.ToString());
Jalankan kode lagi, dan lihat hasilnya. Anda sedang membangun pohon simpul yang mewakili kode. Anda akan melanjutkan pola ini untuk membangun QualifiedNameSyntax untuk namespace System.Collections.Generic. Tambahkan kode berikut ke Program.cs:
name = QualifiedName(name, IdentifierName("Generic"));
WriteLine(name.ToString());
Jalankan program lagi untuk melihat bahwa Anda telah membangun pohon guna menambahkan kode.
Membuat pohon yang dimodifikasi
Anda telah membangun pohon sintaksis kecil yang berisi satu pernyataan. API untuk membuat simpul baru adalah pilihan yang tepat untuk membuat pernyataan tunggal atau blok kode kecil lainnya. Namun, untuk membangun blok kode yang lebih besar, Anda harus menggunakan metode yang menggantikan simpul atau menyisipkan simpul ke dalam pohon yang ada. Ingatlah bahwa pohon sintaksis tidak dapat diubah.
API Sintaks tidak menyediakan mekanisme apa pun untuk memodifikasi pohon sintaksis yang ada setelah konstruksi. Sebaliknya, ini menyediakan metode yang menghasilkan pohon baru berdasarkan perubahan pada yang sudah ada.
With* metode didefinisikan dalam kelas konkret yang berasal dari SyntaxNode atau dalam anggota ekstensi yang dideklarasikan di SyntaxNodeExtensions kelas . Metode ini membuat simpul baru dengan menerapkan perubahan pada properti anak simpul yang ada. Selain itu, ReplaceNode anggota ekstensi dapat digunakan untuk mengganti simpul keturunan dalam subtree. Metode ini juga memperbarui induk untuk menunjuk ke anak yang baru dibuat dan mengulangi proses ini ke seluruh pohon - proses yang dikenal sebagai memutar ulang pohon.
Langkah selanjutnya adalah membuat pohon yang mewakili seluruh program (kecil) lalu memodifikasinya. Tambahkan kode berikut ke awal Program kelas:
private const string sampleCode =
@"using System;
using System.Collections;
using System.Linq;
using System.Text;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello, World!"");
}
}
}";
Nota
Kode contoh menggunakan System.Collections namespace dan bukan System.Collections.Generic namespace.
Selanjutnya, tambahkan kode berikut ke bagian Main bawah metode untuk mengurai teks dan membuat pohon:
SyntaxTree tree = CSharpSyntaxTree.ParseText(sampleCode);
var root = (CompilationUnitSyntax)tree.GetRoot();
Contoh ini menggunakan WithName(NameSyntax) metode untuk mengganti nama dalam simpul UsingDirectiveSyntax dengan yang dibangun dalam kode sebelumnya.
Buat simpul UsingDirectiveSyntax yang baru menggunakan metode WithName(NameSyntax) untuk memperbarui nama System.Collections dengan nama yang Anda buat di kode sebelumnya. Tambahkan kode berikut ke bagian Main bawah metode :
var oldUsing = root.Usings[1];
var newUsing = oldUsing.WithName(name);
WriteLine(root.ToString());
Jalankan program dan lihat dengan cermat outputnya.
newUsing Belum ditempatkan di pohon akar. Pohon asli belum diubah.
Tambahkan kode berikut menggunakan ReplaceNode metode ekstensi untuk membuat pohon baru. Pohon baru adalah hasil dari menggantikan impor yang sudah ada dengan simpul yang diperbarui newUsing. Anda menetapkan pohon baru ini ke variabel root yang sudah ada.
root = root.ReplaceNode(oldUsing, newUsing);
WriteLine(root.ToString());
Jalankan program lagi. Sekarang pohon mengimpor ruang nama System.Collections.Generic dengan benar.
Mengubah pohon menggunakan SyntaxRewriters
Metode With* dan ReplaceNode menyediakan cara yang nyaman untuk mengubah cabang individu dari pohon sintaksis. Kelas Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter melakukan beberapa transformasi pada pohon sintaksis. Kelas Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter ini adalah subkelas dari Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor<TResult>.
CSharpSyntaxRewriter menerapkan transformasi ke jenis SyntaxNode tertentu. Anda dapat menerapkan transformasi ke beberapa jenis SyntaxNode objek di mana pun objek tersebut muncul di pohon sintaksis. Proyek kedua dalam mulai cepat ini membuat refaktor baris perintah yang menghapus jenis eksplisit dalam deklarasi variabel lokal di mana saja inferensi jenis tersebut dapat digunakan.
Buat proyek C# Stand-Alone Code Analysis Tool baru. Di Visual Studio, klik kanan simpul SyntaxTransformationQuickStart solusi. Pilih Tambahkan>Proyek Baru untuk menampilkan dialog Proyek Baru. Di bawah Visual C#>Extensibility, pilih Stand-Alone Code Analysis Tool. Beri nama proyek TransformationCS Anda dan klik OK.
Langkah pertama adalah membuat kelas yang berasal dari CSharpSyntaxRewriter untuk melakukan transformasi Anda. Tambahkan file kelas baru ke proyek. Di Visual Studio, pilihTambahkan Kelas>.... Dalam dialog Tambahkan Item Baru, ketik TypeInferenceRewriter.cs sebagai nama file.
Tambahkan arahan berikut using ke TypeInferenceRewriter.cs file:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Selanjutnya, buat TypeInferenceRewriter kelas memperluas CSharpSyntaxRewriter kelas:
public class TypeInferenceRewriter : CSharpSyntaxRewriter
Tambahkan kode berikut untuk mendeklarasikan bidang yang hanya dapat dibaca privat untuk menyimpan SemanticModel dan menginisialisasinya di dalam konstruktor. Anda akan memerlukan bidang ini nanti untuk menentukan di mana inferensi jenis dapat digunakan:
private readonly SemanticModel SemanticModel;
public TypeInferenceRewriter(SemanticModel semanticModel) => SemanticModel = semanticModel;
Ganti metode VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax).
public override SyntaxNode VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node)
{
}
Nota
Banyak API Roslyn mendeklarasikan jenis pengembalian yang merupakan kelas dasar dari tipe runtime sebenarnya yang dikembalikan. Dalam banyak skenario, satu jenis node dapat digantikan oleh jenis node lain sepenuhnya - atau bahkan dihapus. Dalam contoh ini, metode VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax) mengembalikan SyntaxNode, alih-alih tipe turunan dari LocalDeclarationStatementSyntax. Penulisan ulang ini menghasilkan simpul baru LocalDeclarationStatementSyntax berdasarkan yang sudah ada.
Mulai cepat ini menangani deklarasi variabel lokal. Anda dapat memperluasnya ke deklarasi lain seperti foreach perulangan, for perulangan, ekspresi LINQ, dan ekspresi lambda. Selain itu, penulis ulang ini hanya akan mengubah deklarasi bentuk paling sederhana:
Type variable = expression;
Jika Anda ingin menjelajahi sendiri, pertimbangkan untuk memperluas sampel yang sudah selesai untuk jenis deklarasi variabel ini:
// Multiple variables in a single declaration.
Type variable1 = expression1,
variable2 = expression2;
// No initializer.
Type variable;
Tambahkan kode berikut ke isi VisitLocalDeclarationStatement metode untuk melewati penulisan ulang bentuk deklarasi ini:
if (node.Declaration.Variables.Count > 1)
{
return node;
}
if (node.Declaration.Variables[0].Initializer == null)
{
return node;
}
Metode ini menunjukkan bahwa tidak ada penulisan ulang yang terjadi dengan mengembalikan parameter yang node tidak dimodifikasi. Jika tidak satu pun dari ekspresi if tersebut benar, maka simpul itu mewakili kemungkinan deklarasi dengan inisialisasi. Tambahkan pernyataan ini untuk mengekstrak nama jenis yang ditentukan dalam deklarasi dan mengikatnya menggunakan SemanticModel bidang untuk mendapatkan simbol jenis:
var declarator = node.Declaration.Variables.First();
var variableTypeName = node.Declaration.Type;
var variableType = (ITypeSymbol)SemanticModel
.GetSymbolInfo(variableTypeName)
.Symbol;
Sekarang, tambahkan pernyataan ini untuk mengikat ekspresi inisialisasi:
var initializerInfo = SemanticModel.GetTypeInfo(declarator.Initializer.Value);
Terakhir, tambahkan pernyataan berikut if untuk mengganti nama jenis yang ada dengan kata kunci jika jenis ekspresi inisialisasi cocok dengan var jenis yang ditentukan:
if (SymbolEqualityComparer.Default.Equals(variableType, initializerInfo.Type))
{
TypeSyntax varTypeName = SyntaxFactory.IdentifierName("var")
.WithLeadingTrivia(variableTypeName.GetLeadingTrivia())
.WithTrailingTrivia(variableTypeName.GetTrailingTrivia());
return node.ReplaceNode(variableTypeName, varTypeName);
}
else
{
return node;
}
Kondisional diperlukan karena deklarasi dapat melemparkan ekspresi penginisialisasi ke kelas dasar atau antarmuka. Jika itu diinginkan, jenis di sisi kiri dan kanan dari penugasan tidak cocok. Menghapus jenis eksplisit dalam kasus ini akan mengubah semantik program.
var ditentukan sebagai pengidentifikasi daripada kata kunci karena var merupakan kata kunci kontekstual. Spasi di awal dan akhir (spasi putih) dipindahkan dari nama jenis lama ke kata kunci var untuk mempertahankan spasi putih vertikal dan indentasi. Lebih sederhana menggunakan ReplaceNode untuk mengubah LocalDeclarationStatementSyntax daripada With* karena nama tipe sebenarnya merupakan turunan langsung dari pernyataan deklarasi.
Anda telah menyelesaikan TypeInferenceRewriter. Sekarang kembali ke file Anda Program.cs untuk menyelesaikan contoh. Buat pengujian Compilation dan dapatkan SemanticModel darinya. Gunakan SemanticModel untuk mencoba TypeInferenceRewriter. Anda akan melakukan langkah ini terakhir. Sementara itu, deklarasikan variabel tempat penampung yang mewakili kompilasi pengujian Anda:
Compilation test = CreateTestCompilation();
Setelah menjeda sejenak, Anda akan melihat kesalahan berlekuk muncul melaporkan bahwa tidak ada CreateTestCompilation metode. Tekan Ctrl+Periode untuk membuka bola lampu lalu tekan Enter untuk memanggil perintah Hasilkan Stub Metode . Perintah ini akan menghasilkan stub metode untuk CreateTestCompilation metode di Program kelas . Anda akan kembali untuk mengisi metode ini nanti:
Tulis kode berikut untuk melakukan iterasi pada masing-masing SyntaxTree dalam pengujian Compilation. Untuk masing-masing, inisialisasi TypeInferenceRewriter baru dengan SemanticModel untuk pohon tersebut.
foreach (SyntaxTree sourceTree in test.SyntaxTrees)
{
SemanticModel model = test.GetSemanticModel(sourceTree);
TypeInferenceRewriter rewriter = new TypeInferenceRewriter(model);
SyntaxNode newSource = rewriter.Visit(sourceTree.GetRoot());
if (newSource != sourceTree.GetRoot())
{
File.WriteAllText(sourceTree.FilePath, newSource.ToFullString());
}
}
Di dalam pernyataan yang foreach Anda buat, tambahkan kode berikut untuk melakukan transformasi pada setiap pohon sumber. Kode ini secara kondisional menulis pohon baru yang diubah jika ada pengeditan yang dibuat. Penulis ulang Anda hanya boleh memodifikasi pohon jika menemukan satu atau beberapa deklarasi variabel lokal yang dapat disederhanakan menggunakan inferensi jenis:
SyntaxNode newSource = rewriter.Visit(sourceTree.GetRoot());
if (newSource != sourceTree.GetRoot())
{
File.WriteAllText(sourceTree.FilePath, newSource.ToFullString());
}
Anda seharusnya melihat garis bergelombang di bawah kode File.WriteAllText. Pilih bola lampu, dan tambahkan pernyataan yang diperlukan using System.IO; .
Anda hampir selesai! Ada satu langkah tersisa: membuat pengujian Compilation. Karena Anda belum menggunakan inferensi tipe sama sekali selama panduan cepat ini, akan menjadi kasus pengujian yang sempurna. Sayangnya, membuat Kompilasi dari file proyek C# berada di luar cakupan panduan ini. Tapi untungnya, jika Anda telah mengikuti instruksi dengan hati-hati, ada harapan. Ganti konten CreateTestCompilation metode dengan kode berikut. Ini menghasilkan kompilasi uji yang secara kebetulan cocok dengan proyek yang dijelaskan dalam panduan mulai cepat ini.
String programPath = @"..\..\..\Program.cs";
String programText = File.ReadAllText(programPath);
SyntaxTree programTree =
CSharpSyntaxTree.ParseText(programText)
.WithFilePath(programPath);
String rewriterPath = @"..\..\..\TypeInferenceRewriter.cs";
String rewriterText = File.ReadAllText(rewriterPath);
SyntaxTree rewriterTree =
CSharpSyntaxTree.ParseText(rewriterText)
.WithFilePath(rewriterPath);
SyntaxTree[] sourceTrees = { programTree, rewriterTree };
MetadataReference mscorlib =
MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
MetadataReference codeAnalysis =
MetadataReference.CreateFromFile(typeof(SyntaxTree).Assembly.Location);
MetadataReference csharpCodeAnalysis =
MetadataReference.CreateFromFile(typeof(CSharpSyntaxTree).Assembly.Location);
MetadataReference[] references = { mscorlib, codeAnalysis, csharpCodeAnalysis };
return CSharpCompilation.Create("TransformationCS",
sourceTrees,
references,
new CSharpCompilationOptions(OutputKind.ConsoleApplication));
Silangkan jari-jari Anda dan jalankan proyek. Di Visual Studio, pilih Debug>Mulai Debugging. Anda akan menerima pemberitahuan dari Visual Studio bahwa file di proyek Anda telah berubah. Klik "Ya untuk Semua" untuk memuat ulang file yang dimodifikasi. Perhatikan mereka untuk melihat kehebatanmu. Perhatikan betapa lebih rapi kode terlihat tanpa penentu tipe yang eksplisit dan berlebihan tersebut.
Selamat! Anda telah menggunakan API Compiler untuk menulis refaktorisasi Anda sendiri yang mencari seluruh file dalam proyek C# untuk pola sintaksis tertentu, menganalisis semantik kode sumber yang sesuai dengan pola tersebut, dan mengubahnya. Sekarang Anda resmi menjadi penulis refaktorisasi!