Oktatóanyag: Az első elemző és kódjavítás megírása

A .NET Compiler Platform SDK biztosítja azokat az eszközöket, amelyekre szüksége van az egyéni diagnosztikák (elemzők), a kódjavítások, a kód újrabontása és a C# vagy Visual Basic kódot megcélzó diagnosztikai letiltók létrehozásához. Az elemző olyan kódot tartalmaz, amely felismeri a szabály megsértését. A kódjavítás tartalmazza a szabálysértést javító kódot. A implementált szabályok lehetnek a kódszerkezettől a kódolási stíluson át az elnevezési konvenciókig és egyebekig. A .NET Fordítóplatform biztosítja az elemzés futtatásának keretrendszerét, miközben a fejlesztők kódot írnak, valamint a Visual Studio összes felhasználói felületi funkcióját a kód javításához: a szerkesztőben megjelenő hullámos megjelenítést, a Visual Studio hibalistájának feltöltését, a "villanykörte" javaslatok létrehozását és a javasolt javítások részletes előnézetének megjelenítését.

Ebben az oktatóanyagban egy elemző és egy kapcsolódó kódjavítás létrehozását ismerheti meg a Roslyn API-k használatával. Az elemzővel forráskódelemzést végezhet, és jelentheti a problémát a felhasználónak. Szükség esetén kódjavítás társítható az elemzőhöz, amely a felhasználó forráskódjának módosítását jelzi. Ez az oktatóanyag létrehoz egy elemzőt, amely megkeresi azokat a helyi változódeklarációkat, amelyek deklarálhatók a const módosítóval, de nem. A kísérő kódjavítás módosítja ezeket a deklarációkat a const módosító hozzáadásához.

Előfeltételek

A .NET Compiler Platform SDK-t a Visual Studio telepítőjével kell telepítenie:

Telepítési utasítások – Visual Studio Installer

A .NET Compiler Platform SDK-t kétféleképpen keresheti meg a Visual Studio Installerben:

Telepítés a Visual Studio Telepítő – Számítási feladatok nézetével

A .NET Compiler Platform SDK nincs automatikusan kiválasztva a Visual Studio bővítményfejlesztési számítási feladatának részeként. Választható összetevőként kell kiválasztania.

  1. A Visual Studio Installer futtatása
  2. Válassza a Módosítás lehetőséget
  3. Ellenőrizze a Visual Studio bővítményfejlesztési számítási feladatát.
  4. Nyissa meg a Visual Studio bővítményfejlesztési csomópontját az összefoglaló fában.
  5. Jelölje be a .NET Fordítóplatform SDK jelölőnégyzetét. Az utolsót az opcionális összetevők alatt találja.

Igény szerint azt is szeretné, hogy a DGML-szerkesztő gráfokat jelenítsen meg a vizualizációban:

  1. Nyissa meg az Egyes összetevők csomópontot az összegző fában.
  2. Jelölje be a DGML-szerkesztő jelölőnégyzetét

Telepítés a Visual Studio Installer – Egyes összetevők lap használatával

  1. A Visual Studio Installer futtatása
  2. Válassza a Módosítás lehetőséget
  3. Válassza az Egyes összetevők lapot
  4. Jelölje be a .NET Fordítóplatform SDK jelölőnégyzetét. Ezt a Fordítók, buildelési eszközök és futtatókörnyezetek szakasz tetején találja.

Igény szerint azt is szeretné, hogy a DGML-szerkesztő gráfokat jelenítsen meg a vizualizációban:

  1. Jelölje be a DGML-szerkesztő jelölőnégyzetét. Ezt a Kódeszközök szakaszban találja.

Az elemző létrehozásának és érvényesítésének több lépése is van:

  1. Hozza létre a megoldást.
  2. Regisztrálja az elemző nevét és leírását.
  3. Jelentéselemző figyelmeztetései és javaslatai.
  4. Implementálja a kódjavítást a javaslatok elfogadásához.
  5. Az elemzés javítása egységtesztekkel.

A megoldás létrehozása

  • A Visual Studióban válassza az Új > projekt fájlja>... lehetőséget az Új projekt párbeszédpanel megjelenítéséhez.
  • A Visual C# > bővíthetősége területen válassza az Analyzer with code fix (.NET Standard) (Kódjavítással rendelkező elemző (.NET Standard) lehetőséget.
  • Adja a projektnek a "MakeConst" nevet, majd kattintson az OK gombra.

