Kurz: Napsání prvního analyzátoru a opravy kódu

Sada .NET Compiler Platform SDK poskytuje nástroje potřebné k vytvoření vlastní diagnostiky (analyzátorů), oprav kódu, refaktoringu kódu a diagnostických potlačovačů, které cílí na kód jazyka C# nebo Visual Basic. Analyzátor obsahuje kód, který rozpozná porušení pravidla. Oprava kódu obsahuje kód, který opravuje porušení. Implementované pravidla můžou být cokoli od struktury kódu přes styl kódování až po konvence vytváření názvů a další. .NET Compiler Platform poskytuje architekturu pro spouštění analýz, když vývojáři píší kód, a všechny funkce uživatelského rozhraní sady Visual Studio pro opravu kódu: zobrazení vlnovek v editoru, naplnění seznamu chyb sady Visual Studio, vytváření návrhů "žárovky" a zobrazení bohatého náhledu navrhovaných oprav.

V tomto kurzu prozkoumáte vytvoření analyzátoru a doprovodné opravy kódu pomocí rozhraní Roslyn API. Analyzátor je způsob, jak provést analýzu zdrojového kódu a oznámit uživateli problém. Volitelně může být k analyzátoru přidružena oprava kódu, která představuje změnu zdrojového kódu uživatele. Tento kurz vytvoří analyzátor, který najde deklarace místních proměnných, které by bylo možné deklarovat pomocí modifikátoru const , ale nejsou. Doprovodná oprava kódu upraví tyto deklarace tak, aby přidaly const modifikátor.

Požadavky

Sadu .NET Compiler Platform SDK budete muset nainstalovat prostřednictvím Instalační program pro Visual Studio:

Pokyny k instalaci – Instalační program pro Visual Studio

Existují dva různé způsoby, jak najít .NET Compiler Platform SDK v Instalační program pro Visual Studio:

Instalace pomocí zobrazení Instalační program pro Visual Studio – Úlohy

Sada .NET Compiler Platform SDK není automaticky vybrána jako součást úlohy vývoj rozšíření sady Visual Studio. Musíte ji vybrat jako volitelnou komponentu.

  1. Spustit Instalační program pro Visual Studio
  2. Vyberte Upravit.
  3. Projděte si úlohu vývoj rozšíření sady Visual Studio .
  4. Otevřete uzel Vývoj rozšíření sady Visual Studio ve stromu souhrnu.
  5. Zaškrtněte políčko u .NET Compiler Platform SDK. Najdete ho jako poslední pod volitelnými komponentami.

Volitelně můžete také chtít, aby editor DGML zobrazoval grafy ve vizualizéru:

  1. Otevřete uzel Jednotlivé komponenty ve stromu souhrnu.
  2. Zaškrtněte políčko editoru DGML.

Instalace pomocí karty Instalační program pro Visual Studio – jednotlivé komponenty

  1. Spustit Instalační program pro Visual Studio
  2. Vyberte Upravit.
  3. Vyberte kartu Jednotlivé komponenty .
  4. Zaškrtněte políčko u .NET Compiler Platform SDK. Najdete ho nahoře v části Kompilátory, nástroje sestavení a moduly runtime .

Volitelně můžete také chtít, aby editor DGML zobrazoval grafy ve vizualizéru:

  1. Zaškrtněte políčko Editor DGML. Najdete ho v části Nástroje pro kód .

Existuje několik kroků k vytvoření a ověření analyzátoru:

  1. Vytvořte řešení.
  2. Zaregistrujte název a popis analyzátoru.
  3. Upozornění a doporučení analyzátoru sestav.
  4. Implementujte opravu kódu pro příjem doporučení.
  5. Vylepšete analýzu pomocí testů jednotek.

Vytvoření řešení

  • V sadě Visual Studio zvolte Soubor > Nový > projekt... a zobrazte tak dialogové okno Nový projekt.
  • V části Rozšiřitelnost jazyka Visual C# >zvolte Analyzátor s opravou kódu (.NET Standard).
  • Projekt pojmenujte MakeConst a klikněte na OK.

Poznámka

