AOP with Roslyn–part 5 – getting line number right

There is a problem with inserting lines  – the number of lines is not the same. What I mean by this?

Look at this example

 

 

using System;
namespace Test1
{
    class Program
    {
        static void Main(string[] args)
        {
              var dt=DateTime.Now;
        }
     }
}

 

The DateTime.Now is on line 8

When we insert lines before and after each method, the DateTime.Now is on line 9

using System;
namespace Test1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(" + "\"start Program_Main_6\"" + @");
            var dt = DateTime.Now;
            Console.WriteLine(" + "\"end Program_Main_6\"" + @");
        }
    }
}

This is usually not so important  – until you think about exception and stack trace. If we are moving the number of lines like so, the stack trace that will be raised by the program will make no sense to the programmer that grabs the code from the source control. More, the problem is aggravating with each method – so, if you have 30 methods in a file(I exaggerate a bit ) the number of lines will be increased with 30 for the last method. For others , will be with 29, 28 and so on, until the first method.

Fortunately, there is a solution – the #line directive.

See https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives/preprocessor-line and  https://blogs.msdn.microsoft.com/abhinaba/2005/10/10/c-fun-with-line-directive/

So, what we need is to generate the #line <number> directive.

 

 

AOP with Roslyn–part 4–custom code to the end of the method

 

A good AOP needs also custom code to the beginning and to the end of the method. So we need to add formatter for the last line.

That means that the MetodRewriter constructor will look like this:

 

public MethodRewriter(string formatterFirstLine,string formatterLastLine=null)
{
   FormatterFirstLine = formatterFirstLine;
   FormatterLastLine = formatterLastLine;
}

 

More, we should put code at the beginning and at the end of the method

if (cmdLastLine!= null)
 blockWithNewStatements = blockWithNewStatements.Insert(0, cmdLastLine);
//code
if(cmdFirstLine != null)
 blockWithNewStatements = blockWithNewStatements.Insert(0, cmdFirstLine);

And the test will make the sentence

var dt=DateTime.Now;

between start and end lines:

[TestMethod]
 public void TestMethodRewriterLastLine()
 {

 var rc = new RewriteCode(
 formatterFirstLine: "Console.WriteLine(\"start {nameClass}_{nameMethod}_{lineStartNumber}\");",
 formatterLastLine: "Console.WriteLine(\"end {nameClass}_{nameMethod}_{lineStartNumber}\");"
 );
 rc.Code = @"
using System;
namespace Test1
{
 class Program
 {
 static void Main(string[] args)
 {
 var dt=DateTime.Now;
 }
 }
}";
 var result = rc.RewriteCodeMethod();
 var newCode = @"
using System;

namespace Test1
{
 class Program
 {
 static void Main(string[] args)
 {
 Console.WriteLine(" + "\"start Program_Main_6\"" + @");
 var dt = DateTime.Now;
 Console.WriteLine(" + "\"end Program_Main_6\"" + @");
 }
 }
}";
 Assert.AreEqual(result.Replace(Environment.NewLine, ""), newCode.Replace(Environment.NewLine, ""));
 }


 
 }

 

AOP with Roslyn–part 3–custom code at beginning of each method

Last time(http://msprogrammer.serviciipeweb.ro/2017/11/27/aop-with-roslynpart-2/)  we have injected a new code into each method . However, this code was pretty much hardcoded into the MethodRewriter class – it was a simple

Console.WriteLine(\”{nameClass}_{nameMethod}_{lineStartNumber}\”);

 

Now we want to can customize this code at the will of the programmer. For this, I have modified classes RewriteCode and MethodRewriter to accept a parameter named Formatter . To run the test that worked previously, I made a parameterless  constructor for RewriteCode in order to preserve compatiblity.

 

 

public RewriteCode(): this("Console.WriteLine(\"{nameClass}_{nameMethod}_{lineStartNumber}\");")
{

}
public RewriteCode(string formatter)
{
    Formatter = formatter;
}

 
The VisitMethod is now much simpler:

string nameVariable =Formatter.FormatWith(new { nameClass,nameMethod,lineStartNumber=lineStart.Line});
var cmd = SyntaxFactory.ParseStatement(nameVariable);

 
( The .FormatWith is an extension from https://github.com/crozone/FormatWith )
 
Also, I have made a new test to test this one – and I have inserted into code an variable named s:
 
string s=\”this is method {nameMethod} from class {nameClass} at line {lineStartNumber}\”;”
 
 
The test is

[TestMethod]
public void TestMethodRewriterAddVariable()
{

var rc = new RewriteCode(
"string s=\"this is method {nameMethod} from class {nameClass} at line {lineStartNumber}\";"
);
rc.Code = @"
using System;
namespace Test1
{
    class Program
    {
        static void Main(string[] args)
        {
              var dt=DateTime.Now;
        }
     }
}";
            var result = rc.RewriteCodeMethod();
            var newCode = @"
using System;

namespace Test1
{
    class Program
    {
        static void Main(string[] args)
        {
            string s = ""this is method Main from class Program at line 6"";
            var dt = DateTime.Now;
        }
    }
}";
            Assert.AreEqual(result.Replace(Environment.NewLine, ""), newCode.Replace(Environment.NewLine, ""));
        }
    }

AOP with Roslyn–part 2

I want to transform code by injecting some simple code, like “Console.WriteLine(“method”)

So this code:

 

using System;
namespace Test1
{
    class Program
    {
        static void Main(string[] args)
        {
              var dt=DateTime.Now;
        }
     }
}

 

should be modified to this code:

 

using System;

namespace Test1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(" + "\"Program_Main_6\"" + @");//this is automatically added
            var dt = DateTime.Now;
        }
    }
}

 
How I do the code: I derive from CSharpSyntaxRewriter and override the VisitMethodDeclaration  . I will construct a new node with the Console.WriteLine statement inserted

 