Megjegyzés

Fordítási hiba jelenhet meg (MSB4062: A CompareBuildTaskVersion feladat nem tölthető be)." A probléma megoldásához frissítse a nuGet-csomagokat a megoldásban a NuGet-csomagkezelővel, vagy használja Update-Package a Csomagkezelő konzol ablakában.

Az elemzősablon felfedezése

A kódjavítási sablonnal rendelkező elemző öt projektet hoz létre:

  • Az elemzőt tartalmazó MakeConst.
  • MakeConst.CodeFixes, amely tartalmazza a kódjavítást.
  • MakeConst.Package, amely nuGet-csomag létrehozására szolgál az elemzőhöz és a kódjavításhoz.
  • MakeConst.Test, amely egy egységtesztelési projekt.
  • MakeConst.Vsix, amely az alapértelmezett indítási projekt, amely elindítja a Visual Studio egy második példányát, amely betöltötte az új elemzőt. Nyomja le az F5 billentyűt a VSIX-projekt elindításához.

Megjegyzés

Az elemzőknek a .NET Standard 2.0-t kell célozniuk, mert .NET Core környezetben (parancssori buildekben) és .NET-keretrendszer környezetben (Visual Studio) futtathatók.

Tipp

Az elemző futtatásakor elindítja a Visual Studio egy második példányát. Ez a második példány egy másik beállításjegyzék-struktúra használatával tárolja a beállításokat. Ez lehetővé teszi a Visual Studio két példányának vizualizációs beállításainak megkülönböztetésére. Választhat egy másik témát a Visual Studio kísérleti futtatásához. Emellett ne barangoljon a beállítások között, és ne jelentkezzen be a Visual Studio-fiókjába a Visual Studio kísérleti futtatásával. Így a beállítások másként lesznek.

A struktúra nem csak a fejlesztés alatt álló elemzőt, hanem a korábban megnyitott elemzőket is tartalmazza. A Roslyn hive alaphelyzetbe állításához manuálisan kell törölnie a %LocalAppData%\Microsoft\VisualStudio mappából. A Roslyn hive mappaneve például a következő lesz Roslyn: 16.0_9ae182f9Roslyn. Vegye figyelembe, hogy a hive törlése után szükség lehet a megoldás tisztítására és újraépítésére.

Az imént elindított második Visual Studio-példányban hozzon létre egy új C#-konzolalkalmazás-projektet (bármely célkeret működni fog – az elemzők a forrásszinten működnek).) Vigye az egérmutatót a jogkivonat fölé hullámos aláhúzással, és megjelenik az elemző által biztosított figyelmeztető szöveg.

A sablon létrehoz egy elemzőt, amely figyelmeztetést jelent minden olyan típusdeklaráción, ahol a típusnév kisbetűket tartalmaz, az alábbi ábrán látható módon:

Az Elemző jelentéskészítési figyelmeztetése

A sablon egy kódjavítást is biztosít, amely minden kisbetűs karaktert tartalmazó típusnevet módosít az összes nagybetűre. A javasolt módosítások megtekintéséhez kattintson a figyelmeztetéssel megjelenő villanykörtére. A javasolt módosítások elfogadása frissíti a típus nevét és a megoldásban az adott típusra mutató összes hivatkozást. Most, hogy látta a kezdeti elemzőt működés közben, zárja be a második Visual Studio-példányt, és térjen vissza az elemzőprojekthez.

Nem kell elindítania a Visual Studio második példányát, és új kódot létrehoznia az elemző minden módosításának teszteléséhez. A sablon egy egységtesztelési projektet is létrehoz Önnek. Ez a projekt két tesztet tartalmaz. TestMethod1 egy olyan teszt jellemző formátumát jeleníti meg, amely diagnosztika aktiválása nélkül elemzi a kódot. TestMethod2 megjeleníti a diagnosztikát kiváltó teszt formátumát, majd alkalmazza a javasolt kódjavítást. Az elemző és a kódjavítás összeállítása során különböző kódstruktúrákra vonatkozó teszteket fog írni a munka ellenőrzéséhez. Az elemzők egységtesztjei sokkal gyorsabbak, mint interaktívan tesztelni őket a Visual Studióval.

