1 comment

[ 4.4 ms ] story [ 16.5 ms ] thread
I've been anxious about posting this here for weeks now. I figured that it's too niche or too trivial to be interesting. But today I saw a CSS file hitting the frontpage so I guess I just worried to much.

The idea is simple and functionally it doesn't look like much. Yet I'm quite proud of it, because the implementation was much more difficult than expected for a couple of reasons.

Fighting the Visual Studio SDK: even though the VSSDK is super-powerful, some parts of it (especially CodeLenses) are very poorly documented.

UI styling: the VS team went out of their way to prevent extension developers from re-using the VS styles. They actually don't want extensions to look like VS for CI reasons. It took days of trying random stuff and digging through the VS DLLs until I found what I needed. I'm not even sure this was entirely legal.

Finding the IL for the code-lensed method: this sounded so easy in my head at first. VS/Roslyn gives you a MethodSymbol of the code-lensed method, I'll just have to find it's MethodDefinition in a Mono.Cecil AssemblyDefinition by name. But C# allows method overloading and suddenly I had to cook up a non-trivial matching logic that can resolve overloads without breaking my fingers or brain. Overloads can become arbitrarily complex (e.g. Foo<T>(List<T[]>), Foo<T>(List<List<T>>)) and of top of that the types that have to be matched with one another (MethodSymbols and MethodDefinitions) are structured differently in non-obvious ways.

Unit-testing the overload-resolution: in order not go insane, I had to have unit tests for the above. The idea for those was that each test gets the Roslyn MethodSymbol of a specific dummy C# method and then checks if the overload resolution can uniquely identify it in the Mono.Cecil AssemblyDefinition of the test data assembly where that dummy method is defined. Loading the test data assembly with Roslyn is easy enough, but how to get a specific MethodSymbol? If you only use the method's name, you'd have to deal with overloads again, only this time on the Roslyn side. The solution was to have a special selector method which takes a lambda calling the dummy method. The selector method would then use Roslyn to analyze the test's syntax tree, locate it's own call in the test case, grab the lambda argument passed to it, and then return the MethodSymbol of the dummy method referenced by that lambda. Pretty nifty, I think, but I later found it would have been easier to write only a single test case that just iterates over all MethodSymbols in the test data assembly and tries to resolve them using the matching logic. However, the disadvantage of that approach is that such a test case would lack focus and it would be harder to pinpoint what exactly went wrong when the test case fails.