Může se zobrazit chyba kompilace (MSB4062: Nelze načíst úlohu CompareBuildTaskVersion). Pokud chcete tento problém vyřešit, aktualizujte balíčky NuGet v řešení pomocí Správce balíčků NuGet nebo použijte Update-Package v okně konzoly Správce balíčků.

Prozkoumání šablony analyzátoru

Šablona analyzátoru s opravou kódu vytvoří pět projektů:

  • MakeConst, který obsahuje analyzátor.
  • MakeConst.CodeFixes, který obsahuje opravu kódu.
  • MakeConst.Package, který slouží k vytvoření balíčku NuGet pro analyzátor a opravu kódu.
  • MakeConst.Test, což je projekt testování jednotek.
  • MakeConst.Vsix, což je výchozí spouštěcí projekt, který spustí druhou instanci sady Visual Studio, která načetla nový analyzátor. Stisknutím klávesy F5 spusťte projekt VSIX.

Poznámka

Analyzátory by měly cílit na .NET Standard 2.0, protože můžou běžet v prostředí .NET Core (sestavení příkazového řádku) a prostředí .NET Framework (Visual Studio).

Tip

Při spuštění analyzátoru spustíte druhou kopii sady Visual Studio. Tato druhá kopie používá k ukládání nastavení jiný podregistr registru. To vám umožní odlišit nastavení vizuálu ve dvou kopiích sady Visual Studio. Pro experimentální spuštění sady Visual Studio můžete vybrat jiný motiv. Kromě toho nepoužívat roaming nastavení ani se nepřihlašovat ke svému účtu sady Visual Studio pomocí experimentálního spuštění sady Visual Studio. Tím se nastavení liší.

Podregistr zahrnuje nejen analyzátor ve vývoji, ale také všechny předchozí otevřené analyzátory. Pokud chcete resetovat Podregistr Roslyn, musíte ho ručně odstranit z %LocalAppData%\Microsoft\VisualStudio. Název složky Podregistru Roslyn bude končit Roslynnapříklad 16.0_9ae182f9Roslynna . Všimněte si, že po odstranění podregistru možná budete muset řešení vyčistit a znovu sestavit.

V druhé instanci sady Visual Studio, kterou jste právě spustili, vytvořte nový projekt konzolové aplikace C# (bude fungovat jakákoli cílová architektura – analyzátory fungují na úrovni zdroje). Najeďte myší na token s podtržením vlnovkou a zobrazí se text upozornění poskytnutý analyzátorem.

Šablona vytvoří analyzátor, který hlásí upozornění na každou deklaraci typu, kde název typu obsahuje malá písmena, jak je znázorněno na následujícím obrázku:

Upozornění na generování sestav analyzátoru

Šablona také poskytuje opravu kódu, která změní všechny názvy typů obsahující malá písmena na všechna velká písmena. Můžete kliknout na žárovku zobrazenou s upozorněním a zobrazit navrhované změny. Přijetím navrhovaných změn se v řešení aktualizuje název typu a všechny odkazy na tento typ. Teď, když jste viděli počáteční analyzátor v akci, zavřete druhou instanci sady Visual Studio a vraťte se do projektu analyzátoru.

Nemusíte začínat druhou kopii sady Visual Studio a vytvářet nový kód, který otestuje každou změnu v analyzátoru. Šablona pro vás také vytvoří projekt testování jednotek. Tento projekt obsahuje dva testy. TestMethod1 zobrazuje typický formát testu, který analyzuje kód bez aktivace diagnostiky. TestMethod2 zobrazí formát testu, který aktivuje diagnostiku, a pak použije navrhovanou opravu kódu. Při sestavování analyzátoru a opravy kódu budete psát testy pro různé struktury kódu, abyste ověřili svou práci. Testy jednotek pro analyzátory jsou mnohem rychlejší než interaktivní testování pomocí sady Visual Studio.

Tip

Testy jednotek analyzátoru jsou skvělým nástrojem, když víte, jaké konstruktory kódu by měly a neměly aktivovat analyzátor. Načtení analyzátoru v jiné kopii sady Visual Studio je skvělý nástroj pro zkoumání a hledání konstruktorů, o kterých jste možná ještě nepřemýšleli.