Tipp

Az elemzőegység-tesztek kiváló eszköznek számítanak, ha tudja, hogy milyen kódszerkezeteket kell használnia, és mit nem szabad aktiválnia az elemzőnek. Az elemző betöltése a Visual Studio egy másik példányába nagyszerű eszköz az olyan szerkezetek felfedezéséhez és kereséséhez, amelyekről még nem is gondoltak.

Ebben az oktatóanyagban egy elemzőt fog írni, amely jelentést készít a felhasználónak a helyi állandókká alakítható helyi változódeklarációkról. Vegyük például a következő kódot:

int x = 0;
Console.WriteLine(x);

A fenti kódban a rendszer állandó értéket rendel hozzá, x és soha nem módosítja. Deklarálható a const módosító használatával:

const int x = 0;
Console.WriteLine(x);

Az elemzés annak megállapítására, hogy egy változó konstanssá tehető-e, szintaktikai elemzést, az inicializáló kifejezés állandó elemzését és adatfolyam-elemzést igényel annak biztosításához, hogy a változó soha ne legyen beírva. A .NET Fordítóplatform olyan API-kat biztosít, amelyek megkönnyítik az elemzés végrehajtását.

Elemzőregisztrációk létrehozása

A sablon létrehozza a kezdeti DiagnosticAnalyzerosztályt a MakeConstAnalyzer.cs fájlban. Ez a kezdeti elemző minden elemző két fontos tulajdonságát jeleníti meg.

  • Minden diagnosztikai elemzőnek meg kell adnia egy [DiagnosticAnalyzer] attribútumot, amely leírja, hogy milyen nyelven működik.
  • Minden diagnosztikai elemzőnek (közvetlenül vagy közvetve) a DiagnosticAnalyzer osztályból kell származnia.

A sablon az elemzők alapvető funkcióit is megjeleníti:

  1. Műveletek regisztrálása. A műveletek olyan kódmódosításokat jelölnek, amelyeknek aktiválnia kell az elemzőt a kód megsértéseinek vizsgálatához. Amikor a Visual Studio egy regisztrált műveletnek megfelelő kódrészleteket észlel, meghívja az elemző regisztrált metódusát.
  2. Diagnosztikát hozhat létre. Amikor az elemző szabálysértést észlel, létrehoz egy diagnosztikai objektumot, amelyet a Visual Studio a felhasználó értesítésére használ a szabálysértésről.

A műveleteket a metódus felülbírálásában regisztrálja DiagnosticAnalyzer.Initialize(AnalysisContext) . Ebben az oktatóanyagban felkeresi a szintaxiscsomópontok helyi deklarációit, és láthatja, hogy ezek közül melyek rendelkeznek állandó értékekkel. Ha egy deklaráció állandó lehet, az elemző létrehoz és jelent egy diagnosztikát.

Az első lépés a regisztrációs állandók és Initialize a metódus frissítése, hogy ezek az állandók a "Make Const" elemzőt jelezzék. A sztringállandók többsége a sztringerőforrás-fájlban van definiálva. A könnyebb honosítás érdekében kövesse ezt a gyakorlatot. Nyissa meg a MakeConst analyzer projekt Resources.resx fájljának megnyitását. Ekkor megjelenik az erőforrás-szerkesztő. Frissítse a sztringerőforrásokat az alábbiak szerint:

  • Váltson AnalyzerDescription "Variables that are not modified should be made constants."-ra.
  • Váltson AnalyzerMessageFormat "Variable '{0}' can be made constant"-ra.
  • Váltson AnalyzerTitle "Variable can be made constant"-ra.

Ha végzett, az erőforrás-szerkesztőnek az alábbi ábrán látható módon kell megjelennie:

Sztringerőforrások frissítése

A többi módosítás az elemzőfájlban van. Nyissa meg a MakeConstAnalyzer.cs fájlt a Visual Studióban. Módosítsa a regisztrált műveletet a szimbólumokon működő műveletről a szintaxist használó műveletre. A metódusban MakeConstAnalyzerAnalyzer.Initialize keresse meg azt a sort, amely regisztrálja a műveletet a szimbólumokon:

context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);

Cserélje le a következő sorra:

context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.LocalDeclarationStatement);

