Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
.NET Derleyici Platformu SDK'sı, C# veya Visual Basic kodunu hedefleyen özel tanılamalar (çözümleyiciler), kod düzeltmeleri, kod yeniden düzenleme ve tanılama engelleyicileri oluşturmak için ihtiyacınız olan araçları sağlar. Çözümleyici, kuralınızın ihlallerini tanıyan kod içerir. Kod düzeltmeniz, ihlali düzelten kodu içerir. Uyguladığınız kurallar, kod yapısından kodlama stiline, adlandırma kurallarına ve daha fazlasına kadar her şey olabilir. .NET Derleyici Platformu, geliştiriciler kod yazarken çözümlemeyi çalıştırmak için bir çerçeve sağlar ve tüm Visual Studio kullanıcı arabirimi özelliklerini kodu düzeltmek için sunar: düzenleyicide dalgalı çizgiler gösterir, Visual Studio Hata Listesini doldurur, "ampul" önerileri oluşturur ve önerilen düzeltmelerin zengin bir önizlemesini gösterir.
Bu öğreticide, Roslyn API'lerini kullanarak bir çözümleyici ve eşlik eden kod düzeltmesi oluşturmayı keşfedeceksiniz. Çözümleyici, kaynak kod analizi gerçekleştirmenin ve kullanıcıya bir sorun bildirmenin bir yoludur. İsteğe bağlı olarak, kullanıcının kaynak kodunda yapılan bir değişikliği temsil etmek için çözümleyici ile bir kod düzeltmesi ilişkilendirilebilir. Bu öğretici, const değiştiricisi kullanılabilecek ama kullanılmamış yerel değişken bildirimlerini bulan bir çözümleyici oluşturur. Eşlik eden kod düzeltmesi, bu bildirimlere const değiştiricisini eklemek için onları değiştirir.
Önkoşullar
- Visual Studio 2019 sürüm 16.8 veya üzeri
Visual Studio Yükleyicisi aracılığıyla .NET Derleyici Platformu SDK'sını yüklemeniz gerekir:
Yükleme yönergeleri - Visual Studio Yükleyicisi
Visual Studio Yükleyicisi'nde.NET Derleyici Platformu SDK'sını bulmanın iki farklı yolu vardır:
Visual Studio Yükleyicisi - İş Yükleri görünümünü kullanarak yükleme
.NET Derleyici Platformu SDK'sı, Visual Studio uzantısı geliştirme iş yükünün bir parçası olarak otomatik olarak seçilmez. İsteğe bağlı bir bileşen olarak seçmelisiniz.
- Visual Studio Yükleyicisi'nin çalıştırılması
- Değiştir'i seçin
- Visual Studio uzantısı geliştirme iş yükünü denetleyin.
- Özet ağacında Visual Studio uzantısı geliştirme düğümünü açın.
- .NET Derleyici Platformu SDK'sı kutusunu işaretleyin. Bunu isteğe bağlı bileşenler altında son olarak bulacaksınız.
İsteğe bağlı olarak , DGML düzenleyicisinin görselleştiricide grafikleri görüntülemesini de istersiniz:
- Özet ağacında Tek tek bileşenler düğümünü açın.
- DGML düzenleyicisi kutusunu işaretleyin
Visual Studio Yükleyicisi - Tek bileşenler sekmesini kullanarak yükleme
- Visual Studio Yükleyicisi'nin çalıştırılması
- Değiştir'i seçin
- Tek tek bileşenler sekmesini seçin
- .NET Derleyici Platformu SDK'sı kutusunu işaretleyin. Derleyiciler, derleme araçları ve çalışma zamanları bölümünün altında en üstte bulabilirsiniz.
İsteğe bağlı olarak , DGML düzenleyicisinin görselleştiricide grafikleri görüntülemesini de istersiniz:
- DGML düzenleyicisi kutusunu işaretleyin. Kod araçları bölümünde bulabilirsiniz.
Çözümleyicinizi oluşturmak ve doğrulamak için birkaç adım vardır:
- Çözümü oluşturun.
- Çözümleyici adını ve açıklamasını kaydedin.
- Rapor çözümleyicisi uyarıları ve önerileri.
- Önerileri kabul etmek için kod düzeltmesini uygulayın.
- Birim testleri aracılığıyla analizi geliştirin.
Çözümü oluşturma
- Visual Studio'da Dosya Yeni > Proje... öğesini seçerek > Yeni Proje iletişim kutusunu görüntüleyin.
- Visual C# > Genişletilebilirliği'nin altında Kod düzeltmesi (.NET Standard) ile Çözümleyici'yi seçin.
- Projenize "MakeConst" adını verin ve Tamam'a tıklayın.
Uyarı
Derleme hatası alabilirsiniz (MSB4062: "CompareBuildTaskVersion" görevi yüklenemedi"). Bunu düzeltmek için çözümdeki NuGet paketlerini NuGet Paket Yöneticisi ile güncelleştirin veya Paket Yöneticisi Konsolu penceresinde kullanın Update-Package .
Çözümleyici şablonunu keşfetme
Kod düzeltme şablonuna sahip çözümleyici beş proje oluşturur:
- Çözümleyiciyi içeren MakeConst.
- Kod düzeltmesini içeren MakeConst.CodeFixes.
- Çözümleyici ve kod düzeltmesi için NuGet paketi üretmek için kullanılan MakeConst.Package.
- Birim testi projesi olan MakeConst.Test.
- Yeni çözümleyicinizi yükleyen ikinci bir Visual Studio örneğini başlatan varsayılan başlangıç projesi olan MakeConst.Vsix. VSIX projesini başlatmak için F5 tuşuna basın.
Uyarı
Çözümleyiciler .NET Core ortamında (komut satırı derlemeleri) ve .NET Framework ortamında (Visual Studio) çalışabildiğinden .NET Standard 2.0'ı hedeflemelidir.
Tavsiye
Çözümleyicinizi çalıştırdığınızda, Visual Studio'nun ikinci bir kopyasını başlatırsınız. Bu ikinci kopya, ayarları depolamak için farklı bir kayıt defteri kovanı kullanır. Bu, Visual Studio'nun iki kopyasındaki görsel ayarlarını ayırt etmenizi sağlar. Visual Studio'nun deneysel çalışması için farklı bir tema seçebilirsiniz. Ayrıca, Visual Studio'nun deneysel çalıştırmasını kullanarak ayarlarınızı dolaşmayın veya Visual Studio hesabınızda oturum açmayın. Bu, ayarları farklı tutar.
Kovan yalnızca geliştirme aşamasında olan çözümleyiciyi değil, aynı zamanda açılmış olan önceki çözümleyicileri de içerir. Roslyn hive'ı sıfırlamak için%LocalAppData% \Microsoft\VisualStudio'dan el ile silmeniz gerekir. Roslyn kovanının klasör adı ile biter Roslyn; örneğin, 16.0_9ae182f9Roslyn. Kovanı sildikten sonra çözümü temizlemeniz ve yeniden oluşturmanız gerekebileceğini unutmayın.
Yeni başlattığınız ikinci Visual Studio örneğinde yeni bir C# Konsol Uygulaması projesi oluşturun (herhangi bir hedef çerçeve çalışır; çözümleyiciler kaynak düzeyde çalışır.) Dalgalı alt çizgiyle belirtecin üzerine gelin ve çözümleyici tarafından sağlanan uyarı metni görüntülenir.
Şablon, aşağıdaki şekilde gösterildiği gibi, tür adının küçük harfler içerdiği her tür bildiriminde bir uyarı bildiren bir çözümleyici oluşturur:
Şablon ayrıca küçük harf karakterlerini içeren herhangi bir tür adını tüm büyük harflerle değiştiren bir kod düzeltmesi sağlar. Önerilen değişiklikleri görmek için uyarıyla birlikte görüntülenen ampule tıklayabilirsiniz. Önerilen değişiklikleri kabul etmek, tür adını ve çözümdeki bu türe yapılan tüm başvuruları güncelleştirir. İlk çözümleyiciyi çalışırken gördüğünüze göre, ikinci Visual Studio örneğini kapatın ve çözümleyici projenize dönün.
Çözümleyicinizdeki her değişikliği test etmek için Visual Studio'nun ikinci bir kopyasını başlatmanız ve yeni kod oluşturmanız gerekmez. Şablon ayrıca sizin için bir birim testi projesi oluşturur. Bu proje iki test içerir.
TestMethod1 , tanılama tetiklemeden kodu analiz eden bir testin tipik biçimini gösterir.
TestMethod2 tanılamayı tetikleyen ve ardından önerilen kod düzeltmesini uygulayan testin biçimini gösterir. Çözümleyicinizi ve kod düzeltmenizi oluştururken, çalışmanızı doğrulamak için farklı kod yapıları için testler yazacaksınız. Çözümleyiciler için birim testleri, Visual Studio ile etkileşimli olarak test etmekten çok daha hızlıdır.
Tavsiye
Çözümleyici birim testleri, çözümleyicinizi tetikleyen ve tetiklememesi gereken kod yapılarını bildiğinizde harika bir araçtır. Çözümleyicinizi Visual Studio'nun başka bir kopyasına yüklemek, henüz düşünmemiş olabileceğiniz yapıları keşfetmek ve bulmak için harika bir araçtır.
Bu öğreticide, kullanıcıya yerel sabitlere dönüştürülebilecek tüm yerel değişken bildirimlerini bildiren bir çözümleyici yazacaksınız. Örneğin, aşağıdaki kodu göz önünde bulundurun:
int x = 0;
Console.WriteLine(x);
Yukarıdaki kodda sabit x bir değer atanır ve hiçbir zaman değiştirilmez. Değiştirici kullanılarak const bildirilebilir:
const int x = 0;
Console.WriteLine(x);
Değişkenin sabit yapılıp yapılamayacağını saptamak için yapılan analiz söz dizimsel analiz, başlatıcı ifadesinin sabit analizi ve değişkenin hiçbir zaman yazılmamasını sağlamak için veri akışı analizi gerektirir. .NET Derleyici Platformu, bu çözümlemeyi gerçekleştirmeyi kolaylaştıran API'ler sağlar.
Çözümleyici kayıtları oluşturun
Şablon, DiagnosticAnalyzer dosyasında ilk sınıfı oluşturur. Bu ilk çözümleyici, her çözümleyicinin iki önemli özelliğini gösterir.
- Her tanılama çözümleyicisi, üzerinde çalıştığı dili açıklayan bir
[DiagnosticAnalyzer]öznitelik sağlamalıdır. - Her tanılama çözümleyicisi, doğrudan veya dolaylı olarak DiagnosticAnalyzer sınıfından türetilmelidir.
Şablon ayrıca herhangi bir çözümleyicinin parçası olan temel özellikleri de gösterir:
- Eylemleri kaydetme. Eylemler, kod ihlallerini incelemek için çözümleyicinizi tetikleyecek kod değişikliklerini temsil eder. Visual Studio, kayıtlı bir eylemle eşleşen kod düzenlemelerini algıladığında çözümleyicinizin kayıtlı yöntemini çağırır.
- Tanılama oluşturun. Çözümleyiciniz bir ihlal algıladığında, Visual Studio'nun kullanıcıya ihlali bildirmek için kullandığı bir tanılama nesnesi oluşturur.
DiagnosticAnalyzer.Initialize(AnalysisContext) yöntemini geçersiz kıldığınızda eylemleri kaydedersiniz. Bu öğreticide, yerel bildirimleri arayan söz dizimi düğümlerini ziyaret edin ve hangilerinin sabit değerlere sahip olduğunu göreceksiniz. Bir bildirim sabit olabilirse çözümleyiciniz bir tanılama oluşturur ve bildirir.
İlk adım, kayıt sabitlerini ve Initialize yöntemini güncelleştirerek bu sabitlerin "Make Const" çözümleyicinizi belirtmesini sağlamaktır. Dize sabitlerinin çoğu dize kaynak dosyasında tanımlanır. Daha kolay yerelleştirme için bu uygulamayı izlemeniz gerekir.
MakeConst çözümleyici projesi için Resources.resx dosyasını açın. Bu, kaynak düzenleyicisini görüntüler. Dize kaynaklarını aşağıdaki gibi güncelleştirin:
-
AnalyzerDescriptionöğesini "Variables that are not modified should be made constants." olarak değiştirin. -
AnalyzerMessageFormatöğesini "Variable '{0}' can be made constant" olarak değiştirin. -
AnalyzerTitleöğesini "Variable can be made constant" olarak değiştirin.
İşiniz bittiğinde, kaynak düzenleyicisi aşağıdaki şekilde gösterildiği gibi görünmelidir:
Kalan değişiklikler çözümleyici dosyasındadır. Visual Studio'da MakeConstAnalyzer.cs açın. Kayıtlı eylemi, simgeler üzerinde hareket eden eylemden söz dizimine göre hareket eden eyleme değiştirin. yönteminde MakeConstAnalyzerAnalyzer.Initialize , eylemi simgelere kaydeden satırı bulun:
context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);
Şunu aşağıdaki satırla değiştirin:
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.LocalDeclarationStatement);
Bu değişiklik sonrasında yöntemini silebilirsiniz AnalyzeSymbol . Bu çözümleyici, SyntaxKind.LocalDeclarationStatement değil SymbolKind.NamedType ifadelerini inceler.
AnalyzeNode üzerinde kırmızı dalgalı çizgiler olduğunu fark edin. Az önce eklediğiniz kod bildirilmemiş bir AnalyzeNode yönteme başvurur. Aşağıdaki kodu kullanarak bu yöntemi bildirin:
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
}
Category aşağıdaki kodda gösterildiği gibi Usage içinde öğesini "" olarak değiştirin:
private const string Category = "Usage";
Sabit olabilecek yerel bildirimleri bulma
Artık AnalyzeNode yönteminin ilk sürümünü yazma zamanı geldi. Tek bir yerel bildirim aramalıdır, const gibi görünen ancak aslında olmayan, tıpkı aşağıdaki kodda olduğu gibi.
int x = 0;
Console.WriteLine(x);
İlk adım yerel bildirimleri bulmaktır. Aşağıdaki kodu AnalyzeNode içine MakeConstAnalyzer.cs ekleyin.
var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;
Analiz aracınız yerel bildirimler ve yalnızca yerel bildirimlerdeki değişiklikler için kaydolduğundan, bu tip dönüştürme her zaman başarılı olur. Yalnızca bu düğüm türü AnalyzeNode yönteminize çağrı tetikler. Ardından, bildirgede herhangi bir const değiştirici olup olmadığını kontrol edin. Onları bulursanız hemen geri dönün. Aşağıdaki kod, yerel bildirimdeki herhangi bir const değiştiricisini arar.
// make sure the declaration isn't already const:
if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
{
return;
}
Son olarak, değişkenin const olabileceğini kontrol etmeniz gerekir. Bu, başlatıldıktan sonra yeniden atanmadığından emin olmak anlamına gelir.
SyntaxNodeAnalysisContext kullanarak bazı anlamsal analizler gerçekleştireceksiniz. Yerel değişken bildiriminin yapılabilir olup olmadığını belirlemek için context bağımsız değişkenini kullanırsınız. A Microsoft.CodeAnalysis.SemanticModel , tek bir kaynak dosyadaki tüm anlamsal bilgileri temsil eder.
Anlamsal modelleri kapsayan makalede daha fazla bilgi edinebilirsiniz. Veri akışı analizini gerçekleştirmek amacıyla yerel bildirim deyiminde araç olarak Microsoft.CodeAnalysis.SemanticModel öğesini kullanacaksınız. Ardından, yerel değişkenin başka herhangi bir yerde yeni bir değerle yazılmamasını sağlamak için bu veri akışı analizinin sonuçlarını kullanırsınız.
GetDeclaredSymbol uzantı üyesini çağırarak değişken için ILocalSymbol öğesini alın ve veri akışı analizi koleksiyonunda DataFlowAnalysis.WrittenOutside öğesinin yer almadığını kontrol edin. Yönteminin sonuna AnalyzeNode aşağıdaki kodu ekleyin:
// 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;
}
Yeni eklenen kod değişkenin değiştirilmemesini sağlar ve bu nedenle yapılabilir const. Tanılamayı yükseltme zamanı geldi.
AnalyzeNode içerisine son satır olarak aşağıdaki kodu ekleyin.
context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation(), localDeclaration.Declaration.Variables.First().Identifier.ValueText));
Çözümleyicinizi çalıştırmak için F5 tuşuna basarak ilerlemenizi de kontrol edebilirsiniz. Daha önce oluşturduğunuz konsol uygulamasını yükleyebilir ve ardından aşağıdaki test kodunu ekleyebilirsiniz:
int x = 0;
Console.WriteLine(x);
Ampul görünmelidir ve çözümleyiciniz bir tanılama bildirmelidir. Ancak, Visual Studio sürümünüze bağlı olarak şunları görürsünüz:
- Şablon tarafından oluşturulan kod onarımını kullanmaya devam eden ampul, bunun büyük harfe dönüştürülebileceğini size belirtir.
- Düzenleyicinin üst kısmında yer alan bir başlık iletisi, 'MakeConstCodeFixProvider'ın bir hatayla karşılaştığını ve devre dışı bırakıldığını belirtmektedir. Bunun nedeni, kod düzeltme sağlayıcısının henüz değiştirilmemiş olması ve hala
TypeDeclarationSyntaxöğeleri yerineLocalDeclarationStatementSyntaxöğelerini bulmayı beklemesidir.
Sonraki bölümde kod düzeltmesinin nasıl yazılır açıklanmaktadır.
Kod düzeltmesini yazma
Çözümleyici bir veya daha fazla kod düzeltmesi sağlayabilir. Kod düzeltmesi, bildirilen sorunu gideren bir düzenleme tanımlar. Oluşturduğunuz çözümleyici için const anahtar sözcüğünü ekleyen bir kod düzeltmesi sağlayabilirsiniz:
- int x = 0;
+ const int x = 0;
Console.WriteLine(x);
Kullanıcı düzenleyicideki ampul kullanıcı arabiriminden seçer ve Visual Studio kodu değiştirir.
CodeFixResources.resx dosyasını açın ve "CodeFixTitle" olarak değiştirinMake constant.
Şablon tarafından eklenen MakeConstCodeFixProvider.cs dosyasını açın. Bu kod düzeltmesi zaten tanılama çözümleyiciniz tarafından üretilen Tanılama Kimliğine bağlı ancak henüz doğru kod dönüşümünü uygulamaz.
Ardından yöntem MakeUppercaseAsync silin. Artık geçerli değildir.
Tüm kod düzeltme sağlayıcılarından CodeFixProvider türetilir. Bunların tümü kullanılabilir kod düzeltmelerini bildirmek için geçersiz kılar CodeFixProvider.RegisterCodeFixesAsync(CodeFixContext) . içinde RegisterCodeFixesAsync, aradığınız alt düğüm türünü tanılamayla eşleşecek şekilde değiştirin LocalDeclarationStatementSyntax :
var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<LocalDeclarationStatementSyntax>().First();
Ardından, bir kod düzeltmesi kaydetmek için son satırı değiştirin. Düzeltmeniz, var olan bir bildirime const değiştiricisini eklemekle elde edilen yeni bir doküman oluşturur:
// 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);
Yeni eklediğiniz kodda simge MakeConstAsync üzerinde kırmızı dalgalı çizgiler göreceksiniz. Aşağıdaki kodda gösterildiği gibi MakeConstAsync için bir deklarasyon ekleyin:
private static async Task<Document> MakeConstAsync(Document document,
LocalDeclarationStatementSyntax localDeclaration,
CancellationToken cancellationToken)
{
}
Yeni MakeConstAsync yönteminiz, kullanıcının kaynak dosyasını temsil eden Document yeni Document bir const bildirim içeren dosyaya dönüştürür.
Bildirim deyiminin önüne eklemek için yeni const bir anahtar sözcük belirteci oluşturursunuz. Önce bildirim deyiminin ilk belirtecinden baştaki gereksiz bölümleri çıkarın ve bunları const belirtecine ekleyin.
MakeConstAsync yöntemine aşağıdaki kodu ekleyin:
// 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));
Ardından, aşağıdaki kodu kullanarak belirteci bildirime ekleyin const :
// 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);
Ardından, yeni bildirimi C# biçimlendirme kurallarıyla eşleşecek şekilde biçimlendirin. Değişikliklerinizi mevcut kodla eşleşecek şekilde biçimlendirmek daha iyi bir deneyim oluşturur. Mevcut kodun hemen arkasına aşağıdaki deyimi ekleyin:
// Add an annotation to format the new local declaration.
LocalDeclarationStatementSyntax formattedLocal = newLocal.WithAdditionalAnnotations(Formatter.Annotation);
Bu kod için yeni bir ad alanı gereklidir. Dosyanın en üstüne aşağıdaki using yönergeyi ekleyin:
using Microsoft.CodeAnalysis.Formatting;
Son adım, düzenlemenizi yapmaktır. Bu işlemin üç adımı vardır:
- Var olan belgenin tutamacını alın.
- Var olan bildirimi yeni bildirimle değiştirerek yeni bir belge oluşturun.
- Yeni belgeyi iade edin.
Yönteminin sonuna MakeConstAsync aşağıdaki kodu ekleyin:
// 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);
Kod düzeltmeniz denenmeye hazır. Çözümleyici projesini Visual Studio'nun ikinci bir örneğinde çalıştırmak için F5 tuşuna basın. İkinci Visual Studio örneğinde yeni bir C# Konsol Uygulaması projesi oluşturun ve Main yöntemine sabit değerlerle başlatılan birkaç yerel değişken bildirimi ekleyin. Bunların aşağıdaki gibi uyarı olarak bildirildiğini göreceksiniz.
Çok ilerleme kaydettin. Bildirimler yapılabilir const olanların altında dalgalı çizgiler vardır. Ama hala yapılacak iş var. Bu, const ile başlayan bildirimlere önce i, sonra j ve son olarak k eklerseniz düzgün çalışır. Ancak, değiştiriciyi const ile başlayarak k şeklinde farklı bir sırada eklerseniz, çözümleyiciniz hatalar oluşturur: kconst ve i ikisi de zaten j değilse const bildirilemez. Değişkenlerin bildirilebileceği ve başlatılabildiği farklı yöntemleri işleyebileceğinizden emin olmak için daha fazla analiz yapmanız gerekir.
Birim testleri oluştur
Çözümleyiciniz ve kod düzeltmeniz, const yapılabilen tek bir bildirimin basit bir durumu üzerinde çalışmaktadır. Bu uygulamanın hata yaptığı çok sayıda olası bildirim deyimi vardır. Şablon tarafından yazılan birim testi kitaplığıyla çalışarak bu durumlara değineceksiniz. Visual Studio'nun ikinci bir kopyasını tekrar tekrar açmaktan çok daha hızlıdır.
Birim testi projesinde MakeConstUnitTests.cs dosyasını açın. Şablon, çözümleyici ve kod düzeltme birim testi için iki yaygın deseni izleyen iki test oluşturdu.
TestMethod1 , çözümleyicinin bildirmemesi gereken bir tanılamayı bildirmemesini sağlayan bir testin desenini gösterir.
TestMethod2 bir tanılama raporunu raporlama ve kod düzeltmesini çalıştırma desenini gösterir.
Şablon, birim testi için Microsoft.CodeAnalysis.Testing paketlerini kullanır.
Tavsiye
Test kitaplığı, aşağıdakiler de dahil olmak üzere özel bir işaretleme söz dizimini destekler:
-
[|text|]:textiçin bir tanılama bildirildiğini gösterir. Varsayılan olarak, bu form yalnızca tam olarakDiagnosticDescriptortarafındanDiagnosticAnalyzer.SupportedDiagnosticssağlanan çözümleyicileri test etmek için kullanılabilir. -
{|ExpectedDiagnosticId:text|}: IdExpectedDiagnosticIdiletextiçin bir tanılamanın rapor edildiğini gösterir.
sınıfındaki şablon testlerini MakeConstUnitTest aşağıdaki test yöntemiyle değiştirin:
[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);
}
}
");
}
Başarılı olduğundan emin olmak için bu testi çalıştırın. Visual Studio'da TestWindows>seçerek>Test Gezgini'ni açın. Ardından Tümünü Çalıştır'ı seçin.
Geçerli bildirimler için testler oluşturma
Genel bir kural olarak, çözümleyiciler en az iş yaparak mümkün olan en kısa sürede çıkmalıdır. Kullanıcı kodu düzenlerken Visual Studio kayıtlı çözümleyicileri çağırır. Yanıt verme, önemli bir gereksinimdir. Tanılamanızı tetiklememesi gereken kod için birkaç test durumu vardır. Çözümleyiciniz bu testlerden birkaçını zaten işler. Bu durumları temsil etmek için aşağıdaki test yöntemlerini ekleyin:
[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);
}
}
");
}
Çözümleyiciniz şu koşulları zaten işlediği için bu testler geçer:
- Başlatmadan sonra atanan değişkenler, veri akışı analizi tarafından algılandı.
- Halihazırda
constolan bildirimler,constanahtar sözcüğü denetlenerek filtrelenir. - Başlatıcısı olmayan bildirimler, bildirimin dışındaki atamaları algılayan veri akışı analizi tarafından işlenir.
Ardından, henüz işlemediğiniz koşullar için test yöntemleri ekleyin:
Başlatıcının sabit olmadığı bildirimler, çünkü bunlar derleme zamanı sabitleri olamaz:
[TestMethod] public async Task InitializerIsNotConstant_NoDiagnostic() { await VerifyCS.VerifyAnalyzerAsync(@" using System; class Program { static void Main() { int i = DateTime.Now.DayOfYear; Console.WriteLine(i); } } "); }
C# birden çok bildirime tek bir deyim olarak izin verdiğinden daha da karmaşık olabilir. Aşağıdaki test vakası dize sabitini göz önünde bulundurun:
[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);
}
}
");
}
Değişken i sabit yapılabilir, ancak değişken j yapılamaz. Bu nedenle, bu deyimi const bildirimi yapılamaz.
Testlerinizi yeniden çalıştırdığınızda bu son iki test çalışmasının başarısız olduğunu görürsünüz.
Analizcinizi doğru bildirileri yoksayacak şekilde güncelleyin.
Bu koşullarla eşleşen kodu filtrelemek için çözümleyicinizin AnalyzeNode yönteminde bazı geliştirmeler yapmanız gerekir. Bunların tümü ilgili koşullardır, bu nedenle benzer değişiklikler tüm bu koşulları düzeltir. dosyasında aşağıdaki değişiklikleri AnalyzeNodeyapın:
- Anlam analiziniz tek bir değişken bildirimini inceledi. Bu kodun aynı deyimde bildirilen tüm değişkenleri inceleyen bir
foreachdöngüde olması gerekir. - Bildirilen her değişkenin bir başlatıcısı olması gerekir.
- Bildirilen her değişkenin başlatıcısı bir derleme zamanı sabiti olmalıdır.
Yönteminizde AnalyzeNode özgün anlam analizini değiştirin:
// 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;
}
aşağıdaki kod parçacığıyla:
// 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;
}
}
İlk foreach döngü, her değişken bildirimini, sentaktik analiz kullanarak inceler. İlk denetim, değişkenin bir ilk değere sahip olmasını garanti eder. İkinci denetim, başlatıcının sabit olduğunu garanti eder. İkinci döngüde özgün anlam analizi bulunur. Semantik denetimler, performans üzerinde daha büyük bir etkiye sahip olduğundan ayrı bir döngüdedir. Testlerinizi yeniden çalıştırdığınızda hepsinin başarılı olduğunu görmeniz gerekir.
Son cilayı ekleyin
Neredeyse bitti. Çözümleyicinizin işlemesi gereken birkaç koşul daha vardır. Kullanıcı kod yazarken Visual Studio çözümleyicileri çağırır. Çözümleyiciniz genellikle derlenmeyen kodlar için çağrılır. Tanılama çözümleyicisinin AnalyzeNode yöntemi sabit değerin değişken türüne dönüştürülebilir olup olmadığını denetlemez. Bu nedenle, mevcut uygulama yanlış bir bildirim olan int i = "abc"'yı yerel bir sabite kolaylıkla dönüştürür. Bu durum için bir test yöntemi ekleyin:
[TestMethod]
public async Task DeclarationIsInvalid_NoDiagnostic()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using System;
class Program
{
static void Main()
{
int x = {|CS0029:""abc""|};
}
}
");
}
Ayrıca, başvuru türleri doğru bir şekilde işlenmez. Bir başvuru türü için izin verilen tek sabit değer null'dir, ancak System.String durumu hariç; bu durumda dize sabitlerine izin verilir. Başka bir deyişle, const string s = "abc" yasaldır, ancak const object s = "abc" değildir. Bu kod parçacığı bu koşulu doğrular:
[TestMethod]
public async Task DeclarationIsNotString_NoDiagnostic()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using System;
class Program
{
static void Main()
{
object s = ""abc"";
}
}
");
}
Ayrıntılı olarak, bir dize için sabit bir bildirim oluşturabileceğinizden emin olmak için başka bir test eklemeniz gerekir. Aşağıdaki kod parçacığı hem tanılamayı yükselten kodu hem de düzeltme uygulandıktan sonra kodu tanımlar:
[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"";
}
}
");
}
Son olarak, anahtar sözcüğüyle var bir değişken bildirilirse kod düzeltmesi yanlış bir şey yapar ve C# dili tarafından desteklenmeyen bir const var bildirim oluşturur. Bu hatayı düzeltmek için kod düzeltmesinin anahtar sözcüğünü var çıkarsanan türün adıyla değiştirmesi gerekir:
[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"";
}
}
");
}
Neyse ki, yukarıdaki hataların tümü yeni öğrendiğiniz tekniklerle giderilebilir.
İlk hatayı düzeltmek için önce MakeConstAnalyzer.cs açın ve yerel bildirimin başlatıcılarının her birinin sabit değerlerle atandığından emin olmak için denetlendiği foreach döngüsünü bulun. İlk foreach döngüsünden hemen önce , yerel bildirimin bildirilen türü hakkında ayrıntılı bilgi almak için çağrısı context.SemanticModel.GetTypeInfo() yapın:
TypeSyntax variableTypeName = localDeclaration.Declaration.Type;
ITypeSymbol variableType = context.SemanticModel.GetTypeInfo(variableTypeName, context.CancellationToken).ConvertedType;
Ardından döngünüzün foreach içinde, değişken türüne dönüştürülebilir olduğundan emin olmak için her başlatıcıyı denetleyin. Başlatıcının sabit olduğundan emin olduktan sonra aşağıdaki denetimi ekleyin:
// 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;
}
Önceki değişikliğin üzerine kuruludur. İlk foreach döngüsünün kapanış süslü parantezinden önce, sabit bir dize veya null ise yerel bildirimin türünü kontrol etmek için aşağıdaki kodu ekleyin.
// 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;
}
Anahtar kelimeyi doğru tür adıyla değiştirmek için kod düzeltme aracınıza biraz daha kod eklemeniz gerekir. MakeConstCodeFixProvider.cs'a dönün. Ekleyeceğiniz kod aşağıdaki adımları uygular:
- Bildirimin bir
varbildirimi olup olmadığını denetleyin ve eğer öyleyse: - Çıkarsanan tür için yeni bir tür oluşturun.
- Tip bildiriminin takma ad olmadığına emin olun. Bu durumda,
const varbeyan etmek yasaldır. - Bu programda
var'nin tür adı olmadığından emin olun. (Öyleyse,const varyasaldır). - Tam tür adını basitleştirin
Kulağa çok fazla kod gibi geliyor. Hayır, değil. Bildiren ve başlatan newLocal satırı aşağıdaki kodla değiştirin. Başlatma işleminden hemen sonra newModifiers gelir:
// 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);
Bir using türünü kullanmak için bir Simplifier yönerge eklemeniz gerekir.
using Microsoft.CodeAnalysis.Simplification;
Testlerinizi yapın ve hepsi geçsin. Tamamlanmış çözümleyicinizi çalıştırarak kendinizi tebrik edin. Çözümleyici projesini Roslyn Preview uzantısı yüklü ikinci bir Visual Studio örneğinde çalıştırmak için Ctrl+F5 tuşuna basın.
- İkinci Visual Studio örneğinde yeni bir C# Konsol Uygulaması projesi oluşturun ve Main yöntemine ekleyin
int x = "abc";. İlk hata düzeltmesi sayesinde, bu yerel değişken bildirimi için uyarı bildirilmemelidir (ancak beklendiği gibi bir derleyici hatası vardır). - Ardından Main yöntemine ekleyin
object s = "abc";. İkinci hata düzeltmesi nedeniyle uyarı bildirilmemelidir. - Son olarak, anahtar sözcüğünü
varkullanan başka bir yerel değişken ekleyin. Bir uyarının bildirildiğini ve sol tarafta bir önerinin gösterildiğini görürsünüz. - Düzenleyici imlecini dalgalı alt çizginin üzerine getirin ve Ctrl+. tuşlarına basın. önerilen kod düzeltmesini görüntülemek için. Kod düzeltmenizi seçtikten sonra anahtar sözcüğün
varartık doğru işlendiğini unutmayın.
Son olarak aşağıdaki kodu ekleyin:
int i = 2;
int j = 32;
int k = i + j;
Bu değişikliklerden sonra yalnızca ilk iki değişkende kırmızı dalgalı çizgiler alırsınız. Her iki const ve i öğelerine j ekleyin, ve artık k olabileceğinden dolayı const üzerinde yeni bir uyarı alırsınız.
Tebrikler! Bir sorunu algılamak için anında kod analizi gerçekleştiren ve sorunu düzeltmek için hızlı bir düzeltme sağlayan ilk .NET Derleyici Platformu uzantınızı oluşturdunuz. Bu arada, .NET Derleyici Platformu SDK'sının (Roslyn API'leri) parçası olan kod API'lerinin çoğunu öğrendiniz. Çalışmanızı örnek GitHub depomuzda tamamlanan örneğe göre denetleyebilirsiniz.