V tomto kurzu napíšete analyzátor, který uživateli hlásí všechny deklarace místních proměnných, které lze převést na místní konstanty. Představte si například následující kód:

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

Ve výše uvedeném x kódu je přiřazena konstantní hodnota a není nikdy změněna. Dá se deklarovat pomocí modifikátoru const :

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

Zahrnuje analýzu, která určuje, zda lze proměnnou změnit na konstantu a vyžaduje syntaktickou analýzu, konstantní analýzu výrazu inicializátoru a analýzu toku dat, aby se zajistilo, že proměnná nikdy není zapsána do. .NET Compiler Platform poskytuje rozhraní API, která usnadňují provádění této analýzy.

Vytvoření registrací analyzátoru

Šablona vytvoří počáteční DiagnosticAnalyzer třídu v souboru MakeConstAnalyzer.cs . Tento počáteční analyzátor zobrazuje dvě důležité vlastnosti každého analyzátoru.

  • Každý diagnostický analyzátor musí poskytnout [DiagnosticAnalyzer] atribut, který popisuje jazyk, se kterým pracuje.
  • Každý diagnostický analyzátor musí být odvozen (přímo nebo nepřímo) z DiagnosticAnalyzer třídy.

Šablona také zobrazuje základní funkce, které jsou součástí libovolného analyzátoru:

  1. Registrace akcí Akce představují změny kódu, které by měly aktivovat analyzátor, aby prozkoumal porušení kódu. Když Visual Studio zjistí úpravy kódu, které odpovídají zaregistrované akci, zavolá zaregistrovanou metodu analyzátoru.
  2. Vytvořte diagnostiku. Když analyzátor zjistí porušení, vytvoří diagnostický objekt, který sada Visual Studio použije k upozornění uživatele na porušení.

V přepsání DiagnosticAnalyzer.Initialize(AnalysisContext) metody zaregistrujete akce. V tomto kurzu navštívíte uzly syntaxe a vyhledáte místní deklarace a zjistíte, které z nich mají konstantní hodnoty. Pokud může být deklarace konstantní, analyzátor vytvoří a nahlásí diagnostiku.

Prvním krokem je aktualizace registračních konstant a Initialize metody tak, aby tyto konstanty indikují váš analyzátor "Make Const". Většina řetězcových konstant je definována v řetězcovém souboru prostředků. Měli byste postupovat podle tohoto postupu, abyste usnadnili lokalizaci. Otevřete soubor Resources.resx pro projekt Analyzátor MakeConst . Zobrazí se editor prostředků. Následujícím způsobem aktualizujte prostředky řetězců:

  • Změňte AnalyzerDescription na .Variables that are not modified should be made constants.
  • Změňte AnalyzerMessageFormat na .Variable '{0}' can be made constant
  • Změňte AnalyzerTitle na .Variable can be made constant

Po dokončení by se editor prostředků měl zobrazit, jak je znázorněno na následujícím obrázku:

Aktualizace prostředků řetězců

Zbývající změny jsou v souboru analyzátoru. Otevřete soubor MakeConstAnalyzer.cs v sadě Visual Studio. Změňte zaregistrovanou akci z akce, která funguje na symboly, na akci, která funguje na syntaxi. MakeConstAnalyzerAnalyzer.Initialize V metodě najděte řádek, který registruje akci u symbolů:

context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);

Nahraďte ho následujícím řádkem:

context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.LocalDeclarationStatement);

Po této změně můžete metodu AnalyzeSymbol odstranit. Tento analyzátor zkoumá příkazy SyntaxKind.LocalDeclarationStatement, nikoli SymbolKind.NamedType příkazy. Všimněte si, že AnalyzeNode pod ní jsou červené vlnovku. Kód, který jste právě přidali, odkazuje na metodu AnalyzeNode , která nebyla deklarována. Deklarujte metodu pomocí následujícího kódu:

private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
}

Category V souboru MakeConstAnalyzer.cs změňte na "Usage", jak je znázorněno v následujícím kódu:

private const string Category = "Usage";

Vyhledání místních deklarací, které by mohly být const

Je čas napsat první verzi AnalyzeNode metody. Měl by hledat jednu místní deklaraci, která může být const , ale není, jako následující kód:

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