A módosítás után törölheti a metódust AnalyzeSymbol . Ez az elemző a SyntaxKind.LocalDeclarationStatement, nem SymbolKind.NamedType az utasításokat vizsgálja. Figyelje meg, hogy AnalyzeNode alatta piros hullámos hullám van. Az imént hozzáadott kód egy AnalyzeNode még nem deklarált metódusra hivatkozik. Deklarálja ezt a metódust a következő kóddal:

private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
}

Módosítsa a Category "Usage" értéket a MakeConstAnalyzer.cs fájlban az alábbi kódban látható módon:

private const string Category = "Usage";

Olyan helyi deklarációk keresése, amelyek konstansok lehetnek

Ideje megírni a metódus első verzióját AnalyzeNode . Egyetlen helyi deklarációt kell keresnie, amely lehet const , de nem, mint az alábbi kód:

int x = 0;
Console.WriteLine(x);

Az első lépés a helyi deklarációk megkeresése. Adja hozzá a következő kódot a AnalyzeNodeMakeConstAnalyzer.cs fájlhoz:

var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;

Ez a leadás mindig sikeres, mert az elemző regisztrált a helyi deklarációk módosítására, és csak a helyi deklarációkra. Más csomóponttípus nem indítja el a metódus hívását AnalyzeNode . Ezután ellenőrizze a módosítók deklarációját const . Ha megtalálta őket, azonnal térjen vissza. A következő kód a helyi deklarációban szereplő módosítókat keresi const :

// make sure the declaration isn't already const:
if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
{
    return;
}

Végül ellenőriznie kell, hogy a változó lehet-e const. Ez azt jelenti, hogy az inicializálás után soha ne rendelje hozzá.

A használatával szemantikai elemzést fog végezni.SyntaxNodeAnalysisContext Az argumentum használatával context meghatározhatja, hogy a helyi változó deklarációja végrehajtható-e const. Az A Microsoft.CodeAnalysis.SemanticModel egyetlen forrásfájlban található összes szemantikai információt jelöli. A szemantikai modelleket ismertető cikkben többet is megtudhat. A használatával Microsoft.CodeAnalysis.SemanticModel adatfolyam-elemzést végezhet a helyi deklarációs utasításban. Ezután ennek az adatfolyam-elemzésnek az eredményeivel biztosíthatja, hogy a helyi változó máshol ne legyen megírva új értékkel. Hívja meg a GetDeclaredSymbol bővítménymetódust, hogy lekérje a ILocalSymbol változót, és ellenőrizze, hogy nincs-e benne az DataFlowAnalysis.WrittenOutside adatfolyam-elemzés gyűjteményében. Adja hozzá a következő kódot a AnalyzeNode metódus végéhez:

// 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;
}

Az imént hozzáadott kód biztosítja, hogy a változó ne legyen módosítva, és így létre is hozható const. Itt az ideje a diagnosztikának. Adja hozzá a következő kódot az utolsó sorként a következőben AnalyzeNode:

context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation(), localDeclaration.Declaration.Variables.First().Identifier.ValueText));

Az előrehaladás ellenőrzéséhez nyomja le az F5 billentyűt az elemző futtatásához. Betöltheti a korábban létrehozott konzolalkalmazást, majd hozzáadhatja a következő tesztkódot:

int x = 0;
Console.WriteLine(x);

Megjelenik a villanykörte, és az elemzőnek diagnosztikát kell jelentenie. A Visual Studio verziójától függően azonban a következőt fogja látni:

  • Az izzó, amely továbbra is a sablon által létrehozott kódjavítást használja, azt fogja mondani, hogy nagybetűssé tehető.
  • A szerkesztő tetején egy szalagcímes üzenet, amely szerint a MakeConstCodeFixProvider hibát észlelt, és le lett tiltva." Ennek az az oka, hogy a kódjavítási szolgáltatót még nem módosították, és továbbra is elemek helyett LocalDeclarationStatementSyntax elemeket keresTypeDeclarationSyntax.

A következő szakasz a kódjavítás írását ismerteti.

A kódjavítás írása

Az elemzők egy vagy több kódjavítást is biztosíthatnak. A kódjavítások olyan szerkesztést határoznak meg, amely megoldja a jelentett problémát. A létrehozott elemzőhöz megadhat egy kódjavítást, amely beszúrja a const kulcsszót:

