The more I work with Roslyn the more I appreciate the possibilities it gives and the more I hate it. And I hate it for the same thing as many other projects I worked with in the past. What is it? Well, I like when a system fails fast, fails loudly and fails in the clear way. Unfortunately, Roslyn can do something completely different what sometimes makes working with it the pain in ass. I'll give you some examples.
Issue 1 - Problem with "empty" projects
Here is the code that shows how I usually process documents/files for a given project. It's pretty easy.
var workspace = MSBuildWorkspace.Create(); var sln = await workspace.OpenSolutionAsync(path); foreach (var projectId in sln.ProjectIds) { var project = sln.GetProject(projectId); foreach (var documentId in project.DocumentIds) { // Process a document } }It works quite well but only on my machine :) On 2 other machines I'm observing problems. In general I have an example solution with 2 test projects. One is WPF application and the another is WebAPI.
The problem is that on some machines I can only read and analyze WPF application. If I try to do exactly the same thing with WebAPI application, then the project loaded by Roslyn is empty i.e. contains no documents (DocumentIds property is empty)! I've already tried to load this project in a different way but without success.
To be honest currently I'm stuck and I have no idea what is wrong here. Any suggestions?
Issue 2 - the semantic analysis does not work
With Roslyn we can perfrom the syntax analysis and the semantic analysis of the code. The syntax analysis, with a syntaxt tree, allows you to only see a structure of a program. The semantic analysis is more powerfull and allows you to understand more. For example, having a code like that:
SomeClass x;
With the semantic analysis you can check that SomeClass is defined within SomeNamespace and has X members (methods, properties). For example, here we have a code showing how to use the semantic analysis to check what interfaces are implemented by a given class at any level of the inheritance.
var compilation = await project.GetCompilationAsync(); foreach (var documentId in project.DocumentIds) { var document = project.GetDocument(documentId); // Get a syntax tree var tree = await document.GetSyntaxTreeAsync(); // Get a root of the syntax tree var root = await tree.GetRootAsync(); // Find a node of the syntaxt tree for a first class in a file/document var classNode= root.DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault(); if(classNode== null) continue; // Get a semantic model for the syntax tree var semanticModel = compilation.GetSemanticModel(tree); // Use the semantic model to get symbol info for the found class node var symbol = semanticModel.GetDeclaredSymbol(classNode); // Check what inerfaces are implemnted by the class at any level foreach(var @interface in symbol.AllInterfaces) { // ... } }If you run this code as it is, it again will not throw any exceptions. However, you'll noticed that any found class doesn't implement any interface according to Roslyn. Where is the problem this time?
It's quite obvious if you know that. To perform the semantic analysis Roslyn needs to analyse assemblies used by the project. However, it's not enough to compile the project. You have to explicitly register all required assemblies. I do it in the easy way. I simply register all assemblies found in the output folder.
var compilation = await project.GetCompilationAsync(); // Let's register mscorlib compilation = compilation.AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location)); if (Directory.Exists("PATH TO OUTPUT DIRECTORY")) { var files = Directory.GetFiles(directory, "*.dll").ToList(); // You can also look for *.exe files foreach (var f in files) compilation = compilation.AddReferences(MetadataReference.CreateFromFile(f)); }And again if the semantic analysis can not be performed without that why no exception is thrown?
Issue 3 - Problem with reading projects/solutions
This one I've already described in more details in the post about Roslyn and unit tests. The problem was that:
- MSBuildWorkspace.OpenSolutionAsync method was returning an empty solution if a particular assembly was missing (not fast, not loud)
- MSBuildWorkspace.OpenProjectAsync method was returning the error The language 'C#' is not supported (not in the clear way).
Remember failing fast, loudly and in the clear way does not cost much but can save a lot of time.
*The picture at the beginning of the post comes from own resources and shows cliffs near Cabo da Roca - the westernmost extent of mainland Portugal.
4 comments:
Hello. Have you managed to fix Issue 1 in any way? I too am having same problem at the moment.
Hi. Yes, I've found a solution. I described it in the another post, see: http://www.michalkomorowski.com/2017/04/why-i-hate-roslyn-even-more.html
Roslyn is an open source project. The Microsoft team tends to focus on improving the compiler and adding new features, but if error messages and usability are important to you, I suggest you file an issue with them, and possibly even create a pull request fixing the issue.
Roslyn is a general library which means it's designed to deal with poorly formed projects/code/configurations and many use cases. It's down to every consumer to decide how they want to deal with error cases. If you just want a total meltdown then put something like:
var errors = compilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error)
.Select(d => $"{d.Id}: {d.GetMessage()}").ToList();
if (errors.Any()) throw new Exception(string.Join("\r\n", errors));
Post a Comment