Prvním krokem je vyhledání místních deklarací. Do souboru MakeConstAnalyzer.cs přidejte následující kódAnalyzeNode:

var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;

Toto přetypování bude vždy úspěšné, protože analyzátor zaregistroval změny místních deklarací a pouze místních deklarací. Žádný jiný typ uzlu neaktivuje volání metody AnalyzeNode . Dále zkontrolujte deklaraci všech const modifikátorů. Pokud je najdete, okamžitě se vraťte. Následující kód hledá všechny const modifikátory místní deklarace:

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

Nakonec musíte zkontrolovat, jestli proměnná může být const. To znamená, že se po inicializaci nikdy nepřiřadí.

Pomocí nástroje provedete sémantickou analýzu SyntaxNodeAnalysisContext. Pomocí argumentu context určíte, zda lze deklaraci místní proměnné provést const. A Microsoft.CodeAnalysis.SemanticModel představuje všechny sémantické informace v jednom zdrojovém souboru. Další informace najdete v článku, který se věnuje sémantickým modelům. Použijete Microsoft.CodeAnalysis.SemanticModel k provedení analýzy toku dat u příkazu místní deklarace. Výsledky této analýzy toku dat pak použijete k tomu, abyste zajistili, že místní proměnná nebude nikde jinde zapsána s novou hodnotou. Voláním GetDeclaredSymbol rozšiřující metody načtěte ILocalSymbol proměnnou a zkontrolujte, že není obsažena v DataFlowAnalysis.WrittenOutside kolekci analýzy toku dat. Na konec AnalyzeNode metody přidejte následující kód:

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

Právě přidaný kód zajistí, že proměnná nebude změněna, a proto se dá udělat const. Je čas zvýšit diagnostiku. Přidejte následující kód jako poslední řádek v AnalyzeNode:

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

Průběh můžete zkontrolovat tak, že stisknete klávesu F5 a spustíte analyzátor. Můžete načíst konzolovou aplikaci, kterou jste vytvořili dříve, a pak přidat následující testovací kód:

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

Měla by se zobrazit žárovka a analyzátor by měl hlásit diagnostiku. V závislosti na vaší verzi sady Visual Studio se ale zobrazí:

  • Žárovka, která stále používá šablonu vygenerovanou opravu kódu, vám řekne, že může být velkými písmeny.
  • Bannerová zpráva v horní části editoru s oznámením MakeConstCodeFixProvider zjistila chybu a byla zakázána. Důvodem je to, že zprostředkovatel opravy kódu ještě nebyl změněn a stále očekává, že najde TypeDeclarationSyntax prvky místo elementů LocalDeclarationStatementSyntax .

Další část vysvětluje, jak napsat opravu kódu.

Napsat opravu kódu

Analyzátor může poskytnout jednu nebo více oprav kódu. Oprava kódu definuje úpravu, která řeší nahlášený problém. Pro analyzátor, který jste vytvořili, můžete zadat opravu kódu, která vloží klíčové slovo const:

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

Uživatel ho vybere v uživatelském rozhraní žárovky v editoru a Visual Studio změní kód.

Otevřete soubor CodeFixResources.resx a změňte CodeFixTitle na .Make constant

Otevřete soubor MakeConstCodeFixProvider.cs přidaný šablonou. Tato oprava kódu je už připojená k diagnostickému ID vytvořenému diagnostickým analyzátorem, ale zatím neimplementuje správnou transformaci kódu.

Dále odstraňte metodu MakeUppercaseAsync . Už neplatí.

Všichni zprostředkovatelé oprav kódu jsou odvozeni od CodeFixProvider. Všechny se přepíšou CodeFixProvider.RegisterCodeFixesAsync(CodeFixContext) a hlásí dostupné opravy kódu. V RegisterCodeFixesAsyncsouboru změňte typ nadřazeného uzlu, který hledáte, na odpovídající LocalDeclarationStatementSyntax diagnostice:

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

Dále změňte poslední řádek a zaregistrujte opravu kódu. Oprava vytvoří nový dokument, který je výsledkem přidání modifikátoru const do existující deklarace:

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

V kódu, který jste právě přidali na symbol MakeConstAsync, si všimnete červených vlnovek . Přidejte deklaraci pro MakeConstAsync podobný následujícímu kódu:

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

