Membuat penganalisis Roslyn menggunakan pengujian enkapsulasi sebagai contoh

Apa itu Roslyn?


Roslyn adalah seperangkat kompiler sumber terbuka dan API analisis kode untuk bahasa C # dan VisualBasic .NET dari Microsoft.


Penganalisa Roslyn adalah alat yang ampuh untuk menganalisis kode, menemukan kesalahan, dan memperbaikinya.


Sintaksis pohon dan model semantik


Untuk menganalisis kode, Anda harus memiliki pemahaman tentang pohon sintaks dan model semantik, karena ini adalah dua komponen utama untuk analisis statis.


Sintaksis pohon adalah elemen yang dibangun berdasarkan kode sumber program, dan diperlukan untuk analisis kode. Selama analisis kode, itu bergerak sepanjang itu.


Setiap kode memiliki struktur sintaksis. Untuk objek kelas berikutnya


class A { void Method() { } } 

pohon sintaks akan terlihat seperti ini:


Pohon


Objek bertipe SyntaxTree adalah pohon sintaksis. Tiga elemen utama dapat dibedakan di pohon: SyntaxNodes, SyntaxTokens, SyntaxTrivia.


Syntaxnodes menggambarkan konstruksi sintaks, yaitu deklarasi, operator, ekspresi, dll. Dalam C #, konstruksi sintaksis mewakili kelas tipe SyntaxNode.


Syntaxtokens menjelaskan elemen-elemen seperti: pengidentifikasi, kata kunci, karakter khusus. Dalam C #, ini adalah jenis kelas SyntaxToken.


Syntaxtrivia menjelaskan elemen yang tidak akan dikompilasi, yaitu spasi, umpan baris, komentar, arahan preprosesor. Dalam C #, itu didefinisikan oleh kelas tipe SyntaxTrivia.


Model semantik merepresentasikan informasi tentang objek dan tipenya. Berkat alat ini, Anda dapat melakukan analisis yang mendalam dan kompleks. Dalam C #, itu didefinisikan oleh kelas tipe SemanticModel.


Membuat penganalisis


Untuk membuat analisa statis, Anda perlu menginstal komponen .NETCompilerPlatformSDK berikut.


Fungsi utama yang membentuk alat analisis apa pun meliputi:


  1. Registrasi tindakan.
    Tindakan adalah perubahan kode yang harus dimulai oleh penganalisa untuk memeriksa kode untuk pelanggaran. Ketika VisualStudio mendeteksi perubahan kode yang sesuai dengan tindakan terdaftar, itu memanggil metode penganalisa terdaftar.
  2. Buat diagnostik.
    Ketika pelanggaran terdeteksi, penganalisa membuat objek diagnostik yang digunakan oleh VisualStudio untuk memberi tahu pengguna pelanggaran.

Ada beberapa langkah untuk membuat dan menguji penganalisis:


  1. Buat solusi.
  2. Daftarkan nama dan deskripsi penganalisa.
  3. Peringatan dan rekomendasi penganalisa laporan.
  4. Lakukan perbaikan kode untuk menerima rekomendasi.
  5. Meningkatkan analisis dengan unit test.

Tindakan dicatat dalam menggantikan metode DiagnosticAnalyzer.Initialize (AnalysisContext), di mana AnalysisContext adalah metode di mana pencarian untuk objek yang dianalisis diperbaiki.


Penganalisa dapat memberikan satu atau lebih koreksi kode. Patch kode mengidentifikasi perubahan yang mengatasi masalah yang dilaporkan. Pengguna memilih perubahan dari antarmuka pengguna (bola lampu di editor), dan VisualStudio mengubah kode. Metode RegisterCodeFixesAsync menjelaskan cara mengubah kode.


Contoh


Sebagai contoh, kami akan menulis penganalisis bidang publik. Aplikasi ini harus memperingatkan pengguna tentang bidang publik dan menyediakan kemampuan untuk merangkum bidang dengan properti.


Inilah yang harus Anda dapatkan:


contoh kerja


Mari kita cari tahu apa yang perlu dilakukan untuk ini.


Pertama, Anda perlu membuat solusi.


pengambilan keputusan


Setelah menciptakan solusi, kami melihat bahwa sudah ada tiga proyek.


pohon keputusan


Kami membutuhkan dua kelas:


1) Class AnalyzerPublicFieldsAnalyzer, di mana kami menentukan kriteria untuk menganalisis kode untuk menemukan bidang publik dan deskripsi peringatan untuk pengguna.


