Začínáme s sémantickou analýzou

V tomto kurzu se předpokládá, že znáte rozhraní API syntaxe. Článek začínáme s analýzou syntaxe poskytuje dostatečný úvod.

V tomto kurzu prozkoumáte rozhraní API pro symboly a API pro vazby. Tato rozhraní API poskytují informace o sémantickém významu programu. Umožňují klást a odpovídat na otázky týkající se typů reprezentovaných libovolným symbolem v programu.

Budete muset nainstalovat sadu SDK pro platformu .NET Compiler:

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.

  1. Spuštění instalačního programu sady Visual Studio
  2. Vyberte Upravit.
  3. Zkontrolujte pracovní zátěž vývoje rozšíření pro Visual Studio.
  4. Otevřete uzel vývoje rozšíření sady Visual Studio v přehledovém stromu.
  5. Ujistěte se, že políčko pro sadu .NET Compiler Platform SDK je zaškrtnuté.
  6. Vyberte Upravit.

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

  1. Otevřete uzel Jednotlivé komponenty v přehledovém stromu.
  2. Zaškrtněte políčko pro editor DGML.

Instalace pomocí instalačního programu sady Visual Studio – karta Jednotlivé komponenty

  1. Spuštění instalačního programu sady Visual Studio
  2. Vyberte Upravit.
  3. Vyberte kartu Jednotlivé součásti
  4. 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 .
  5. Vyberte Upravit.

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

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

Principy kompilací a symbolů

Při práci se sadou .NET Compiler SDK se seznámíte s rozdíly mezi rozhraním API syntaxe a sémantickým rozhraním API. Rozhraní API syntaxe umožňuje podívat se na strukturu programu. Často však chcete bohatší informace o sémantice nebo významu programu. I když soubor nebo fragment kódu volného kódu jazyka Visual Basic nebo C# je možné syntakticky analyzovat izolovaně, není smysluplné klást otázky, jako je například "jaký je typ této proměnné" ve vakuu. Význam názvu typu může být závislý na odkazech na sestavení, importech oboru názvů nebo jiných souborech kódu. Tyto otázky jsou zodpovězeny pomocí sémantického rozhraní API, konkrétně třídy Microsoft.CodeAnalysis.Compilation .

Instance Compilation je podobná jednomu projektu, jak je vidět kompilátorem, a představuje vše potřebné ke kompilaci programu jazyka Visual Basic nebo C#. Kompilace zahrnuje sadu zdrojových souborů, které se mají kompilovat, odkazy na sestavení a možnosti kompilátoru. K významu kódu můžete použít všechny ostatní informace v tomto kontextu. A Compilation umožňuje najít Symbols — entity, na které názvy a další výrazy odkazují, jako jsou typy, obory názvů, členy a proměnné. Proces přiřazování názvů a výrazů se symboly se nazývá Propojení.

Stejně jako Microsoft.CodeAnalysis.SyntaxTree, Compilation je abstraktní třída s odvozenými třídami specifickými pro jazyk. Při vytváření instance kompilace je nutné vyvolat objektu factory metody ve Microsoft.CodeAnalysis.CSharp.CSharpCompilation třídě (nebo Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation).

Dotazování symbolů

V tomto kurzu se znovu podíváte na program "Hello World". Tentokrát zadáte dotaz na symboly v programu, abyste porozuměli typům těchto symbolů. Dotazujete se na typy v oboru názvů a naučíte se, jak najít metody, které jsou dostupné pro určitý typ.

Hotový kód této ukázky si můžete prohlédnout v našem úložišti GitHub.

Poznámka:

Typy stromu syntaxe používají dědičnost k popisu různých prvků syntaxe, které jsou platné v různých umístěních v programu. Použití těchto rozhraní API často znamená přetypování vlastností nebo členů kolekce na konkrétní odvozené typy. V následujících příkladech jsou přiřazení a přetypování samostatné příkazy, které používají explicitně typované proměnné. Kód si můžete přečíst, abyste viděli návratové typy rozhraní API a typ běhového prostředí vrácených objektů. V praxi je častější používat implicitně typované proměnné a spoléhat se na názvy rozhraní API, které popisují typ objektů, které se prověřují.

