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 Compiler Platform SDK'sı, C# veya Visual Basic kodunu hedefleyen özel tanılama (çö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 Compiler Platform, geliştiriciler kod yazarken çözümlemeyi çalıştırma çerçevesini ve kodu düzeltmek için tüm Visual Studio kullanıcı arabirimi özelliklerini sağlar: düzenleyicide dalgalı çizgiler gösterme, Visual Studio Hata Listesi'ni doldurma, "ampul" önerilerini oluşturma ve önerilen düzeltmelerin zengin önizlemesini gösterme.
Bu öğreticide, Roslyn API'lerini kullanarak bir çözümleyici ve eşlik eden bir kod düzeltmesi oluşturmayı keşfedeceksiniz. Çözümleyici, kaynak kod analizi gerçekleştirmenin ve kullanıcıya sorun bildirmenin bir yoludur. İsteğe bağlı olarak, kullanıcının kaynak kodunda yapılan bir değişikliği göstermek için çözümleyici ile bir kod düzeltmesi ilişkilendirilebilir. Bu öğretici, değiştirici kullanılarak const
bildirilebilen ancak bildirilmeyen yerel değişken bildirimlerini bulan bir çözümleyici oluşturur. Eşlik eden kod düzeltmesi, değiştiriciyi eklemek const
için bu bildirimleri değiştirir.
Önkoşullar
- Visual Studio 2019 sürüm 16.8 veya üzeri
Visual Studio Yükleyicisi aracılığıyla .NET Compiler Platform SDK'sını yüklemeniz gerekir:
Yükleme yönergeleri - Visual Studio Yükleyicisi
Visual Studio Yükleyicisi .NET Compiler Platform 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 Compiler Platform SDK,Visual Studio uzantısı geliştirme iş yükünün bir parçası olarak otomatik olarak seçilmez. bunu isteğe bağlı bir bileşen olarak seçmelisiniz.
- Visual Studio Yükleyicisi çalıştırma
- 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 Compiler Platform SDK kutusunu işaretleyin. Bunu en son isteğe bağlı bileşenler altında bulabilirsiniz.
İ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 - Bağımsız bileşenler sekmesini kullanarak yükleme
- Visual Studio Yükleyicisi çalıştırma
- Değiştir'i seçin
- Tek tek bileşenler sekmesini seçin
- .NET Compiler Platform SDK kutusunu işaretleyin. Bunu 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şturmanın ve doğrulamanın 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.
Not
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 oluşturmak için kullanılan MakeConst.Package.
- Birim testi projesi olan MakeConst.Test.
- Yeni çözümleyicinizi yükleyen visual studio'nun ikinci bir örneğini başlatan varsayılan başlangıç projesi olan MakeConst.Vsix. VSIX projesini başlatmak için F5 tuşuna basın.
Not
Çözümleyiciler .NET Core ortamında (komut satırı derlemeleri) ve .NET Framework ortamında (Visual Studio) çalışabileceğinden .NET Standard 2.0'ı hedeflemelidir.
İpucu
Çö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şıma aç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çılan önceki çözümleyicileri de içerir. Roslyn kovanlarını sıfırlamak için bunu %LocalAppData%\Microsoft\VisualStudio'dan el ile silmeniz gerekir. Roslyn kovanının klasör adı , örneğin 16.0_9ae182f9Roslyn
ile biterRoslyn
. 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üzeyinde ç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 her tür adını büyük harfle değiştiren bir kod düzeltmesi de 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ümleyicinin çalıştığını 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ılamayı 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.
İpucu
Çözümleyici birim testleri, çözümleyicinizin hangi kod yapılarının tetiklenmesi ve tetiklenmemesi gerektiğini 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 yerel değişken bildirimlerini bildiren bir çözümleyici yazarsı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);
Bir değişkenin sabit yapılıp yapılamayacağını belirleyen 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 gerekir. .NET Compiler Platform, bu analizi gerçekleştirmeyi kolaylaştıran API'ler sağlar.
Çözümleyici kayıtları oluşturma
Şablon, MakeConstAnalyzer.cs dosyasında ilk DiagnosticAnalyzer
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 sınıfından DiagnosticAnalyzer türetilmelidir (doğrudan veya dolaylı olarak).
Şablon ayrıca herhangi bir çözümleyicinin parçası olan temel özellikleri de gösterir:
- Eylemleri kaydetme. Eylemler, çözümleyicinizin kodu ihlallere karşı incelemesi için tetiklemesi gereken kod değişikliklerini temsil eder. Visual Studio, kayıtlı bir eylemle eşleşen kod düzenlemeleri algıladığında çözümleyicinizin kayıtlı yöntemini çağırır.
- Tanılama oluşturma. Çözümleyiciniz bir ihlal algıladığında, Visual Studio'nun kullanıcıya ihlali bildirmek için kullandığı bir tanılama nesnesi oluşturur.
Eylemleri geçersiz kılma DiagnosticAnalyzer.Initialize(AnalysisContext) yönteminize kaydedersiniz. Bu öğreticide, yerel bildirimleri bulmak için söz dizimi düğümlerini ziyaret edip 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:
- "Variables that are not modified should be made constants." olarak değiştirin
AnalyzerDescription
. - "Variable '{0}' can be made constant" olarak değiştirin
AnalyzerMessageFormat
. - "Variable can be made constant" olarak değiştirin
AnalyzerTitle
.
İş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 dosyasını açın. Kayıtlı eylemi, simgeler üzerinde işlem yapan eylemden söz dizimine göre hareket eden eyleme değiştirin. yönteminde MakeConstAnalyzerAnalyzer.Initialize
, simgelerde eylemi kaydeden satırı bulun:
context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);
Bunu 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 deyimleri SyntaxKind.LocalDeclarationStatementdeğil SymbolKind.NamedType , inceler. AnalyzeNode
Altında kırmızı dalgalı çizgiler olduğuna dikkat 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 MakeConstAnalyzer.cs dosyasında öğesini "Usage" olarak değiştirin:
private const string Category = "Usage";
Sabit olabilecek yerel bildirimleri bulma
Yöntemin ilk sürümünü yazma zamanı AnalyzeNode
geldi. Aşağıdaki kod gibi olabilecek const
ancak olmayan tek bir yerel bildirim aramalıdır:
int x = 0;
Console.WriteLine(x);
İlk adım yerel bildirimleri bulmaktır. MakeConstAnalyzer.cs dosyasına aşağıdaki kodu AnalyzeNode
ekleyin:
var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;
Çözümleyiciniz yerel bildirimlere ve yalnızca yerel bildirimlere kayıtlı olduğundan bu atama her zaman başarılı olur. Başka hiçbir düğüm türü yönteminize çağrı tetiklemez AnalyzeNode
. Ardından, herhangi bir const
değiştiricinin bildirimini denetleyin. Onları bulursanız hemen geri dönün. Aşağıdaki kod, yerel bildirimdeki değiştiricileri const
arar:
// make sure the declaration isn't already const:
if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
{
return;
}
Son olarak değişkeninin olup const
olmadığını denetlemeniz gerekir. Bu, başlatıldıktan sonra hiçbir zaman atanmadığından emin olmak anlamına gelir.
kullanarak SyntaxNodeAnalysisContextbazı anlamsal analizler gerçekleştireceksiniz. Yerel değişken bildiriminin yapılıp yapılamayacağını const
belirlemek için bağımsız değişkenini kullanırsınızcontext
. A Microsoft.CodeAnalysis.SemanticModel , tek bir kaynak dosyadaki tüm anlamsal bilgileri temsil eder. Anlamsal modelleri kapsayan makalede daha fazla bilgi edinebilirsiniz. Yerel bildirim deyiminde veri akışı analizi gerçekleştirmek için öğesini kullanacaksınız Microsoft.CodeAnalysis.SemanticModel . Ardından, yerel değişkenin başka bir yerde yeni bir değerle yazılmadığından emin olmak için bu veri akışı analizinin sonuçlarını kullanırsınız. değişkenini GetDeclaredSymbol almak ILocalSymbol için uzantı yöntemini çağırın ve veri akışı analizi koleksiyonunda DataFlowAnalysis.WrittenOutside yer almadığından emin olun. 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. son satırı AnalyzeNode
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 düzeltmesini kullanmaya devam eden ampul, bunun büyük harfle yapılabilmesini size bildirir.
- Düzenleyicinin üst kısmındaki 'MakeConstCodeFixProvider' öğesinin bir hatayla karşılaştığını ve devre dışı bırakıldığını belirten bir başlık iletisi. Bunun nedeni kod düzeltme sağlayıcısının henüz değiştirilmemiş olması ve yine de öğeler yerine öğeleri bulmayı
TypeDeclarationSyntax
beklemesidirLocalDeclarationStatementSyntax
.
Sonraki bölümde kod düzeltmesinin nasıl yaz olduğu 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ı bunu düzenleyicideki ampul kullanıcı arabiriminden seçer ve Visual Studio kodu değiştirir.
CodeFixResources.resx dosyasını açın ve "Make constant" olarak değiştirinCodeFixTitle
.
Şablon tarafından eklenen MakeConstCodeFixProvider.cs dosyasını açın. Bu kod düzeltmesi, tanılama çözümleyiciniz tarafından oluşturulan Tanılama Kimliğine zaten bağlanmış ancak henüz doğru kod dönüşümünü uygulamaz.
Ardından yöntemini silin MakeUppercaseAsync
. Artık geçerli değildir.
Tüm kod düzeltme sağlayıcıları' ndan CodeFixProvidertü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, değiştiriciyi var olan bir bildirime eklemenin const
sonucu olan yeni bir belge 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);
simgesine MakeConstAsync
yeni eklediğiniz kodda kırmızı dalgalı çizgiler olduğunu göreceksiniz. için aşağıdaki koda benzer bir bildirim MakeConstAsync
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 dosyasını şimdi bildirim içeren yeni Document bir const
dosyaya dönüştürürDocument.
Bildirim deyiminin önüne eklenecek yeni const
bir anahtar sözcük belirteci oluşturursunuz. Öncelikle bildirim deyiminin ilk belirtecinden önde gelen tüm trivia'ları kaldırmaya ve belirtece eklemeye const
dikkat edin. 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ğıda gösterildiği gibi uyarı olarak bildirildiğini göreceksiniz.
Çok ilerleme kaydettin. Bildirimlerin altında yapılabilir const
dalgalı çizgiler vardır. Ama hala yapacak iş var. bu, ile i
j
başlayan bildirimlere ve son olarak k
eklerseniz const
düzgün çalışır. Ancak, ile başlayarak k
değiştiriciyi const
farklı bir sırayla eklerseniz çözümleyiciniz hatalar oluşturur: k
hem j
de zaten const
olmadığı sürece i
bildirilemiyorconst
. Değişkenlerin bildirilebileceği ve başlatılabildiği farklı yöntemleri işlediğinden emin olmak için daha fazla analiz yapmanız gerekir.
Birim testleri oluşturma
Çözümleyiciniz ve kod düzeltmeniz, sabit hale getirilebilen tek bir bildirimin basit bir örneği üzerinde çalışır. Bu uygulamanın hata yaptığı çok sayıda olası bildirim bildirimi 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
tanılama raporlama ve kod düzeltmesini çalıştırma desenini gösterir.
Şablon, birim testi için Microsoft.CodeAnalysis.Testing paketlerini kullanır.
İpucu
Test kitaplığı, aşağıdakiler de dahil olmak üzere özel bir işaretleme söz dizimini destekler:
[|text|]
: içintext
bir tanılama bildirildiğini gösterir. Varsayılan olarak, bu form yalnızca tarafındanDiagnosticAnalyzer.SupportedDiagnostics
sağlanan çözümleyicileriDiagnosticDescriptor
test etmek için kullanılabilir.{|ExpectedDiagnosticId:text|}
: ile IdExpectedDiagnosticId
bir tanılamanın içintext
bildirildiğ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);
}
}
");
}
Geçtiğinden emin olmak için bu testi çalıştırın. Visual Studio'da TestWindows>Test Gezgini'ni 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 hızı önemli bir gereksinimdir. Tanılamanızı tetiklemeyecek kod için birkaç test çalışması vardır. Çözümleyiciniz, başlatıldıktan sonra bir değişkenin atandığı durum olan bu testlerden birini zaten işler. Bu durumu göstermek için aşağıdaki test yöntemini ekleyin:
[TestMethod]
public async Task VariableIsAssigned_NoDiagnostic()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using System;
class Program
{
static void Main()
{
int i = 0;
Console.WriteLine(i++);
}
}
");
}
Bu test de geçer. Ardından, henüz işlemediğiniz koşullar için test yöntemleri ekleyin:
Zaten olan bildirimler
const
, zaten en sabit oldukları için:[TestMethod] public async Task VariableIsAlreadyConst_NoDiagnostic() { await VerifyCS.VerifyAnalyzerAsync(@" using System; class Program { static void Main() { const int i = 0; Console.WriteLine(i); } } "); }
Kullanılacak değer olmadığından başlatıcısı olmayan bildirimler:
[TestMethod] public async Task NoInitializer_NoDiagnostic() { await VerifyCS.VerifyAnalyzerAsync(@" using System; class Program { static void Main() { int i; i = 0; Console.WriteLine(i); } } "); }
Başlatıcının sabit olmadığı bildirimler, çünkü 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 çalışması 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 ifade const bildirimi yapılamaz.
Testlerinizi yeniden çalıştırdığınızda bu yeni test çalışmalarının başarısız olduğunu görürsünüz.
Doğru bildirimleri yoksaymak için çözümleyicinizi güncelleştirme
Bu koşullarla eşleşen kodu filtrelemek için çözümleyicinizin AnalyzeNode
yönteminde bazı geliştirmeler yapmanız gerekir. Bunların hepsi 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 AnalyzeNode
yapın:
- Anlam analiziniz tek bir değişken bildirimini inceledi. Bu kodun aynı deyimde bildirilen tüm değişkenleri inceleyen bir
foreach
dö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, bozulmamış analizi kullanarak inceler. İlk denetim, değişkenin başlatıcısı olduğunu 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 lehçeyi 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 der olmayan 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, geçerli uygulama gibi yanlış bir bildirimi int i = "abc"
yerel sabite mutlu bir şekilde 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 düzgün işlenmez. Başvuru türü için izin verilen tek sabit değer, null
dize değişmez değerlerine System.Stringizin veren , dışındadır. Başka bir deyişle, const string s = "abc"
yasaldır, ancak const object s = "abc"
değildir. Bu kod parçacığı şu 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ı olmak gerekirse, 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ı oluşturan 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 dosyasını 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ı bilgileri 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 her başlatıcıyı denetleerek değişken türüne dönüştürülebilir olduğundan emin olun. 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;
}
Bir sonraki değişiklik, sonuncusunun üzerine inşa eder. İlk foreach döngüsünün kapanış küme ayracından önce, sabit bir dize veya null olduğunda yerel bildirimin türünü denetlemek 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 sözcüğünü doğru tür adıyla değiştirmek var
için kod düzeltme sağlayıcınızda biraz daha kod yazmanız gerekir. MakeConstCodeFixProvider.cs dosyasına dönün. Ekleyeceğiniz kod aşağıdaki adımları uygular:
- Bildirimin bir
var
bildirim olup olmadığını ve olup olmadığını denetleyin: - Çıkarsanan tür için yeni bir tür oluşturun.
- Tür bildiriminin diğer ad olmadığından emin olun. Öyleyse, bildirmek
const var
yasaldır. - Bunun bu programda tür adı olmadığından emin
var
olun. (Öyleyse,const var
yasaldır). - Tam tür adını basitleştirme
Bu çok fazla koda benziyor. Hayır, değil. Bildiren ve başlatan newLocal
satırı aşağıdaki kodla değiştirin. başlatılmasından newModifiers
hemen sonra gider:
// 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);
Türünü kullanmak Simplifier için bir using
yönerge eklemeniz gerekir:
using Microsoft.CodeAnalysis.Simplification;
Testlerinizi yapın, hepsi geçsin. Tamamlanmış çözümleyicinizi çalıştırarak kendinizi tebrik edin. Çözümleyici projesini Visual Studio'nun ikinci bir örneğinde Roslyn Preview uzantısı yüklü olarak ç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 hiçbir uyarı bildirilmemelidir. - Son olarak, anahtar sözcüğünü
var
kullanan başka bir yerel değişken ekleyin. Bir uyarı bildirildiğini ve sol tarafta bir önerinin göründüğünü görürsünüz. - Düzenleyici şapka işaretini dalgalı alt çizginin üzerine getirin ve Ctrl tuşuna+ basın.. önerilen kod düzeltmesini görüntülemek için. Kod düzeltmenizi seçtikten sonra anahtar sözcüğün
var
artı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. hem hem j
de i
öğesine ekleyinconst
; artık olabileceği const
için yeni k
bir uyarı alırsınız.
Tebrikler! Bir sorunu algılamak için anında kod analizi gerçekleştiren ve düzeltmek için hızlı bir düzeltme sağlayan ilk .NET Compiler Platform uzantınızı oluşturdunuz. Bu arada, .NET Compiler Platform SDK'sının (Roslyn API'leri) parçası olan kod API'lerinin çoğunu öğrendiniz. Örnek GitHub depomuzda tamamlanan örnekteki çalışmanızı denetleyebilirsiniz.