Zelfstudie: Uw eerste analyse- en codeoplossing schrijven
De .NET Compiler Platform SDK biedt de hulpprogramma's die u nodig hebt om aangepaste diagnostische gegevens (analyzers), codecorrecties, codeherstructurering en diagnostische suppressors te maken die zijn gericht op C# of Visual Basic-code. Een analyse bevat code die schendingen van uw regel herkent. Uw codecorrectie bevat de code waarmee de schending wordt opgelost. De regels die u implementeert, kunnen van alles zijn, van codestructuur tot coderingsstijl tot naamconventies en meer. Het .NET Compiler Platform biedt het framework voor het uitvoeren van analyses terwijl ontwikkelaars code schrijven, en alle Visual Studio UI-functies voor het herstellen van code: het weergeven van kronkelingen in de editor, het invullen van de Visual Studio-foutenlijst, het maken van de 'gloeilamp'-suggesties en het weergeven van de uitgebreide preview van de voorgestelde oplossingen.
In deze zelfstudie verkent u het maken van een analyse en een bijbehorende codeoplossing met behulp van de Roslyn-API's. Een analysefunctie is een manier om broncodeanalyse uit te voeren en een probleem aan de gebruiker te melden. Optioneel kan een codefix worden gekoppeld aan de analyse om een wijziging in de broncode van de gebruiker aan te geven. In deze zelfstudie maakt u een analyse die lokale variabeledeclaraties vindt die kunnen worden gedeclareerd met behulp van de const
wijzigingsfunctie, maar dat niet zijn. De bijbehorende codefix wijzigt deze declaraties om de const
wijzigingsfunctie toe te voegen.
Vereisten
- Visual Studio 2019 versie 16.8 of hoger
U moet de .NET Compiler Platform SDK installeren via het Visual Studio-installatieprogramma:
Installatie-instructies - Visual Studio Installer
Er zijn twee verschillende manieren om de .NET Compiler Platform SDK te vinden in het Visual Studio-installatieprogramma:
Installeren met behulp van het visual studio-installatieprogramma - weergave Workloads
De .NET Compiler Platform SDK wordt niet automatisch geselecteerd als onderdeel van de ontwikkelworkload van de Visual Studio-extensie. U moet deze selecteren als een optioneel onderdeel.
- Visual Studio Installer uitvoeren
- Selecteer Wijzigen
- Controleer de workload voor de ontwikkeling van de Visual Studio-extensie .
- Open het ontwikkelknooppunt visual Studio-extensie in de overzichtsstructuur.
- Schakel het selectievakje in voor .NET Compiler Platform SDK. U vindt deze als laatste onder de optionele onderdelen.
U wilt desgewenst ook dat de DGML-editor grafieken weergeeft in de visualizer:
- Open het knooppunt Afzonderlijke onderdelen in de overzichtsstructuur.
- Schakel het selectievakje voor DGML-editor in
Installeren met behulp van het visual studio-installatieprogramma - tabblad Afzonderlijke onderdelen
- Visual Studio Installer uitvoeren
- Selecteer Wijzigen
- Selecteer het tabblad Afzonderlijke onderdelen
- Schakel het selectievakje in voor .NET Compiler Platform SDK. U vindt deze bovenaan in de sectie Compilers, build tools en runtimes .
U wilt desgewenst ook dat de DGML-editor grafieken weergeeft in de visualizer:
- Schakel het selectievakje voor DGML-editor in. U vindt deze in de sectie Codehulpprogramma's .
Er zijn verschillende stappen voor het maken en valideren van uw analyse:
- Maak de oplossing.
- Registreer de naam en beschrijving van de analyse.
- Waarschuwingen en aanbevelingen voor Rapportanalyse.
- Implementeer de codefix om aanbevelingen te accepteren.
- Verbeter de analyse door middel van eenheidstests.
De oplossing maken
- Kies in Visual Studio Bestand > Nieuw > project... om het dialoogvenster Nieuw project weer te geven.
- Kies onder Visual C# > Extensibilityde optie Analyzer with code fix (.NET Standard).
- Geef uw project de naam 'MakeConst' en klik op OK.
Notitie
Mogelijk krijgt u een compilatiefout (MSB4062: de taak CompareBuildTaskVersion kan niet worden geladen'). U kunt dit oplossen door de NuGet-pakketten in de oplossing bij te werken met NuGet Package Manager of te gebruiken Update-Package
in het venster Package Manager Console.
De analysesjabloon verkennen
Met de sjabloon analyse met codefix worden vijf projecten gemaakt:
- MakeConst, dat de analyse bevat.
- MakeConst.CodeFixes, dat de codefix bevat.
- MakeConst.Package, dat wordt gebruikt om het NuGet-pakket te produceren voor de analyse en codefix.
- MakeConst.Test, een eenheidstestproject.
- MakeConst.Vsix, het standaardstartproject waarmee een tweede exemplaar van Visual Studio wordt gestart dat uw nieuwe analyse heeft geladen. Druk op F5 om het VSIX-project te starten.
Notitie
Analyzers moeten zijn gericht op .NET Standard 2.0, omdat ze kunnen worden uitgevoerd in een .NET Core-omgeving (opdrachtregelbuilds) en .NET Framework omgeving (Visual Studio).
Tip
Wanneer u uw analyse uitvoert, start u een tweede kopie van Visual Studio. Deze tweede kopie maakt gebruik van een andere registercomponent om instellingen op te slaan. Hiermee kunt u de visuele instellingen in de twee kopieën van Visual Studio onderscheiden. U kunt een ander thema kiezen voor de experimentele uitvoering van Visual Studio. Bovendien hoeft u uw instellingen niet te roamen en u niet aan te melden bij uw Visual Studio-account met behulp van de experimentele uitvoering van Visual Studio. Hierdoor blijven de instellingen anders.
De hive omvat niet alleen de analyse die in ontwikkeling is, maar ook eventuele eerdere analyses die zijn geopend. Als u Roslyn Hive opnieuw wilt instellen, moet u deze handmatig verwijderen uit %LocalAppData%\Microsoft\VisualStudio. De mapnaam van Roslyn Hive eindigt op Roslyn
, bijvoorbeeld 16.0_9ae182f9Roslyn
. Houd er rekening mee dat u de oplossing mogelijk moet opschonen en opnieuw moet opbouwen nadat u de hive hebt verwijderd.
In het tweede Visual Studio-exemplaar dat u net hebt gestart, maakt u een nieuw C#-consoletoepassingsproject (elk doelframework werkt- analyses werken op bronniveau.) Beweeg de muisaanwijzer over het token met een golvende onderstreping en de waarschuwingstekst van een analyse wordt weergegeven.
De sjabloon maakt een analyse die een waarschuwing rapporteert voor elke typedeclaratie waarbij de typenaam kleine letters bevat, zoals wordt weergegeven in de volgende afbeelding:
De sjabloon biedt ook een codeoplossing waarmee een typenaam met kleine letters wordt gewijzigd in hoofdletters. U kunt klikken op het gloeilampje dat wordt weergegeven met de waarschuwing om de voorgestelde wijzigingen te zien. Als u de voorgestelde wijzigingen accepteert, worden de typenaam en alle verwijzingen naar dat type in de oplossing bijgewerkt. Nu u de eerste analyse in actie hebt gezien, sluit u het tweede Visual Studio-exemplaar en gaat u terug naar uw analyseproject.
U hoeft geen tweede kopie van Visual Studio te starten en nieuwe code te maken om elke wijziging in uw analyse te testen. Met de sjabloon wordt ook een eenheidstestproject voor u gemaakt. Dat project bevat twee tests. TestMethod1
toont de typische indeling van een test die code analyseert zonder een diagnose te activeren. TestMethod2
toont de indeling van een test die een diagnose activeert en past vervolgens een voorgestelde codeoplossing toe. Terwijl u uw analyse en codeoplossing bouwt, schrijft u tests voor verschillende codestructuren om uw werk te verifiëren. Eenheidstests voor analysen zijn veel sneller dan interactief testen met Visual Studio.
Tip
Analyse-eenheidstests zijn een uitstekend hulpmiddel als u weet welke codeconstructies uw analyse wel en niet moeten activeren. Het laden van uw analyse in een ander exemplaar van Visual Studio is een geweldig hulpmiddel om constructies te verkennen en te vinden waar u misschien nog niet aan hebt gedacht.
In deze zelfstudie schrijft u een analyse die aan de gebruiker alle declaraties van lokale variabelen rapporteert die kunnen worden geconverteerd naar lokale constanten. Bekijk bijvoorbeeld de volgende code:
int x = 0;
Console.WriteLine(x);
In de bovenstaande x
code wordt een constante waarde toegewezen en wordt deze nooit gewijzigd. Deze kan worden gedeclareerd met behulp van de const
wijzigingsfunctie:
const int x = 0;
Console.WriteLine(x);
De analyse om te bepalen of een variabele constant kan worden gemaakt, is vereist syntactische analyse, constante analyse van de initialisatie-expressie en gegevensstroomanalyse om ervoor te zorgen dat de variabele nooit naar wordt geschreven. Het .NET Compiler Platform biedt API's waarmee u deze analyse eenvoudiger kunt uitvoeren.
Analyseregistraties maken
De sjabloon maakt de eerste DiagnosticAnalyzer
klasse in het bestand MakeConstAnalyzer.cs . Deze initiële analyse toont twee belangrijke eigenschappen van elke analyse.
- Elke diagnostische analyse moet een
[DiagnosticAnalyzer]
kenmerk opgeven waarmee de taal wordt beschreven. - Elke diagnostische analyse moet (direct of indirect) zijn afgeleid van de DiagnosticAnalyzer klasse.
De sjabloon toont ook de basisfuncties die deel uitmaken van een analyse:
- Acties registreren. De acties vertegenwoordigen codewijzigingen die uw analyse moeten activeren om code te onderzoeken op schendingen. Wanneer Visual Studio codebewerkingen detecteert die overeenkomen met een geregistreerde actie, wordt de geregistreerde methode van uw analyse aangeroepen.
- Diagnostische gegevens maken. Wanneer uw analyse een schending detecteert, wordt er een diagnostisch object gemaakt dat Visual Studio gebruikt om de gebruiker op de hoogte te stellen van de schending.
U registreert acties in de onderdrukking van DiagnosticAnalyzer.Initialize(AnalysisContext) de methode. In deze zelfstudie gaat u naar syntaxisknooppunten op zoek naar lokale declaraties en ziet u welke daarvan constante waarden hebben. Als een declaratie constant kan zijn, maakt en rapporteert uw analyse een diagnose.
De eerste stap bestaat uit het bijwerken van de registratieconstanten en Initialize
-methode, zodat deze constanten aangeven dat uw 'Make Const'-analyse is. De meeste tekenreeksconstanten worden gedefinieerd in het bronbestand van de tekenreeks. U moet deze procedure volgen om de lokalisatie eenvoudiger te maken. Open het bestand Resources.resx voor het project MakeConst Analyzer. Hiermee wordt de resource-editor weergegeven. Werk de tekenreeksresources als volgt bij:
- Wijzig
AnalyzerDescription
in 'Variables that are not modified should be made constants.'. - Wijzig
AnalyzerMessageFormat
in 'Variable '{0}' can be made constant'. - Wijzig
AnalyzerTitle
in 'Variable can be made constant'.
Wanneer u klaar bent, wordt de resource-editor weergegeven zoals wordt weergegeven in de volgende afbeelding:
De resterende wijzigingen bevinden zich in het analysebestand. Open MakeConstAnalyzer.cs in Visual Studio. Wijzig de geregistreerde actie van een actie die op symbolen reageert in een actie die op syntaxis reageert. Zoek in de MakeConstAnalyzerAnalyzer.Initialize
-methode de regel waarmee de actie op symbolen wordt geregistreerd:
context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);
Vervang deze door de volgende regel:
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.LocalDeclarationStatement);
Na deze wijziging kunt u de AnalyzeSymbol
methode verwijderen. Met deze analyse worden niet-instructies onderzochtSyntaxKind.LocalDeclarationStatementSymbolKind.NamedType. U ziet dat AnalyzeNode
er rode kronkelen onder zijn. De code die u zojuist hebt toegevoegd, verwijst naar een AnalyzeNode
methode die niet is gedeclareerd. Declareer deze methode met behulp van de volgende code:
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
}
Wijzig de Category
in "Usage" in MakeConstAnalyzer.cs , zoals wordt weergegeven in de volgende code:
private const string Category = "Usage";
Lokale declaraties zoeken die kunnen worden gebruikt
Het is tijd om de eerste versie van de AnalyzeNode
methode te schrijven. Er moet worden gezocht naar één lokale declaratie die kan zijn const
, maar dat niet is, zoals de volgende code:
int x = 0;
Console.WriteLine(x);
De eerste stap is het zoeken naar lokale declaraties. Voeg de volgende code toe aan AnalyzeNode
in MakeConstAnalyzer.cs:
var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;
Deze cast slaagt altijd omdat uw analyse is geregistreerd voor wijzigingen in lokale declaraties en alleen lokale declaraties. Geen ander knooppunttype activeert een aanroep van uw AnalyzeNode
methode. Controleer vervolgens de declaratie op eventuele const
wijzigingsfuncties. Als u ze hebt gevonden, keert u onmiddellijk terug. Met de volgende code wordt gezocht naar eventuele const
wijzigingsfuncties voor de lokale declaratie:
// make sure the declaration isn't already const:
if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
{
return;
}
Ten slotte moet u controleren of de variabele kan zijn const
. Dit betekent dat u ervoor moet zorgen dat deze nooit wordt toegewezen nadat deze is geïnitialiseerd.
U voert een semantische analyse uit met behulp van de SyntaxNodeAnalysisContext. U gebruikt het context
argument om te bepalen of de lokale variabeledeclaratie kan worden gemaakt const
. A Microsoft.CodeAnalysis.SemanticModel vertegenwoordigt alle semantische informatie in één bronbestand. Meer informatie vindt u in het artikel over semantische modellen. U gebruikt de Microsoft.CodeAnalysis.SemanticModel om gegevensstroomanalyse uit te voeren op de lokale declaratie-instructie. Vervolgens gebruikt u de resultaten van deze gegevensstroomanalyse om ervoor te zorgen dat de lokale variabele nergens anders met een nieuwe waarde wordt geschreven. Roep de GetDeclaredSymbol extensiemethode aan om de ILocalSymbol voor de variabele op te halen en controleer of deze niet is opgenomen in de DataFlowAnalysis.WrittenOutside verzameling van de gegevensstroomanalyse. Voeg de volgende code aan het einde van de methode AnalyzeNode
toe:
// 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;
}
De code die zojuist is toegevoegd, zorgt ervoor dat de variabele niet wordt gewijzigd en daarom kan worden gemaakt const
. Het is tijd om de diagnose te verhogen. Voeg de volgende code toe als laatste regel in AnalyzeNode
:
context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation(), localDeclaration.Declaration.Variables.First().Identifier.ValueText));
U kunt uw voortgang controleren door op F5 te drukken om de analyse uit te voeren. U kunt de consoletoepassing laden die u eerder hebt gemaakt en vervolgens de volgende testcode toevoegen:
int x = 0;
Console.WriteLine(x);
Het gloeilampje moet verschijnen en uw analyse moet een diagnose rapporteren. Afhankelijk van uw versie van Visual Studio ziet u echter het volgende:
- Het gloeilamp, dat nog steeds gebruikmaakt van de door de sjabloon gegenereerde codefix, vertelt u dat deze hoofdletters kunnen worden gemaakt.
- Een bannerbericht boven aan de editor met de mededeling 'MakeConstCodeFixProvider' heeft een fout aangetroffen en is uitgeschakeld.' Dit komt omdat de provider van de code-fix nog niet is gewijzigd en nog steeds verwacht elementen te vinden
TypeDeclarationSyntax
in plaats vanLocalDeclarationStatementSyntax
elementen.
In de volgende sectie wordt uitgelegd hoe u de codefix schrijft.
De code-fix schrijven
Een analyse kan een of meer codecorrecties bieden. Een codefix definieert een bewerking waarmee het gerapporteerde probleem wordt opgelost. Voor de analyse die u hebt gemaakt, kunt u een codecorrectie opgeven waarmee het trefwoord const wordt ingevoegd:
- int x = 0;
+ const int x = 0;
Console.WriteLine(x);
De gebruiker kiest deze in de gebruikersinterface van de gloeilamp in de editor en Visual Studio wijzigt de code.
Open het bestand CodeFixResources.resx en wijzig in CodeFixTitle
'Make constant'.
Open het bestand MakeConstCodeFixProvider.cs dat door de sjabloon is toegevoegd. Deze codeoplossing is al gekoppeld aan de diagnostische id die door uw diagnostische analyse wordt geproduceerd, maar de juiste codetransformatie wordt nog niet geïmplementeerd.
Verwijder vervolgens de MakeUppercaseAsync
methode. Deze is niet meer van toepassing.
Alle providers voor code-oplossingen zijn afgeleid van CodeFixProvider. Ze worden allemaal overschreven CodeFixProvider.RegisterCodeFixesAsync(CodeFixContext) om beschikbare codecorrecties te rapporteren. Wijzig in RegisterCodeFixesAsync
het type bovenliggend knooppunt dat u zoekt in een LocalDeclarationStatementSyntax zodat deze overeenkomt met de diagnose:
var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<LocalDeclarationStatementSyntax>().First();
Wijzig vervolgens de laatste regel om een codefix te registreren. Met de oplossing wordt een nieuw document gemaakt dat het resultaat is van het toevoegen van de const
wijzigingsfunctie aan een bestaande declaratie:
// 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);
U ziet rode kronkelen in de code die u zojuist hebt toegevoegd aan het symbool MakeConstAsync
. Voeg een declaratie toe voor MakeConstAsync
, zoals de volgende code:
private static async Task<Document> MakeConstAsync(Document document,
LocalDeclarationStatementSyntax localDeclaration,
CancellationToken cancellationToken)
{
}
De nieuwe MakeConstAsync
methode transformeert het Document bronbestand van de gebruiker in een nieuwe Document die nu een const
declaratie bevat.
U maakt een nieuw const
trefwoordtoken dat u wilt invoegen aan de voorzijde van de declaratie-instructie. Zorg ervoor dat u eerst alle voorlooptrivia uit het eerste token van de declaratie-instructie verwijdert en deze aan het const
token koppelt. Voeg de volgende code aan de MakeConstAsync
methode toe:
// 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));
Voeg vervolgens het const
token toe aan de declaratie met behulp van de volgende code:
// 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);
Maak vervolgens de nieuwe declaratie op zodat deze overeenkomt met de C#-opmaakregels. Als u de wijzigingen zo opmaakt dat deze overeenkomen met bestaande code, wordt de ervaring verbeterd. Voeg direct na de bestaande code de volgende instructie toe:
// Add an annotation to format the new local declaration.
LocalDeclarationStatementSyntax formattedLocal = newLocal.WithAdditionalAnnotations(Formatter.Annotation);
Er is een nieuwe naamruimte vereist voor deze code. Voeg de volgende using
instructie toe aan het begin van het bestand:
using Microsoft.CodeAnalysis.Formatting;
De laatste stap is het aanbrengen van de bewerking. Dit proces bestaat uit drie stappen:
- Een handvat ophalen voor het bestaande document.
- Maak een nieuw document door de bestaande declaratie te vervangen door de nieuwe declaratie.
- Retourneer het nieuwe document.
Voeg de volgende code aan het einde van de methode MakeConstAsync
toe:
// 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);
Uw codeoplossing is klaar om te proberen. Druk op F5 om het analyseproject uit te voeren in een tweede exemplaar van Visual Studio. Maak in het tweede Visual Studio-exemplaar een nieuw C#-consoletoepassingsproject en voeg enkele lokale variabeledeclaraties toe die zijn geïnitialiseerd met constante waarden aan de methode Main. U ziet dat ze worden gerapporteerd als waarschuwingen, zoals hieronder.
Je hebt veel vooruitgang geboekt. Er zijn golven onder de declaraties die kunnen worden gemaakt const
. Maar er is nog werk te doen. Dit werkt prima als u toevoegt const
aan de declaraties te beginnen met i
, vervolgens j
en ten slotte k
. Maar als u de const
wijzigingsfunctie in een andere volgorde toevoegt, te beginnen met k
, maakt uw analyzer fouten: k
kunnen niet worden gedeclareerd const
, tenzij i
en j
beide al const
zijn. U moet meer analyses uitvoeren om ervoor te zorgen dat u de verschillende manieren waarop variabelen kunnen worden gedeclareerd en geïnitialiseerd, verwerkt.
Build unit tests
Uw analyse- en codecorrectie werken op een eenvoudig geval van één declaratie die const kan worden gemaakt. Er zijn talloze mogelijke declaratieverklaringen waarbij deze implementatie fouten maakt. U gaat deze gevallen aanpakken door te werken met de moduletestbibliotheek die is geschreven door de sjabloon. Dit is veel sneller dan het herhaaldelijk openen van een tweede exemplaar van Visual Studio.
Open het bestand MakeConstUnitTests.cs in het eenheidstestproject. Met de sjabloon zijn twee tests gemaakt die de twee algemene patronen volgen voor een analyse- en codefix-eenheidstest. TestMethod1
toont het patroon voor een test die ervoor zorgt dat de analyse geen diagnose rapporteert wanneer dat niet het geval is. TestMethod2
toont het patroon voor het rapporteren van een diagnose en het uitvoeren van de codeoplossing.
De sjabloon maakt gebruik van Microsoft.CodeAnalysis.Testing-pakketten voor eenheidstests.
Tip
De testbibliotheek ondersteunt een speciale syntaxis voor markeringen, waaronder de volgende:
[|text|]
: geeft aan dat er een diagnose is gerapporteerd voortext
. Standaard mag dit formulier alleen worden gebruikt voor het testen van analysen met precies éénDiagnosticDescriptor
die wordt geleverd doorDiagnosticAnalyzer.SupportedDiagnostics
.{|ExpectedDiagnosticId:text|}
: geeft aan dat een diagnose met IdExpectedDiagnosticId
wordt gerapporteerd voortext
.
Vervang de sjabloontests in de MakeConstUnitTest
klasse door de volgende testmethode:
[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);
}
}
");
}
Voer deze test uit om te controleren of deze slaagt. Open testverkenner in Visual Studio door Test>Windows>Test Explorer te selecteren. Selecteer vervolgens Alles uitvoeren.
Tests maken voor geldige declaraties
Over het algemeen moeten analysers zo snel mogelijk worden afgesloten, met minimale hoeveelheid werk. Visual Studio roept geregistreerde analyses aan wanneer de gebruiker code bewerkt. Reactiesnelheid is een belangrijke vereiste. Er zijn verschillende testcases voor code die uw diagnose niet moeten verhogen. Uw analyser verwerkt al een van deze tests, het geval waarin een variabele wordt toegewezen nadat deze is geïnitialiseerd. Voeg de volgende testmethode toe om dat geval weer te geven:
[TestMethod]
public async Task VariableIsAssigned_NoDiagnostic()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using System;
class Program
{
static void Main()
{
int i = 0;
Console.WriteLine(i++);
}
}
");
}
Deze test slaagt ook. Voeg vervolgens testmethoden toe voor voorwaarden die u nog niet hebt verwerkt:
Declaraties die al
const
zijn, omdat ze al const zijn:[TestMethod] public async Task VariableIsAlreadyConst_NoDiagnostic() { await VerifyCS.VerifyAnalyzerAsync(@" using System; class Program { static void Main() { const int i = 0; Console.WriteLine(i); } } "); }
Declaraties die geen initialisatiefunctie hebben, omdat er geen waarde is om te gebruiken:
[TestMethod] public async Task NoInitializer_NoDiagnostic() { await VerifyCS.VerifyAnalyzerAsync(@" using System; class Program { static void Main() { int i; i = 0; Console.WriteLine(i); } } "); }
Declaraties waarbij de initialisatiefunctie geen constante is, omdat ze geen compilatietijdconstanten kunnen zijn:
[TestMethod] public async Task InitializerIsNotConstant_NoDiagnostic() { await VerifyCS.VerifyAnalyzerAsync(@" using System; class Program { static void Main() { int i = DateTime.Now.DayOfYear; Console.WriteLine(i); } } "); }
Het kan nog ingewikkelder zijn omdat C# meerdere declaraties als één instructie toestaat. Houd rekening met de volgende testcasetekenreeksconstante:
[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 variabele i
kan constant worden gemaakt, maar de variabele j
niet. Daarom kan deze verklaring niet als const-verklaring worden opgesteld.
Voer uw tests opnieuw uit en u ziet dat deze nieuwe testcases mislukken.
Uw analyse bijwerken om juiste declaraties te negeren
U hebt enkele verbeteringen nodig in de methode van AnalyzeNode
uw analyse om code te filteren die aan deze voorwaarden voldoet. Het zijn allemaal gerelateerde voorwaarden, dus vergelijkbare wijzigingen zullen al deze voorwaarden oplossen. Breng de volgende wijzigingen aan in AnalyzeNode
:
- Uw semantische analyse heeft één variabeledeclaratie onderzocht. Deze code moet zich in een
foreach
lus bevinden die alle variabelen onderzoekt die in dezelfde instructie zijn gedeclareerd. - Elke gedeclareerde variabele moet een initialisatiefunctie hebben.
- De initialisatiefunctie van elke gedeclareerde variabele moet een compilatietijdconstante zijn.
Vervang in uw AnalyzeNode
methode de oorspronkelijke semantische analyse:
// 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;
}
met het volgende codefragment:
// 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;
}
}
De eerste foreach
lus onderzoekt elke variabeledeclaratie met behulp van syntactische analyse. De eerste controle garandeert dat de variabele een initialisatiefunctie heeft. De tweede controle garandeert dat de initialisatiefunctie een constante is. De tweede lus bevat de oorspronkelijke semantische analyse. De semantische controles bevinden zich in een afzonderlijke lus omdat deze een grotere invloed heeft op de prestaties. Voer uw tests opnieuw uit. Als het goed is, ziet u dat ze allemaal zijn geslaagd.
Voeg de laatste polijst toe
U bent bijna klaar. Er zijn nog enkele voorwaarden die uw analyse moet verwerken. Visual Studio roept analyses aan terwijl de gebruiker code schrijft. Het is vaak het geval dat uw analyse wordt aangeroepen voor code die niet wordt gecompileerd. De methode van AnalyzeNode
de diagnostische analyse controleert niet of de constante waarde converteerbaar is naar het variabeletype. De huidige implementatie converteert dus graag een onjuiste declaratie, zoals int i = "abc"
naar een lokale constante. Voeg een testmethode toe voor dit geval:
[TestMethod]
public async Task DeclarationIsInvalid_NoDiagnostic()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using System;
class Program
{
static void Main()
{
int x = {|CS0029:""abc""|};
}
}
");
}
Bovendien worden verwijzingstypen niet correct verwerkt. De enige constante waarde die is toegestaan voor een verwijzingstype is null
, behalve in het geval van System.String, waardoor letterlijke tekenreeksen zijn toegestaan. Met andere woorden, const string s = "abc"
is legaal, maar const object s = "abc"
niet. Met dit codefragment wordt die voorwaarde geverifieerd:
[TestMethod]
public async Task DeclarationIsNotString_NoDiagnostic()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using System;
class Program
{
static void Main()
{
object s = ""abc"";
}
}
");
}
Om grondig te zijn, moet u nog een test toevoegen om ervoor te zorgen dat u een constante declaratie voor een tekenreeks kunt maken. Het volgende codefragment definieert zowel de code die de diagnose genereert als de code nadat de fix is toegepast:
[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"";
}
}
");
}
Als ten slotte een variabele wordt gedeclareerd met het var
trefwoord, doet de codeoplossing het verkeerde en wordt een const var
declaratie gegenereerd, die niet wordt ondersteund door de C#-taal. Als u deze fout wilt oplossen, moet de codeoplossing het var
trefwoord vervangen door de naam van het afgeleide type:
[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"";
}
}
");
}
Gelukkig kunnen alle bovenstaande fouten worden opgelost met behulp van dezelfde technieken die u zojuist hebt geleerd.
Als u de eerste fout wilt oplossen, opent u eerst MakeConstAnalyzer.cs en zoekt u de foreach-lus waar elk van de initialisatieprogramma's van de lokale declaratie wordt gecontroleerd om ervoor te zorgen dat ze worden toegewezen met constante waarden. Direct vóór de eerste foreach-lus roept context.SemanticModel.GetTypeInfo()
u aan om gedetailleerde informatie op te halen over het opgegeven type lokale declaratie:
TypeSyntax variableTypeName = localDeclaration.Declaration.Type;
ITypeSymbol variableType = context.SemanticModel.GetTypeInfo(variableTypeName, context.CancellationToken).ConvertedType;
Controleer vervolgens in uw foreach
lus elke initialisatiefunctie om er zeker van te zijn dat deze converteerbaar is naar het variabeletype. Voeg de volgende controle toe nadat u hebt gecontroleerd of de initialisatiefunctie een constante is:
// 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;
}
De volgende wijziging bouwt voort op de laatste wijziging. Voeg vóór de accolade sluiten van de eerste foreach-lus de volgende code toe om het type lokale declaratie te controleren wanneer de constante een tekenreeks of null is.
// 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;
}
U moet iets meer code schrijven in uw codeoplossingsprovider om het var
trefwoord te vervangen door de juiste typenaam. Ga terug naar MakeConstCodeFixProvider.cs. De code die u toevoegt, voert de volgende stappen uit:
- Controleer of de declaratie een
var
declaratie is en of dit het volgende is: - Maak een nieuw type voor het afgeleide type.
- Zorg ervoor dat de typedeclaratie geen alias is. Zo ja, dan is het legaal om aan te geven
const var
. - Zorg ervoor dat dit
var
programma geen typenaam is. (Als dat het zo is,const var
is dit legaal). - De volledige typenaam vereenvoudigen
Dat klinkt als een hoop code. Dat is het niet. Vervang de regel die declareert en initialiseert newLocal
door de volgende code. Het gaat onmiddellijk na de initialisatie van newModifiers
:
// 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);
U moet één using
-instructie toevoegen om het Simplifier type te gebruiken:
using Microsoft.CodeAnalysis.Simplification;
Voer uw tests uit en ze moeten allemaal slagen. Feliciteer uzelf door uw voltooide analyse uit te voeren. Druk op Ctrl+F5 om het analyseproject uit te voeren in een tweede exemplaar van Visual Studio met de Roslyn Preview-extensie geladen.
- Maak in het tweede Visual Studio-exemplaar een nieuw C#-consoletoepassingsproject en voeg deze toe
int x = "abc";
aan de methode Main. Dankzij de eerste foutoplossing moet er geen waarschuwing worden gerapporteerd voor deze lokale variabeledeclaratie (hoewel er een compilerfout is zoals verwacht). - Voeg vervolgens toe
object s = "abc";
aan de methode Main. Vanwege de tweede foutoplossing mag er geen waarschuwing worden gerapporteerd. - Voeg ten slotte nog een lokale variabele toe die gebruikmaakt van het
var
trefwoord. U ziet dat er een waarschuwing wordt gerapporteerd en dat er linksonder een suggestie wordt weergegeven. - Beweeg de caret van de editor over de golvende onderstreping en druk op Ctrl+.. om de voorgestelde codeoplossing weer te geven. Wanneer u uw codefix selecteert, moet u er rekening mee houden dat het
var
trefwoord nu correct wordt verwerkt.
Voeg ten slotte de volgende code toe:
int i = 2;
int j = 32;
int k = i + j;
Na deze wijzigingen krijgt u alleen rode kronkelingen op de eerste twee variabelen. Voeg toe const
aan en j
i
en u krijgt een nieuwe waarschuwing omdat k
deze nu kan zijnconst
.
Gefeliciteerd U hebt uw eerste .NET Compiler Platform-extensie gemaakt die on-the-fly codeanalyses uitvoert om een probleem te detecteren en een snelle oplossing biedt om dit probleem op te lossen. Onderweg hebt u veel van de code-API's geleerd die deel uitmaken van de .NET Compiler Platform SDK (Roslyn API's). U kunt uw werk controleren op basis van het voltooide voorbeeld in onze GitHub-opslagplaats met voorbeelden.