- int x = 0;
+ const int x = 0;
Console.WriteLine(x);

A felhasználó a szerkesztő villanykörte felhasználói felületén választja ki, és a Visual Studio módosítja a kódot.

Nyissa meg a CodeFixResources.resx fájlt, és váltson CodeFixTitle "Make constant"-ra.

Nyissa meg a sablon által hozzáadott MakeConstCodeFixProvider.cs fájlt. Ez a kódjavítás már be van állítva a diagnosztikai elemző által létrehozott diagnosztikai azonosítóhoz, de még nem implementálja a megfelelő kódátalakítást.

Ezután törölje a metódust MakeUppercaseAsync . Ez már nem érvényes.

Az összes kódjavítási szolgáltató a forrásból CodeFixProviderszármazik. Mindegyik felülbírálja CodeFixProvider.RegisterCodeFixesAsync(CodeFixContext) a rendelkezésre álló kódjavítások jelentését. A fájlban RegisterCodeFixesAsyncmódosítsa a keresett őscsomóponttípust a LocalDeclarationStatementSyntax diagnosztikának megfelelőre:

var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<LocalDeclarationStatementSyntax>().First();

Ezután módosítsa az utolsó sort a kódjavítás regisztrálásához. A javítás létrehoz egy új dokumentumot, amely a const módosító meglévő deklarációhoz való hozzáadását eredményezi:

// 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);

A szimbólumhoz MakeConstAsynchozzáadott kódban piros hullámos hullámokat fog látni. Adjon hozzá egy deklarációt MakeConstAsync a következő kódhoz hasonlóan:

private static async Task<Document> MakeConstAsync(Document document,
    LocalDeclarationStatementSyntax localDeclaration,
    CancellationToken cancellationToken)
{
}

Az új MakeConstAsync metódus átalakítja a Document felhasználó forrásfájlját egy új, deklarációt const tartalmazó fájlláDocument.

Létre kell hoznia egy új const kulcsszó-jogkivonatot a deklarációs utasítás elejére. Ügyeljen arra, hogy először távolítsa el a kezdő triviálisokat a deklarációs utasítás első jogkivonatából, és csatolja azt a const jogkivonathoz. Adja hozzá a következő kódot a MakeConstAsync metódushoz:

// 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));

Ezután adja hozzá a const jogkivonatot a deklarációhoz a következő kóddal:

// 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);

Ezután formázza az új deklarációt a C# formázási szabályainak megfelelően. A módosítások meglévő kóddal való formázása jobb élményt nyújt. Adja hozzá a következő utasítást közvetlenül a meglévő kód után:

// Add an annotation to format the new local declaration.
LocalDeclarationStatementSyntax formattedLocal = newLocal.WithAdditionalAnnotations(Formatter.Annotation);

Ehhez a kódhoz új névtér szükséges. Adja hozzá a következő using irányelvet a fájl elejéhez:

using Microsoft.CodeAnalysis.Formatting;

Az utolsó lépés a szerkesztés. A folyamatnak három lépése van:

  1. Fogópont lekérése a meglévő dokumentumhoz.
  2. Hozzon létre egy új dokumentumot úgy, hogy lecseréli a meglévő deklarációt az új deklarációra.
  3. Adja vissza az új dokumentumot.

Adja hozzá a következő kódot a MakeConstAsync metódus végéhez:

// 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);

A kódjavítás készen áll a kipróbálásra. Az F5 billentyű lenyomásával futtassa az elemzőprojektet a Visual Studio egy második példányában. A második Visual Studio-példányban hozzon létre egy új C#-konzolalkalmazás-projektet, és adjon hozzá néhány állandó értékekkel inicializált helyi változódeklarációt a Main metódushoz. Látni fogja, hogy a jelentések figyelmeztetésként jelennek meg az alábbiak szerint.

Konstans figyelmeztetéseket készíthet

Sokat haladtál. A deklarációk alatt vannak hullámos kapcsolók, amelyek végrehajthatók const. De még van tennivaló. Ez jól működik, const ha hozzáadja a deklarációkat a következővel kezdődően i: , majd j végül k. Ha azonban a const módosítót egy másik sorrendben adja hozzá, kezdve a következővelk: az elemző hibát hoz létre: k nem deklarálható const, kivéve, ha ji mindkettő már constlétezik. Több elemzést kell végeznie annak érdekében, hogy kezelni tudja a változók deklarálható és inicializálható különböző módjait.

