Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
De .NET Compiler Platform SDK biedt de hulpprogramma's die u nodig hebt om aangepaste diagnostische gegevens (analysefuncties), codecorrecties, codeherstructurering en diagnostische onderdrukkingen te maken die gericht zijn op C# of Visual Basic-code. Een analyse bevat code waarmee schendingen van uw regel worden herkend. 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 gebruikersinterfacefuncties van Visual Studio voor het oplossen van code: het weergeven van kronkels in de editor, het vullen van de Visual Studio Foutenlijst, het maken van de suggesties van het 'lampje', en het tonen van een uitgebreide preview van de voorgestelde oplossingen.
In deze zelfstudie verkent u het maken van een analyse en een bijbehorende codefix met behulp van de Roslyn-API's. Een analysefunctie is een manier om broncodeanalyse uit te voeren en een probleem aan de gebruiker te rapporteren. Optioneel kan een codeoplossing worden gekoppeld aan de analyse om een wijziging in de broncode van de gebruiker aan te geven. In deze zelfstudie maakt u een analyzer die lokale variabeledeclaraties vindt die met de const
modifier zouden kunnen worden gedeclareerd, maar niet. Met de bijbehorende codefix worden deze declaraties gewijzigd om de const
wijzigingsfunctie toe te voegen.
Vereiste voorwaarden
- Visual Studio 2019 versie 16.8 of hoger
U moet de .NET Compiler Platform SDK installeren via het Installatieprogramma van Visual Studio:
Installatie-instructies - Visual Studio Installer
Er zijn twee verschillende manieren om de .NET Compiler Platform SDK te vinden in het Installatieprogramma van Visual Studio:
Installeren met de Visual Studio Installer - Workloads-weergave
De .NET Compiler Platform SDK wordt niet automatisch geselecteerd als onderdeel van de ontwikkelworkload van de Visual Studio-extensie. U moet het selecteren als een optioneel onderdeel.
- Visual Studio Installer uitvoeren
- Selecteer Wijzigen
- Controleer de ontwikkelworkload van de Visual Studio-extensie .
- Open het ontwikkelknooppunt van de Visual Studio-extensie in de overzichtsstructuur.
- Schakel het selectievakje voor .NET Compiler Platform SDK in. U vindt deze laatste onder de optionele onderdelen.
Desgewenst wilt u ook dat de DGML-editor grafieken weergeeft in de visualisatie:
- Open het knooppunt Afzonderlijke onderdelen in de overzichtsstructuur.
- Schakel het selectievakje voor DGML-editor in
Installeren met behulp van het tabblad Afzonderlijke onderdelen van Visual Studio Installer
- Visual Studio Installer uitvoeren
- Selecteer Wijzigen
- Het tabblad Afzonderlijke onderdelen selecteren
- Schakel het selectievakje voor .NET Compiler Platform SDK in. Je vindt het bovenaan in de sectie Compilers, bouwtools en runtimes.
Desgewenst wilt u ook dat de DGML-editor grafieken weergeeft in de visualisatie:
- 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 analyzer.
- Waarschuwingen en aanbevelingen van de rapportenanalysator.
- Implementeer de codeoplossing om aanbevelingen te accepteren.
- Verbeter de analyse via eenheidstests.
De oplossing maken
- Kies in Visual Studio Bestand > nieuw > project... om het dialoogvenster Nieuw project weer te geven.
- Kies Analyse met codefix (.NET Standard) onder Visual C#>-uitbreidbaarheid.
- Geef uw project de naam MakeConst en klik op OK.
Opmerking
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 analyse met de sjabloon voor codefix worden vijf projecten gemaakt:
- MakeConst, die de analyse bevat.
- MakeConst.CodeFixes, die de codefix bevat.
- MakeConst.Package, dat wordt gebruikt om een NuGet-pakket te produceren voor de analyse- en codeoplossing.
- MakeConst.Test, een eenheidstestproject.
- MakeConst.Vsix, het standaard opstartproject dat een tweede exemplaar van Visual Studio start dat uw nieuwe analyse heeft geladen. Druk op F5 om het VSIX-project te starten.
Opmerking
Analyzers moeten zich richten op .NET Standard 2.0 omdat ze kunnen worden uitgevoerd in .NET Core-omgeving (opdrachtregelversies) en .NET Framework-omgeving (Visual Studio).
Aanbeveling
Wanneer u uw analyse uitvoert, start u een tweede exemplaar van Visual Studio. Deze tweede kopie maakt gebruik van een andere register hive voor het opslaan van instellingen. Hiermee kunt u onderscheid maken tussen de visuele instellingen in de twee kopieën van Visual Studio. U kunt een ander thema kiezen voor de experimentele uitvoering van Visual Studio. Daarnaast moet u uw instellingen niet synchroniseren en niet inloggen op uw Visual Studio-account met de experimentele versie van Visual Studio. Hierdoor blijven de instellingen anders.
De hive omvat niet alleen de analyzer die wordt ontwikkeld, maar ook alle eerder geopende analyzers. Als u Roslyn Hive opnieuw wilt instellen, moet u deze handmatig verwijderen uit %LocalAppData%\Microsoft\VisualStudio. De mapnaam van Roslyn hive zal eindigen in `Roslyn
`, bijvoorbeeld `16.0_9ae182f9Roslyn
`. Houd er rekening mee dat u de oplossing mogelijk moet opschonen en opnieuw moet opbouwen na het verwijderen van de hive.
In het tweede Visual Studio-exemplaar dat u zojuist hebt gestart, maakt u een nieuw C#-consoletoepassingsproject (elk doelframework werkt -- analyzers 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 elke typenaam met kleine letters wordt gewijzigd in hoofdletters. U kunt klikken op de gloeilamp die 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 keert 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 codefix 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.
Aanbeveling
Analyse-eenheidstests zijn een geweldig hulpmiddel wanneer u weet welke codeconstructies uw analyse zouden moeten activeren en welke dat niet zouden moeten doen. Het laden van uw analyse in een andere kopie van Visual Studio is een uitstekend hulpmiddel om constructies te ontdekken en te vinden waarover u mogelijk nog niet hebt nagedacht.
In deze zelfstudie schrijft u een analyse die rapporteert aan de gebruiker eventuele lokale variabeledeclaraties die kunnen worden geconverteerd naar lokale constanten. Denk bijvoorbeeld aan 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 kan worden geconstateerd, is complex, waarbij syntactische analyse, analyse van constanten van de initialisatie-expressie en gegevensstroomanalyse nodig is om te waarborgen dat er nooit naar de variabele 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 aan 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 waarop deze werkt. - Elke diagnostische analyse moet (direct of indirect) worden afgeleid van de DiagnosticAnalyzer klasse.
De sjabloon bevat ook de basisfuncties die deel uitmaken van een analyse:
- Acties registreren. De acties vertegenwoordigen codewijzigingen die uw analyzer zouden moeten triggeren om code te controleren 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 uw override van de DiagnosticAnalyzer.Initialize(AnalysisContext)-methode. In deze zelfstudie bezoekt u syntaxknooppunten om lokale declaraties te zoeken en te zien welke van die declaraties constante waarden hebben. Als een declaratie constant kan zijn, maakt en rapporteert uw analyse een diagnose.
De eerste stap is het bijwerken van de registratieconstanten en Initialize
-methode, zodat deze constanten uw "Make Const" analyzer aangeven. De meeste tekenreeksconstanten worden gedefinieerd in het tekenreeksresourcebestand. U moet die oefening volgen om de lokalisatie te vereenvoudigen. Open het bestand Resources.resx voor het MakeConst Analyzer-project. Hiermee wordt de resource-editor weergegeven. Werk de stringbronnen als volgt bij:
- Wijzig
AnalyzerDescription
naar Variables that are not modified should be made constants.. - Wijzig
AnalyzerMessageFormat
in "Variable '{0}' can be made constant". - Wijzig
AnalyzerTitle
naar "Variable can be made constant".
Wanneer u klaar bent, zou de resource-editor moeten worden weergegeven zoals 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 handelt 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 SyntaxKind.LocalDeclarationStatement onderzocht, niet SymbolKind.NamedType instructies. U ziet dat AnalyzeNode
rode kriebels onder zich heeft. De code die u zojuist hebt toegevoegd, verwijst naar een AnalyzeNode
methode die niet is gedeclareerd. Declareer die methode met behulp van de volgende code:
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
}
Wijzig het Category
in 'Usage' in MakeConstAnalyzer.cs , zoals wordt weergegeven in de volgende code:
private const string Category = "Usage";
Identificeer lokale declaraties die mogelijk const zijn
Het is tijd om de eerste versie van de AnalyzeNode
methode te schrijven. Er moet worden gezocht naar één lokale declaratie die const
zou kunnen zijn, maar niet is, zoals de volgende code:
int x = 0;
Console.WriteLine(x);
De eerste stap is het vinden van lokale declaraties. Voeg de volgende code toe aan AnalyzeNode
in MakeConstAnalyzer.cs:
var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;
Deze cast slaagt altijd omdat je analyzer geregistreerd is voor wijzigingen aan lokale declaraties, en alleen lokale declaraties. Geen ander knooppunttype activeert een aanroep naar uw AnalyzeNode
methode. Controleer vervolgens de declaratie op eventuele const
modifiers. Als u ze vindt, keert u onmiddellijk terug. Met de volgende code wordt gezocht naar eventuele const
modifiers 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 zorgt 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 declaratie van de lokale variabele kan worden uitgevoerd const
. A Microsoft.CodeAnalysis.SemanticModel vertegenwoordigt alle semantische informatie in één bronbestand. Meer informatie vindt u in het artikel waarin semantische modellen worden behandeld. U gebruikt de Microsoft.CodeAnalysis.SemanticModel om een dataflowanalyse 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 variabele op te halen en controleer of deze niet is opgenomen in de DataFlowAnalysis.WrittenOutside verzameling van de gegevensstroomanalyse. Voeg de volgende code toe aan het einde van de AnalyzeNode
methode:
// 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 kan daarom worden gemaakt const
. Het is tijd om het diagnostische proces te verbeteren. 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 uw 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);
De gloeilamp moet worden weergegeven en uw analyser moet een diagnose geven. Afhankelijk van uw versie van Visual Studio ziet u echter het volgende:
- De gloeilamp, die nog steeds gebruikmaakt van de door de sjabloon gegenereerde codefix, geeft aan dat het naar hoofdletters kan worden gewijzigd.
- Er is een bannerbericht bovenaan de editor dat aangeeft dat de 'MakeConstCodeFixProvider' een fout is tegengekomen en is uitgeschakeld. Dit komt doordat de codeoplossingsprovider 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 codeoplossing schrijven
Een analyse kan een of meer codeoplossingen bieden. Een codeoplossing definieert een bewerking waarmee het gemelde 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 verander CodeFixTitle
naar "Make constant".
Open het MakeConstCodeFixProvider.cs bestand dat door de sjabloon is toegevoegd. Deze codeoplossing is al verbonden met de diagnostische id die uw diagnostische analyser genereert, maar implementeert nog niet de juiste code transformatie.
Verwijder vervolgens de MakeUppercaseAsync
methode. Het is niet meer van toepassing.
Alle codefixproviders zijn afgeleid van CodeFixProvider. Ze overschrijven CodeFixProvider.RegisterCodeFixesAsync(CodeFixContext) allemaal om beschikbare codecorrecties te rapporteren. Wijzig in RegisterCodeFixesAsync
het voorouder knooppunttype dat u zoekt naar een LocalDeclarationStatementSyntax om overeen te komen met de diagnose:
var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<LocalDeclarationStatementSyntax>().First();
Wijzig vervolgens de laatste regel om een codefix te registreren. Uw oplossing maakt een nieuw document dat het resultaat is van het toevoegen van de const
wijziging 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);
Je zult rode golflijntjes zien in de code die je net hebt toegevoegd rond 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)
{
}
Uw nieuwe MakeConstAsync
-methode zal het Document dat het bronbestand van de gebruiker vertegenwoordigt, transformeren in een nieuw Document dat nu een const
-declaratie bevat.
U maakt een nieuw const
trefwoordtoken dat u aan de voorzijde van de declaratie-instructie wilt invoegen. Verwijder eerst alle voorafgaande trivia uit het eerste token van de declaratieverklaring en voeg deze toe aan het const
token. Voeg de volgende code toe aan de methode MakeConstAsync
:
// 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. Het opmaken van uw wijzigingen zodat deze overeenkomen met bestaande code, zorgt voor een betere ervaring. 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);
Voor deze code is een nieuwe naamruimte vereist. Voeg de volgende using
instructie toe aan het begin van het bestand:
using Microsoft.CodeAnalysis.Formatting;
De laatste stap is het aanbrengen van uw bewerking. Er zijn drie stappen voor dit proces:
- Verkrijg een verwijzing naar het bestaande document.
- Maak een nieuw document door de bestaande declaratie te vervangen door de nieuwe declaratie.
- Het nieuwe document retourneren.
Voeg de volgende code toe aan het einde van de MakeConstAsync
methode:
// 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 code-update is klaar om te testen. 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 main-methode. U ziet dat ze als waarschuwingen worden gerapporteerd, zoals hieronder.
Je hebt veel vooruitgang geboekt. Er zijn kronkelingen onder de verklaringen die kunnen worden gedaan const
. Maar er is nog werk te doen. Dit werkt prima als u toevoegt const
aan de declaraties die beginnen met i
, dan j
en ten slotte k
. Maar als u de const
wijzigingsfunctie in een andere volgorde toevoegt, beginnend met k
, maakt uw analyse fouten: k
kan 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 kunt afhandelen waarop variabelen kunnen worden gedeclareerd en geïnitialiseerd.
Moduletests bouwen
Uw analyser en codefix werken aan een eenvoudig geval van een enkele declaratie die const gemaakt kan worden. Er zijn talloze mogelijke verklaringsverklaringen waarbij deze implementatie fouten maakt. Je zult deze gevallen aanpakken door te werken met de unittestbibliotheek die door het sjabloon wordt meegeleverd. Het is veel sneller dan herhaaldelijk een tweede exemplaar van Visual Studio te openen.
Open het bestand MakeConstUnitTests.cs in het eenheidstestproject. De sjabloon heeft twee tests gemaakt die de twee algemene patronen voor een analyzer- en codefix-unittest volgen.
TestMethod1
toont het patroon voor een test dat 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 codefix.
De sjabloon maakt gebruik van Microsoft.CodeAnalysis.Testing-pakketten voor eenheidstests.
Aanbeveling
De testbibliotheek ondersteunt een speciale syntaxis voor markeringen, waaronder de volgende:
-
[|text|]
: geeft aan dat een diagnose wordt gerapporteerd voortext
. Standaard mag dit formulier alleen worden gebruikt voor het testen van analyzers met precies éénDiagnosticDescriptor
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 er zeker van te zijn dat deze is geslaagd. Open in Visual Studio de Test Explorer door Test>Windows>Test Explorer te selecteren. Selecteer Vervolgens Alles uitvoeren.
Tests maken voor geldige declaraties
Over het algemeen moeten analyses zo snel mogelijk afsluiten met minimale inspanning. Visual Studio roept geregistreerde analyses aan terwijl de gebruiker code bewerkt. Reactiesnelheid is een belangrijke vereiste. Er zijn verschillende testcases voor code die uw diagnose niet mogen verhogen. Uw analyse verwerkt al verschillende van deze tests. Voeg de volgende testmethoden toe om deze gevallen 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++);
}
}
");
}
[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);
}
}
");
}
Deze tests worden doorstaan omdat uw analyse al deze voorwaarden verwerkt:
- Variabelen die na de initialisatie zijn toegewezen, worden gedetecteerd door gegevensstroomanalyse.
- Declaraties die al
const
zijn, worden eruit gefilterd door het trefwoordconst
te controleren. - Declaraties zonder initialisatiefunctie worden verwerkt door de gegevensstroomanalyse waarmee toewijzingen buiten de declaratie worden gedetecteerd.
Voeg vervolgens testmethoden toe voor voorwaarden die u nog niet hebt verwerkt:
Declaraties waarbij de initialisatiefunctie geen constante is, omdat ze geen compilatieconstanten 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 testcase-tekenreeksconstante:
[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
kan niet. Daarom kan deze const declaratie niet worden gemaakt.
Voer uw tests opnieuw uit en u ziet dat deze laatste twee testcases mislukken.
Uw analyse bijwerken om juiste declaraties te negeren
U hebt enkele verbeteringen nodig aan de methode van AnalyzeNode
uw analyse om code te filteren die overeenkomt met deze voorwaarden. Ze 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 waarin alle variabelen die in dezelfde instructie zijn gedeclareerd, worden onderzocht. - 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 initializer 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 en u ziet dat ze allemaal zijn geslaagd.
Geef de laatste finishing touch
U bent bijna klaar. Er zijn nog enkele voorwaarden voor uw analyser om te 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 AnalyzeNode
methode van de diagnostische analyzer controleert niet of de constante waarde kan worden omgezet in een variabeletype. De huidige implementatie converteert dus graag een onjuiste declaratie, bijvoorbeeld 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""|};
}
}
");
}
Daarnaast worden verwijzingstypen niet goed verwerkt. De enige constante waarde die is toegestaan voor een verwijzingstype is null
, behalve in het geval van System.String, waarmee letterlijke tekenreeksen worden toegestaan. Met andere woorden, const string s = "abc"
is legaal, maar const object s = "abc"
niet. Met dit codefragment wordt die voorwaarde gecontroleerd:
[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 een andere 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"";
}
}
");
}
Ten slotte, als een variabele wordt gedeclareerd met het var
trefwoord, doet de codeoplossing het verkeerde en genereert een const var
declaratie, die niet wordt ondersteund door de C#-taal. Om deze fout op te lossen, moet de codefix 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 bugs worden aangepakt 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 initialisatiemiddelen van de lokale declaratie wordt gecontroleerd om ervoor te zorgen dat deze worden toegewezen met constante waarden. Onmiddellijk vóór de eerste foreach-lus roept u context.SemanticModel.GetTypeInfo()
aan om gedetailleerde informatie op te halen over het gedeclareerde type van de lokale variabele.
TypeSyntax variableTypeName = localDeclaration.Declaration.Type;
ITypeSymbol variableType = context.SemanticModel.GetTypeInfo(variableTypeName, context.CancellationToken).ConvertedType;
Controleer vervolgens in uw foreach
lus elke initialisatiefunctie om ervoor te zorgen dat deze converteerbaar is naar het variabele type. Voeg de volgende controle toe nadat u ervoor hebt gezorgd dat de initializer een constante moet zijn:
// 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 afsluitende accolade 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 wat meer code schrijven in uw codefixprovider 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 wettelijk om te verklaren
const var
. - Zorg ervoor dat dit
var
geen typenaam is in dit programma. (Zo ja,const var
is legaal). - Vereenvoudig de volledige typenaam
Dat klinkt als veel code. Dat is het niet. Vervang de regel die declareert en initialiseert newLocal
door de volgende code. Het gaat direct 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 kunnen gebruiken:
using Microsoft.CodeAnalysis.Simplification;
Voer uw tests uit en ze moeten allemaal slagen. Complimenteer 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, waarbij de Roslyn Preview-extensie is geladen.
- Maak in het tweede Visual Studio-exemplaar een nieuw C#-consoletoepassingsproject en voeg deze toe
int x = "abc";
aan de main-methode. 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 Main-methode. Vanwege de tweede foutoplossing moet er geen waarschuwing worden gerapporteerd. - Voeg ten slotte nog een lokale variabele toe die gebruikmaakt van het
var
trefwoord. U ziet een waarschuwing en er wordt een suggestie weergegeven onderaan aan de linkerkant. - Verplaats de editor-cursor naar de golvende onderstreping en druk op Ctrl+.. om de voorgestelde codeoplossing weer te geven. Wanneer u de codeoplossing 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 krabbeltjes bij de eerste twee variabelen. Voeg const
toe aan zowel i
als j
, en u krijgt een nieuwe waarschuwing op k
omdat het nu const
kan zijn.
Gefeliciteerd! U hebt uw eerste .NET Compiler Platform-extensie gemaakt die on-the-fly codeanalyses uitvoert om een probleem te detecteren en biedt een snelle oplossing om dit te corrigeren. 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 het voltooide voorbeeld in onze GitHub-opslagplaats met voorbeelden.