Vytvořte nový projekt C# samostatného nástroje pro analýzu kódu:

  • V sadě Visual Studio zvolte Soubor>nový>projekt , aby se zobrazilo dialogové okno Nový projekt.
  • V části Visual C#>Rozšiřitelnost zvolte Samostatný nástroj pro analýzu kódu.
  • Pojmenujte projekt "SemanticQuickStart" a klikněte na OK.

Budete analyzovat základní program "Hello World!" zobrazený dříve. Přidejte text pro program Hello World jako konstantu ve třídě Program :

        const string programText =
@"using System;
using System.Collections.Generic;
using System.Text;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(""Hello, World!"");
        }
    }
}";

Dále přidejte následující kód, který vytvoří strom syntaxe pro text kódu v konstantě programText . Do metody přidejte následující řádek Main :

SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);

CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

Dále sestavte CSharpCompilation ze stromu, který jste už vytvořili. Ukázka "Hello World" spoléhá na typy String a Console. Musíte odkazovat na sestavení, které deklaruje tyto dva typy v kompilaci. Přidejte do Main své metody následující řádek, který vytvoří kompilaci stromu syntaxe, včetně odkazu na příslušné sestavení:

var compilation = CSharpCompilation.Create("HelloWorld")
    .AddReferences(MetadataReference.CreateFromFile(
        typeof(string).Assembly.Location))
    .AddSyntaxTrees(tree);

Metoda CSharpCompilation.AddReferences přidá odkazy na kompilaci. Metoda MetadataReference.CreateFromFile načte sestavení jako referenci.

Dotazování sémantického modelu

Jakmile budete mítCompilation, můžete požádat o SemanticModel pro jakékoliv SyntaxTree obsažené v tom Compilation. Sémantický model si můžete představit jako zdroj všech informací, které byste normálně získali z intellisense. SemanticModel může odpovídat na otázky jako: "Jaké názvy jsou ve viditelnosti na tomto místě?", "Jaké členy jsou přístupné z této metody?", "Jaké proměnné se používají v tomto bloku textu?" a "Na co tento název nebo výraz odkazuje?" Přidejte toto tvrzení pro vytvoření sémantického modelu:

SemanticModel model = compilation.GetSemanticModel(tree);

Vazba názvu

Compilation vytvoří SemanticModel z SyntaxTree. Po vytvoření modelu se na něj můžete dotazovat, abyste našli první using direktivu a načetli informace o symbolech System pro obor názvů. Přidejte do Main metody tyto dva řádky, abyste vytvořili sémantický model a načetli symbol první using direktivy:

// Use the syntax tree to find "using System;"
UsingDirectiveSyntax usingSystem = root.Usings[0];
NameSyntax systemName = usingSystem.Name;

// Use the semantic model for symbol information:
SymbolInfo nameInfo = model.GetSymbolInfo(systemName);

Předchozí kód ukazuje, jak vytvořit vazbu názvu v první using direktivě pro načtení Microsoft.CodeAnalysis.SymbolInfo pro obor názvů System. Předchozí kód také ukazuje, že pomocí modelu syntaxe zjistíte strukturu kódu; použijete sémantický model k pochopení jeho významu. Model syntaxe najde řetězec System v direktivě using. Sémantický model obsahuje všechny informace o typech definovaných v System oboru názvů.

Z objektu SymbolInfo můžete získat Microsoft.CodeAnalysis.ISymbol pomocí vlastnosti SymbolInfo.Symbol. Tato vlastnost vrátí symbol, na který tento výraz odkazuje. U výrazů, které neodkazují na nic (například číselné literály), je tato vlastnost null. SymbolInfo.Symbol Pokud hodnota null není, ISymbol.Kind označuje typ symbolu. V tomto příkladu je vlastnost ISymbol.KindSymbolKind.Namespace. Do metody Main přidejte následující kód. Načte symbol pro obor názvů System a pak zobrazí všechny podřízené obory názvů deklarované v oboru názvů System:

var systemSymbol = (INamespaceSymbol?)nameInfo.Symbol;
if (systemSymbol?.GetNamespaceMembers() is not null)
{
    foreach (INamespaceSymbol ns in systemSymbol?.GetNamespaceMembers()!)
    {
        Console.WriteLine(ns);
    }
}

Spusťte program a měli byste vidět následující výstup:

System.Collections
System.Configuration
System.Deployment
System.Diagnostics
System.Globalization
System.IO
System.Numerics
System.Reflection
System.Resources
System.Runtime
System.Security
System.StubHelpers
System.Text
System.Threading
Press any key to continue . . .

Poznámka:

Výstup nezahrnuje každou podřízenou oblast názvů oboru System. Zobrazí každý obor názvů, který je v této kompilaci, který odkazuje pouze na sestavení, kde System.String je deklarováno. Pro tuto kompilaci nejsou známy všechny obory názvů deklarované v jiných sestaveních.

Vytvoření vazby výrazu

Předchozí kód ukazuje, jak najít symbol vazbou na název. V programu jazyka C# existují další výrazy, které mohou být vázány, které nejsou názvy. Abychom si tuto funkci ukázali, přistupme k vazbě na jednoduchý řetězcový literál.

Program "Hello World" obsahuje Microsoft.CodeAnalysis.CSharp.Syntax.LiteralExpressionSyntaxřetězec "Hello, World!" zobrazený na konzoli.

Řetězec "Hello, World!" najdete tak, že v programu vyhledáte konkrétní textový literál. Jakmile pak najdete uzel syntaxe, získejte informace o typu pro tento uzel z sémantického modelu. Do metody přidejte následující kód Main :

// Use the syntax model to find the literal string:
LiteralExpressionSyntax helloWorldString = root.DescendantNodes()
.OfType<LiteralExpressionSyntax>()
.Single();

// Use the semantic model for type information:
TypeInfo literalInfo = model.GetTypeInfo(helloWorldString);

Struktura Microsoft.CodeAnalysis.TypeInfo obsahuje vlastnost TypeInfo.Type, která umožňuje přistupovat k sémantickým informacím o typu literálu. V tomto příkladu je to typ string. Přidejte deklaraci, která přiřadí tuto vlastnost místní proměnné:

var stringTypeSymbol = (INamedTypeSymbol?)literalInfo.Type;

K dokončení tohoto kurzu vytvoříme dotaz LINQ, který vytvoří sekvenci všech veřejných metod deklarovaných v string typu, který vrací string. Tento dotaz je složitý, takže ho sestavíme řádek po řádce a pak ho rekonstruujme jako jediný dotaz. Zdrojem pro tento dotaz je posloupnost všech členů deklarovaných v string typu:

var allMembers = stringTypeSymbol?.GetMembers();

Tato zdrojová sekvence obsahuje všechny členy, včetně vlastností a polí, a proto je vyfiltrujte pomocí ImmutableArray<T>.OfType metody k vyhledání prvků, které jsou Microsoft.CodeAnalysis.IMethodSymbol objekty:

var methods = allMembers?.OfType<IMethodSymbol>();

Dále přidejte další filtr, který vrátí pouze ty metody, které jsou veřejné a vrátí string:

var publicStringReturningMethods = methods?
    .Where(m => SymbolEqualityComparer.Default.Equals(m.ReturnType, stringTypeSymbol) &&
    m.DeclaredAccessibility == Accessibility.Public);

Vyberte pouze vlastnost názvu a pouze jedinečné názvy odstraněním jakýchkoli přetížení:

var distinctMethods = publicStringReturningMethods?.Select(m => m.Name).Distinct();

Úplný dotaz můžete také sestavit pomocí syntaxe dotazu LINQ a pak zobrazit všechny názvy metod v konzole:

foreach (string name in (from method in stringTypeSymbol?
                         .GetMembers().OfType<IMethodSymbol>()
                         where SymbolEqualityComparer.Default.Equals(method.ReturnType, stringTypeSymbol) &&
                         method.DeclaredAccessibility == Accessibility.Public
                         select method.Name).Distinct())
{
    Console.WriteLine(name);
}

Sestavte a spusťte program. Měl by se zobrazit následující výstup:

Join
Substring
Trim
TrimStart
TrimEnd
Normalize
PadLeft
PadRight
ToLower
ToLowerInvariant
ToUpper
ToUpperInvariant
ToString
Insert
Replace
Remove
Format
Copy
Concat
Intern
IsInterned
Press any key to continue . . .

Pomocí sémantického rozhraní API jste našli a zobrazili informace o symbolech, které jsou součástí tohoto programu.