Egységtesztek összeállítása

Az elemző és a kódjavítás egyetlen deklaráció egyszerű esetén dolgozik, amelyet konstansként lehet megadni. Számos lehetséges nyilatkozat létezik, ahol ez a megvalósítás hibákat követ el. Ezeket az eseteket a sablon által írt egységteszttár használatával fogja kezelni. Sokkal gyorsabb, mint a Visual Studio második példányának ismételt megnyitása.

Nyissa meg a MakeConstUnitTests.cs fájlt az egységtesztelési projektben. A sablon két tesztet hozott létre, amelyek az elemző és a kódjavítási egység tesztelésének két gyakori mintáját követik. TestMethod1 egy teszt mintáját jeleníti meg, amely biztosítja, hogy az elemző ne jelentsen diagnosztikát, amikor nem kellene. TestMethod2 megjeleníti a diagnosztikát és a kódjavítás futtatásának mintáját.

A sablon a Microsoft.CodeAnalysis.Testing csomagokat használja az egységteszteléshez.

Tipp

A tesztelési kódtár egy speciális korrektúraszintaxist támogat, beleértve a következőket:

  • [|text|]: azt jelzi, hogy a diagnosztika a következőhöz texttartozik: . Alapértelmezés szerint ez az űrlap csak olyan elemzők tesztelésére használható, amely pontosan az DiagnosticDescriptor által DiagnosticAnalyzer.SupportedDiagnosticsbiztosított.
  • {|ExpectedDiagnosticId:text|}: azt jelzi, hogy a(z) diagnosztika IdExpectedDiagnosticId a következőhöz texttartozik: .

Cserélje le a sablonteszteket a MakeConstUnitTest osztályban a következő vizsgálati módszerre:

        [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);
    }
}
");
        }

Futtassa ezt a tesztet annak ellenőrzéséhez, hogy az megfelel-e. A Visual Studióban nyissa meg a Tesztböngészőt aWindows>Test Explorer tesztelése> lehetőség kiválasztásával. Ezután válassza az Összes futtatása lehetőséget.

Tesztek létrehozása érvényes deklarációkhoz

Általános szabály, hogy az elemzőknek a lehető leggyorsabban ki kell lépnie, minimális munkát végezve. A Visual Studio meghívja a regisztrált elemzőket, amikor a felhasználó módosítja a kódot. A válaszképesség kulcsfontosságú követelmény. A kódnak számos olyan tesztesete van, amelyek nem emelik a diagnosztikát. Az elemző már kezeli az egyik ilyen tesztet, az esetet, amikor egy változót az inicializálás után rendelnek hozzá. Adja hozzá a következő tesztmetódust az eset ábrázolásához:

        [TestMethod]
        public async Task VariableIsAssigned_NoDiagnostic()
        {
            await VerifyCS.VerifyAnalyzerAsync(@"
using System;

class Program
{
    static void Main()
    {
        int i = 0;
        Console.WriteLine(i++);
    }
}
");
        }

Ez a teszt is megfelel. Ezután adja hozzá a még nem kezelt feltételek tesztelési módszereit:

  • Deklarációk, amelyek már const, mert már konstansok:

            [TestMethod]
            public async Task VariableIsAlreadyConst_NoDiagnostic()
            {
                await VerifyCS.VerifyAnalyzerAsync(@"
    using System;
    
    class Program
    {
        static void Main()
        {
            const int i = 0;
            Console.WriteLine(i);
        }
    }
    ");
            }
    
  • Inicializáló nélküli deklarációk, mert nincs használható érték:

            [TestMethod]
            public async Task NoInitializer_NoDiagnostic()
            {
                await VerifyCS.VerifyAnalyzerAsync(@"
    using System;
    
    class Program
    {
        static void Main()
        {
            int i;
            i = 0;
            Console.WriteLine(i);
        }
    }
    ");
            }
    
  • Deklarációk, amelyekben az inicializáló nem állandó, mert nem lehetnek fordítási-idő állandók:

            [TestMethod]
            public async Task InitializerIsNotConstant_NoDiagnostic()
            {
                await VerifyCS.VerifyAnalyzerAsync(@"
    using System;
    
    class Program
    {
        static void Main()
        {
            int i = DateTime.Now.DayOfYear;
            Console.WriteLine(i);
        }
    }
    ");
            }
    

Ez még bonyolultabb lehet, mert a C# több deklarációt is lehetővé tesz egyetlen utasításként. Vegye figyelembe a következő teszteset-sztring-állandót:

        [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);
    }
}
");
        }

A változó i konstanssá tehető, de a változó j nem. Ezért ez a nyilatkozat nem tehető const deklarációvá.

Futtassa újra a teszteket, és látni fogja, hogy ezek az új tesztesetek sikertelenek.

Az elemző frissítése a helyes deklarációk figyelmen kívül hagyásához

Az elemző AnalyzeNode metódusának néhány továbbfejlesztésére van szüksége az ilyen feltételeknek megfelelő kód szűréséhez. Ezek mind kapcsolódó feltételek, így a hasonló változások minden feltételt kijavítanak. Végezze el a következő módosításokat:AnalyzeNode

  • A szemantikai elemzés egyetlen változódeklarációt vizsgált. Ennek a kódnak egy foreach hurokban kell lennie, amely megvizsgálja az ugyanabban az utasításban deklarált összes változót.
  • Minden deklarált változónak inicializálóval kell rendelkeznie.
  • Minden deklarált változó inicializálójának fordítási időállandónak kell lennie.

A metódusban AnalyzeNode cserélje le az eredeti szemantikai elemzést:

// 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 következő kódrészlettel:

// 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;
    }
}

