Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
.NET Compiler Platform SDK fornisce gli strumenti necessari per creare diagnostica personalizzata (analizzatori), correzioni del codice, refactoring del codice e soppressori di diagnostica destinati a codice C# o Visual Basic. Un analizzatore contiene codice che riconosce le violazioni della regola. La correzione del codice contiene il codice che corregge la violazione. Le regole implementate possono essere qualsiasi elemento, dalla struttura del codice allo stile di codifica, alle convenzioni di denominazione e altro ancora. La piattaforma del compilatore .NET fornisce il framework per l'esecuzione dell'analisi mentre gli sviluppatori scrivono codice, e tutte le funzionalità dell'interfaccia utente di Visual Studio per la correzione del codice: visualizzare le onde nell'editor, popolare l'elenco degli errori di Visual Studio, creare i suggerimenti "lampadina" e mostrare l'anteprima dettagliata delle correzioni suggerite.
In questa esercitazione si esaminerà la creazione di un analizzatore e una correzione del codice associata usando le API Roslyn. Un analizzatore è un modo per eseguire l'analisi del codice sorgente e segnalare un problema all'utente. Facoltativamente, una correzione del codice può essere associata all'analizzatore per rappresentare una modifica al codice sorgente dell'utente. Questa esercitazione crea un analizzatore che individua dichiarazioni di variabili locali che potrebbero essere dichiarate usando il modificatore const ma non lo sono. La correzione del codice associata modifica tali dichiarazioni per aggiungere il const modificatore.
Prerequisiti
- Visual Studio 2019 versione 16.8 o successiva
È necessario installare .NET Compiler Platform SDK tramite il programma di installazione di Visual Studio:
Istruzioni di installazione - Programma di installazione di Visual Studio
Esistono due modi diversi per trovare .NET Compiler Platform SDK nel programma di installazione di Visual Studio:
Eseguire l'installazione con il programma di installazione di Visual Studio - Visualizzazione Carichi di lavoro
Il .NET Compiler Platform SDK non viene selezionato automaticamente come parte del carico di lavoro per lo sviluppo di estensioni di Visual Studio. È necessario selezionarlo come componente facoltativo.
- Eseguire il programma di installazione di Visual Studio
- Selezionare Modifica
- Controllare il carico di lavoro sviluppo delle estensioni di Visual Studio .
- Aprire il nodo di sviluppo dell'estensione di Visual Studio nell'albero di riepilogo.
- Selezionare la casella .NET Compiler Platform SDK. Lo troverai per ultimo sotto i componenti facoltativi.
Facoltativamente, è anche necessario che l'editor DGML visualizzi i grafici nel visualizzatore:
- Aprire il nodo Singoli componenti nell'albero di riepilogo.
- Selezionare la casella per l'editor DGML
Eseguire l'installazione usando la scheda Programma di installazione di Visual Studio - Singoli componenti
- Eseguire il programma di installazione di Visual Studio
- Selezionare Modifica
- Selezionare la scheda Singoli componenti
- Selezionare la casella .NET Compiler Platform SDK. È disponibile nella parte superiore della sezione Compilatori, strumenti di compilazione e runtime .
Facoltativamente, è anche necessario che l'editor DGML visualizzi i grafici nel visualizzatore:
- Selezionare la casella per l'editor DGML. È disponibile nella sezione Strumenti di codice .
Esistono diversi passaggi per creare e convalidare l'analizzatore:
- Creare la soluzione.
- Registrare il nome e la descrizione dell'analizzatore.
- Avvisi e raccomandazioni dell'analizzatore del report.
- Implementare la correzione del codice per accettare raccomandazioni.
- Migliorare l'analisi tramite unit test.
Creare la soluzione
- In Visual Studio scegliere File > nuovo > progetto per visualizzare la finestra di dialogo Nuovo progetto.
- In Estendibilità di Visual C# >scegliere Analizzatore con correzione del codice (.NET Standard).
- Assegnare al progetto il nome "MakeConst" e fare clic su OK.
Annotazioni
È possibile che venga visualizzato un errore di compilazione (MSB4062: impossibile caricare l'attività "CompareBuildTaskVersion". Per risolvere questo problema, aggiornare i pacchetti NuGet nella soluzione con Gestione pacchetti NuGet o usarli Update-Package nella finestra Console di Gestione pacchetti.
Esplorare il modello di analizzatore
L'analizzatore con il modello di correzione del codice crea cinque progetti:
- MakeConst, che contiene l'analizzatore.
- MakeConst.CodeFixes, che contiene la correzione del codice.
- MakeConst.Package, usato per produrre il pacchetto NuGet per l'analizzatore e la correzione del codice.
- MakeConst.Test, ovvero un progetto di unit test.
- MakeConst.Vsix, ovvero il progetto di avvio predefinito che avvia una seconda istanza di Visual Studio che ha caricato il nuovo analizzatore. Premere F5 per avviare il progetto VSIX.
Annotazioni
Gli analizzatori devono essere destinati a .NET Standard 2.0 perché possono essere eseguiti nell'ambiente .NET Core (build della riga di comando) e nell'ambiente .NET Framework (Visual Studio).
Suggerimento
Quando si esegue l'analizzatore, si avvia una seconda copia di Visual Studio. Questa seconda copia usa un hive del Registro di sistema diverso per archiviare le impostazioni. Ciò consente di distinguere le impostazioni visive nelle due copie di Visual Studio. È possibile scegliere un tema diverso per l'esecuzione sperimentale di Visual Studio. Inoltre, non sincronizzare le impostazioni o accedere all'account di Visual Studio usando la versione sperimentale di Visual Studio. Ciò mantiene le impostazioni diverse.
L'hive include non solo l'analizzatore in fase di sviluppo, ma anche qualsiasi analizzatore precedente aperto. Per reimpostare l'hive roslyn, è necessario eliminarlo manualmente da %LocalAppData%\Microsoft\VisualStudio. Il nome della cartella di Roslyn hive terminerà in Roslyn, ad esempio . 16.0_9ae182f9Roslyn Si noti che potrebbe essere necessario pulire la soluzione e ricompilarla dopo l'eliminazione dell'hive.
Nella seconda istanza di Visual Studio appena avviata creare un nuovo progetto di applicazione console C# (qualsiasi framework di destinazione funzionerà: gli analizzatori funzionano a livello di origine). Passare il puntatore del mouse sul token con una sottolineatura ondulata e viene visualizzato il testo dell'avviso fornito da un analizzatore.
Il modello crea un analizzatore che segnala un avviso per ogni dichiarazione di tipo in cui il nome del tipo contiene lettere minuscole, come illustrato nella figura seguente:
Il modello fornisce anche una correzione del codice che converte qualsiasi nome di tipo contenente caratteri minuscoli in caratteri maiuscoli. È possibile fare clic sulla lampadina visualizzata con l'avviso per visualizzare le modifiche suggerite. L'accettazione delle modifiche suggerite aggiorna il nome del tipo e tutti i riferimenti a tale tipo nella soluzione. Dopo aver visto l'analizzatore iniziale in azione, chiudere la seconda istanza di Visual Studio e tornare al progetto dell'analizzatore.
Non è necessario avviare una seconda copia di Visual Studio e creare un nuovo codice per testare ogni modifica nell'analizzatore. Il modello crea automaticamente anche un progetto di unit test. Il progetto contiene due test.
TestMethod1 mostra il formato tipico di un test che analizza il codice senza attivare una diagnostica.
TestMethod2 mostra il formato di un test che attiva una diagnostica e quindi applica una correzione del codice suggerita. Durante la compilazione dell'analizzatore e della correzione del codice, si scriveranno test per strutture di codice diverse per verificare il lavoro. Gli unit test per gli analizzatori sono molto più rapidi rispetto ai test interattivi con Visual Studio.
Suggerimento
Gli unit test dell'analizzatore sono un ottimo strumento quando si sa quali costrutti di codice devono e non devono attivare l'analizzatore. Il caricamento dell'analizzatore in un'altra copia di Visual Studio è un ottimo strumento per esplorare e trovare costrutti che potrebbero non essere ancora stati considerati.
In questa esercitazione si scrive un analizzatore che segnala all'utente qualsiasi dichiarazione di variabile locale che può essere convertita in costanti locali. Si consideri ad esempio il codice seguente:
int x = 0;
Console.WriteLine(x);
Nel codice precedente viene x assegnato un valore costante e non viene mai modificato. Può essere dichiarata usando il const modificatore:
const int x = 0;
Console.WriteLine(x);
L'analisi per determinare se una variabile può essere resa costante è complessa, richiedendo un'analisi sintattica, un'analisi dell'espressione di inizializzazione e l'analisi del flusso di dati per garantire che la variabile non venga mai modificata. .NET Compiler Platform fornisce API che semplificano l'esecuzione di questa analisi.
Creare registrazioni dell'analizzatore
Il modello crea la classe iniziale DiagnosticAnalyzer , nel file MakeConstAnalyzer.cs . Questo analizzatore iniziale mostra due proprietà importanti di ogni analizzatore.
- Ogni analizzatore di diagnostica deve fornire un
[DiagnosticAnalyzer]attributo che descrive il linguaggio su cui opera. - Ogni analizzatore di diagnostica deve derivare (direttamente o indirettamente) dalla DiagnosticAnalyzer classe .
Il modello mostra anche le funzionalità di base che fanno parte di qualsiasi analizzatore:
- Registrare le azioni. Le azioni rappresentano le modifiche al codice che devono attivare l'analizzatore per esaminare il codice per individuare eventuali violazioni. Quando Visual Studio rileva modifiche al codice che corrispondono a un'azione registrata, chiama il metodo registrato dell'analizzatore.
- Creare diagnostiche. Quando l'analizzatore rileva una violazione, crea un oggetto di diagnostica usato da Visual Studio per notificare all'utente la violazione.
Vengono registrate le azioni nell'override del metodo DiagnosticAnalyzer.Initialize(AnalysisContext). In questa esercitazione si esamineranno i nodi della sintassi che cercano dichiarazioni locali e si noterà quale di questi hanno valori costanti. Se una dichiarazione può essere costante, l'analizzatore crea e segnala una diagnostica.
Il primo passaggio consiste nell'aggiornare le costanti di registrazione e il metodo Initialize affinché queste costanti indichino l'analizzatore "Make Const". La maggior parte delle costanti stringa è definita nel file di risorse stringa. È consigliabile seguire questa procedura per facilitare la localizzazione. Aprire il file Resources.resx per il progetto di analizzatore MakeConst . Verrà visualizzato l'editor di risorse. Aggiornare le risorse stringa come indicato di seguito:
- Passare
AnalyzerDescriptiona "Variables that are not modified should be made constants.". - Passare
AnalyzerMessageFormata "Variable '{0}' can be made constant". - Passare
AnalyzerTitlea "Variable can be made constant".
Al termine, l'editor di risorse verrà visualizzato come illustrato nella figura seguente:
Le modifiche rimanenti si trovano nel file dell'analizzatore. Aprire MakeConstAnalyzer.cs in Visual Studio. Modificare l'azione registrata da una che agisce sui simboli a una che agisce sulla sintassi. Nel metodo MakeConstAnalyzerAnalyzer.Initialize, trovare la riga che registra l'azione sui simboli.
context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);
Sostituirlo con la riga seguente:
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.LocalDeclarationStatement);
Dopo questa modifica, è possibile eliminare il AnalyzeSymbol metodo . Questo analizzatore esamina le SyntaxKind.LocalDeclarationStatement dichiarazioni, non le SymbolKind.NamedType istruzioni. Si noti che sotto AnalyzeNode è presente una sottolineatura ondulata rossa. Il codice appena aggiunto fa riferimento a un AnalyzeNode metodo che non è stato dichiarato. Dichiarare il metodo usando il codice seguente:
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
}
Modificare "Category" in "Usage" nel file MakeConstAnalyzer.cs come illustrato nel codice seguente.
private const string Category = "Usage";
Trovare dichiarazioni locali che potrebbero essere costanti
È il momento di scrivere la prima versione del AnalyzeNode metodo . Deve cercare una singola dichiarazione locale che potrebbe essere const ma non, come nel codice seguente:
int x = 0;
Console.WriteLine(x);
Il primo passaggio consiste nel trovare le dichiarazioni locali. Aggiungere il codice seguente a AnalyzeNode in MakeConstAnalyzer.cs:
var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;
Questo cast ha sempre esito positivo perché l'analizzatore ha registrato le modifiche alle dichiarazioni locali e solo le dichiarazioni locali. Nessun altro tipo di nodo attiva una chiamata al AnalyzeNode metodo. Controllare quindi la dichiarazione per eventuali const modificatori. Se li trovi, torna immediatamente. Il codice seguente cerca eventuali const modificatori nella dichiarazione locale:
// make sure the declaration isn't already const:
if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
{
return;
}
Infine, è necessario verificare che la variabile possa essere const. Ciò significa assicurarsi che non venga mai assegnato dopo l'inizializzazione.
Si eseguiranno alcune analisi semantiche usando .SyntaxNodeAnalysisContext Usi l'argomento context per determinare se la dichiarazione di variabile locale può essere creata const. Un Microsoft.CodeAnalysis.SemanticModel oggetto rappresenta tutte le informazioni semantiche in un singolo file di origine. Per altre informazioni, vedere l'articolo relativo ai modelli semantici. Si utilizzerà il Microsoft.CodeAnalysis.SemanticModel per eseguire l'analisi del flusso di dati sull'istruzione di dichiarazione locale. Usare quindi i risultati di questa analisi del flusso di dati per assicurarsi che la variabile locale non venga scritta con un nuovo valore altrove. Chiamare il membro dell'estensione GetDeclaredSymbol per recuperare il ILocalSymbol della variabile e verificare che non sia contenuto nella DataFlowAnalysis.WrittenOutside raccolta nella analisi del flusso di dati. Aggiungere il codice seguente alla fine del AnalyzeNode metodo :
// 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;
}
Il codice appena aggiunto garantisce che la variabile non venga modificata e quindi possa essere eseguita const. È il momento di sollevare la diagnostica. Aggiungere il codice seguente come ultima riga in AnalyzeNode:
context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation(), localDeclaration.Declaration.Variables.First().Identifier.ValueText));
È possibile controllare lo stato di avanzamento premendo F5 per eseguire l'analizzatore. È possibile caricare l'applicazione console creata in precedenza e quindi aggiungere il codice di test seguente:
int x = 0;
Console.WriteLine(x);
La lampadina dovrebbe apparire e l'analizzatore dovrebbe riportare una diagnosi. Tuttavia, a seconda della versione di Visual Studio, si vedrà:
- La lampadina, che usa ancora la correzione del codice generata dal modello di template, indicherà che può essere trasformata in lettere maiuscole.
- Messaggio banner nella parte superiore dell'editor che indica che "MakeConstCodeFixProvider" ha rilevato un errore ed è stato disabilitato. Ciò è dovuto al fatto che il provider di correzione del codice non è ancora stato modificato e prevede ancora di trovare
TypeDeclarationSyntaxelementi anzichéLocalDeclarationStatementSyntaxelementi.
La sezione successiva illustra come scrivere la correzione del codice.
Scrivere la correzione del codice
Un analizzatore può fornire una o più correzioni di codice. Una correzione del codice definisce una modifica che risolve il problema segnalato. Per l'analizzatore creato, è possibile fornire una correzione del codice che inserisce la parola chiave const:
- int x = 0;
+ const int x = 0;
Console.WriteLine(x);
L'utente lo sceglie dall'interfaccia utente della lampadina nell'editor e Visual Studio modifica il codice.
Aprire il file CodeFixResources.resx e passare CodeFixTitle a "Make constant".
Aprire il file MakeConstCodeFixProvider.cs aggiunto dal modello. Questa correzione del codice è già collegata all'ID di diagnostica prodotto dall'analizzatore diagnostico, ma non implementa ancora la corretta trasformazione del codice.
Eliminare quindi il MakeUppercaseAsync metodo . Non è più applicabile.
Tutti i provider di correzione del codice derivano da CodeFixProvider. Tutti eseguono l'override di CodeFixProvider.RegisterCodeFixesAsync(CodeFixContext) per segnalare le correzioni del codice disponibili. In RegisterCodeFixesAsync, modificare il tipo di nodo antenato che si sta cercando in un LocalDeclarationStatementSyntax per farlo corrispondere alla diagnostica.
var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<LocalDeclarationStatementSyntax>().First();
Modificare quindi l'ultima riga per registrare una correzione del codice. La correzione creerà un nuovo documento risultante dall'aggiunta del const modificatore a una dichiarazione esistente:
// 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);
Noterai le sottolineature ondulate rosse nel codice appena aggiunto sul simbolo MakeConstAsync. Aggiungere una dichiarazione per MakeConstAsync come il codice seguente:
private static async Task<Document> MakeConstAsync(Document document,
LocalDeclarationStatementSyntax localDeclaration,
CancellationToken cancellationToken)
{
}
Il nuovo MakeConstAsync metodo trasformerà l'oggetto Document che rappresenta il file di origine dell'utente in un nuovo Document oggetto che ora contiene una const dichiarazione.
Si crea un nuovo const token di parola chiave da inserire all'inizio della dichiarazione. Prestare innanzitutto attenzione a rimuovere eventuali caratteri non significativi dal primo token della dichiarazione e collegarli al token const. Aggiungere al metodo MakeConstAsync il codice seguente:
// 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));
Aggiungere quindi il const token alla dichiarazione usando il codice seguente:
// 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);
Formattare quindi la nuova dichiarazione in modo che corrisponda alle regole di formattazione C#. La formattazione delle modifiche in base al codice esistente crea un'esperienza migliore. Aggiungere l'istruzione seguente subito dopo il codice esistente:
// Add an annotation to format the new local declaration.
LocalDeclarationStatementSyntax formattedLocal = newLocal.WithAdditionalAnnotations(Formatter.Annotation);
È necessario un nuovo spazio dei nomi per questo codice. Aggiungere la direttiva seguente using all'inizio del file:
using Microsoft.CodeAnalysis.Formatting;
Il passaggio finale consiste nell'apportare la modifica. Per questo processo sono necessari tre passaggi:
- Ottieni un riferimento al documento esistente.
- Creare un nuovo documento sostituendo la dichiarazione esistente con la nuova dichiarazione.
- Restituisce il nuovo documento.
Aggiungere il codice seguente alla fine del MakeConstAsync metodo :
// 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);
La correzione del codice è pronta per essere provata. Premere F5 per eseguire il progetto analizzatore in una seconda istanza di Visual Studio. Nella seconda istanza di Visual Studio creare un nuovo progetto applicazione console C# e aggiungere alcune dichiarazioni di variabili locali inizializzate con valori costanti al metodo Main. Si noterà che vengono segnalati come avvisi come di seguito.
Hai fatto un sacco di progressi. Le dichiarazioni che possono essere fatte const hanno delle sottolineature ondulate. Ma c'è ancora lavoro da fare. Questa operazione funziona correttamente se si aggiungono const alle dichiarazioni che iniziano con i, quindi j e infine k. Tuttavia, se si aggiunge il const modificatore in un ordine diverso, a partire da k, l'analizzatore crea errori: k non può essere dichiarato const, a meno che i e j non siano già constentrambi . È necessario eseguire altre analisi per assicurarsi di gestire i diversi modi in cui le variabili possono essere dichiarate e inizializzate.
Scrivere test unitari
L'analizzatore e la correzione del codice funzionano in un semplice caso di una singola dichiarazione che può essere effettuata const. Esistono numerose istruzioni di dichiarazione possibili in cui questa implementazione commette errori. Questi casi verranno affrontati usando la libreria di unit test scritta dal modello. È molto più veloce rispetto all'apertura ripetuta di una seconda copia di Visual Studio.
Aprire il file MakeConstUnitTests.cs nel progetto di unit test. Il modello ha creato due test che seguono i due schemi comuni per un analizzatore e un test unitario di correzione del codice.
TestMethod1 mostra il modello per un test che garantisce che l'analizzatore non restituisca una diagnostica quando non dovrebbe.
TestMethod2 mostra il modello per segnalare una diagnostica ed eseguire la correzione del codice.
Il modello usa pacchetti Microsoft.CodeAnalysis.Testing per unit test.
Suggerimento
La libreria di test supporta una sintassi di markup speciale, tra cui:
-
[|text|]: indica che viene segnalata una diagnostica pertext. Per impostazione predefinita, questo modulo può essere utilizzato solo per testare gli analizzatori con esattamente unoDiagnosticDescriptorfornito daDiagnosticAnalyzer.SupportedDiagnostics. -
{|ExpectedDiagnosticId:text|}: indica che il diagnostico con IdExpectedDiagnosticIdviene segnalato pertext.
Sostituire i test del modello nella MakeConstUnitTest classe con il metodo di test seguente:
[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);
}
}
");
}
Eseguire questo test per assicurarsi che venga superato. In Visual Studio, apri Esplora test selezionando Test>Windows>Esplora test. Quindi selezionare Esegui Tutto.
Creare test per dichiarazioni valide
Come regola generale, gli analizzatori devono uscire il più rapidamente possibile, eseguendo operazioni minime. Visual Studio chiama gli analizzatori registrati quando l'utente modifica il codice. La velocità di risposta è un requisito fondamentale. Esistono diversi test case per il codice che non devono generare la tua diagnostica. L'analizzatore gestisce già diversi di questi test. Aggiungere i metodi di test seguenti per rappresentare questi casi:
[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);
}
}
");
}
Questi test vengono superati perché l'analizzatore gestisce già queste condizioni:
- Le variabili assegnate dopo l'inizializzazione vengono rilevate dall'analisi del flusso di dati.
- Le dichiarazioni già
constsono escluse verificando il termineconst. - Le dichiarazioni senza inizializzatore vengono gestite dall'analisi del flusso di dati che rileva le assegnazioni all'esterno della dichiarazione.
Aggiungere quindi i metodi di test per le condizioni non ancora gestite:
Dichiarazioni in cui l'inizializzatore non è una costante, perché non possono essere costanti in fase di compilazione:
[TestMethod] public async Task InitializerIsNotConstant_NoDiagnostic() { await VerifyCS.VerifyAnalyzerAsync(@" using System; class Program { static void Main() { int i = DateTime.Now.DayOfYear; Console.WriteLine(i); } } "); }
Può essere ancora più complicato perché C# consente più dichiarazioni come un'unica istruzione. Si consideri la costante di stringa del caso di test seguente:
[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);
}
}
");
}
La variabile i può essere resa costante, ma la variabile j non può. Pertanto, questa istruzione non può essere trasformata in una dichiarazione const.
Esegui di nuovo i test e vedrai che questi ultimi due casi di test avranno esito negativo.
Aggiorna l'analizzatore per ignorare le dichiarazioni corrette
Sono necessari alcuni miglioramenti al metodo dell'analizzatore AnalyzeNode per filtrare il codice che corrisponde a queste condizioni. Sono tutte condizioni correlate, quindi modifiche simili correggeranno tutte queste condizioni. Modificare AnalyzeNode nel modo seguente:
- L'analisi semantica ha esaminato una singola dichiarazione di variabile. Questo codice deve trovarsi in un
foreachciclo che esamina tutte le variabili dichiarate nella stessa istruzione. - Ogni variabile dichiarata deve avere un inizializzatore.
- L'inizializzatore di ogni variabile dichiarata deve essere una costante in fase di compilazione.
Nel tuo AnalyzeNode metodo, sostituisci l'analisi semantica originale:
// 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;
}
con il frammento di codice seguente:
// 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;
}
}
Il primo foreach ciclo esamina ogni dichiarazione di variabile usando l'analisi sintattica. Il primo controllo garantisce che la variabile abbia un inizializzatore. Il secondo controllo garantisce che l'inizializzatore sia una costante. Il secondo ciclo ha l'analisi semantica originale. I controlli semantici si trovano in un ciclo separato perché ha un maggiore impatto sulle prestazioni. Esegui nuovamente i tuoi test e dovresti vederli tutti superare.
Aggiungere la lucidatura finale
La procedura è quasi terminata. Esistono alcune altre condizioni che l'analizzatore deve gestire. Visual Studio chiama analizzatori mentre l'utente sta scrivendo codice. Spesso l'analizzatore verrà chiamato per il codice che non viene compilato. Il metodo dell'analizzatore AnalyzeNode di diagnostica non verifica se il valore costante è convertibile nel tipo di variabile. Pertanto, l'implementazione corrente convertirà felicemente una dichiarazione errata, int i = "abc" ad esempio in una costante locale. Aggiungere un metodo di test per questo caso:
[TestMethod]
public async Task DeclarationIsInvalid_NoDiagnostic()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using System;
class Program
{
static void Main()
{
int x = {|CS0029:""abc""|};
}
}
");
}
Inoltre, i tipi riferimento non vengono gestiti correttamente. L'unico valore costante consentito per un tipo riferimento è null, tranne nel caso di System.String, che consente valori letterali stringa. In altre parole, const string s = "abc" è legale, ma const object s = "abc" non è. Questo frammento di codice verifica tale condizione:
[TestMethod]
public async Task DeclarationIsNotString_NoDiagnostic()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using System;
class Program
{
static void Main()
{
object s = ""abc"";
}
}
");
}
Per essere accurati, è necessario aggiungere un altro test per assicurarsi di poter creare una dichiarazione costante per una stringa. Il frammento di codice seguente definisce sia il codice che genera la diagnostica e il codice dopo l'applicazione della correzione:
[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"";
}
}
");
}
Infine, se una variabile viene dichiarata con la var parola chiave , la correzione del codice esegue l'operazione errata e genera una const var dichiarazione, che non è supportata dal linguaggio C#. Per correggere questo bug, la correzione del codice deve sostituire la var parola chiave con il nome del tipo dedotto:
[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"";
}
}
");
}
Fortunatamente, tutti i bug precedenti possono essere risolti usando le stesse tecniche appena apprese.
Per correggere il primo bug, aprire prima MakeConstAnalyzer.cs e individuare il ciclo foreach in cui vengono controllati gli inizializzatori della dichiarazione locale per assicurarsi che vengano assegnati con valori costanti. Immediatamente prima del primo ciclo foreach, chiamare context.SemanticModel.GetTypeInfo() per recuperare informazioni dettagliate sul tipo dichiarato della dichiarazione locale:
TypeSyntax variableTypeName = localDeclaration.Declaration.Type;
ITypeSymbol variableType = context.SemanticModel.GetTypeInfo(variableTypeName, context.CancellationToken).ConvertedType;
Quindi, all'interno del foreach ciclo, controlla ogni inizializzatore per assicurarti che sia convertibile al tipo di variabile. Dopo aver verificato che l'inizializzatore sia una costante, aggiungere il seguente controllo:
// 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;
}
La modifica successiva si basa sull'ultima. Prima della parentesi graffa di chiusura del primo ciclo foreach, aggiungere il codice seguente per controllare il tipo della dichiarazione locale quando la costante è una stringa o 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;
}
È necessario scrivere un po' di codice nel provider di correzione del codice per sostituire la var parola chiave con il nome del tipo corretto. Tornare a MakeConstCodeFixProvider.cs. Il codice che si aggiungerà esegue i passaggi seguenti:
- Controllare se la dichiarazione è una
vardichiarazione e, se è: - Creare un nuovo tipo per il tipo dedotto.
- Assicurarsi che la dichiarazione di tipo non sia un alias. In tal caso, è legale dichiarare
const var. - Assicurarsi che
varnon sia un nome di tipo in questo programma. (In tal caso,const varè legale). - Semplificare il nome completo del tipo
Sembra un sacco di codice. Non lo è. Sostituire la riga che dichiara e inizializza newLocal con il codice seguente. Viene eseguita immediatamente dopo l'inizializzazione di 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);
È necessario aggiungere una using direttiva per usare il Simplifier tipo :
using Microsoft.CodeAnalysis.Simplification;
Esegui i tuoi test, e dovrebbero passare tutti. Congratulati per aver completato l'analizzatore. Premere CTRL+F5 per eseguire il progetto analizzatore in una seconda istanza di Visual Studio con l'estensione Roslyn Preview caricata.
- Nella seconda istanza di Visual Studio creare un nuovo progetto applicazione console C# e aggiungerlo
int x = "abc";al metodo Main. Grazie alla prima correzione di bug, non dovrebbe essere segnalato alcun avviso per questa dichiarazione di variabile locale (anche se si verifica un errore del compilatore come previsto). -
object s = "abc";Aggiungere quindi al metodo Main. A causa della seconda correzione di bug, non dovrebbe essere segnalato alcun avviso. - Aggiungere infine un'altra variabile locale che usa la
varparola chiave . Si noterà che viene segnalato un avviso e sotto a sinistra viene visualizzato un suggerimento. - Spostare il cursore dell'editor sulla sottolineatura ondulata e premere CTRL+.. per visualizzare la correzione del codice suggerita. Dopo aver selezionato la correzione del codice, si noti che la
varparola chiave è ora gestita correttamente.
Aggiungere infine il codice seguente:
int i = 2;
int j = 32;
int k = i + j;
Dopo queste modifiche, si ottengono ondulature rosse solo sulle prime due variabili. Aggiungere const a i e je viene visualizzato un nuovo avviso in k perché ora può essere const.
Congratulazioni! È stata creata la prima estensione .NET Compiler Platform che esegue l'analisi del codice on-the-fly per rilevare un problema e fornisce una correzione rapida per correggerla. Nel corso della procedura sono state apprese molte delle API di codice che fanno parte di .NET Compiler Platform SDK (API Roslyn). È possibile verificare il tuo lavoro con l'esempio completato nel repository di esempi su GitHub.