Kurz: vytvoření prvního analyzátoru a opravy kódu

Sada .NET Compiler Platform SDK poskytuje nástroje, které potřebujete k vytvoření vlastní diagnostiky (analyzátory), oprav kódu, refaktoringu kódu a diagnostických potlačení, které cílí na kód jazyka C# nebo Visual Basic. Analyzátor obsahuje kód, který rozpoznává porušení vašeho pravidla. Oprava kódu obsahuje kód, který vyřeší porušení. Pravidla, která implementujete, může být cokoli od struktury kódu až po kódování stylu a vytváření názvů. .NET Compiler Platform poskytuje rozhraní pro spouštění analýzy, protože vývojáři píší kód a všechny funkce uživatelského rozhraní sady Visual Studio pro úpravu kódu: zobrazení vlnovek v editoru, naplnění Seznam chyb sady Visual Studio, vytváření návrhů "světlé žárovky" a zobrazení obsáhlé verze Preview navrhovaných oprav.

V tomto kurzu se seznámíte s vytvořením analyzátoru a s doprovodnou opravou kódu pomocí rozhraní API Roslyn. Analyzátor je způsob, jak provádět analýzu zdrojového kódu a nahlásit problém uživateli. Volitelně lze opravu kódu přidružit k analyzátoru, aby reprezentovala úpravu zdrojového kódu uživatele. V tomto kurzu se vytvoří analyzátor, který najde deklarace místních proměnných, které by se daly deklarovat pomocí const modifikátoru, ale ne. Oprava doprovodného kódu upraví tyto deklarace a přidá const modifikátor.

Požadavky

.NET COMPILER Platform SDK bude nutné nainstalovat pomocí instalační program pro Visual Studio:

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

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

Instalace pomocí zobrazení Instalační program pro Visual Studio-úlohy

Sada .NET Compiler Platform SDK není automaticky vybraná jako součást úlohy vývoje rozšíření sady Visual Studio. Je nutné ji vybrat jako volitelnou součást.

  1. Spustit instalační program pro Visual Studio
  2. Vybrat Upravit
  3. Projděte si úlohu vývoj rozšíření sady Visual Studio .
  4. Ve stromové struktuře souhrnu otevřete uzel vývoj rozšíření sady Visual Studio .
  5. Zaškrtněte políčko pro sadu .NET Compiler Platform SDK. V rámci volitelných komponent najdete ho jako poslední.

Volitelně také budete chtít, aby Editor DGML zobrazoval grafy v Vizualizér:

  1. Ve stromové struktuře souhrnu otevřete uzel jednotlivé komponenty .
  2. Zaškrtněte políčko pro Editor DGML .

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

  1. Spustit instalační program pro Visual Studio
  2. Vybrat Upravit
  3. Výběr karty jednotlivé součásti
  4. Zaškrtněte políčko pro sadu .NET Compiler Platform SDK. Najdete ho nahoře v části kompilátory, nástroje sestavení a moduly runtime .

Volitelně také budete chtít, aby Editor DGML zobrazoval grafy v Vizualizér:

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

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. Implementací opravy kódu přijměte doporučení.
  5. Zlepšete analýzu prostřednictvím jednotkových testů.

Vytvoření řešení

  • V aplikaci Visual Studio vyberte soubor > nový > projekt... zobrazí se dialogové okno Nový projekt.
  • V části Visual C# > rozšiřitelnosti vyberte možnost analyzátor s opravou kódu (.NET Standard).
  • Pojmenujte projekt "MakeConst" a klikněte na tlačítko OK.

Prozkoumat šablonu analyzátoru

Analyzátor s šablonou opravy kódu vytváří pět projektů:

  • MakeConst, který obsahuje analyzátor.
  • MakeConst. CodeFixes, který obsahuje opravu kódu.
  • MakeConst. Package, který se používá k tvorbě balíčku NuGet pro analyzátor a opravu kódu.
  • MakeConst. test, což je projekt testování částí.
  • MakeConst. vsix, což je výchozí spouštěný projekt, který spouští druhou instanci sady Visual Studio, která zavedla nový analyzátor. Stisknutím klávesy F5 spusťte projekt VSIX.

Poznámka