Nová MakeConstAsync metoda transformuje Document zdrojový soubor uživatele na nový Document , který teď obsahuje const deklaraci.

Vytvoříte nový const token klíčového slova, který se vloží na začátek příkazu deklarace. Dávejte pozor, abyste nejprve z prvního tokenu deklarace odstranili všechny počáteční triviály a připojili ho k tokenu const . Do metody MakeConstAsync přidejte následující kód:

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

Dále přidejte const token do deklarace pomocí následujícího kódu:

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

Dále naformátujte novou deklaraci tak, aby odpovídala pravidlům formátování jazyka C#. Formátováním změn tak, aby odpovídaly stávajícímu kódu, je lepší prostředí. Bezprostředně za existující kód přidejte následující příkaz:

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

Pro tento kód se vyžaduje nový obor názvů. Na začátek souboru přidejte následující using direktivu:

using Microsoft.CodeAnalysis.Formatting;

Posledním krokem je provedení úprav. Tento proces má tři kroky:

  1. Získejte popisovač existujícího dokumentu.
  2. Vytvořte nový dokument nahrazením stávající deklarace novou deklarací.
  3. Vraťte nový dokument.

Na konec MakeConstAsync metody přidejte následující kód:

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

Oprava kódu je připravená k vyzkoušení. Stisknutím klávesy F5 spusťte projekt analyzátoru v druhé instanci sady Visual Studio. V druhé instanci sady Visual Studio vytvořte nový projekt konzolové aplikace jazyka C# a přidejte do metody Main několik deklarací místních proměnných inicializovaných s konstantními hodnotami. Uvidíte, že se hlásí jako upozornění, jak je uvedeno níže.

Může vymýšlit upozornění na const

Udělal jsi hodně pokroku. Pod deklaracemi, které lze provést const, jsou vlnovku . Ale pořád je tu práce. To funguje správně, pokud přidáte const do deklarací začínající ina , pak j a nakonec k. Pokud ale přidáte const modifikátor v jiném pořadí, počínaje parametrem k, analyzátor vytvoří chyby: k nelze deklarovat const, pokud ijconstnejsou oba . Musíte provést další analýzu, abyste měli jistotu, že zpracováváte různé způsoby deklarace a inicializace proměnných.

Sestavení testů jednotek

Analyzátor a oprava kódu pracují na jednoduchém případu jedné deklarace, kterou lze vytvořit const. Existuje mnoho možných deklarací příkazů, kde tato implementace dělá chyby. Tyto případy vyřešíte tak, že budete pracovat s knihovnou testů jednotek napsanou šablonou. Je to mnohem rychlejší než opakované otevírání druhé kopie sady Visual Studio.

Otevřete soubor MakeConstUnitTests.cs v projektu testování jednotek. Šablona vytvořila dva testy, které se řídí dvěma běžnými vzory pro analyzátor a test jednotek opravy kódu. TestMethod1 zobrazí vzor pro test, který zajistí, že analyzátor nehlásí diagnostiku, když by neměl. TestMethod2 zobrazuje vzor pro hlášení diagnostiky a spuštění opravy kódu.

Šablona používá k testování částí balíčky Microsoft.CodeAnalysis.Testing .

Tip

Testovací knihovna podporuje speciální syntaxi značek, včetně následujících:

  • [|text|]: označuje, že je hlášena diagnostika pro text. Ve výchozím nastavení lze tento formulář použít pouze pro testování analyzátorů s přesně tím, který DiagnosticDescriptorDiagnosticAnalyzer.SupportedDiagnosticsposkytuje .
  • {|ExpectedDiagnosticId:text|}: označuje, že pro je hlášena diagnostika s textparametrem IdExpectedDiagnosticId .

Nahraďte testy šablony ve MakeConstUnitTest třídě následující testovací metodou:

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

Spusťte tento test, abyste se ujistili, že projde. V sadě Visual Studio otevřete Průzkumníka testů tak, že vyberete TestOvat>Průzkumníka testůWindows>. Pak vyberte Spustit vše.

Vytvoření testů pro platné deklarace