Kami menunjukkan properti berikut:


 public const string DiagnosticId = "PublicField"; private const string Title = "Filed is public"; private const string MessageFormat = "Field '{0}' is public"; private const string Category = "Syntax"; private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } } 

Setelah itu, kami menunjukkan dengan kriteria apa analisis bidang publik akan berlangsung.


 private static void AnalyzeSymbol(SymbolAnalysisContext context) { var fieldSymbol = context.Symbol as IFieldSymbol; if (fieldSymbol != null && fieldSymbol.DeclaredAccessibility == Accessibility.Public && !fieldSymbol.IsConst && !fieldSymbol.IsAbstract && !fieldSymbol.IsStatic && !fieldSymbol.IsVirtual && !fieldSymbol.IsOverride && !fieldSymbol.IsReadOnly && !fieldSymbol.IsSealed && !fieldSymbol.IsExtern) { var diagnostic = Diagnostic.Create(Rule, fieldSymbol.Locations[0], fieldSymbol.Name); context.ReportDiagnostic(diagnostic); } } 

Kami mendapatkan bidang objek tipe IFieldSymbol, yang memiliki properti untuk mendefinisikan pengubah bidang, nama dan lokasinya. Apa yang kita butuhkan untuk diagnosis.


Masih menginisialisasi penganalisis dengan menentukan dalam metode yang diganti


 public override void Initialize(AnalysisContext context) { context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Field); } 

2) Sekarang kami melanjutkan untuk mengubah kode yang diusulkan oleh pengguna berdasarkan analisis kode. Ini terjadi di kelas AnalyzerPublicFieldsCodeFixProvider.


Untuk melakukan ini, tunjukkan hal berikut:


 private const string title = "Encapsulate field"; public sealed override ImmutableArray<string> FixableDiagnosticIds { get { return ImmutableArray.Create(AnalyzerPublicFieldsAnalyzer.DiagnosticId); } } public sealed override FixAllProvider GetFixAllProvider() { return WellKnownFixAllProviders.BatchFixer; } public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken) .ConfigureAwait(false); var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var initialToken = root.FindToken(diagnosticSpan.Start); context.RegisterCodeFix( CodeAction.Create(title, c => EncapsulateFieldAsync(context.Document, initialToken, c), AnalyzerPublicFieldsAnalyzer.DiagnosticId), diagnostic); } 

Dan kami menentukan kemampuan untuk merangkum bidang dengan properti dalam metode EncapsulateFieldAsync.


 private async Task<Document> EncapsulateFieldAsync(Document document, SyntaxToken declaration, CancellationToken cancellationToken) { var field = FindAncestorOfType<FieldDeclarationSyntax>(declaration.Parent); var fieldType = field.Declaration.Type; ChangeNameFieldAndNameProperty(declaration.ValueText, out string fieldName, out string propertyName); var fieldDeclaration = CreateFieldDecaration(fieldName, fieldType); var propertyDeclaration = CreatePropertyDecaration(fieldName, propertyName, fieldType); var root = await document.GetSyntaxRootAsync(); var newRoot = root.ReplaceNode(field, new List<SyntaxNode> { fieldDeclaration, propertyDeclaration }); var newDocument = document.WithSyntaxRoot(newRoot); return newDocument; } 

Untuk melakukan ini, buat bidang pribadi.


 private FieldDeclarationSyntax CreateFieldDecaration(string fieldName, TypeSyntax fieldType) { var variableDeclarationField = SyntaxFactory.VariableDeclaration(fieldType) .AddVariables(SyntaxFactory.VariableDeclarator(fieldName)); return SyntaxFactory.FieldDeclaration(variableDeclarationField) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); } 

Kemudian buat properti publik yang mengembalikan dan menerima bidang pribadi ini.


 private PropertyDeclarationSyntax CreatePropertyDecaration(string fieldName, string propertyName, TypeSyntax propertyType) { var syntaxGet = SyntaxFactory.ParseStatement($"return {fieldName};"); var syntaxSet = SyntaxFactory.ParseStatement($"{fieldName} = value;"); return SyntaxFactory.PropertyDeclaration(propertyType, propertyName) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)) .AddAccessorListAccessors( SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithBody(SyntaxFactory.Block(syntaxGet)), SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithBody(SyntaxFactory.Block(syntaxSet))); } 

Pada saat yang sama, kami menyimpan jenis dan nama bidang sumber. Nama bidang dikonstruksi sebagai berikut "_name", dan nama properti "Name".


Referensi


  1. Sumber GitHub
  2. Platform .NET Compiler Platform SDK

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


All Articles