Roslyn–analiza kodu za pomocą LINQ

Analiza kodu za pomocą LINQ to chyba esencja Roslyn.  Bez Roslyn, kod był dla nas jak zwykły tekst (string) i w przypadku jakiejkolwiek analizy, musieliśmy sami parsować tekst i rozpoznawać odpowiednie fragmenty.

Najpierw tworzymy SyntaxTree za pomocą zwykłego tekstu:

SyntaxTree tree = CSharpSyntaxTree.ParseText(@" public class Class1 { public void TestMethod1() { } } public class Class2 { public void TestMethod2() { } } "); SyntaxNode root = tree.GetRoot();

Metoda DescendantNodes zwraca wszystkich potomków, również tych niebezpośrednich. Jeśli chcemy zwrócić wszystkie klasy wystarczy szukać węzłów typu ClassDecelarionSyntax:

foreach (ClassDeclarationSyntax classDeclarationSyntax in root.DescendantNodes().OfType<ClassDeclarationSyntax>()) { Console.WriteLine(classDeclarationSyntax.Identifier); }

Analogicznie, w celu wyświetlenia metod wystarczy:

foreach(ClassDeclarationSyntax classDeclarationSyntax in root.DescendantNodes().OfType<ClassDeclarationSyntax>()) { Console.WriteLine(classDeclarationSyntax.Identifier); foreach (var method in classDeclarationSyntax.DescendantNodes().OfType<MethodDeclarationSyntax>()) { Console.WriteLine("\t{0}",method.Identifier); } }

Jak wspomniałem, DescendantNodes zwraca wszystkich potomków więc możemy wyświetlić metody bezpośrednio korzystając z korzenia:

foreach (MethodDeclarationSyntax methodDeclarationSyntax in root.DescendantNodes().OfType<MethodDeclarationSyntax>()) { Console.WriteLine(methodDeclarationSyntax.Identifier); }

Jeśli chcemy tylko bezpośrednich potomków (czyli dzieci) wtedy korzystamy z ChildNodes:

foreach (MethodDeclarationSyntax methodDeclarationSyntax in root.ChildNodes().OfType<MethodDeclarationSyntax>()) { Console.WriteLine(methodDeclarationSyntax.Identifier); }

Zmodyfikujmy trochę kod, aby zawierał metodę z parametrami:

SyntaxTree tree = CSharpSyntaxTree.ParseText(@" public class Class1 { public void TestMethod1() { } public void TestMethod1(string a, string b, int c) { } } ");

Zapytanie zwracające wszystkie metody, które zawierają parametry to:

SyntaxNode root = tree.GetRoot(); MethodDeclarationSyntax methodWithPars = root.DescendantNodes().OfType<MethodDeclarationSyntax>().First(n => n.ParameterList.Parameters.Any()); Console.WriteLine(methodWithPars.Identifier);

Wyświetlenie parametrów sprowadza się do prostej pętli:

foreach (var par in methodWithPars.ParameterList.Parameters) { Console.WriteLine(par.ToString()); }

Często potrzebujemy pójść w drugą strony, to znaczy mamy wskaźnik na potomka, ale chcemy znaleźć rodzica. Do dyspozycji mamy metodę Ancestors:

foreach (var par in methodWithPars.ParameterList.Parameters) { string className = par.Ancestors().OfType<ClassDeclarationSyntax>().Single().Identifier.ToString(); Console.WriteLine("{0}:{1}",className,par.ToString()); }

Za pomocą LINQ możemy uzyskać dostęp do każdego fragmentu kodu, nie tylko deklaracji. Możemy np. wylistować IF”Y:

SyntaxTree tree = CSharpSyntaxTree.ParseText(@" public class Class1 { public void TestMethod1() { } public void TestMethod1(string a, string b, int c) { if(c==5 { } } } "); SyntaxNode root = tree.GetRoot(); var ifStatements = root.DescendantNodes().OfType<IfStatementSyntax>(); foreach (var ifStatementSyntax in ifStatements) { Console.WriteLine(ifStatementSyntax.ToString()); }

Analogicznie sprawa wygląda z wywołaniami metod, klauzulami using, switch\case, pętlami, zwracaniem wartości jak i z KAŻDYM elementem kodu. Innymi słowy drzewo jest na tyle szczegółowe, że “bez problemu” możemy napisać konwerter z C# na jakikolwiek inny język np. JavaScript, bez konieczności ręcznego parsowania kodu C#.

Leave a Reply

Your email address will not be published.