Obecně platí, že analyzátory by se měly co nejrychleji ukončit a provádět minimální práci. Visual Studio volá registrované analyzátory, protože uživatel upravuje kód. Klíčovým požadavkem je rychlost odezvy. Existuje několik testovacích případů pro kód, které by neměly vyvolat vaši diagnostiku. Váš analyzátor už zpracovává jeden z těchto testů, tedy případ, kdy se po inicializaci přiřadí proměnná. Přidejte následující testovací metodu, která tento případ představuje:

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

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

Tento test také projde. Dále přidejte testovací metody pro podmínky, které jste ještě nezpracovali:

  • Deklarace, které už constjsou , protože už jsou const:

            [TestMethod]
            public async Task VariableIsAlreadyConst_NoDiagnostic()
            {
                await VerifyCS.VerifyAnalyzerAsync(@"
    using System;
    
    class Program
    {
        static void Main()
        {
            const int i = 0;
            Console.WriteLine(i);
        }
    }
    ");
            }
    
  • Deklarace, které nemají inicializátor, protože neexistuje žádná hodnota, kterou by bylo možné použít:

            [TestMethod]
            public async Task NoInitializer_NoDiagnostic()
            {
                await VerifyCS.VerifyAnalyzerAsync(@"
    using System;
    
    class Program
    {
        static void Main()
        {
            int i;
            i = 0;
            Console.WriteLine(i);
        }
    }
    ");
            }
    
  • Deklarace, kde inicializátor není konstanta, protože nemohou být konstantami v čase kompilace:

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

Může to být ještě složitější, protože jazyk C# umožňuje více deklarací jako jeden příkaz. Zvažte následující konstantu řetězce testovacího případu:

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

Proměnná i může být konstantní, ale proměnná j ne. Z tohoto důvodu nelze toto prohlášení učinit prohlášením const.

Spusťte testy znovu a uvidíte, že tyto nové testovací případy selžou.

Aktualizace analyzátoru tak, aby ignoroval správné deklarace

K odfiltrování kódu, který odpovídá těmto podmínkám, potřebujete v metodě analyzátoru AnalyzeNode určitá vylepšení. Všechny jsou to související podmínky, takže podobné změny opraví všechny tyto podmínky. Proveďte následující změny:AnalyzeNode

  • Sémantická analýza prozkoumala deklaraci jedné proměnné. Tento kód musí být ve smyčce foreach , která prozkoumá všechny proměnné deklarované ve stejném příkazu.
  • Každá deklarovaná proměnná musí mít inicializátor.
  • Inicializátor každé deklarované proměnné musí být konstantou v čase kompilace.

V metodě nahraďte AnalyzeNode původní sémantickou analýzu:

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

s následujícím fragmentem kódu:

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

První foreach smyčka prozkoumá každou deklaraci proměnné pomocí syntaktické analýzy. První kontrola zaručuje, že proměnná má inicializátor. Druhá kontrola zaručuje, že inicializátor je konstanta. Druhá smyčka má původní sémantickou analýzu. Sémantické kontroly jsou v samostatné smyčce, protože mají větší dopad na výkon. Spusťte testy znovu a měli byste vidět, že všechny projdou.

Přidání konečného lesku

Už jste téměř hotovi. Existuje několik dalších podmínek, které může analyzátor zpracovat. Visual Studio volá analyzátory, když uživatel píše kód. Často se stává, že váš analyzátor bude volána pro kód, který se nekompiluje. Metoda diagnostického AnalyzeNode analyzátoru nekontroluje, jestli je konstantní hodnota převoditelná na typ proměnné. Aktuální implementace tedy s radostí převede nesprávnou deklaraci, například int i = "abc" na místní konstantu. Přidejte testovací metodu pro tento případ:

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

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

Kromě toho nejsou správně zpracovány odkazové typy. Jedinou povolenou konstantní hodnotou pro typ odkazu je null, s výjimkou případu System.String, který povoluje řetězcové literály. Jinými slovy, const string s = "abc" je legální, ale const object s = "abc" není. Tento fragment kódu ověřuje tuto podmínku:

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

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

Abyste byli důkladní, musíte přidat další test, abyste měli jistotu, že pro řetězec můžete vytvořit konstantní deklaraci. Následující fragment kódu definuje kód, který vyvolá diagnostiku, i kód po použití opravy:

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

Nakonec, pokud je proměnná deklarována pomocí klíčového var slova, oprava kódu udělá špatnou const var věc a vygeneruje deklaraci, která není podporována jazykem C#. Pokud chcete tuto chybu opravit, musí oprava kódu nahradit var klíčové slovo názvem odvozeného typu:

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

Všechny výše uvedené chyby se naštěstí dají vyřešit pomocí stejných technik, které jste se právě naučili.

Pokud chcete opravit první chybu, nejprve otevřete soubor MakeConstAnalyzer.cs a vyhledejte smyčku foreach, ve které jsou kontrolovány inicializátory jednotlivých místních deklarací, aby se zajistilo, že jsou přiřazeny konstantními hodnotami. Bezprostředně před první smyčkou foreach voláním context.SemanticModel.GetTypeInfo() načtěte podrobné informace o deklarovaných typech místní deklarace:

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

Pak ve smyčce zkontrolujte foreach jednotlivé inicializátory a ujistěte se, že je převoditelný na typ proměnné. Po ověření, že je inicializátor konstantou, přidejte následující kontrolu:

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

Další změna vychází z poslední změny. Před závěrečnou složenou závorku první smyčky foreach přidejte následující kód, který zkontroluje typ místní deklarace, pokud je konstantou řetězec nebo 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;
}

Pokud chcete nahradit klíčové slovo správným názvem typu, musíte do zprostředkovatele opravy kódu napsat var trochu další kód. Vraťte se do souboru MakeConstCodeFixProvider.cs. Kód, který přidáte, provede následující kroky:

  • Zkontrolujte, jestli je var deklarace deklarace a jestli je:
  • Vytvořte nový typ pro odvozený typ.
  • Ujistěte se, že deklarace typu není alias. Pokud ano, je zákonné deklarovat const var.
  • Ujistěte se, že var v tomto programu není název typu. (Pokud ano, const var je legální).
  • Zjednodušení úplného názvu typu

To zní jako spousta kódu. Není to tak. Řádek, který deklaruje a inicializuje newLocal , nahraďte následujícím kódem. Jde okamžitě po inicializaci 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);

