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, protože 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, vytvoření návrhů "žárovky" a zobrazení bohatého náhledu navrhovaných oprav.
V tomto kurzu prozkoumáte vytvoření analyzátoru a opravu doprovodného kódu pomocí rozhraní API Roslyn. Analyzátor je způsob, jak provést analýzu zdrojového kódu a nahlásit uživateli problém. Volitelně můžete k analyzátoru přidružit opravu 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. Oprava doprovodného const kódu upraví tyto deklarace a přidá modifikátor.
Požadavky
Sadu .NET Compiler Platform SDK je potřeba nainstalovat pomocí 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ů.
- Ujistěte se, že políčko pro sadu .NET Compiler Platform SDK je zaškrtnuté.
- Vyberte Upravit.
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 .
- Vyberte Upravit.
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 .
Vytvoření a ověření analyzátoru zahrnuje 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 vyberte Soubor>nový>projekt nebo řešení a otevřete tak dialogové okno Nový projekt.
- Do vyhledávacího pole zadejte Analyzer a najděte analyzátor pomocí šablony Oprava kódu (.NET Standard).
- Vyberte Další.
- Pojmenujte projekt MakeConst a vyberte Vytvořit.
Poznámka:
Může se zobrazit chyba kompilace (MSB4062: Úlohu CompareBuildTaskVersion nelze načíst). Pokud chcete tuto chybu opravit, 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. Tento rozdíl umožňuje 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 nepřihlašujte k nastavení ani se nepřihlašujte ke svému účtu sady Visual Studio pomocí experimentálního spuštění sady Visual Studio. 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 pro Roslyn hive končí na Roslyn, například 16.0_9ae182f9Roslyn. Po odstranění úlu může být nutné ř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. Můžete vybrat žárovku zobrazenou s upozorněním a 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ž vidíte 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ý 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é instanci sady Visual Studio je skvělý nástroj pro prozkoumávání a objevování konstrukcí, o kterých jste dosud možná 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);
V předchozím x kódu je přiřazena konstantní hodnota a nikdy se nezmění. Dá se deklarovat pomocí modifikátoru const :
const int x = 0;
Console.WriteLine(x);
Analýza, která určuje, jestli je možné vytvořit proměnnou konstantu, vyžaduje syntaktickou analýzu, konstantní analýzu výrazu inicializátoru a analýzu toku dat, aby se zajistilo, že se proměnná nikdy nezapisuje. 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í DiagnosticAnalyzer třídu 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 hlá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. Postupujte podle tohoto postupu pro snadnější lokalizaci. Otevřete soubor Resources.resx pro projekt analyzátoru MakeConst . Tento úkon zobrazí editor zdrojů. Následujícím způsobem aktualizujte prostředky řetězce:
- Změňte
AnalyzerDescriptionna "Variables that aren't modified should be made constants.". - Změňte
AnalyzerMessageFormatna "Variable '{0}' can be made constant". - Změňte
AnalyzerTitlena "Variable can be made constant".
Po dokončení by se měl editor prostředků zobrazit, jak je znázorněno na následujícím obrázku:
Proveďte zbývající změny 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á není deklarována. Deklarujte danou metodu pomocí následujícího kódu:
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
}
Category V Usage změňte hodnotu na "", 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
Teď jste připraveni napsat první verzi AnalyzeNode metody. Měla by hledat jednu místní deklaraci, která by mohla být const , ale ne, například následující kód:
int x = 0;
Console.WriteLine(x);
Prvním krokem je vyhledání místních deklarací. Do AnalyzeNode 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 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. Tato kontrola zajistí, že se proměnná po inicializaci nikdy nepřiřadí.
Proveďte sémantickou analýzu pomocí funkce SyntaxNodeAnalysisContext. Pomocí argumentu context určete, zda lze vytvořit constdeklaraci 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žijte Microsoft.CodeAnalysis.SemanticModel k provedení analýzy toku dat v příkazu místní deklarace. Pak pomocí výsledků této analýzy toku dat zajistěte, aby místní proměnná nebyla zapsána s novou hodnotou kdekoli jinde. Zavolejte člena rozšíření GetDeclaredSymbol pro načtení ILocalSymbol proměnné a zkontrolujte, zda není obsažena v kolekci DataFlowAnalysis.WrittenOutside 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;
}
Kód, který jste právě přidali, zajistí, že proměnná nebude změněna, a takže ji lze udělat const. Je čas zlepšit diagnostickou úroveň. Jako poslední řádek přidejte AnalyzeNodená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 říká, že u 'MakeConstCodeFixProvider' došlo k chybě a byla deaktivována. K této chybě dochází, protože zprostředkovatel opravy kódu ještě nebyl změněn a stále očekává nalezení
TypeDeclarationSyntaxprvků místoLocalDeclarationStatementSyntaxprvků.
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. Vidíte, že jsou hlášené jako upozornění, jak je znázorněno na následujícím obrázku.
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 ja 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. Potřebujete 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
Analýza a úprava kódu pracují na jednoduchém případě jedné deklarace, kterou můžete označit jako const. Existuje mnoho možných deklarací, ve kterých 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 IdExpectedDiagnosticIdje 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 váš kód, které by neměly vyvolat diagnózu. 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ž
constbyly 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, kde inicializátor není konstantní, protože nemůže být konstantou v době 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ěnná i může být konstantní, ale proměnná j nemůže. Proto tento příkaz nemůže být deklarací konstanty.
Znovu spusťte testy a uvidíte, že 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, je potřeba vylepšit metodu AnalyzeNode analyzátoru. Všechny tyto stavy jsou provázány, takže podobné změny opraví všechny stavy. 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
foreachsmyč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 se stává, že se analyzátor volá pro kód, který nejde zkompilovat. Metoda diagnostického AnalyzeNode analyzátoru nekontroluje, jestli je konstantní hodnota konvertibilní na typ proměnné. Aktuální implementace tedy šťastně 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 referenční typy zpracovány správně. 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"";
}
}
");
}
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, kterou jazyk C# nepodporuje. 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 můžete naštěstí vyřešit pomocí stejných technik, které jste se právě naučili.
Pokud chcete opravit první chybu, otevřete MakeConstAnalyzer.cs a vyhledejte smyčku foreach , ve které jsou kontrolovány inicializátory místních deklarací, aby se zajistilo, že jsou přiřazeny konstantní hodnoty. Bezprostředně před první foreach smyčkou zavolejte context.SemanticModel.GetTypeInfo() pro načtení podrobných informací 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í foreach smyčky přidejte následující kód, který zkontroluje typ místní deklarace, jestliže je konstanta ř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
vardeklaraci, 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
varnení jméno typu v tomto programu. (Pokud ano,const varje 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 se pro tuto deklaraci místní proměnné nehlásí žá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. Kvůli druhé opravě chyby se nenahlásí žádné upozornění. - Nakonec přidejte další místní proměnnou, která používá
varklíčové slovo. Uvidíte, že se zobrazí upozornění a nalevo se zobrazí 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
varklíč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í místní 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 úložišti GitHub s ukázkami.