Analyzátory by měly cílit .NET Standard 2,0, protože mohou 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 uložení nastavení jiný podregistr registru. To umožňuje odlišit nastavení vizuálu v obou kopiích sady Visual Studio. Můžete vybrat jiný motiv pro experimentální běh sady Visual Studio. Kromě toho nevytvářejte roaming nastavení ani přihlášení k účtu aplikace Visual Studio pomocí experimentálního spuštění sady Visual Studio. Který udržuje nastavení odlišně.

Podregistr zahrnuje nejen analyzátor ve vývoji, ale také otevřené všechny předchozí 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, například Roslyn 16.0_9ae182f9Roslyn . Všimněte si, že možná budete muset řešení vyčistit a po odstranění podregistru ho znovu sestavit.

Ve druhé instanci sady Visual Studio, kterou jste právě spustili, vytvořte nový projekt konzolové aplikace v jazyce C# (všechny cílové rozhraní .NET Framework budou fungovat na zdrojové úrovni). Najeďte myší na token podtrženým vlnovkou a zobrazí se text upozornění, který je k dispozici v analyzátoru.

Šablona vytvoří analyzátor, který ohlá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í pro generování sestav analyzátoru

Šablona také poskytuje opravu kódu, která změní jakýkoli název typu obsahující malá písmena na velká písmena. Můžete kliknout na žárovku, která se zobrazuje s upozorněním, a zobrazit navrhované změny. Přijetí navrhovaných změn aktualizuje název typu a všechny odkazy na daný typ v řešení. 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 spouštět druhou kopii sady Visual Studio a vytvořit nový kód pro otestování všech změn v analyzátoru. Šablona také vytvoří projekt testu jednotek za vás. Tento projekt obsahuje dva testy. TestMethod1 zobrazuje typický formát testu, který analyzuje kód bez aktivace diagnostiky. TestMethod2 zobrazuje formát testu, který aktivuje diagnostiku a poté 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 svoji 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, které konstrukce kódu by měly a nemají aktivovat analyzátor. Načítání analyzátoru v jiné kopii sady Visual Studio je skvělý nástroj pro zkoumání a hledání konstrukcí, na které jste se ještě nemuseli myslet.

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

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

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

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

Analýza, která určuje, zda může být proměnná vytvořena, je vyžadována syntaktickou analýzou, konstantní analýzou výrazu inicializátoru a analýzou toku dat, aby se zajistilo, že proměnná nebude nikdy 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í poskytovat [DiagnosticAnalyzer] atribut, který popisuje jazyk, na kterém pracuje.
  • Každý diagnostický analyzátor musí z třídy odvodit (přímo nebo nepřímo) DiagnosticAnalyzer .

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

  1. Proveďte registraci akcí. Akce reprezentují změny kódu, které by měly aktivovat analyzátor, aby prozkoumali kód pro porušení. Když Visual Studio detekuje úpravy kódu, které odpovídají zaregistrované akci, volá metodu zaregistrovanou analyzátorem.
  2. Vytvořte diagnostiku. Když analyzátor zjistí porušení, vytvoří objekt diagnostiky, který Visual Studio používá k upozorňování uživatele na porušení.

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

Prvním krokem je aktualizovat konstanty a Initialize metodu registrace, aby tyto konstanty označovaly analyzátor "Make const". Většina řetězcových konstant je definována v souboru prostředků řetězce. Pro snazší lokalizaci byste měli postupovat podle tohoto postupu. Otevřete soubor Resources. resx pro projekt analyzátoru MakeConst . Tím se zobrazí editor prostředků. Aktualizujte prostředky řetězce následujícím způsobem:

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

Po dokončení by se měl editor prostředků zobrazit tak, jak ukazuje následující obrázek:

Aktualizace prostředků řetězců

Zbývající změny jsou v souboru analyzátoru. Otevřete MakeConstAnalyzer. cs v aplikaci Visual Studio. Změňte zaregistrovanou akci z jedné, která funguje na symbolech, které fungují na syntaxi. V MakeConstAnalyzerAnalyzer.Initialize metodě Najděte řádek, který zaregistruje akci na symboly:

context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);

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

context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.LocalDeclarationStatement);

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

private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
}

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

private const string Category = "Usage";

Najde místní deklarace, které by mohly být const.

Je čas zapsat první verzi AnalyzeNode metody. Měl by hledat jednu místní deklaraci, která by mohla být const , ale ne, podobně jako v následujícím kódu:

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