Abyste mohli použít typ, budete muset přidat jednu using direktivu Simplifier :

using Microsoft.CodeAnalysis.Simplification;

Spusťte testy a všechny by měly projít. Pogratulujte si spuštěním hotového analyzátoru. Stisknutím klávesy Ctrl+F5 spusťte projekt analyzátoru v druhé instanci sady Visual Studio s načteným rozšířením Roslyn Preview.

  • V druhé instanci sady Visual Studio vytvořte nový projekt konzolové aplikace jazyka C# a přidejte int x = "abc"; ho do metody Main. Díky první opravě chyby by se pro tuto deklaraci místní proměnné nemělo hlásit žádné upozornění (i když podle očekávání došlo k chybě kompilátoru).
  • Dále přidejte object s = "abc"; do metody Main. Kvůli druhé opravě chyby by se nemělo hlásit žádné upozornění.
  • Nakonec přidejte další místní proměnnou, která používá var klíčové slovo . Uvidíte, že se zobrazí upozornění a pod vlevo se zobrazí návrh.
  • Přesuňte stříšku editoru přes podtržení vlnovkou a stiskněte ctrl+. a zobrazí se navrhovaná oprava kódu. Po výběru opravy kódu si všimněte, že var klíčové slovo je nyní zpracováno správně.

Nakonec přidejte následující kód:

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

Po těchto změnách se zobrazí červená vlnovka pouze u prvních dvou proměnných. Přidejte const do a ija zobrazí se nové upozornění, k protože teď může být const.

Gratulujeme! Vytvořili jste první rozšíření .NET Compiler Platform, které provádí průběžnou analýzu kódu, aby zjistilo problém a poskytuje rychlou opravu, která ho opraví. Seznámili jste se s mnoha rozhraními API pro kód, která jsou součástí sady .NET Compiler Platform SDK (Roslyn API). Svou práci můžete zkontrolovat oproti dokončené ukázce v našem úložišti GitHub s ukázkami.

Další prostředky