Az első foreach hurok az egyes változódeklarációkat szintaktikai elemzéssel vizsgálja. Az első ellenőrzés garantálja, hogy a változó inicializálóval rendelkezik. A második ellenőrzés garantálja, hogy az inicializáló állandó. A második hurok az eredeti szemantikai elemzéssel rendelkezik. A szemantikai ellenőrzések külön ciklusban vannak, mert nagyobb hatással vannak a teljesítményre. Futtassa újra a teszteket, és az összes sikeresnek kell lennie.

Az utolsó polírozás hozzáadása

Már majdnem kész. Az elemzőnek még néhány feltételt kell kezelnie. A Visual Studio meghívja az elemzőket, miközben a felhasználó kódot ír. Gyakran előfordul, hogy a rendszer meghívja az elemzőt olyan kódhoz, amely nem fordítható le. A diagnosztikai elemző metódusa AnalyzeNode nem ellenőrzi, hogy az állandó érték átváltható-e a változó típusára. Így a jelenlegi implementáció boldogan átalakít egy helytelen deklarációt, például int i = "abc" egy helyi állandót. Adjon hozzá egy tesztmetódust ehhez az esethez:

        [TestMethod]
        public async Task DeclarationIsInvalid_NoDiagnostic()
        {
            await VerifyCS.VerifyAnalyzerAsync(@"
using System;

class Program
{
    static void Main()
    {
        int x = {|CS0029:""abc""|};
    }
}
");
        }

Emellett a referenciatípusok kezelése nem megfelelő. A referenciatípushoz nullcsak állandó érték adható meg, kivéve az esetet System.String, amely lehetővé teszi a sztringkonstansokat. Más szóval, const string s = "abc" jogi, de const object s = "abc" nem. Ez a kódrészlet a következő feltételt ellenőrzi:

        [TestMethod]
        public async Task DeclarationIsNotString_NoDiagnostic()
        {
            await VerifyCS.VerifyAnalyzerAsync(@"
using System;

class Program
{
    static void Main()
    {
        object s = ""abc"";
    }
}
");
        }

Ahhoz, hogy alapos legyen, hozzá kell adnia egy másik tesztet, hogy meggyőződjön arról, hogy konstans deklarációt hozhat létre egy sztringhez. A következő kódrészlet határozza meg a diagnosztika és a javítás alkalmazása utáni kódot is:

        [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"";
    }
}
");
        }

Végül, ha egy változó deklarálva van a var kulcsszóval, a kódjavítás helytelenül cselekszik, és létrehoz egy deklarációt const var , amelyet a C# nyelv nem támogat. A hiba kijavításához a kódjavításnak a var kulcsszót a kikövetkeztetett típus nevére kell cserélnie:

        [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"";
    }
}
");
        }