Prvním krokem je najít místní deklarace. Do AnalyzeNode MakeConstAnalyzer. cs přidejte následující kód:

var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;

Toto přetypování vždy proběhne úspěšně, protože analyzátor zaregistroval pro změny místních deklarací a pouze místní deklarace. Žádný jiný typ uzlu neaktivuje volání AnalyzeNode metody. V dalším kroku ověřte deklaraci všech const modifikátorů. Pokud je najdete, vraťte se hned. Následující kód vyhledá všechny const modifikátory v místní deklaraci:

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

Nakonec je nutné ověřit, zda může být proměnná const . To znamená, že se po inicializaci nikdy nepřiřazuje.

Pomocí nástroje se provede nějaká Sémantická analýza SyntaxNodeAnalysisContext . Argument můžete použít context k určení, zda lze vytvořit deklaraci lokální proměnné const . Microsoft.CodeAnalysis.SemanticModelPředstavuje všechny sémantické informace v jednom zdrojovém souboru. Další informace najdete v článku, který pokrývá sémantické modely. Použijete Microsoft.CodeAnalysis.SemanticModel k provedení analýzy toku dat v příkazu místní deklarace. Pak použijete výsledky této analýzy toku dat, abyste zajistili, že místní proměnná nebude zapsána novou hodnotou kdekoli jinde. Zavolejte GetDeclaredSymbol metodu rozšíření pro načtení ILocalSymbol proměnné a ověřte, že není obsažena v DataFlowAnalysis.WrittenOutside kolekci analýzy toku dat. Na konec metody přidejte následující kód AnalyzeNode :

// 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 zajišťuje, že proměnná nebude změněna, a je proto možné ji vytvořit const . Je čas vyvolat diagnostiku. Jako poslední řádek přidejte následující kód AnalyzeNode :

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

Svůj průběh můžete zjistit stisknutím klávesy F5 ke spuštění analyzátoru. Můžete načíst konzolovou aplikaci, kterou jste vytvořili dříve, a pak přidat následující zkušební kód:

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

Měla by se zobrazit žárovka a analyzátor by měl ohlásit diagnostiku. Žárovka však stále používá opravu kódu vygenerovanou šablonou a oznamuje vám, že může být proveden na velká písmena. V další části se dozvíte, jak napsat opravu kódu.

Zapsat opravu kódu

Analyzátor může poskytovat 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 zvolí tuto možnost z uživatelského 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 již zapojena do ID diagnostiky vytvořeného analyzátorem diagnostiky, ale ještě neimplementuje správnou transformaci kódu.

Dále odstraňte MakeUppercaseAsync metodu. Už se nepoužívá.

Všichni poskytovatelé oprav kódu jsou odvozeni z CodeFixProvider . Všechny jsou popsány CodeFixProvider.RegisterCodeFixesAsync(CodeFixContext) , aby nahlásily dostupné opravy kódu. V nástroji RegisterCodeFixesAsync změňte typ nadřazeného uzlu, který hledáte, aby LocalDeclarationStatementSyntax odpovídal diagnostice:

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

Dále změňte poslední řádek pro registraci opravy kódu. Vaše oprava vytvoří nový dokument, který je výsledkem přidání const modifikátoru k existující deklaraci:

// 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šimněte si červené vlnovky v kódu, který jste právě přidali na symbol MakeConstAsync . Přidejte deklaraci MakeConstAsync jako následující kód:

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

Vaše nová MakeConstAsync Metoda provede transformaci Document zdrojového souboru uživatele do nového Document , který nyní obsahuje const deklaraci.

Vytvoříte nový const token klíčového slova pro vložení na začátek prohlášení. Buďte opatrní, abyste nejdřív odebrali všechny úvodní minihry z prvního tokenu příkazu deklarace a připojili ho k const tokenu. 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 k deklaraci 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í změn tak, aby odpovídalo existujícímu kódu, vytvoří lepší prostředí. Přidejte následující příkaz hned za existující kód:

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

Pro tento kód je vyžadován nový obor názvů. usingDo horní části souboru přidejte následující direktivu:

using Microsoft.CodeAnalysis.Formatting;

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

  1. Získejte popisovač pro existující dokument.
  2. Vytvořte nový dokument nahrazením existující deklarace novou deklarací.
  3. Vrátí nový dokument.

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

