Poznámka
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Sada .NET Compiler Platform SDK poskytuje nástroje, které potřebujete k vytvoření vlastní diagnostiky (analyzátorů), 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á porušení vašeho pravidla. Oprava kódu obsahuje kód, který opravuje porušení předpisů. Pravidla, která implementujete, můžou být cokoli od struktury kódu až po styl kódování až po konvence vytváření názvů a další. Platforma kompilátoru .NET poskytuje architekturu pro spouštění analýz během psaní kódu vývojáři a všechny funkce uživatelského rozhraní sady Visual Studio pro opravu kódu: zobrazení vlnovek v editoru, zobrazení seznamu chyb ve Visual Studiu, vytvoření návrhů pomocí možnosti 'žárovka' a zobrazení bohatého náhledu navrhovaných oprav.
V tomto kurzu prozkoumáte vytvoření analyzátoru a doprovodnou opravu kódu pomocí rozhraní ROSlyn API. Analyzátor je způsob, jak provést analýzu zdrojového kódu a nahlásit uživateli problém. Volitelně může být oprava kódu přidružena k analyzátoru, aby představovala úpravu zdrojového kódu uživatele. Tento kurz vytvoří analyzátor, který najde deklarace lokálních proměnných, které by mohly být deklarovány pomocí modifikátoru const
, ale nejsou. Oprava doprovodného const
kódu upraví tyto deklarace a přidá modifikátor.
Požadavky
- Visual Studio 2019 verze 16.8 nebo novější
Sadu .NET Compiler Platform SDK budete muset nainstalovat prostřednictvím instalačního programu sady Visual Studio:
Pokyny k instalaci – Instalační program sady Visual Studio
Sada .NET Compiler Platform SDK v instalačním programu sady Visual Studio se dá najít dvěma různými způsoby:
Instalace pomocí instalačního programu sady Visual Studio – zobrazení úloh
Sada .NET Compiler Platform SDK není automaticky vybrána jako součást sady funkcí pro vývoj rozšíření sady Visual Studio. Musíte ho vybrat jako volitelnou komponentu.
- Spuštění instalačního programu sady Visual Studio
- Vyberte Upravit.
- Zkontrolujte pracovní úlohu vývoje rozšíření pro Visual Studio.
- Rozbalte uzel vývoje rozšíření sady Visual Studio ve stromu přehledů.
- Zaškrtněte to okénko pro sadu .NET Compiler Platform SDK. Najdete ho jako poslední v volitelných součástech.
Volitelně můžete také chtít, aby editor DGML zobrazoval grafy ve vizualizéru:
- Otevřete uzel Jednotlivé komponenty v přehledovém stromu.
- Zaškrtněte políčko editoru DGML.
Instalace pomocí instalačního programu sady Visual Studio – karta Jednotlivé komponenty
- Spuštění instalačního programu sady Visual Studio
- Vyberte Upravit.
- Vyberte kartu Jednotlivé součásti
- Zaškrtněte to okénko pro sadu .NET Compiler Platform SDK. Najdete ho v horní části v části Kompilátory, nástroje sestavení a moduly runtime .
Volitelně můžete také chtít, aby editor DGML zobrazoval grafy ve vizualizéru:
- Zaškrtněte políčko pro editor DGML. Najdete ho v části Nástroje kódu .
Při vytváření a ověřování analyzátoru existuje několik kroků:
- Vytvořte řešení.
- Zaregistrujte název a popis analyzátoru.
- Zpráva obsahující upozornění a doporučení analyzátoru
- Implementujte opravu kódu a přijměte doporučení.
- Vylepšete analýzu prostřednictvím jednotkových testů.
Vytvoření řešení
- V sadě Visual Studio zvolte Soubor > nový > projekt... a zobrazte dialogové okno Nový projekt.
- V části Rozšiřitelnost jazyka Visual C# >zvolte Analyzátor s opravou kódu (.NET Standard).
- Pojmenujte projekt MakeConst a klikněte na OK.
Poznámka:
Může se zobrazit chyba kompilace (MSB4062: Úlohu CompareBuildTaskVersion nelze načíst). 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ů.
Prozkoumejte šablonu analyzátoru
Analyzátor se šablonou opravy kódu vytvoří pět projektů:
- MakeConst, který obsahuje analyzátor.
- MakeConst.CodeFixes, která obsahuje opravu kódu.
- MakeConst.Package, který se používá 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).
Návod
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 se během experimentálního běhu sady Visual Studio nezkoušejte přihlašovat k vašemu účtu ani měnit nastavení. To zachovává odlišnost nastavení.
Skupina zahrnuje nejen analyzátor, který je 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 Roslyn hive bude končit Roslyn
, například 16.0_9ae182f9Roslyn
. Mějte na paměti, že po odstranění úlu možná budete muset řešení vyčistit a znovu sestavit.
Ve druhé instanci sady Visual Studio, kterou jste právě spustili, vytvořte nový projekt konzolové aplikace jazyka C# (každá cílová architektura bude fungovat – analyzátory fungují na úrovni zdroje.) Najeďte myší na token podtržením vlnovek 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:
Šablona také poskytuje opravu kódu, která změní libovolný název typu obsahující malá písmena na všechna velká písmena. Kliknutím na žárovku zobrazenou s upozorněním můžete zobrazit navrhované změny. Přijetí navrhovaných změn aktualizuje název typu a všechny odkazy na tento 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 k projektu analyzátoru.
Nemusíte začínat druhou kopii sady Visual Studio a vytvářet nový kód, který bude testovat všechny změny v analyzátoru. Šablona také vytvoří projekt testování jednotek za vás. 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 použije navrženou opravu kódu. Při sestavování analyzátoru a opravy kódu napíšete testy pro různé struktury kódu, abyste ověřili svou práci. Jednotkové testy analyzátorů jsou mnohem rychlejší než interaktivní testování ve Visual Studio.
Návod
Testy jednotek analyzátoru jsou skvělým nástrojem, když víte, jaké konstrukce kódu by měly a neměly by aktivovat váš analyzátor. Načtení vašeho analyzátoru v jiné kopii sady Visual Studio je skvělým nástrojem pro zkoumání a hledání konstrukcí, o kterých jste dosud neuvažovali.
V tomto kurzu napíšete analyzátor, který hlásí uživateli 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 nikdy se nezmění. Lze ji deklarovat pomocí modifikátoru const
:
const int x = 0;
Console.WriteLine(x);
Analýza, která určuje, zda lze proměnnou učinit konstantní, vyžaduje syntaktickou analýzu, analýzu konstantnosti výrazu inicializátoru a analýzu toku dat, aby se zajistilo, že do proměnné není nikdy zapsáno. Platforma kompilátoru .NET poskytuje rozhraní API, která usnadňují provádění této analýzy.
Vytvořte registrace analyzátoru
Šablona vytvoří počáteční třídu DiagnosticAnalyzer
v souboru MakeConstAnalyzer.cs. Tento počáteční analyzátor ukazuje dvě důležité vlastnosti každého analyzátoru.
- Každý diagnostický analyzátor musí poskytnout
[DiagnosticAnalyzer]
atribut, který popisuje jazyk, na kterém pracuje. - Každý diagnostický analyzátor musí být odvozen (přímo nebo nepřímo) od DiagnosticAnalyzer.
Šablona také zobrazuje základní funkce, které jsou součástí jakéhokoli analyzátoru:
- Zaregistrujte akce. Akce představují změny kódu, které by měly aktivovat analyzátor, aby prozkoumaly porušení kódu. Když Visual Studio zjistí úpravy kódu, které odpovídají registrované akci, zavolá zaregistrovanou metodu analyzátoru.
- Vytvoření diagnostiky Když analyzátor zjistí porušení, vytvoří diagnostický objekt, který Sada Visual Studio používá k upozornění uživatele na porušení.
Akce zaregistrujete tím, že přepíšete metodu DiagnosticAnalyzer.Initialize(AnalysisContext). 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 aktualizace konstant registrace a Initialize
metody, aby tyto konstanty indikovaly váš analyzátor "Make Const". Většina řetězcových konstant je definována v souboru zdrojů pro řetězce. Měli byste postupovat podle tohoto postupu pro snadnější lokalizaci. Otevřete soubor Resources.resx pro projekt analyzátoru MakeConst . Zobrazí se editor prostředků. Následujícím způsobem aktualizujte prostředky řetězce:
- 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:
Zbývající změny jsou v souboru analyzátoru. Otevřete MakeConstAnalyzer.cs v Visual Studio. Změňte registrovanou akci z akce, která funguje na symbolech, na druhou, která funguje na syntaxi.
MakeConstAnalyzerAnalyzer.Initialize
V metodě vyhledejte řádek, který zaregistruje 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ím jsou červené vlnovky. Kód, který jste právě přidali, odkazuje na metodu AnalyzeNode
, která nebyla deklarována. Deklarujte danou metodu pomocí následujícího kódu:
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
}
Category
V MakeConstAnalyzer.cs změňte hodnotu na "Usage", jak je znázorněno v následujícím kódu:
private const string Category = "Usage";
Najděte místní deklarace, které by mohly být const
Je čas napsat první verzi AnalyzeNode
metody. Měl by hledat jednu místní deklaraci, která by mohla být const
, ale ne, jako je následující kód:
int x = 0;
Console.WriteLine(x);
Prvním krokem je vyhledání místních deklarací. Do MakeConstAnalyzer.cs přidejte následující kódAnalyzeNode
:
var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;
Toto přetypování vždy proběhne úspěšně, protože analyzátor zaregistroval změny místních deklarací a pouze místní deklarace. Žádný jiný typ uzlu neaktivuje volání vaší AnalyzeNode
metody. Dále zkontrolujte deklaraci všech const
modifikátorů. Pokud je najdete, vraťte se okamžitě. 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 potřeba zkontrolovat, že proměnná může být const
. To znamená zajistit, že po inicializaci se to nikdy nepřiřadí.
Provedete sémantickou analýzu pomocí funkce SyntaxNodeAnalysisContext. Pomocí argumentu context
určíte, zda lze vytvořit const
deklaraci místní proměnné . A Microsoft.CodeAnalysis.SemanticModel představuje všechny sémantické informace v jednom zdrojovém souboru. Další informace najdete v článku, který se zabývá sémantickými modely. Použijete Microsoft.CodeAnalysis.SemanticModel k analýze datového toku u lokální deklarace. Pak použijete výsledky této analýzy toku dat, abyste zajistili, že místní proměnná nebude zapsána s novou hodnotou kdekoli jinde. Zavolejte metodu GetDeclaredSymbol rozšíření, která načte ILocalSymbol proměnnou, a zkontrolujte, jestli 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 ji lze učinit const
. Je čas zlepšit diagnostickou úroveň. Jako poslední řádek přidejte AnalyzeNode
následující kód:
context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation(), localDeclaration.Declaration.Variables.First().Identifier.ValueText));
Průběh můžete zkontrolovat stisknutím klávesy F5 a spustit 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 nahlásit diagnostiku. V závislosti na vaší verzi sady Visual Studio se ale zobrazí tyto možnosti:
- Žárovka, která stále používá opravu kódu vygenerovanou šablonou, vám řekne, že může být napsána s velkými písmeny.
- Bannerová zpráva v horní části editoru informuje, že „MakeConstCodeFixProvider“ narazil na chybu a byl deaktivován. Důvodem je to, že zprostředkovatel opravy kódu ještě nebyl změněn a stále očekává, že místo elementů
LocalDeclarationStatementSyntax
najdeTypeDeclarationSyntax
prvky.
V další části se dozvíte, jak napsat opravu kódu.
Napsání opravy 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 vytvořený analyzátor 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 z uživatelského rozhraní žárovky v editoru a Visual Studio změní kód.
Otevřete soubor CodeFixResources.resx a přejděte CodeFixTitle
na "Make constant".
Otevřete soubor MakeConstCodeFixProvider.cs přidaný šablonou. Tato oprava kódu je již připojena k diagnostickému ID vytvořenému analyzátorem diagnostiky, ale ještě neimplementuje správnou transformaci kódu.
Dále odstraňte metodu MakeUppercaseAsync
. Už to neplatí.
Všichni zprostředkovatelé oprav kódu jsou odvozeni od CodeFixProvider. Všichni přepisují CodeFixProvider.RegisterCodeFixesAsync(CodeFixContext) ke hlášení dostupných oprav kódu. Změňte typ nadřazeného uzlu v RegisterCodeFixesAsync
, který hledáte, na LocalDeclarationStatementSyntax, aby odpovídal diagnostice.
var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<LocalDeclarationStatementSyntax>().First();
V dalším kroku změňte poslední řádek a zaregistrujte opravu kódu. Oprava vytvoří nový dokument, který bude 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);
Kód, který jste právě přidali, obsahuje symbol MakeConstAsync
, u kterého si všimnete červených vlnovek. Přidejte deklaraci pro MakeConstAsync
následující kód:
private static async Task<Document> MakeConstAsync(Document document,
LocalDeclarationStatementSyntax localDeclaration,
CancellationToken cancellationToken)
{
}
Vaše nová MakeConstAsync
metoda transformuje reprezentaci Document zdrojového souboru uživatele na nový Document , který teď obsahuje const
deklaraci.
Vytvoříte nový token klíčového slova const
, který vložíte na začátek příkazu deklarace. Dávejte pozor, abyste nejprve odebrali všechny úvodní trivia z prvního tokenu deklarace 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);
V dalším kroku 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 bezprostředně za existující kód:
// 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:
- Získejte popisovač existujícího dokumentu.
- Vytvořte nový dokument nahrazením existující deklarace novou deklarací.
- 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. Ve druhé instanci sady Visual Studio vytvořte nový projekt konzolové aplikace jazyka 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ášené jako upozornění, jak je uvedeno níže.
Udělal jsi hodně pokroku. Pod deklaracemi, které lze učinit const
, jsou vlnovky. Ale pořád je tu práce. To funguje správně, pokud přidáte const
do deklarací počínaje i
, pak j
a nakonec k
. Ale pokud přidáte const
modifikátor v jiném pořadí, začínaje k
, váš analyzátor vytvoří chyby: k
nelze deklarovat jako const
, pokud i
a j
nejsou oba již const
. Musíte provést další analýzu, abyste zajistili, že budete zpracovávat různé způsoby, jak lze proměnné deklarovat a inicializovat.
Vytvořit jednotkové testy
Analyzátor a oprava kódu fungují na jednoduchém případě jedné deklarace, která se dá vytvořit const. Existuje mnoho možných prohlášení, kde tato implementace dělá chyby. Tyto případy vyřešíte tím, že budete pracovat s knihovnou jednotkových testů napsanou pomocí šablony. Je mnohem rychlejší než opakované otevření 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 testní jednotky pro opravu kódu.
TestMethod1
zobrazuje vzor testu, který zajistí, že analyzátor nehlásí diagnostiku, když by neměl.
TestMethod2
zobrazuje vzor pro vytváření zprávy o diagnostice a spuštění opravy chyb v kódu.
Šablona používá balíčky Microsoft.CodeAnalysis.Testing pro testování jednotek.
Návod
Testovací knihovna podporuje speciální syntaxi značek, včetně následujících:
-
[|text|]
: indikuje, že je diagnostika hlášena protext
. Ve výchozím nastavení lze tento formulář použít pouze pro testování analyzátorů s přesně jednímDiagnosticDescriptor
, poskytovanýmDiagnosticAnalyzer.SupportedDiagnostics
. -
{|ExpectedDiagnosticId:text|}
: indikuje, že diagnostika s IdExpectedDiagnosticId
je hlášena protext
.
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 a ujistěte se, že projde. V aplikaci Visual Studio otevřete Průzkumníka testů výběrem možnosti Test>Windows>Průzkumník testů. Pak vyberte Spustit vše.
Vytvoření testů pro platné deklarace
Obecně platí, že analyzátory by se měly co nejrychleji ukončit svou činnost a provádět minimální množství práce. Visual Studio volá registrované analyzátory, protože uživatel upravuje kód. Odezva je klíčovým požadavkem. Existuje několik testovacích případů pro kód, které by neměly vyvolat vaši diagnostiku. Váš analyzátor už zpracovává několik těchto testů. Přidejte následující testovací metody, které představují tyto případy:
[TestMethod]
public async Task VariableIsAssigned_NoDiagnostic()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using System;
class Program
{
static void Main()
{
int i = 0;
Console.WriteLine(i++);
}
}
");
}
[TestMethod]
public async Task VariableIsAlreadyConst_NoDiagnostic()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using System;
class Program
{
static void Main()
{
const int i = 0;
Console.WriteLine(i);
}
}
");
}
[TestMethod]
public async Task NoInitializer_NoDiagnostic()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using System;
class Program
{
static void Main()
{
int i;
i = 0;
Console.WriteLine(i);
}
}
");
}
Tyto testy projdou, protože váš analyzátor už tyto podmínky zpracovává:
- Proměnné přiřazené po inicializaci se detekují analýzou toku dat.
- Deklarace, které již
const
byly označeny, jsou odfiltrovány na základě klíčového slovaconst
. - Deklarace bez inicializátoru se zpracovávají analýzou toku dat, která detekuje přiřazení mimo deklaraci.
Dále přidejte testovací metody pro podmínky, které jste ještě nezpracovali:
Deklarace, ve kterých inicializátor není konstanta, protože nemohou být konstanty 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. Představte si 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ěnnou i
lze vytvořit konstantní, ale proměnná j
nemůže. Proto nelze toto prohlášení vytvořit jako deklaraci const.
Znovu spusťte testy a tyto poslední dva testovací případy selžou.
Aktualizujte svůj analyzátor tak, aby ignoroval správné deklarace
K vyfiltrování kódu, který odpovídá těmto podmínkám, potřebujete určitá vylepšení metody analyzátoru AnalyzeNode
. Jsou to všechny související podmínky, takže podobné změny opraví všechny tyto podmínky. Proveďte následující změny AnalyzeNode
:
- Vaše sémantická analýza prozkoumala jednu deklaraci proměnné. Tento kód musí být ve
foreach
smyčce, která zkoumá všechny proměnné deklarované ve stejném příkazu. - Každá deklarovaná proměnná musí mít inicializátor.
- Každý deklarovaný inicializátor proměnné musí být konstanta kompilačního času.
Ve vaší metodě AnalyzeNode
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 zkoumá 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. Znovu spusťte testy a měli byste vidět, že jsou všechny úspěšné.
Provést závěrečné úpravy
Už jste téměř hotovi. Váš analyzátor musí zpracovat ještě několik dalších podmínek. Visual Studio volá analyzátory, když uživatel píše kód. Často dochází k tomu, že váš analyzátor bude volán pro kód, který se nekompiluje. Metoda diagnostického AnalyzeNode
analyzátoru nekontroluje, zda je konstantní hodnota konvertibilní na typ proměnné. Aktuální implementace tedy bez problémů 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 odkazové typy správně zpracovány. Jedinou konstantní hodnotou povolenou pro typ odkazu 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 const object s = "abc"
není. Tento fragment kódu ověří tuto podmínku:
[TestMethod]
public async Task DeclarationIsNotString_NoDiagnostic()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using System;
class Program
{
static void Main()
{
object s = ""abc"";
}
}
");
}
Pokud chcete být 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 jak kód, který vyvolá diagnostiku, tak 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"";
}
}
");
}
A konečně, pokud je proměnná deklarována pomocí klíčového var
slova, oprava kódu provede nesprávnou const var
věc a vygeneruje deklaraci, která není podporována jazykem C#. Chcete-li 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í řešit pomocí stejných technik, které jste se právě naučili.
Pokud chcete opravit první chybu, nejprve otevřete MakeConstAnalyzer.cs a vyhledejte smyčku foreach, kde jsou kontrolovány všechny inicializátory místních deklarací, aby se zajistilo, že jsou přiřazeny konstantními hodnotami. Bezprostředně před první smyčkou foreach, spusťte context.SemanticModel.GetTypeInfo()
, aby se načetly podrobné informace o deklarovaném typu místní deklarace.
TypeSyntax variableTypeName = localDeclaration.Declaration.Type;
ITypeSymbol variableType = context.SemanticModel.GetTypeInfo(variableTypeName, context.CancellationToken).ConvertedType;
Pak ve smyčce foreach
zkontrolujte jednotlivé inicializátory, abyste se ujistili, že jsou 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ěna vychází z poslední změny. Před pravou složenou závorku první smyčky foreach přidejte následující kód, který zkontroluje typ místní deklarace, pokud je konstanta typu ř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 var
klíčové slovo správným názvem typu, musíte do zprostředkovatele opravy kódu napsat trochu více kódu. Vraťte se do MakeConstCodeFixProvider.cs. Kód, který přidáte, provede následující kroky:
- Zkontrolujte, jestli deklarace představuje
var
deklaraci, a pokud se jedná o: - Vytvořte nový typ pro odvozený typ.
- Ujistěte se, že deklarace typu není alias. Je-li tomu tak, je legální učinit prohlášení
const var
. - Ujistěte se, že
var
není jméno typu v tomto programu. (Pokud ano,const var
je právní). - Zjednodušení úplného názvu typu
To zní jako spousta kódu. To není. Řádek, který deklaruje newLocal
a inicializuje, nahraďte následujícím kódem. 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 tento typ použít, musíte přidat jednu using
direktivu Simplifier :
using Microsoft.CodeAnalysis.Simplification;
Spusťte testy a všechny by měly úspěšně projít. Pogratulujte si spuštěním hotového analyzátoru. Stisknutím kláves 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 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ž došlo k chybě kompilátoru podle očekávání). - Dále přidejte
object s = "abc";
do metody Main. Z důvodu druhé opravy chyb 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 ním vlevo se objeví návrh. - Přesuňte kurzor editoru nad vlnovkovým podtržením a stiskněte Ctrl+.. pro zobrazení navrhované opravy 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é vlnovky pouze u prvních dvou proměnných. Přidejte const
do obou i
a j
, a zobrazí se nové upozornění na k
, protože teď může být const
.
Gratulujeme! Vytvořili jste první rozšíření platformy kompilátoru .NET, které provádí průběžnou analýzu kódu za účelem zjištění problému a poskytuje rychlou opravu, která ji opraví. Zároveň jste se seznámili s mnoha rozhraními API kódu, která jsou součástí sady .NET Compiler Platform SDK (rozhraní Roslyn API). Svou práci můžete zkontrolovat oproti dokončené ukázce v našem úložišti GitHub s ukázkami.