public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
        {


            if (node.Body == null || node.Body.Statements.Count == 0)
                return base.VisitMethodDeclaration(node);
            var parent = node.Parent as ClassDeclarationSyntax;
            if (parent == null)
                return base.VisitMethodDeclaration(node);

            var nameMethod = node.Identifier.Text;
            var nameClass = parent.Identifier.Text;
            Console.WriteLine(nameMethod);
            node = (MethodDeclarationSyntax)base.VisitMethodDeclaration(node);
            var lineStart = node.GetLocation().GetLineSpan().StartLinePosition;
            string nameVariable = $"{nameClass}_{nameMethod}_{lineStart.Line}";
            var cmd = SyntaxFactory.ParseStatement($"Console.WriteLine(\"{nameVariable}\");//this is automatically added");

            var blockWithNewStatements = new SyntaxList<StatementSyntax>();
            
            for (int i = node.Body.Statements.Count - 1; i >= 0; i--)
            {
                var st = node.Body.Statements[i];
                blockWithNewStatements = blockWithNewStatements.Insert(0, st);
            }

            blockWithNewStatements = blockWithNewStatements.Insert(0, cmd);

            var newBlock = SyntaxFactory.Block(blockWithNewStatements);

            var newMethod = SyntaxFactory.MethodDeclaration
                (node.AttributeLists, node.Modifiers, node.ReturnType,
                node.ExplicitInterfaceSpecifier, node.Identifier, node.TypeParameterList,
                node.ParameterList, node.ConstraintClauses,
                newBlock,
                node.ExpressionBody, node.SemicolonToken);


            var newNode = node.ReplaceNode(node, newMethod);

            return base.VisitMethodDeclaration(newNode);
        }

 

As test, I have created a TestMethodRewriterSimple that verifies that.

        [TestMethod]
        public void TestMethodRewriterSimple()
        {

            var rc = new RewriteCode();
            rc.Code = @"
using System;
namespace Test1
{
    class Program
    {
        static void Main(string[] args)
        {
              var dt=DateTime.Now;
        }
     }
}";
            var result= rc.RewriteCodeMethod();
            var newCode = @"
using System;

namespace Test1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(" + "\"Program_Main_6\"" + @");//this is automatically added
            var dt = DateTime.Now;
        }
    }
}";
            Assert.AreEqual(newCode.Replace(Environment.NewLine,""), newCode.Replace(Environment.NewLine, ""));
        }
    }

You can find the code on https://github.com/ignatandrei/AOP_With_Roslyn

Friday links 244

  1. The Quiet Crisis unfolding in Software Development — Medium
  2. Microsoft meets Open Source · Ted Neward’s Blog
  3. Your Software is Never Perfect
  4. Docker: Windows containers on Windows host – step by step in Stapp.space
  5. 5 Ways Remote Teams Can Create a Culture of Accountability
  6. How to Build a Search Page with Elasticsearch and .NET
  7. Sayed Ibrahim Hashimi – MSBuild, Web Deploy (MSDeploy), ASP.NET – How to publish one web project from a solution
  8. Building ClickOnce Applications from the Command Line
  9. 24 Data Science, R, Python, Excel, and Machine Learning Cheat Sheets – Data Science Central
  10. 4 easy steps to becoming a data scientist – Data Science Central
  11. Entity Framework Customizing Code First to an Existing Database
  12. 10 Ways Introverts Can Succeed at Networking
  13. Snoop, the WPF Spy Utility – Home
  14. Top 10: Cei mai buni comandanţi militari din istorie | Historia
  15. Introducing HyperDev – Joel on Software
  16. keyboardDrummer/SmartReactives: A .NET library that detects when an expression changes its value
  17. Using Windows IExpress To Package IT Tools | NlightU Blog
  18. The top 10 projects to try out with your Raspberry Pi 3 – TechRepublic
  19. Ways You Need To Tell The Browser How To Optimize | CSS-Tricks
  20. My surprisingly positive take on .Net Core’s current direction | The Shade Tree Developer
  21. 3 ways to keep your asp.net mvc controllers thin – JonHilton.Net
  22. Builder: C# · Ted Neward’s Blog
  23. Fluentassertions
  24. Steve Blank on the Tech Bubble: ‘VCs Won’t Admit They’re in a Ponzi Scheme’ | Inc.com
  25. Using LAST_VALUE – SQLServerCentral
  26. Simple Source Code for Generating ‘2 of 5 Interleaved’ Image Barcode – CodeProject

AOP with Roslyn

What I am interested in is to make a tool that logs in when the program comes in and out of a method.

It’s good for identifying problems in code and for logging.

What already exists on the market:

PostSharp – one of the best – see https://www.postsharp.net/alternatives
Cecil http://www.mono-project.com/docs/tools+libraries/libraries/Mono.Cecil/
Fody https://github.com/Fody
NConcern: https://github.com/Virtuoze/NConcern
What I do not like is that they are hard to configure. So, instead of writing a configurator, I’m going to write a Roslyn AOP so it’s easy to use POST build event in a CI scenario

I’ve inspired at https://github.com/KeenSoftwareHouse/SpaceEngineers – See https://github.com/KeenSoftwareHouse/SpaceEngineers/tree/master/Sources/VRage.Scripting. Do not compile in VS2017, but you can analyze the code …

The second source of inspiration was http://cezarywalenciuk.pl/blog/programing/post/roslyn-kompilator-net-rewrite-z-csharpsyntaxrewriter – in Polish, but you can see the code … (it gets complicated at the end)

My code will be on https://github.com/ignatandrei/AOP_With_Roslyn