// 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 aplikace Visual Studio. Ve druhé instanci sady Visual Studio vytvořte nový projekt konzolové aplikace v jazyce C# a přidejte několik deklarací místních proměnných inicializovaných s konstantními hodnotami do metody Main. Uvidíte, že jsou hlášeny jako upozornění, jak je uvedeno níže.

Může vytvořit konstantní upozornění.

Provedli jste spoustu pokroku. V deklaracích, které mohou být provedeny, jsou vlnovky const . I když ale pořád funguje. To funguje dobře, pokud přidáte const do deklarací, které začínají na i , j a nakonec a nakonec k . Pokud však přidáte const Modifikátor v jiném pořadí, počínaje verzí k , váš analyzátor vytvoří chyby: k nelze deklarovat const , pokud i j již nejsou a const . Máte možnost provést další analýzu, abyste se ujistili, že je možné zpracovat různé způsoby, které lze deklarovat a inicializovat.

Sestavit testy jednotek

Analyzátor a oprava kódu fungují na jednoduchém případu jedné deklarace, která může být vytvořena jako const. Existuje mnoho možných příkazů deklarace, kde tato implementace způsobuje chyby. Tyto případy se řeší při práci s knihovnou testu jednotek zapsanou šablonou. Je mnohem rychlejší než opakované otevření druhé kopie sady Visual Studio.

Otevřete soubor MakeConstUnitTests. cs v projektu testování částí. Šablona vytvořila dva testy, které následují dva běžné vzory pro analyzátor a opravu testování částí kódu. TestMethod1 zobrazuje vzor testu, který zajišťuje, že analyzátor neohlásí diagnostiku, pokud by neměl. TestMethod2 zobrazuje vzor pro vytváření sestav diagnostiky a spuštění opravy kódu.

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

Tip

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

  • [|text|]: označuje, že je hlášena Diagnostika text . Ve výchozím nastavení se tento formulář dá použít jenom pro testovací analyzátory jenom s jedním DiagnosticDescriptor poskytovaným nástrojem DiagnosticAnalyzer.SupportedDiagnostics .
  • {|ExpectedDiagnosticId:text|}: označuje, že Id ExpectedDiagnosticId je hlášena Diagnostika s nástrojem text .

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 se předává. V aplikaci Visual Studio otevřete Průzkumníka testů výběrem test > > Průzkumník testů systému Windows. Pak vyberte Spustit vše.

Vytvořit testy pro platné deklarace

Obecně platí, že analyzátory by měly skončit co nejrychleji, což má minimální práci. Visual Studio volá zaregistrované analyzátory jako kód úprav uživatele. Odezva je klíčovým požadavkem. Existuje několik testovacích případů pro kód, který by neměl vyvolat diagnostiku. Analyzátor již zpracovává jeden z těchto testů, případ, kdy je přiřazena proměnná po inicializaci. Přidejte následující testovací metodu pro reprezentaci tohoto případu:

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

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

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

  • Deklarace, které jsou již const , protože jsou již 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í žádný inicializátor, protože neexistuje žádná hodnota, která se má 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 konstanty 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 být ještě složitější, protože C# umožňuje více deklarací v jednom příkazu. Vezměte v úvahu následující řetězcové konstanty 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 vytvořená jako konstanta, ale proměnná ale j nemůže. Proto tento příkaz nelze vytvořit jako deklaraci konstanty.

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 AnalyzeNode odfiltrování kódu, který splňuje tyto podmínky, potřebujete nějaké vylepšení metody analyzátoru. Jsou to všechny související podmínky, takže podobné změny vyřeší všechny tyto podmínky. Proveďte následující změny AnalyzeNode :

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

V AnalyzeNode metodě nahraďte 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 prověřuje každou deklaraci proměnné pomocí syntaktické analýzy. První ověření zaručuje, že proměnná má inicializátor. Druhá kontrolu garantuje, že inicializátor je konstanta. Druhá smyčka má původní sémantickou analýzu. Sémantické kontroly jsou samostatné smyčky, protože mají větší vliv na výkon. Spusťte testy znovu a měli byste je vidět, že jsou všechny passované.

Přidat konečný polský

Už jste téměř hotovi. Analyzátor může zpracovat několik dalších podmínek. Visual Studio volá analyzátory, zatímco uživatel píše kód. Často se jedná o případ, kdy se analyzátor bude volat pro kód, který se nekompiluje. Metoda diagnostického analyzátoru AnalyzeNode nekontroluje, zda je konstantní hodnota převoditelná na typ proměnné. Aktuální implementace tak bude Happily převést nesprávnou deklaraci, jako je 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""|};
    }
}
");
        }