Szerencsére a fenti hibákat az imént megtanult technikákkal lehet elhárítani.

Az első hiba kijavításához először nyissa meg a MakeConstAnalyzer.cs fájlt, és keresse meg a foreach hurkot, ahol a helyi deklaráció inicializálóinak ellenőrzésekor győződjön meg arról, hogy állandó értékekkel vannak hozzárendelve. Közvetlenül az első foreach hurok előtt hívja meg a helyi context.SemanticModel.GetTypeInfo() deklarált típus részletes információinak lekéréséhez:

TypeSyntax variableTypeName = localDeclaration.Declaration.Type;
ITypeSymbol variableType = context.SemanticModel.GetTypeInfo(variableTypeName, context.CancellationToken).ConvertedType;

Ezután a foreach hurokon belül ellenőrizze az egyes inicializálókat, hogy az átváltható-e a változó típusára. Adja hozzá a következő ellenőrzést, miután meggyőződett arról, hogy az inicializáló állandó:

// 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;
}

A következő módosítás az utolsóra épül. Az első foreach hurok záró kapcsos zárójele előtt adja hozzá a következő kódot a helyi deklaráció típusának ellenőrzéséhez, ha az állandó sztring vagy null.

// 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;
}

Egy kicsit több kódot kell írnia a kódjavítási szolgáltatóba, hogy a kulcsszót a var megfelelő típusnévre cserélje. Térjen vissza a MakeConstCodeFixProvider.cs fájlhoz. A hozzáadni kívánt kód a következő lépéseket hajtja végre:

  • Ellenőrizze, hogy a deklaráció var deklaráció-e, és hogy a következő-e:
  • Hozzon létre egy új típust a kikövetkesztett típushoz.
  • Győződjön meg arról, hogy a típusdeklaráció nem alias. Ha igen, a deklarálása const vartörvényes.
  • Győződjön meg arról, hogy var ez nem egy típusnév ebben a programban. (Ha igen, const var jogi).
  • A teljes típusnév egyszerűsítése

Ez sok kódnak hangzik. Nem az. Cserélje le a deklarált és inicializálható newLocal sort a következő kódra. A következő inicializálása newModifiersután azonnal elindul:

// 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);

A típus használatához egy using direktívát kell hozzáadnia Simplifier :

using Microsoft.CodeAnalysis.Simplification;

Futtassa a teszteket, és mindegyiknek át kell mennie. Gratuláljon saját magának a kész elemző futtatásával. Nyomja le a CtrlF5billentyűkombinációt+ az elemzőprojekt a Visual Studio egy második példányában való futtatásához a Roslyn Preview bővítmény betöltésével.

  • A második Visual Studio-példányban hozzon létre egy új C#-konzolalkalmazás-projektet, és adja hozzá int x = "abc"; a Main metódushoz. Az első hibajavításnak köszönhetően nem kell figyelmeztetést jelenteni ehhez a helyi változódeklarációhoz (bár fordítóhiba történt a várt módon).
  • Ezután adja hozzá object s = "abc"; a Main metódust. A második hibajavítás miatt nem kell figyelmeztetést jelenteni.
  • Végül adjon hozzá egy másik helyi változót, amely a kulcsszót var használja. Megjelenik egy figyelmeztetés, és megjelenik egy javaslat a bal oldalon.
  • Vigye a szerkesztőt a hullámos aláhúzás fölé, és nyomja le a Ctrl billentyűt+. a javasolt kódjavítás megjelenítéséhez. A kódjavítás kiválasztásakor vegye figyelembe, hogy a var kulcsszó kezelése most már megfelelően történik.

Végül adja hozzá a következő kódot:

int i = 2;
int j = 32;
int k = i + j;

A módosítások után csak az első két változón kap piros hullámos váltógombokat. Adja hozzá const a és ja elemet isi, és új figyelmeztetést k kap, mert az most már lehet const.

Gratulálunk! Létrehozta az első .NET Fordítóplatform-bővítményt, amely menet közbeni kódelemzést végez a probléma észleléséhez, és gyors javítást biztosít a hiba elhárításához. Az út során számos olyan kód API-t tanult meg, amelyek a .NET Fordítóplatform SDK (Roslyn API-k) részét képezik. A GitHub-adattárban található mintamintákban ellenőrizheti a munkáját.

Egyéb erőforrások