Odkazové typy se navíc nezpracovávají správně. Jediná hodnota konstanty povolená pro odkazový typ je null , s výjimkou případu System.String , který umožňuje řetězcové literály. Jinými slovy, const string s = "abc" je právní, ale není const object s = "abc" . 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"";
    }
}
");
        }

Chcete-li být důkladnější, je nutné přidat další test, abyste se ujistili, že můžete vytvořit konstantní deklarace pro řetězec. Následující fragment kódu definuje kód, který vyvolává diagnostiku, a 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 s var klíčovým slovem, oprava kódu nesprávné věci a vygeneruje const var deklaraci, která není podporována jazykem C#. Chcete-li opravit tuto chybu, oprava kódu musí 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"";
    }
}
");
        }

Naštěstí můžete všechny výše uvedené chyby vyřešit pomocí stejných technik, které jste právě naučili.

Chcete-li opravit první chybu, nejprve otevřete MakeConstAnalyzer. cs a vyhledejte smyčku foreach, kde jsou zkontrolovány jednotlivé Inicializátory místní deklarace, aby se zajistilo jejich přiřazení s konstantními hodnotami. Bezprostředně před první smyčkou foreach volejte, context.SemanticModel.GetTypeInfo() aby načetla podrobné informace o deklarovaném typu místní deklarace:

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

Potom v rámci foreach smyčky zkontrolujte každý inicializátor a ujistěte se, že je převoditelné na typ proměnné. Po ověření, že inicializátor je konstanta, 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ěny se vytvoří po poslední. Před pravou složenou závorkou první smyčky foreach přidejte následující kód pro zkontrolování typu místní deklarace, je-li konstanta řetězec nebo hodnota 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;
}

Chcete-li nahradit var klíčové slovo se správným názvem typu, je nutné ve svém poskytovateli opravy kódu napsat trochu větší kód. Vraťte se do MakeConstCodeFixProvider. cs. Kód, který přidáte, provede následující kroky:

  • Ověřte, zda je deklarace deklarace var , a pokud je:
  • Vytvoří nový typ pro odvozený typ.
  • Ujistěte se, že deklarace typu není alias. V takovém případě je právní deklarace platná const var .
  • Ujistěte se, že var v tomto programu není název typu. (Pokud ano, const var je právní).
  • Zjednodušit úplný název typu

Tyto zvuky jako velké množství kódu. Není to. Nahraďte řádek, který deklaruje a inicializuje, newLocal pomocí následujícího kódu. Hned 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);

usingK použití tohoto typu budete muset přidat jednu direktivu Simplifier :

using Microsoft.CodeAnalysis.Simplification;

Spusťte testy a všechny by měly být passované. Congratulate se tak, že spustíte kompletní analyzátor. 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.

  • Ve druhé instanci sady Visual Studio vytvořte nový projekt konzolové aplikace v jazyce C# a přidejte int x = "abc"; ho do metody Main. Děkujeme, že při první opravě chyby by se pro tuto místní proměnnou proměnné nemělo hlásit žádné upozornění (i když dojde k chybě kompilátoru, jak se očekávalo).
  • Dále přidejte object s = "abc"; do metody Main. Z důvodu druhé opravy 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 nahlásí upozornění a na levé straně se zobrazí návrh.
  • Přesuňte kurzor editoru na podtržení vlnovkou a stiskněte klávesu CTRL + .. pro zobrazení navrhované opravy kódu. Po výběru opravy kódu si všimněte, že var klíčové slovo se nyní zpracovává 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 získáte červené vlnovky pouze první dvě proměnné. Přidejte const do obou a a zobrazí se i j nové upozornění, protože se k teď může jednat o const .

Gratulujeme! Vytvořili jste první rozšíření .NET Compiler Platform, které provádí průběžnou analýzu kódu k detekci problému a nabízí rychlou opravu pro její opravu. Na cestě jste se naučili mnoho rozhraní API kódu, která jsou součástí sady .NET Compiler Platform SDK (rozhraní API Roslyn). Práci s dokončenou ukázkou najdete v našem úložišti GitHub Samples.

Další prostředky